Cover Page

Copyright © 2002 by Microsoft Corporation


44ap01b.htm

PUBLISHED BY
Microsoft Press
A Division of Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399

Copyright © 2002 by Microsoft Corporation
All rights reserved. No part of the contents of this book may be reproduced or transmitted in any form or by any means without the written permission of the publisher.

Library of Congress Cataloging-in-Publication Data
MCAD/MCSD Self-Paced Training Kit: Developing Web Applications with Microsoft Visual Basic .NET
and Microsoft Visual C# .NET
        p. cm.
      Includes index.
      ISBN 0-7356-1584-5
      1. Electronic data processing personnel--Certification. 2. Microsoft
software--Examinations--Study guides. 3. Microsoft .NET Framework. 4. Microsoft
Windows (Computer file) I. Microsoft Corporation.

QA76.3 .M3267 2002
005.7'2--dc21                                               2002023561
Printed and bound in the United States of America.

1 2 3 4 5 6 7 8 9 QWT 7 6 5 4 3 2

Distributed in Canada by H.B. Fenn and Company Ltd..

A CIP catalogue record for this book is available from the British Library.

Microsoft Press books are available through booksellers and distributors worldwide. For further information about international editions, contact your local Microsoft Corporation office or contact Microsoft Press International directly at fax (425) 936-7329. Visit our Web site at www.microsoft.com/mspress. Send comments to mspinput@microsoft.com.

ActiveX, FrontPage, IntelliSense, JScript, Microsoft, Microsoft Press, PhotoDraw, Visual Basic, Visual C++, Visual C#, Visual Studio, Windows, Windows Media Player, and Windows NT are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries. Other product and company names mentioned herein may be the trademarks of their respective owners.

The example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious. No association with any real company, organization, product, domain name, e-mail address, logo, person, place, or event is intended or should be inferred.

Acquisitions Editor: Kathy Harding
Project Editor: Karen Szall
Technical Editor: Brian Johnson
Author: Jeff Webb

Chapter 7 references the Credit Card Verifier Web Service with the permission of Christopher Chenoweth at CDYNE Systems and the Daily Quote Web Service with the permission of Cameron Braidwood at effective web computing, ltd.


About This Book

Welcome to MCAD/MCSD Training Kit—Developing Web Applications with Microsoft Visual Basic .NET and Microsoft Visual C# .NET. By completing the lessons and exercises in this book, you will acquire the skills and knowledge necessary to develop Web-based applications in Microsoft Visual Basic .NET or Microsoft Visual C#.

This book is designed for developers interested in developing Web applications at an intermediate and advanced level. It includes developing server-based applications that use the .NET Framework to present HTML content and retrieve data from client browsers running over the Internet.

This book also addresses the objectives of the Microsoft Certified Professional Exam 70-305 and Exam 70-315.

note | For more information about becoming a Microsoft Certified Application Developer (MCAD) or a Microsoft Certified Solution Developer (MCSD), see the section titled “The Microsoft Certified Professional Program” later in this introduction.

The “Getting Started” section of this introduction provides important setup instructions that describe the hardware and software requirements to complete the exercises in this course. It also provides information about the networking configuration necessary to complete some of the hands-on exercises. Read through this section thoroughly before you start the lessons.

Intended Audience

This book was created for software developers who need to design, plan, implement and support Web applications or who plan to take the related Microsoft Certified Professional exams:

  • Developing and Implementing Web Applications with Microsoft Visual Basic .NET and Microsoft Visual Studio .NET (Exam 70-305).

  • Developing and Implementing Web Applications with Microsoft Visual C# .NET and Microsoft Visual Studio .NET (Exam 70-315).

Prerequisites

This course requires that students meet the prerequisites that follow.

  • Be able to create Windows applications using Microsoft Visual Studio .NET in either the Visual Basic .NET or Visual C# .NET programming language.

  • Have a basic understanding of the object-oriented programming concepts including classes, properties, methods, and events.

  • Understand the fundamental elements in the Hypertext Markup Language (HTML) used to author Web content.

  • Understand how Web content is stored and accessed over the Internet. This includes being able to explain the roles of Web servers, Internet protocols, and Web clients (such as browsers).

Reference Materials

You might find the following reference materials useful:

  • The Visual Studio .NET online Help

  • The World Wide Web Consortium Web site (http://www.w3c.org)

  • The Microsoft ASP.NET public newsgroup ( news://msnews.microsoft.com/microsoft.public.dotnet.framework.aspnet)

About the CD-ROM

The Supplemental Course Materials CD-ROM contains a variety of informational aids that may be used throughout this book:

  • eBook. A complete electronic version (eBook) of this training kit.

  • Lesson samples. Most lessons in this training kit include sample Visual Studio projects that organize the code and Web forms shown in the training kit. Some of these samples are complete applications, but most are limited snippets that demonstrate specific skills.

  • Completed labs. Each chapter in this training kit concludes with a lab containing a series of exercises that reinforce the skills you learned. Completed versions of these applications are included so that you can compare your results. You can also use these completed applications as a reference if you get stuck while completing an exercise.

  • Required files. Practice files, such as sample databases, that are required to perform the hands-on procedures. You should use these files when indicated in the exercises.

  • Sample exam questions. To practice taking a certification exam, you can use the sample exam questions provided on the CD-ROM. The sample questions help you assess your understanding of the materials presented in this book.

About the DVD

The DVD contains a 60-day evaluation edition of Microsoft Visual Studio .NET Professional.

caution | The 60-day Evaluation Edition provided with this training is not the full retail product and is provided only for the purposes of training and evaluation. Microsoft Technical Support does not support this evaluation edition.

For additional support information regarding this book and the CD-ROM and DVD (including answers to commonly asked questions about installation and use), visit the Microsoft Press Technical Support Web site at http://www.microsoft.com/mspress/support/. You can also email tkinput@microsoft.com, or send a letter to Microsoft Press, Attn: Microsoft Press Technical Support, One Microsoft Way Redmond, WA 98502-6399.

Features of This Book

Each chapter contains sections that are designed to help you get the most educational value from the chapter.

  • Each chapter opens with a “Before You Begin” section, which prepares you for completing the chapter.

  • The chapters are then divided into lessons. Each lesson contains the reference and procedural information used for a specific skill.

  • The “Summary” section identifies the key concepts from the lesson.

  • The “Lab” section provides hands-on exercises that reinforce each of the skills taught in each of the chapter lessons. The exercises give you an opportunity to use the skills being presented or explore the part of the application being described. Wherever possible, the exercises in a lab build on each other to create a complete application by the end of that lab.

  • At the end of each chapter is the “Review” section that you can use to test what you have learned.

The appendix, “Questions and Answers,” contains all of the questions asked in each chapter review and the corresponding answers.

Notes

Several types of Notes appear throughout the lessons.

  • Notes marked Tip contain explanations of possible results or alternative methods for performing tasks.

  • Notes marked Important contain information that is essential to completing a task.

  • Notes marked Note contain supplemental information.

  • Notes marked Caution contain warnings about possible loss of data.

Notational Conventions

The following notational conventions are used throughout this book.

  • Characters or commands that you type appear in bold type.

  • Italic in syntax statements indicates placeholders for variable information. Italic is also used for book titles.

  • Names of files and folders appear in initial capital letters, except when you are to type them directly. Unless otherwise indicated, you can use lowercase letters when you type a file name in a dialog box or at a command prompt.

  • Filename extensions, when they appear without a file name, are in lowercase letters.

  • Acronyms appear in all uppercase letters.

  • Monospace type represents code samples, examples of screen text, or entries that you might type at a command prompt or in initialization files.

Chapter and Appendix Overview

This self-paced training kit combines notes, hands-on procedures, and review questions to teach you how to create Web applications with Visual Studio .NET. It is designed to be completed from beginning to end, but you can choose a customized track and complete only the sections that interest you. (See the next section, “Finding the Best Starting Point for You,” for more information.) If you choose the customized track option, see the “Before You Begin” section in each chapter. Any hands-on procedures that require preliminary work from preceding chapters refer to the appropriate chapters.

The book is divided into the following sections and chapters:

  • The “About This Book” section contains an overview and introduces the components of this training kit. Read this section thoroughly to get the greatest educational value from this training kit and to plan which lessons you will complete.

  • Chapter 1, “Introduction to Web Programming,” introduces you to the concepts and terms used throughout this book, including how Web applications work, the parts of a Web application, how the .NET Framework is organized, and how to use the Visual Studio programming environment. The sections in this chapter are intended to provide a high-level overview of concepts that are explained in greater detail in subsequent chapters.

  • Chapter 2, “Creating Web Forms Applications,” teaches how to create a Web application using Visual Studio .NET and ASP.NET. You learn how to start a new project, create a basic user interface, write code to respond to events, and preserve data within your application. Because ASP.NET is integrated with Microsoft Internet Information Services (IIS), you also learn about IIS and how to use it to organize your Web application.

  • Chapter 3, “Working with Web Objects,” explains creating and organizing objects in Visual Studio .NET, using the objects provided by ASP.NET, and saving objects and data between requests to Web forms. Visual Basic, Visual C#, and ASP.NET are based on a common framework, so the object-oriented programming techniques you learn in this chapter apply through all aspects of .NET programming.

  • Chapter 4, “Creating a User Interface,” demonstrates how to use ASP.NET controls to create a user interface for a multi-page Web forms application. This chapter teaches you about different styles of Web forms, how to choose controls based on the tasks you want to perform, how to validate data fields, and how to navigate between the Web forms in your application.

  • Chapter 5, “Storing and Retrieving Data with ADO.NET,” shows how to use ADO.NET to access and modify data stored in SQL and OLE databases from Web forms. You learn about the data tools included with Visual Studio and how to use them to create connections to, get records from, and perform commands on databases. You will also learn how to display data on a Web form using either data binding or code. Finally, you learn how to use transactions to maintain the integrity of a database.

  • Chapter 6, “Catching and Correcting Errors,” explains how to deal with problems that may occur in your application due to external circumstances. This chapter helps you identify, anticipate, and handle these types of problems effectively within your application.

  • Chapter 7, “Advanced Web Forms Programming,” teaches how to perform advanced programming tasks that don' t fit neatly into any other category. Although you can successfully create Web applications without knowing the topics covered in this chapter, the lessons found here are an important part of mainstream Web programming and they help complete your skill set.

  • Chapter 8, “Maintaining Security,” covers how to control access to your Web application using the three different techniques that ASP.NET provides for identifying and authorizing users. You also learn how to secure data transmitted across the Internet so that it cannot be read by others.

  • Chapter 9, “Building and Deploying Web Applications,” describes how to publish your completed Web applications on a Web server for general use. You also learn how to monitor and maintain the application once it is in use so that it continues to perform well as the demands of your users change.

  • Chapter 10, “Testing Web Applications,” teaches how to systematically test your Web application during development. You learn how to plan, create, run, and correct problems found by tests.

  • Chapter 11, “Creating Custom Web Controls,” demonstrates how to create your own, custom user-interface components. This chapter explains the different types of controls you can create, describes the relative advantages of each, and shows you how to create each different type of control.

  • Chapter 12, “Working with Multimedia,” shows how to add sound, video, and animation to Web applications and how to control their display and playback in code.

  • Chapter 13, “Formatting Web Application Output,” teaches you how to format the user interface of a Web application using cascading style sheets (CSS) and Extensible Stylesheet Language Transformations (XSLT). This chapter explains the best uses for each formatting technique and tells you how to combine the two techniques.

  • Chapter 14, “Providing Help,” discusses the different ways that you can display user assistance (Help) from within a Web application. You learn how to use the HTML Help Workshop to create compiled Help files, and how to display those files from a Web application interactively.

  • Chapter 15, “Globalizing Web Applications,” explains how to detect a user' s culture and create appropriate responses, ranging from redirecting the user to a separate, localized Web application, to handing cultural differences within code and displaying a localized user interface.

  • The appendix, “Questions and Answers,” lists all of the review questions from the book, showing the page number for each question and the suggested answer.

  • The glossary provides definitions for many of the terms and concepts presented in this training kit.

Finding the Best Starting Point for You

Because this book is self-paced, you can skip some lessons and revisit them later. Use the following table to find the best starting point for you:

If you | Follow this learning path
Are preparing to take the Microsoft Certified Professional exam 70-305 or 70-315 | Read the “Getting Started” section. Then work through Chapters 1-4. Work through the remaining chapters in any order.
Want to review information about specific topics from the exam | Use the “Where to Find Specific Skills in This Book” section that follows this table.
Where to Find Specific Skills in This Book

The following tables provide a list of the skills measured on certification Exam 70-305, Developing and Implementing Web Applications with Microsoft Visual Basic .NET and Microsoft Visual Studio .NET, or Exam 70-315, Developing and Implementing Web Applications with Microsoft Visual C# .NET and Microsoft Visual Studio .NET. The table provides the skill, and where in this book you will find the lesson relating to that skill.

note | Exam skills are subject to change without prior notice and at the sole discretion of Microsoft.
Creating User Services

Skill Being Measured

Location in Book

Create ASP.NET pages:

  • Add and set directives on ASP.NET pages

  • Separate user interface resources from business logic

Chapter 2, Lesson 1

  • Chapter 6, Lesson 2, and Chapter 11, Lesson 2

  • Chapter 3, Lesson 1

Add Web server controls, HTML server controls, user controls, and HTML code to ASP.NET pages:

  • Set properties on controls

  • Load controls dynamically

  • Apply templates

  • Set styles on ASP.NET pages by using cascading style sheets

  • Instantiate and invoke an ActiveX control

Chapter 4, Lessons 1 and 2

  • Chapter 4, Lesson 1

  • Chapter 4, Lesson 1

  • Chapter 4, Lesson 1, and Chapter 5, Lesson 2

  • Chapter 13, Lesson 1

  • Chapter 12, Lessons 1 and 2

Implement navigation for the user interface:

  • Manage the view state

  • Manage data during post-back events

  • Use session state to manage data across pages

Chapter 4, Lesson 3; Chapter 2, Lesson 2; and Chapter 3, Lesson 3

  • Chapter 3, Lesson 3

  • Chapter 3, Lesson 3

  • Chapter 3, Lesson 3

Validate user input:

  • Validate non-Latin user input

Chapter 4, Lesson 2

  • Chapter 15, Lesson 3

Implement error handling in the user interface:

  • Configure custom error pages

  • Implement Global.asax, application, page-level, and page event error handling

Chapter 6, Lessons 1, 2, and 3

  • Chapter 6, Lesson 2

  • Chapter 6, Lesson 2

Implement online user assistance

Chapter 14, Lessons 1, 2, and 3

Incorporate existing code into ASP.NET pages

Chapter 1, Lesson 2

Display and update data:

  • Transform and filter data

  • Bind data to the user interface

  • Use controls to display data

Chapter 4, Lesson 1

  • Chapter 5, Lessons 1 and 2; Chapter 13, Lesson 2

  • Chapter 4, Lesson 1

  • Chapter 4, Lesson 1

Instantiate and invoke Web services or components:

  • Instantiate and invoke a Web service

  • Instantiate and invoke a COM or COM+ component

  • Instantiate and invoke a .NET component

Chapter 7, Lesson 4

  • Chapter 7, Lesson 4

  • Chapter 10, Lesson 2

  • Chapter 3, Lesson 1

Implement globalization:

  • Implement localizability for the user interface

  • Convert existing encodings

  • Implement right-to-left and left-to-right mirroring

  • Prepare culture-specific formatting

  • Chapter 15, Lesson 2

  • Chapter 15, Lesson 3

  • Chapter 15, Lesson 1

  • Chapter 15, Lesson 1

Handle events:

  • Create event handlers

  • Raise events

Chapter 2, Lesson 2

Implement accessibility features

Chapter 13, Lesson 1

Use and edit intrinsic objects, including response, request, session, server, and application:

  • Retrieve values from the properties of intrinsic objects

  • Set values on the properties of intrinsic objects

  • Use intrinsic objects to perform operations

Chapter 3, Lesson 2
Creating and Managing Components and .NET Assemblies

Skill Being Measured

Location in Book

Create and modify a .NET assembly:

  • Create and implement satellite assemblies

  • Create resource-only assemblies

Chapter 9, Lesson 1, and Chapter 11, Lesson 3

  • Chapter 15, Lesson 2

  • Chapter 15, Lesson 2

Consuming and Manipulating Data
Skill Being Measured | Location in Book
Access and manipulate data from a Microsoft SQL Server database by creating and using ad hoc queries and stored procedures | Chapter 5, Lesson 1
Access and manipulate data from a data store. Data stores include relational databases, XML documents, and flat files. Methods include XML technique and ADO.NET. | Chapter 5, Lesson 1
Handle data errors | Chapter 6, Lesson 1
Testing and Debugging

Skill Being Measured

Location in Book

Create a unit test plan

Chapter 10, Lesson 1

Implement tracing:

  • Add trace listeners and trace switches to an application

  • Display trace output

  • Chapter 10, Lesson 3

  • Chapter 6, Lesson 3

Debug, rework, and resolve defects in code:

  • Configure the debugging environment

  • Create and apply debugging code to components, pages, and applications

  • Provide multicultural test data to components, pages, and applications

  • Execute tests

  • Resolve errors and rework code

  • Chapter 10, Lesson 3

  • Chapter 10, Lesson 1

  • Chapter 15, Lesson 2

  • Chapter 10, Lesson 2

  • Chapter 10, Lesson 3
Deploying a Web Application

Skill Being Measured

Location in Book

Plan the deployment of a Web application:

  • Plan the deployment of an application to a Web garden, a Web farm, or a cluster

Chapter 9, Lesson 2

  • Chapter 9, Lesson 4

Deploy a Web application

Chapter 9, Lesson 2

Add assemblies to the global assembly cache

Chapter 9, Lesson 2

Maintaining and Supporting a Web Application
Skill Being Measured | Location in Book
Optimize the performance of a Web application | Chapter 9, Lesson 3
Diagnose and resolve errors and issues | Chapter 9, Lesson 3; Chapter 6, Lesson 3; and Chapter 10, Lesson 3
Configuring and Securing a Web Application

Skill Being Measured

Location in Book

Configure a Web application:

  • Modify the Web.config file

  • Modify the Machine.config file

  • Add and modify application settings

Chapter 9, Lesson 1

  • Chapter 9, Lesson 1; Chapter 8, Lesson 1; and Chapter 15, Lesson 3

  • Chapter 9, Lesson 1

  • Chapter 9, Lesson 1; Chapter 8, Lesson 1; and Chapter 15, Lesson 3

Configure security for a Web application:

  • Select and configure authentication type. Authentication types include Windows Authentication, None, forms-based, Microsoft Passport, Internet Information Services (IIS) authentication, and custom authentication.

Chapter 8, Lessons 1, 2, and 3

Configure authorization. Authorization methods include file-based methods and URL-based methods:

  • Configure role-based authorization

  • Implement impersonation

Chapter 8, Lessons 1, 2, 3, and 4

  • Chapter 8, Lesson 2

  • Chapter 8, Lesson 1

Configure and implement caching. Caching types include output, fragment, and data:

  • Use a cache object

  • Use cache directives

Chapter 9, Lesson 3

Configure and implement session state in various topologies such as a Web garden and a Web farm:

  • Use session state within a process

  • Use session state with session state service

  • Use session state with Microsoft SQL server

  • Chapter 3, Lesson 3

  • Chapter 9, Lesson 4

  • Chapter 9, Lesson 4

Install and configure server services:

  • Install and configure a Web server

  • Install and configure FrontPage Server Extensions

Chapter 2, Lesson 1

  • Chapter 2, Lessons 1 and 3; Chapter 6, Lesson 2; Chapter 8, Lessons 1 and 5; and Chapter 9, Lesson 2

  • Chapter 2, Lesson 1, and Chapter 9, Lesson 3
Getting Started

This self-paced training kit contains hands-on procedures to help you learn about developing Web applications.

To complete some of these procedures, you must have two networked computers or be connected to a larger network. Both computers must be capable of running Microsoft Windows XP Professional Edition or Windows 2000.

caution | Several exercises require you to make changes to your servers. This might cause undesirable results if you are connected to a larger network. Check with your network administrator before attempting these exercises.
Hardware Requirements

Each computer must have the following minimum configuration. All hardware should be on the Microsoft Windows XP or Microsoft Windows 2000 Hardware Compatibility List.

  • Pentium II–class processor, 450 megahertz (MHz)

  • 160 MB physical memory, 256 MB recommended

  • CD-ROM or DVD drive, 12x or faster recommended

    note

    A DVD drive is required to install the Visual Studio .NET Professional Evaluation Edition software.

  • 3.5 gigabytes (GB) on installation drive, which includes 500 megabytes (MB) on system drive.

  • Super VGA (800 x 600) or higher-resolution monitor with 256 colors.

  • Microsoft Mouse or compatible pointing device

  • Internet connection and networking card (56K modem is the minimum acceptable Internet connection speed, higher speed connection is recommended)

Software Requirements

The following software is required to complete the procedures in this course.

  • Microsoft Windows XP Professional Edition or Windows 2000.

    note

    The default installation configuration for these operating systems includes the Microsoft Internet Information Services (IIS) software required for developing Web applications.

  • Microsoft Visual Studio .NET Professional Edition or Visual Studio .NET Enterprise Developer. Professional Edition is recommended, and Enterprise Developer Edition is ideal.

Setup Instructions

Set up your computer according to the manufacturer' s instructions.

The Lesson Files
To install the lesson files to your hard disk drive

  1. Insert the Supplemental Course Materials CD-ROM into your CD-ROM drive.

    2. note | If AutoRun is disabled on your machine, refer to the Readme.txt file on the CD-ROM.

  2. Click Lesson Files on the user interface menu to start the install program.

After you run that installer, you' ll need to run the following scripts to set up the virtual directories required to run these applications in IIS:

  1. Go to the Windows Command Prompt:

    • If you have Visual Studio .NET installed, do this by clicking Start, Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and then Visual Studio .NET Command Prompt. This ensures that the PATH environment variable includes the location of the .NET Framework compilers.

    • If you have the .NET Framework SDK installed, but not Visual Studio .NET, do this by clicking Start, Programs, Accessories, Command Prompt. You must then add the location of the .NET Framework compilers to your PATH environment variable. These are found in the windows folder\Microsoft.NET\Framework\version folder. For example, to update the PATH variable on a Windows XP system, enter the following:

      set path=%path%;C:\Windows\Microsoft.NET\Framework\v1.0.3705

      (The Windows directory is named WINNT on Windows 2000 systems and on Windows XP systems that have been upgraded from Windows 2000. Also, the final version number of the installed .NET Framework may be different than this one.)

  2. At the Command Prompt, go to the folder in which you' ve installed the samples. For example, if you' ve installed the samples at C:\MCSDWebApps (recommended), enter the following:

    C:\> cd MCSDWebApps
C:\MCSDWebApps>

  3. Execute the script that configures IIS so that all the samples are available from your Web server. You must enter the directory in which you installed the samples (the current directory) as a parameter on the command line:

    D:\MCSDWebApps> makeVirtualDirectories
D:\MCSDWebApps
The eBook

The CD-ROM also includes an electronic version of the book that you can view using a Hypertext Markup Language (HTML) browser.

To use the eBook

  1. Insert the Supplemental Course Materials CD-ROM into your CD-ROM drive.

    2. note | If AutoRun is disabled on your machine, refer to the Readme.txt file on the CD-ROM.

  2. Click Training Kit eBook on the user interface menu and follow the prompts.

note | You must have the Supplemental Course Materials CD-ROM inserted in your CD-ROM drive to run the eBook.
Sample Exam Questions
To install the sample exam questions to your hard disk drive

  1. Insert the Supplemental Course Materials CD-ROM into your CD-ROM drive.

    2. note | If AutoRun is disabled on your machine, refer to the Readme.txt file on the CD-ROM.

  2. Click Sample Exam Questions on the user interface menu and then select the exercise file you want to view.

The Microsoft Certified Professional Program

The Microsoft Certified Professional (MCP) program provides the best method to prove your command of current Microsoft products and technologies. Microsoft, an industry leader in certification, is on the forefront of testing methodology. Our exams and corresponding certifications are developed to validate your mastery of critical competencies as you design and develop, or implement and support, solutions with Microsoft products and technologies. Computer professionals who become Microsoft certified are recognized as experts and are sought after industry-wide.

The Microsoft Certified Professional program offers multiple certifications, based on specific areas of technical expertise, including:

  • Microsoft Certified Application Developer (MCAD) for Microsoft .NET. Qualified to develop and maintain department-level applications, components, Web or desktop clients, or back-end data services.

  • Microsoft Certified Solution Developer (MCSD). Qualified to design and develop custom business solutions with Microsoft development tools, technologies, and platforms and Microsoft Windows architecture.

  • Microsoft Certified Professional (MCP). Demonstrated in-depth knowledge of at least one Microsoft Windows operating system or architecturally significant platform. An MCP is qualified to implement a Microsoft product or technology as part of a business solution for an organization.

  • Microsoft Certified Systems Engineer (MCSE) on Windows 2000. Qualified to effectively analyze the business requirements and design and implement the infrastructure for business solutions based on the Microsoft Windows 2000 platform and Microsoft .NET Enterprise Servers.

  • Microsoft Certified Systems Administrator (MCSA) on Microsoft Windows 2000. Individuals who implement, manage, and troubleshoot existing network and system environments based on the Microsoft Windows 2000 and Windows .NET Server operating systems.

  • Microsoft Certified Database Administrator (MCDBA) on Microsoft SQL Server 2000. Individuals who derive physical database designs, develop logical data models, create physical databases, create data services by using Transact-SQL, manage and maintain databases, configure and manage security, monitor and optimize databases, and install and configure Microsoft SQL Server.

  • Microsoft Certified Trainer (MCT). Instructionally and technically qualified to deliver Microsoft Official Curriculum through a Microsoft Certified Technical Education Center (CTEC).

Microsoft Certification Benefits

Microsoft certification, one of the most comprehensive certification programs available for assessing and maintaining software-related skills, is a valuable measure of an individual' s knowledge and expertise. Microsoft certification is awarded to individuals who have successfully demonstrated their ability to perform specific tasks and implement solutions with Microsoft products. Not only does this provide an objective measure for employers to consider, it also provides guidance for what an individual should know to be proficient. And as with any skills-assessment and benchmarking measure, certification brings a variety of benefits: to the individual, and to employers and organizations.

Microsoft Certification Benefits for Individuals

As a Microsoft Certified Professional, you receive many benefits:

  • Industry recognition of your knowledge and proficiency with Microsoft products and technologies.

  • A Microsoft Developer Network subscription. MCPs receive rebates or discounts on a one-year subscription to the Microsoft Developer Network (msdn.microsoft.com/subscriptions/) during the first year of certification. (Fulfillment details will vary, depending on your location; please see your Welcome Kit.)

  • Access to technical and product information direct from Microsoft through a secured area of the MCP Web site (go to http://www.microsoft.com/traincert/mcp/mcpsecure.asp/).

  • Access to exclusive discounts on products and services from selected companies. Individuals who are currently certified can learn more about exclusive discounts by visiting the MCP secured Web site (go to http://www.microsoft.com/traincert/mcp/mcpsecure.asp/ and select the “Other Benefits” link).

  • MCP logo, certificate, transcript, wallet card, and lapel pin to identify you as a Microsoft Certified Professional (MCP) to colleagues and clients. Electronic files of logos and transcript may be downloaded from the MCP secured Web site (go to http://www.microsoft.com/traincert/mcp/mcpsecure.asp/) upon certification.

  • Invitations to Microsoft conferences, technical training sessions, and special events.

  • Free access to Microsoft Certified Professional Magazine Online, a career and professional development magazine. Secured content on the Microsoft Certified Professional Magazine Online Web site includes the current issue (available only to MCPs), additional online-only content and columns, an MCP-only database, and regular chats with Microsoft and other technical experts.

  • Discount on membership to PASS (for MCPs only), the Professional Association for SQL Server. In addition to playing a key role in the only worldwide, user-run SQL Server user group endorsed by Microsoft, members enjoy unique access to a world of educational opportunities (go to http://www.microsoft.com/traincert/mcp/mcpsecure.asp/).

An additional benefit is received by Microsoft Certified System Engineers (MCSEs):

  • A 50-percent rebate or discount off the estimated retail price of a one-year subscription to TechNet or TechNet Plus during the first year of certification. (Fulfillment details will vary, depending on your location. Please see your Welcome Kit.) In addition, about 95 percent of the CD-ROM content is available free online at the TechNet Web site (http://www.microsoft.com/technet/).

An additional benefit is received by Microsoft Certified System Database Administrators (MCDBAs):

  • A 50-percent rebate or discount off the estimated retail price of a one-year subscription to TechNet or TechNet Plus during the first year of certification. (Fulfillment details will vary, depending on your location. Please see your Welcome Kit.) In addition, about 95 percent of the CD-ROM content is available free online at the TechNet Web site (http://www.microsoft.com/technet/).

  • A one-year subscription to SQL Server Magazine. Written by industry experts, the magazine contains technical and how-to tips and advice—a must for anyone working with SQL Server.

A list of benefits for Microsoft Certified Trainers (MCTs) can be found at http://www.microsoft.com/traincert/mcp/mct/benefits.asp.

Microsoft Certification Benefits for Employers and Organizations

Through certification, computer professionals can maximize the return on investment in Microsoft technology. Research shows that Microsoft certification provides organizations with:

  • Excellent return on training and certification investments by providing a standard method of determining training needs and measuring results.

  • Increased customer satisfaction and decreased support costs through improved service, increased productivity, and greater technical self-sufficiency.

  • Reliable benchmark for hiring, promoting, and career planning.

  • Recognition and rewards for productive employees by validating their expertise.

  • Retraining options for existing employees so they can work effectively with new technologies.

  • Assurance of quality when outsourcing computer services.

Requirements for Becoming a Microsoft Certified Professional

The certification requirements differ for each certification and are specific to the products and job functions addressed by the certification.

To become a Microsoft Certified Professional, you must pass rigorous certification exams that provide a valid and reliable measure of technical proficiency and expertise. These exams are designed to test your expertise and ability to perform a role or task with a product, and are developed with the input of professionals in the industry. Questions in the exams reflect how Microsoft products are used in actual organizations, giving them “real-world” relevance.

  • Microsoft Certified Product candidates are required to pass one operating system exam. Candidates may pass additional Microsoft certification exams to further qualify their skills with other Microsoft products, development tools, or desktop applications.

  • Microsoft Certified Systems Engineers are required to pass five core exams and two elective exams.

  • Microsoft Certified Systems Administrators are required to pass three core exams and one elective exam that provide a valid and reliable measure of technical proficiency and expertise.

  • Microsoft Certified Database Administrators are required to pass three core exams and one elective exam that measures technical proficiency and expertise.

  • Microsoft Certified Solution Developers are required to pass three core Microsoft Windows operating system technology exams and one BackOffice technology elective exam.

  • Microsoft Certified Trainers are required to meet instructional and technical requirements specific to each Microsoft Official Curriculum course they are certified to deliver. The MCT program requires ongoing training to meet the requirements for the annual renewal of certification. For more information about becoming a Microsoft Certified Trainer, visit http://www.microsoft.com/traincert/mcp/mct/ or contact a regional service center near you.

Technical Training for Computer Professionals

Technical training is available in a variety of ways, with instructor-led classes, online instruction, or self-paced training available at thousands of locations worldwide.

Self-Paced Training

For motivated learners who are ready for the challenge, self-paced instruction is the most flexible, cost-effective way to increase your knowledge and skills.

A full-line of self-paced print and computer-based training materials is available direct from the source—Microsoft Press. Microsoft Official Curriculum courseware kits from Microsoft Press are designed for advanced computer system professionals and are available from Microsoft Press and the Microsoft Developer Division. Self-paced training kits from Microsoft Press feature print-based instructional materials, along with CD-ROM-based product software, multimedia presentations, lab exercises, and practice files. The Mastering Series provides in-depth, interactive training on CD-ROM for experienced developers. They' re both great ways to prepare for Microsoft Certified Professional (MCP) exams.

Online Training

For a more flexible alternative to instructor-led classes, turn to online instruction. It' s as near as the Internet and it' s ready whenever you are. Learn at your own pace and on your own schedule in a virtual classroom, often with easy access to an online instructor. Without ever leaving your desk, you can gain the expertise you need. Online instruction covers a variety of Microsoft products and technologies. It includes options ranging from Microsoft Official Curriculum to choices available nowhere else. It' s training on demand, with access to learning resources 24 hours a day. Online training is available through Microsoft Certified Technical Education Centers.

Microsoft Certified Technical Education Centers

Microsoft Certified Technical Education Centers (CTECs) are the best source for instructor-led training that can help you prepare to become a Microsoft Certified Professional. The Microsoft CTEC program is a worldwide network of qualified technical training organizations that provide authorized delivery of Microsoft Official Curriculum courses by Microsoft Certified Trainers to computer professionals.

For a listing of CTEC locations in the United States and Canada, visit the Web site at http://www.microsoft.com/traincert/ctec/.

Technical Support

Every effort has been made to ensure the accuracy of this book and the contents of the companion disc. If you have comments, questions, or ideas regarding this book or the companion disc, please send them to Microsoft Press using either of the following methods:

E-mail: | TKINPUT@MICROSOFT.COM
Postal Mail: | Microsoft Press Attn: MCAD/MCSD Training Kit: Developing Web Applications with Microsoft Visual Basic .NET and Visual C# .NET Editor One Microsoft Way Redmond, WA 98052-6399

The Microsoft Press Web site (http://www.microsoft.com/mspress/support/) provides corrections for books. Please note that product support is not offered through this Web site. For further information regarding Microsoft software support options, please connect to http://www.microsoft.com/support/ or call Microsoft Support Network Sales at (800) 936-3500.

For information about ordering the full version of any Microsoft software, please call Microsoft Sales at (800) 426-9400 or visit http://www.microsoft.com.


Introduction to Web Programming

Chapter 1
Introduction to Web Programming
Lesson 1: Types of Applications
Lesson 2: Using ASP.NET
Lesson 3: Using Visual Studio .NET
About This Chapter

In this chapter, you will learn about Internet applications and the tools you use to create them. This chapter introduces you to the concepts and terms used throughout this book, including how Web applications work, the parts of a Web application, how the Microsoft .NET Framework is organized, and how to use the Microsoft Visual Studio .NET programming environment. The sections in this chapter will provide a high-level overview of concepts that are explained in greater detail in subsequent chapters.

Before You Begin

To complete the lessons in this chapter, you must have

  • Installed Visual Studio .NET on your computer

  • Internet access through a local area network (LAN), broadband, or modem connection


Lesson 1: Types of Applications

Web applications are one of four types of Internet applications that you can create using Visual Studio .NET and Microsoft ASP.NET. In this lesson, you will learn a little about the different types of Internet applications and get an overview of how a Web application works.

After this lesson, you will be able to

  • Describe four different types of Internet applications and know where to look for training on developing each type of application

  • Explain how a Web application executes over the Internet and how that differs from a traditional, static Web site

  • Understand the role that ASP.NET plays in creating Web applications

  • List the parts that make up ASP.NET and describe some of its advantages over other Web application technologies, such as the Common Gateway Interface (CGI)

Estimated lesson time: 5 minutes
What Can You Create?

Strictly speaking, an Internet application is any application that uses the Internet in any way. That means applications that request users register over the Internet or that provide Help through the Internet are, to some degree, Internet applications.

That definition is too broad for the subject of a single book. To narrow the focus a bit, let' s identify four types of Internet applications:

  • Web applications. These provide content from a server to client machines over the Internet. Users view the Web application through a Web browser, such as Microsoft Internet Explorer or Netscape Navigator.

  • Web services. These components provide processing services from a server to other applications over the Internet.

  • Internet-enabled applications. These are stand-alone applications that incorporate aspects of the Internet to provide online registration, Help, updates, or other services to the user over the Internet.

  • Peer-to-peer applications. These are stand-alone applications that use the Internet to communicate with other users running their own instances of the application.

You can use Visual Studio .NET to create each of these types of applications. The first type, Web applications, is the subject of this book. Table 1-1 shows the Visual Studio .NET Help topics and the books in this series that deal with each type of Internet applications.

Table 1-1Sources of Information about Internet Applications
Application type | Use these topics in online Help | Prepare for the MCSD test using
Web applications | ASP.NET, Web Forms, System.Web namespace | This book.
Web services | ASP.NET, XML Web Services, System.Web.Services namespace | MCSD Training Kit: Developing Web Services and Server Components
Internet-enabled applications | Windows forms, HTML Help, WebBrowser control, System.Net namespace | MCSD Training Kit: Developing Windows Applications with the Microsoft .NET Framework or Desktop Applications with Microsoft Visual C++ 6.0 MCSD Training Kit
Peer-to-peer applications | Accessing the Internet, pluggable protocols, System.Net.Sockets namespace | MCSD Training Kit: Developing Web Services and Server Components
How Web Applications Work

Web applications use a client/server architecture. The Web application resides on a server and responds to requests from multiple clients over the Internet, as shown in Figure 1-1.

Figure 1-1Web application architecture

On the client side, the Web application is hosted by a browser, such as Internet Explorer or Netscape Navigator. The application' s user interface takes the form of Hypertext Markup Language (HTML) pages that are interpreted and displayed by the client' s browser.

On the server side, the Web application runs under Microsoft Internet Information Services (IIS). IIS manages the application, passes requests from clients to the application, and returns the application' s responses to the client. These requests and responses are passed across the Internet using Hypertext Transport Protocol (HTTP). A protocol is a set of rules that describe how two or more items communicate over a medium, such as the Internet. Figure 1-2 shows how the client and server interact over the Internet.

Figure 1-2Client/server interaction in a Web application

The Web application composes responses to requests from resources found on the server. These resources include the executable code running on the server (what we traditionally think of as the “application” in Microsoft Windows programming), Web Forms, HTML pages, image files, and other media that make up the content of the application.

Web applications are much like traditional Web sites, except that the content presented to the user is actually composed dynamically by executable, rather than being served from a static page stored on the server. Figure 1-3 shows how a Web application composes the HTML returned to a user.

Figure1-3 A Web application response from server resources

The executable portion of the Web application enables you to do many things that you cannot do with a static Web site, such as:

  • Collect information from the user and store that information on the server.

  • Perform tasks for the user such as placing an order for a product, performing complex calculations, or retrieving information from a database.

  • Identify a specific user and present an interface that is customized for him or her.

  • Present content that is highly volatile, such as inventory, pending order, and shipment information.

This is only a partial list. Basically, you can do anything with a Web application that you can imagine doing with any client/server application. What makes a Web application special is that the client/server interaction takes place over the Internet.

What ASP.NET Provides

ASP.NET is the platform that you use to create Web applications and Web services that run under IIS. ASP.NET is not the only way to create a Web application. Other technologies, notably the CGI, also enable you to create Web applications. What makes ASP.NET special is how tightly it is integrated with the Microsoft server, programming, data access, and security tools.

ASP.NET provides a high level of consistency across Web application development. In a way, it is similar to the level of consistency that Microsoft Office brought to desktop applications. ASP.NET is part of the .NET Framework and is made up of several different components.

  • Visual Studio .NET Web development tools. These include visual tools for designing Web pages and application templates, project management, and deployment tools for Web applications.

  • The System.Web namespaces. These are part of the .NET Framework, and include the programming classes that deal with Web-specific items such as HTTP requests and responses, browsers, and e-mail.

  • Server and HTML controls. These are the user-interface components that you use to gather information from and provide responses to users.

In addition to the preceding components, ASP.NET also uses the following, more general programming components and Windows tools. These items aren' t part of ASP.NET. However, they are key to ASP.NET programming.

  • Microsoft Internet Information Services (IIS). As mentioned in the previous section, IIS hosts Web applications on the Windows server.

  • The Microsoft Visual Basic .NET, Microsoft Visual C#, and JScript programming languages. These three languages have integrated support in Visual Studio .NET for creating Web applications.

  • The .NET Framework. This is the complete set of Windows programming classes; they include the ASP.NET classes as well as classes for other programming tasks such as file access, data type conversion, array and string manipulation, and so on.

  • ADO.NET database classes and tools. These components provide access to Microsoft SQL Server and ODBC databases. Data access is often a key component of Web applications.

  • Microsoft Application Center Test (ACT). This Visual Studio .NET component provides an automated way to stress-test Web applications.

ASP.NET is the most complete platform for developing Web applications that run under IIS. However, it is important to remember that ASP.NET is not platform-independent. Because it is hosted under IIS, ASP.NET must run on Windows servers. To create Web applications that run on non-Windows/IIS servers, such as Linux/Apache, you must use other tools—generally CGI.

Advantages of ASP.NET

ASP.NET has many advantages over other platforms when it comes to creating Web applications. Probably the most significant advantage is its integration with the Windows server and programming tools. Web applications created with ASP.NET are easier to create, debug, and deploy because those tasks can all be performed within a single development environment—Visual Studio .NET.

ASP.NET delivers the following other advantages to Web application developers:

  • Executable portions of a Web application compiled so they execute more quickly than interpreted scripts

  • On-the-fly updates of deployed Web applications without restarting the server

  • Access to the .NET Framework, which extends the Windows API

  • Use of the widely known Visual Basic programming language, which has been enhanced to fully support object-oriented programming

  • Introduction of the new Visual C# programming language, which provides a type-safe, object-oriented version of the C programming language

  • Automatic state management for controls on a Web page (called server controls) so that they behave much more like Windows controls

  • The ability to create new, customized server controls from existing controls

  • Built-in security through the Windows server or through other authentication/authorization methods

  • Integration with Microsoft ADO.NET to provide database access and database design tools from within Visual Studio .NET

  • Full support for Extensible Markup Language (XML), cascading style sheets (CSS), and other new and established Web standards

  • Built-in features for caching frequently requested Web pages on the server, localizing content for specific languages and cultures, and detecting browser capabilities


Lesson 2: Using ASP.NET

In this lesson, you will learn how ASP.NET organizes a Web application into parts, and you will learn the roles and names of those parts. You will be introduced to Web Forms, which are the central user-interface element of Web applications.

ASP.NET is part of the larger .NET Framework, so this lesson will also discuss how the .NET Framework is organized and how .NET applications run differently from the traditional Windows applications you might be used to.

Finally, this lesson ends with a discussion of the programming languages you can use to create Web applications. ASP.NET is not bound to any one programming language, and the end of this lesson lists some of the other available languages and explains some of the major differences between the two languages (Visual Basic .NET and Visual C#) featured in this book.

After this lesson, you will be able to

  • List the parts of a Web application and describe how they run on the server

  • Explain how a Web form differs from and is similar to both an HTML page and a Windows form

  • Describe some of the different components you can place on a Web form

  • Explain the parts of the .NET Framework and how the common language runtime (CLR) executes .NET applications

  • Understand how the .NET Framework is organized and know where to look for classes that handle common application programming tasks

  • Compare the Visual Basic .NET and Visual C# programming languages

Estimated lesson time: 10 minutes
Parts of a Web Application

A Web application consists of three parts: content, program logic, and Web configuration information. Table 1-2 summarizes these parts and gives examples of where they reside in an ASP.NET Web application.

Table 1-2 Parts of an ASP.NET Web Application
Part | Types of files | Description
Content | Web Forms, HTML, images, audio, video, other data | Content files determine the appearance of a Web application. They can contain static text and images as well as elements that are composed on the fly by the program logic (as in the case of a database query).
Program logic | Executable files, scripts | The program logic determines how the application responds to user actions. ASP.NET Web applications have a dynamic-link library (DLL) file that runs on the server, and they can also include scripts that run on the client machine.
Configuration | Web configuration file, Style sheets, IIS settings | The configuration files and settings determine how the application runs on the server, who has access, how errors are handled, and other details.

The Web form is the key element of a Web application. A Web form is a cross between a regular HTML page and a Windows form: It has the same appearance and similar behavior to an HTML page, but it also has controls that respond to events and run code, like a Windows form.

In a completed Web application, the executable portion of the Web form is stored in an executable (.dll) that runs on the server under the control of IIS. The content portion of the Web form resides in a content directory of the Web server, as shown in Figure 1-4.

Figure 1-4 Web application parts on a Web server

When a user navigates to one of the Web Forms pages from his or her browser, the following sequence occurs:

  1. IIS starts the Web application' s executable if it is not already running.

  2. The executable composes a response to the user based on the content of the Web Forms page that the user requested and any program logic that provides dynamic content.

  3. IIS returns the response to the user in the form of HTML.

Once the user gets the requested Web form, he or she can enter data, select options, click buttons, and use any other controls that appear on the page. Some controls, such as buttons, cause the page to be posted back to the server for event processing and the sequence repeats itself, as shown in Figure 1-5.

Figure 1-5 How the parts interact

This cycle of events is described in greater detail in Lesson 2 of Chapter 2, “Creating Web Forms Applications.”

Web Form Components

Web Forms can contain several different types of components, as summarized in Table 1-3.

Table 1-3Components on a Web Form
Component | Examples | Description
Server controls | TextBox, Label, Button, ListBox, DropDownList, DataGrid | These controls respond to user events by running event procedures on the server. Server controls have built-in features for saving data that the user enters between page displays. You use server controls to define the user interface of a Web form.
HTML controls | Text area, Table, Image, Submit Button, Reset Button | These represent the standard visual elements provided in HTML. HTML controls are useful when the more complete feature set provided by server controls is not needed.
Data controls | SqlConnection, SqlCommand, OleDbConnection, OleDbCommand, DataSet | Data controls provide a way to connect to, perform commands on, and retrieve data from SQL and OLE databases and XML data files.
System components | FileSystemWatcher, EventLog, MessageQueue | These components provide access to various system-level events that occur on the server.

You use the server and HTML controls to create the user interface on a Web form. The data controls and system components appear on the Web form only at design time to provide a visual way for you to set their properties and handle their events. At run time, data controls and system components do not have a visual representation. Figure 1-6 shows a Web form containing components.

Figure 1-6 A Web form with components

Chapter 4, “Creating a User Interface,” provides more detail about using server and HTML controls on a Web form.

The .NET Framework

ASP.NET is an important part of the .NET Framework, but it is just one part. Understanding what else the .NET Framework provides will help you program your ASP.NET application effectively and avoid writing new code to perform tasks that are already implemented within the .NET Framework.

First, a little background. The .NET Framework is the new Microsoft programming platform for developing Windows and Web software. It is made up of two parts:

  • An execution engine called the common language runtime (CLR)

  • A class library that provides core programming functions, such as those formerly available only through the Windows API, and application-level functions used for Web development (ASP.NET), data access (ADO.NET), security, and remote management

.NET applications aren' t executed the same way as the traditional Windows applications you might be used to creating. Instead of being compiled into an executable containing native code, .NET application code is compiled into Microsoft intermediate language (MSIL) and stored in a file called an assembly. At run time, the assembly is compiled to its final state by the CLR. While running, the CLR provides memory management, type-safety checks, and other run-time tasks for the application. Figure 1-7 shows how this works.

Figure 1-7 How a .NET application runs

Applications that run under the CLR are called managed code because the CLR takes care of many of the tasks that would have formerly been handled in the application' s executable itself. Managed code solves the Windows programming problems of component registration and versioning (sometimes called DLL Hell) because the assembly contains all the versioning and type information that the CLR needs to run the application. The CLR handles registration dynamically at run time, rather than statically through the system registry as is done with applications based on the Common Object Model (COM).

The .NET class library provides access to all the features of the CLR. The .NET class library is organized into namespaces. Each namespace contains a functionally related group of classes. Table 1-4 summarizes the .NET namespaces that are of the most interest to Web application programmers.

Table 1-4A Summary of the .NET Framework Class Library
Category | Namespaces | Provides classes for
Common types | System | All the common data types, including strings, arrays, and numeric types. These classes include methods for converting types, for manipulating strings and arrays, and for math and random number tasks.
Data access | System.Data, System.Data.Common, System.Data.OleDb, System.Data.SqlClient, System.Data.SqlTypes | Accessing databases. These classes include methods for connecting to databases, performing commands, retrieving data, and modifying data.
Debugging | System.Diagnostics | Debugging and tracing application execution.
File access | System.IO, System.IO.IsolatedStorage, System.DirectoryServices | Accessing the file system. These include methods for reading and writing files and getting paths and filenames.
Network communication | System.Net, System.Net.Sockets | Communicating over the Internet using low-level protocols such as TCP/IP. These classes are used when creating peer-to-peer applications.
Security | System.Security, System.Security.Cryptography, System.Security.Permissions, System.Security.Policy, System.Web.Security | Providing user authentication, user authorization, and data encrypting.
Web applications | System.Web, System.Web.Caching, System.Web.Configuration, System.Web.Hosting, System.Web.Mail, System.Web.SessionState, System.Web.UI, System.Web.UI.Design, System.Web.UI.WebControls, System.Web.UI.HtmlControls | Creating client-server applications that run over the Internet. These are the core classes used to create ASP.NET Web applications.
Web services | System.Web.Services, System.Web.Services.Configuration, System.Web.Services.Description, System.Web.Services.Discovery, System.Web.Services.Protocols | Creating and publishing components that can be used over the Internet. These are the core classes used to create ASP.NET Web services.
Windows applications | System.Windows.Forms, System.Windows.Forms.Design | Creating applications using the Microsoft Windows user interface components. These classes provide Windows forms and controls as well as the ability to create custom controls.
XML data | System.Xml, System.Xml.Schema, System.Xml.Serialization, System.Xml.Xpath, System.Xml.Xsl | Creating and accessing XML files.

Since the .NET namespaces organize classes by function, you can use them to help locate the classes and class members that provide the CLR features you want to use. For example, the System namespace is one of the most commonly used namespaces because it contains the classes for all the fundamental data types. Any time you declare a variable with a numeric, string, or array type, you are using the System namespace.

This approach allows the .NET Framework to provide built-in methods for converting data types and manipulating strings and arrays. For instance, the following lines of code use the built-in methods of the String and Array classes to sort a list.

Visual Basic .NET
' Declare and initialize a string.
Dim strFruit As String = "oranges apples peaches kumquats nectarines mangos"
' Declare an array.
Dim arrFruit As String()
' Place each word in an array element
arrFruit = strFruit.Split(" ")
' Sort the array.
System.Array.Sort(arrFruit)
' Put the sorted data back in the string.
strFruit = String.Join(" ", arrFruit)
Visual C#
// Declare and initialize a string.
string strFruit = "oranges apples peaches kumquats nectarines mangos";
// Declare an array
string[] arrFruit;
// Place each word in an array element.
arrFruit = strFruit.Split(" ".ToCharArray());
// Sort the array.
System.Array.Sort(arrFruit);
// Put the sorted array back in the string.
strFruit = System.String.Join(" ", arrFruit);

Many of the class methods in the System namespace can be used directly without first creating an object from the class. These are called shared members in Visual Basic .NET and static members in Visual C#. Shared and static members can be called from the class name itself, as in the System.Array.Sort line in the preceding code. Another example of a class with shared/static members is the Math class, as shown by the following Pi and Pow methods:

Visual Basic .NET
' Get the area of a circle.
dblCircArea = System.Math.Pi * System.Math.Pow(intRadius, 2)
Visual C#
// Get the area of a circle.
dblCircArea = System.Math.PI * System.Math.Pow(intRadius, 2);

The .NET Framework provides about 100 different namespaces. Only about 40 of the most common ones are summarized in Table 1-4. For a complete list of the .NET Framework namespaces, see the topic titled “Class Library” in the Visual Studio .NET online Help.

Programming Languages

ASP.NET and, indeed, the whole .NET Framework are programming language–independent. That means you can choose any language that has implemented a CLR-compliant compiler. In addition to developing its own programming languages, Microsoft has formed partnerships with many language vendors to provide .NET support for Perl, Pascal, Eiffel, Cobol, Python, Smalltalk, and other programming languages.

This book covers creating Web applications with the Visual Basic .NET and the Visual C# programming languages. These two languages are functionally equivalent, which means that they each provide equal abilities to create Web applications. The differences between the two languages are syntactical and stylistic.

Most current programmers will choose the language they are most familiar with. Current Visual Basic programmers will be more comfortable developing Web applications in Visual Basic .NET; C or C++ programmers will be more comfortable developing with Visual C#.

If you are new to programming or if you are choosing to extend your programming skills to new languages, learning both Visual Basic .NET and Visual C# is a practical goal. This is especially true when you create Web applications, since most of the tasks are performed through the .NET Framework classes, which means Visual Basic .NET code and Visual C# code often look nearly identical.

Table 1-5 summarizes some significant differences between Visual Basic .NET and Visual C#. This information is useful to keep in mind if you are choosing a programming language for the first time or if you are planning to switch between languages.

Table 1-5 Visual Basic .NET and Visual C# Differences

Feature

Visual Basic .NET

Visual C# .NET

Case sensitive

Not case sensitive:

response.write("Yo") ' OK

Case sensitive:

response.write("Yo"); // Error Response.Write("Yo"); // OK

Functional blocks

Use beginning and ending statements to declare functional blocks of code:

Sub Show(strX as String)
  Response.Write(strX)
End Sub

Use braces to declare functional blocks of code:

void Show (string strX)
{
Response.Write(strX);
}

Type conversion

Implicit type conversions are permitted by default:

Dim intX As Integer
intX = 3.14  ' Permitted

You can limit conversions by including an Option Strict On statement at the beginning of modules.

Type conversions are performed explicitly by casts:

int intX;
intX = 3.14; // Error!
intX = (int)3.14; //Cast, OK.

Or, use type conversion methods:

string strX;
strX = intX.ToString();

Arrays

Array elements are specified using parentheses:

arrFruit(1) = "Apple"

Array elements are specified using square brackets:

arrFruit[1] = "Apple";

Methods

You can omit parentheses after method names if arguments are omitted:

strX = objX.ToString

You must include parentheses after all methods:

strX = objX.ToString();

Statement termination

Statements are terminated by carriage return:

Response.Write("Hello")

Statements are terminated by the semicolon (;):

Response.Write("Hello");

Statement continuation

Statements are continued using the underscore (_):

intX = System.Math.Pi * _
  intRadius

Statements continue until the semicolon (;) and can span multiple lines if needed:

intX = System.Math.PI *
  intRadius;

String operator

Use the ampersand (&) or plus sign (+) to join strings:

strFruit = "Apples" & _
  " Oranges"

Use the plus sign (+) to join strings:

strFruit = "Apples" +
  " Oranges";

Comparison operators

Use =, >, <, >=, <=, <> to compare values:

If intX >= 5 Then

Use ==, >, <, >=, <=, != to compare values:

if (intX >= 5)

Negation

Use the Not keyword to express logical negation:

If Not IsPostBack Then

Use the ! operator to express logical negation:

if (!IsPostBack)

Object comparison

Use the Is keyword to compare object variables:

If objX Is objY Then

Use == to compare object variables:

if (objX == objY)

Object existence

Use the Nothing keyword or the IsNothing function to check if an object exists:

If IsNothing(objX) Then

Use the null keyword to check if an object exists:

if (objX == null)

In addition to the differences shown in Table 1-5, there are significant keyword differences between the two languages. The code examples throughout this book illustrate those differences. The Visual Studio .NET Help topic “Language Equivalents” provides a complete comparison of Visual Basic .NET, Visual C#, and other Microsoft languages.


Lesson 3: Using Visual Studio .NET

The Visual Studio .NET programming environment presents new window types, new ways to manage those windows, and new integration with Internet content. This lesson offers a tour of these new features as well as an overview of some of the older Visual Studio .NET debugging and Help features presented from a Web application–programming viewpoint.

If you' ve programmed with previous versions of Visual Studio .NET and feel like skipping this lesson, be aware that you can no longer make changes to an application while debugging without restarting the application. That particular Visual Studio .NET feature, called edit-and-continue, is no longer available in Visual Basic .NET or Visual C#.

After this lesson, you will be able to

  • Use the Start page to open new or existing projects, get current product information, and set environment preferences

  • List the two different Visual Studio .NET window types and use the Auto Hide feature to make the most out of screen space for editing documents

  • Cut and paste items using the Clipboard Ring on the Toolbox

  • Edit Web forms and HTML pages visually or in HTML

  • Write code using the Code Editor' s automated features and modify Visual Studio .NET settings to turn those features on or off

  • Build, run, and debug applications using Visual Studio .NET

  • Get Help and set Help filters for your preferred programming language

Estimated lesson time: 30 minutes
The Start Page

When you start Visual Studio .NET, the first thing you see is the Start page, as shown in Figure 1-8. The Start page contains various panes to make information easier to find and to help simplify some common tasks, such as opening a recent file.

The Projects pane, shown in Figure 1-8, displays the four most recently saved projects in the form of hyperlinks. To open one of these recent projects, click the project name. To create a new project or to open an existing project not displayed in the recent projects list, click the appropriate button on the Projects pane.

To the left of the Start page is a list of other topics containing current information about Visual Studio .NET, other Microsoft products, programming, Web site hosting, and other information. If you click one of these topics, the Start page displays the topic, as shown in Figure 1-9.

Figure 1-8 The Visual Studio .NET Start page
Figure 1-9 Current Visual Studio .NET headlines

The information displayed in the Start page is dynamic, with much of the information coming from the Internet. This ensures that the information is current; it' s a good idea to check the Headlines and Downloads panes occasionally to get the latest news.

Of particular interest to Web application developers is the Web Hosting pane, shown in Figure 1-10. This pane links to Web sites that will host your ASP.NET Web applications on the Internet.

Figure 1-10 ASP.NET hosting services

Some of the sites, like Brinkster, offer limited free hosting. These hosting services are extremely useful when learning ASP.NET since they allow you to share your work with the world without the effort and expense of setting up your own Web server.

The My Profile pane of the Start page lets you set your preferences for Visual Studio .NET, as shown in Figure 1-11.

These options let you change the default window layout for Visual Studio .NET, set the programming language you most commonly use, and determine whether Help is displayed in the Visual Studio .NET design panes or in a window as a separate application. The Help window can get a little cramped when displayed within Visual Studio .NET, so unless you have a 19-inch monitor, it is a good idea to select the External Help option.

Figure 1- 11 Set your preferences in the My Profile pane
Visual Studio .NET Windows

Visual Studio .NET has two types of windows: Document windows and Tool windows. Document windows display the content of your application: the forms, Web pages, and code all appear in Document windows. You can have multiple Document windows open at once, and you can choose between them by clicking their tabs near the top of the screen, as shown in Figure 1-12.

Tool windows display the components you use to create your application. These components include the controls, database connections, classes, and properties you use in the project. Tool windows are displayed to the left and right of the Document windows and they can be set to slide in or out of view by clicking their tabs, as shown in Figure 1-13.

Figure 1-12 A Document window
Figure 1-13 The Toolbox window

To cause a tabbed Tool window to remain on screen, toggle the Auto Hide icon at the top right of the Tool window. The Auto Hide icon looks like a tiny pushpin. Click the pushpin again to cause the Tool window to return to tabbed display. You can use the tabbed display to hide the Tool windows on both sides of the Document window and provide more space for editing your application' s content, as shown in Figure 1-14.

Figure 1-14 Tabbed Tool windows around the Document window

The tabbed display of the Document and Tool windows is the default setting for Visual Studio .NET. You can turn off this feature to use a more traditional windowed display by choosing Options from the Tools menu, and then selecting your preferences from the dialog box shown in Figure 1-15.

Figure 1-15 The Options dialog box
The Toolbox

The Visual Studio .NET Toolbox displays the controls and components you can add to a Document window. The contents of the Toolbox change depending on the type of document you are currently editing. When you are editing a Web form, for example, the Toolbox displays the server controls, HTML controls, data controls, and other components that you can add to a Web form, as shown in Figure 1-16.

Figure 1-16 The Toolbox window

The components in the Toolbox are categorized as shown in Figure 1-16. When you click one of the categories, the Toolbox displays the items in that category. You can scroll through the items in the Toolbox by clicking the up and down arrows at the top and the bottom of the component list.

When the current document is code, the Toolbox contains only the Clipboard Ring, as shown in Figure 1-17. The Clipboard Ring keeps track of the last 20 items you have copied (Ctrl+C) or cut (Ctrl+X) so that you can paste them back into a document.

To paste an item from the Clipboard Ring, click the item and drag it to where you want to insert it. When you move the mouse pointer over an item in the Clipboard Ring, Visual Studio expands that item to show you more of the text it contains.

Figure 1-17 The Clipboard Ring in the Toolbox window
Editing Web Documents

You can edit Web forms and HTML documents visually by using the same drag-and-drop techniques that you use when editing Windows forms, or you can edit them as text files. To switch between edit modes, click the Design or HTML tabs at the bottom of the Document window, as shown in Figure 1-18.

Figure 1-18 A Web document in design mode

There is no way to do some tasks visually, so you will often need to edit Web documents as text. Using the HTML mode can also be more convenient than using the visual tools if you are already familiar with HTML. The IntelliSense technology in Visual Studio .NET provides help for completing HTML elements, as shown in Figure 1-19.

Figure 1-19 IntelliSense for HTML elements in Visual Studio .NET

You can switch back to Design mode to preview any changes you make in HTML mode simply by clicking on the Design tab at the bottom of the Document window.

Editing Code

Web Forms pages have code-behind files associated with them. These files are created automatically when you create a new Web form. They have the same base name as the Web form with the .vb or .cs filename extension added, as shown in Figure 1-20 and Figure 1-21.

Visual Studio .NET generates a class definition, initialization procedure, and Page_Load event procedure for each Web form' s code-behind file. You shouldn' t change the code in the regions marked Web Form Designer Generated Code, because that code might later be modified by Visual Studio .NET and your changes could be overwritten.

You can hide the generated code by clicking the minus sign (-) to the left of the #Region directive. Clicking the minus sign collapses the region into a single line and changes the minus sign to a plus sign (+), which you can click to expand the region again. You can use this same outlining feature to collapse or expand other blocks of code, such as class definitions and procedures.

Figure 1-20 A Web form' s code-behind file (Visual Basic .NET)
Figure 1-21A Web form's code-behind file (Visual C#)

The Visual Studio .NET Code Editor also provides completion through IntelliSense for keywords and class members that you use in code, as shown in Figure 1-22.

Figure 1-22 The autocomplete feature

If you are programming in Visual Basic, the autocomplete feature will also correct the capitalization of keywords and member names when you complete a line. If you are using Visual C#, however, Visual Studio .NET will not recognize a keyword or member name if it is not capitalized correctly. This is because Visual Basic .NET is not case sensitive, but Visual C# is.

The Visual Studio .NET Code Editor highlights syntax errors and undeclared variables as you complete each line. These errors are underlined with a squiggly line, and if you move the mouse pointer over the error, a description of the error is displayed, as shown in Figure 1-23.

You can turn most of the Code Editor' s automatic features on or off by changing the settings in the Options dialog box shown in Figure 1-15. You can also use the Options dialog box to change automatic indentation, code block completion, and other language-specific settings.

Figure 1-23 Error detection in the Code Editor
The Solution Explorer

Visual Studio .NET organizes applications into projects and solutions. A project is a collection of files that will ultimately make up a single executable. A solution is a group of projects that make up a single functional unit. You view the files in a solution by using the Solution Explorer, as shown in Figure 1-24.

Figure 1-24 The Solution Explorer

The project shown in bold is the start-up project. The start-up project is the project that runs when you click Start in Visual Studio .NET. When you' re developing multiple projects as part of a single solution, the start-up project usually calls the other projects in the solution.

Information about a solution is stored in a solution file (.sln), which is placed in your My Documents folder by default. You can open the solution using this file, or you can open projects directly using their project files (.vbproj or .csproj), which are placed in the project folders. If you open a project file, Visual Studio .NET creates a new solution file when you save the project.

Running a Project

You can run a project within Visual Studio .NET by clicking Start on the toolbar, by choosing Start from the Debug menu, or by pressing F5. When you run a project, Visual Studio .NET builds the project files and displays any errors that occur in the Task List window, as shown in Figure 1-25.

Figure 1-25 Running a project with build errors

Double-clicking the error description in the Task List selects the line with the error in the Document window so that you can correct the error.

If no errors occur during the build, Visual Studio .NET starts the application in debug mode and, in the case of a Web application, starts Internet Explorer, and displays the application' s start page. If an error occurs while the application is running in debug mode, Visual Studio .NET displays the error in the browser, as shown in Figure 1-26.

Figure 1-26 A Web Forms project with run-time errors

You have two choices at this point:

  • If you know what caused the error, you can stop the application by closing the browser window to return to Visual Studio .NET and correct the error shown.

  • If you are unsure of what caused the error, you can click Back in the browser, switch to Visual Studio .NET to set a breakpoint at a position in the code before the error occurred, and then switch back to the browser to try the task again. Visual Studio .NET will stop the application at the breakpoint you set so that you can step through the code to locate the source of the error.

Once you locate the error, you must stop the application before you can correct it. In previous versions of Visual Studio .NET, you could correct errors in debug mode and continue running the application.

Setting Breakpoints and Watching Variables

You can stop a project at a particular line of code by setting a breakpoint. When Visual Studio .NET runs the project, Visual Studio .NET will stop the project and display the line with the breakpoint in the Code Editor before that line executes, as shown in Figure 1-27.

Figure 1-27 A project stopped at a breakpoint

To set a breakpoint, click the gray margin to the left of the line you want to break at, or select the line and press F9. Figure 1-28 shows a breakpoint that has been set.

Figure 1-28 Setting a breakpoint

Once Visual Studio .NET stops at a breakpoint, you can view the value of active variables by moving the mouse pointer over the variable. If the variable is a complex type, such as an object or an array, you can view its data by adding it to the Watch window, as shown in Figure 1-29.

Figure 1-29 The Watch window

To add an item to the Watch window, select the item and drag it to the Watch window. Click the plus sign next to the item in the Watch window to view subitems, such as array elements or object properties.

Executing Statements

After you have stopped at a breakpoint, you can continue running your application by clicking Continue on the toolbar or by pressing F5. Alternately, you can execute one line at a time by pressing F10 or F11.

F10 executes each procedure call as a single statement. In other words, it steps over a procedure by executing it and stopping at the next line in the current procedure. F11 executes procedure calls by stepping in to the procedure and stopping at the first line in the called procedure. To execute a single line of code outside of the context of the project, type the code in the Command window. Figure 1-30 shows these different techniques.

The results of statements entered in the Command window are directed to the next line of the command window. For example, the statement ?System.Math.PI displays 3.1415926535897931 on the next line.

Figure 1-30 Ways to execute statements
Getting Help

Visual Studio .NET includes a combined collection of Help for the programming environment, languages, .NET Framework, technical support, and developer' s network articles. The Help is displayed either within a Document window or outside Visual Studio .NET in a separate window, depending on the preferences you set on the Start page or in the Options dialog box.

The Help system includes three ways to find topics: the Contents window, the Index window, and the Search window. These windows act like the Tool windows in Visual Studio .NET: they can be “docked” and then hidden or displayed using tabs, just as they can with the Toolbox, as shown in Figure 1-31.

Each of the navigation windows provides a Filter drop-down list that lets you choose a particular programming language or subject to look in. This feature is especially useful in the Search and Index windows because the combined Help collection is large. The Visual Basic And Related filter and the Visual C# And Related filter include most of the topics you need for this book.

Topics that include syntax or code samples have a language filter icon at the top of each page that looks like a funnel. Click the filter icon to change the programming language displayed in the topic or to view all language samples, as shown in Figure 1-32.

Figure 1-31 The Help navigation windows
Figure 1-32 Setting the language filter

In addition to the Help included with Visual Studio .NET, Microsoft hosts the GotDotNet Web site at www.gotdotnet.com. That Web site includes tutorials on using ASP.NET and contains links to many other related Web sites.


Summary

  • There are four types of Internet applications: Web applications, Web services, Internet-enabled applications, and peer-to-peer applications.

  • Web applications run on a server, processing user requests for pages and composing those pages using executable code and static resources on the server.

  • Web applications can provide dynamic content based on dynamic server resources, such as a database, and based on user inputs, such as creating a mortgage payoff table from user loan information.

  • ASP.NET is a platform for creating Web applications that run on Windows servers using IIS and the .NET Framework.

  • Web applications are made up of content, an executable, and configuration files.

  • The content of a Web application is presented through Web forms. Web forms use HTML components like conventional HTML pages; like Windows forms, however, they can also respond to user events such as mouse clicks.

  • The Web application' s executable is stored in a .dll file called an assembly. Assemblies are compiled to an intermediate state, and the final compilation is done by the CLR just before running the application.

  • The .NET Framework is made up of the CLR and the .NET class library. The .NET class library makes the CLR run-time tasks available to programmers.

  • The .NET classes are grouped by programming task into namespaces. These groupings help you locate the class, method, or property you need to accomplish a task.

  • Use the Visual Studio .NET Start page to view current product information, to open new or existing projects, to set user environment preferences, and to sign up for Web hosting services.

  • Edit Web Forms pages and HTML pages visually by using the Document window' s Design mode; edit them as text by using the Document window' s HTML mode.

  • Set the Help language filter to view code samples in a single programming language or in multiple languages.

  • Modify the Visual Studio .NET environment features using the Options dialog box.


Lab: Getting Started with Visual Studio .NET

In this lab, you will familiarize yourself with the Visual Studio .NET programming environment, sign up for Web application hosting, and view the ASP.NET QuickStart tutorials. These exercises establish a foundation for the specific programming skills you will learn in later chapters.

Estimated lesson time: 30 minutes
Exercise 1: Customize the Visual Studio .NET Environment

In this exercise, you will customize the window display in Visual Studio .NET to maximize the design area. You will also change your Visual Studio .NET settings to display Help in a separate, external window and specify a language filter within Help. When complete, the Visual Studio .NET environment will appear as shown in Figure 1-33.

Figure 1-33 The customized Visual Studio .NET environment

When you start Visual Studio .NET for the first time, the default window layout displays the Server Explorer and Toolbox windows as tabs on the left side of the screen, and the Solution Explorer and Properties windows on the right side of the screen.

To maximize the screen space for editing Web documents and code

  • From the Window menu, choose Auto Hide All.

    Or

  • Click Auto Hide in the upper right corner of the Solution Explorer and the Properties windows. The Auto Hide button looks like a pushpin.

When the Auto Hide feature is on, the window disappears when the mouse pointer moves off it. The window reappears when the mouse pointer moves over the window' s tab at the edge of the screen.

Another Visual Studio .NET default setting is to display Help within a Visual Studio .NET Document window. Help itself has quite a few windows, so this window-within-window display can become crowded.

To display Help in its own window

  1. From the Tools menu, choose Options. Visual Studio .NET displays the Options dialog box.

  2. On the Options dialog box, click Help under the Environment folder. Visual Studio .NET displays the Help options, as shown in Figure 1-34.

    Figure 1-34 Setting Help Options

  3. Click the External help option, and then click OK.

If you experiment with the Visual Studio .NET window layout and don' t like the results, you can restore the default window settings.

To restore the default window settings

  1. From the Tools menu, choose Options. Visual Studio .NET displays the Options dialog box.

  2. In the Options dialog box, under the Environment folder, select General.

  3. Click the Reset Window Layout button. Visual Studio .NET displays a warning asking you to confirm that you want to reset the window layout to the default.

  4. Click OK to clear the warning, and then click OK again to close the Options dialog box. Visual Studio .NET restores the default window layout.

Exercise 2: Setting Up a Web Hosting Account

In this exercise, you will set up a Web hosting account that will allow you to deploy Web applications for public testing and evaluation over the Internet. You don' t have to have a Web hosting account to complete the lessons in this book, since you can run and debug Web applications locally on your workstation. However, having a Web hosting account enables you to test how your Web application handles multiple simultaneous users and it also allows you to share your programming achievements with others, which is just plain fun.

To set up Web hosting

  1. Choose a hosting service provider.

  2. Register with the service.

  3. Sign on to the service and upload your application.

  4. To display the Web Hosting pane in Visual Studio .NET, select the Start page Document window, click Web Hosting, and then click the Hosting Services tab. The Web Hosting pane appears, as shown in Figure 1-35.

    Figure 1-35 ASP.NET Web application hosting services

The following sections describe the steps for setting up a Web hosting account in greater detail.

Exercise 3: Choosing a Service Provider

The Web Hosting pane lists a number of Web hosting service providers that support ASP.NET Web applications. Each of these providers offers a different combination of free and fee-based services. To evaluate which hosting service is right for you, visit the hosting service Web sites by clicking on their links on the Visual Studio Start Page. In addition to cost, you should consider the following:

  • Level of support. Does the hosting service provide a users' forum or other area where your questions can be answered? If it does, check it out to see what other users are saying.

  • Ability to migrate. Will you be able to easily move applications from a testing stage to full deployment? Does the hosting service provide the storage and performance you will need?

  • Database support. Most services provide SQL database hosting, but each service charges differently for this feature.

To register with a service provider
note | Register with the service to create your own account. The exact procedure varies for each service provider, but they all follow these basic steps.

  1. On the Web Hosting pane, click the Sign Up With link for the service provider you have chosen. Visual Studio .NET displays the service provider' s registration procedure in a Document window, as shown in Figure 1-36.

    Figure 1-36 ProTier Welcome page

  2. Follow the service provider' s instructions, which usually involve providing name, address, and e-mail information. When the information is complete, the service provider will e-mail you a user name and password to provide access to your new account.

The service providers shown on the Web Hosting pane all provide One-Click Hosting, which means that you can upload your completed Web applications directly from the Visual Studio .NET Web Hosting pane.

To upload an application to the hosting service

  1. From the Visual Studio .NET Web Hosting pane, click the Upload Directly To Your Account link for the service provider you signed up for. Visual Studio .NET displays the service' s logon page in a Document window, as shown in Figure 1-37.

    Figure 1-37 ProTier logon page

  2. On the logon page, enter the user name and password provided for your account. (Usually this is sent to you in e-mail after you sign up for the account.) Once you sign on, the service provider displays an upload page in a Document window, as shown in Figure 1-38.

    Figure 1-38 ProTier upload page

  3. From the list on the left, select the folder containing the application to upload. From the list on the right, select a folder on the server to upload the application to. Click Upload to upload the project to the hosting service.

After you' ve uploaded an application, you and others can view it by navigating to its location on the Internet. The Web hosting service providers don' t require users to sign on to view applications, just to upload them.

Exercise 4: Exploring the ASP.NET QuickStart Tutorials

In this exercise, you will install the .NET SDK Samples QuickStart Tutorials and view the QuickStart Tutorial for ASP.NET. The QuickStart Tutorials contain a host of information about the different aspects of the .NET Framework and is one of the best resources for learning how to program using ASP.NET.

To install and view the QuickStart tutorial for ASP.NET

  1. From the Windows Start menu, choose All Programs, choose Microsoft .NET Framework SDK, and select Samples And QuickStart Tutorials. Windows displays the Microsoft .NET Framework QuickStarts, Tutorials And Samples install page, as shown in Figure 1-39.

    Figure 1-39 The ASP.NET QuickStart Tutorial page

  2. Click the Step 1 Install The .NET Framework Samples Database hyperlink to install and configure the SQL database used by the QuickStart samples.

  3. Click the Step 2 Set Up The QuickStarts hyperlink to install and configure the Web sites used by the QuickStart samples.

  4. The next time you access the Microsoft .NET Framework QuickStart Tutorials from the Start menu, Internet Explorer skips the install page and takes you directly to the Microsoft .NET Framework SDK QuickStarts, Tutorials, And Samples page, as shown by Figure 1-40.

    Figure 1-40 The Microsoft .NET Framework QuickStarts, Tutorials and Samples install page

  5. Click the ASP.NET QuickStarts hyperlink to view the tutorial page, as shown in Figure 1-41.

    Figure 1-41 The Microsoft .NET Framework QuickStarts, Tutorials and Samples page

The ASP.NET QuickStart Tutorial demonstrates how to perform various ASP.NET programming tasks in Visual Basic .NET, Visual C# and JScript. They present Web Forms in HTML rather than in Design mode, as is done for most of this book.


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. Give three examples of how an ASP.NET Web application is different from a traditional Windows application.

  2. What are the two main parts of the .NET Framework?

  3. How do you restore the default window settings in Visual Studio .NET?

  4. Why doesn' t the Visual Studio .NET Code Editor automatically complete the following partial line of code (Visual C# users only)?

    int intX = system.math

  5. When can' t you use ASP.NET to create a Web application?


Creating Web Forms Applications

Chapter 2
Creating Web Forms Applications
Lesson 1: Creating a Web Application Project
Lesson 2: Responding to Events
Lesson 3: Where Does Processing Occur?
About This Chapter

In this chapter, you will learn how to create a Web application using Microsoft Visual Studio .NET and ASP.NET. You will learn how to start a new project, create a basic user interface, write code to respond to events, and preserve data within your application. Because ASP.NET is integrated with Microsoft Internet Information Services (IIS), you will also learn about IIS and how to use it to organize your Web application.

Before You Begin

To complete the lessons in this chapter, you must

  • Have installed Visual Studio .NET, Internet Information Services (IIS), and the Microsoft FrontPage Server Extensions on your computer.

  • Be familiar with the basics of using Visual Studio .NET, including how to add controls to a Windows form, how to add code to controls, and how to run an application within Visual Studio .NET. See the Quick Start tutorials included with Visual Studio .NET for instructions on these tasks.


Lesson 1: Creating a Web Application Project

Web forms are the application objects that define the user interface of your Web application. The text and controls you place on a Web form determine what a user sees when he or she runs your application. In this way, Web forms are similar to Windows forms in a Windows application.

Unlike Windows applications, however, Web applications run on a server and are distributed to clients through the Internet. Therefore, how you create and organize a Web application project has significant differences from working with Windows applications in Visual Studio .NET. These differences are explained in the following sections.

After this lesson, you will be able to

  • Start a Web Forms project in Visual Studio .NET

  • Set the physical location where your projects will be stored

  • Add server controls to a Web form and write code to respond to events on those controls

  • Run the Web application in the programming environment

  • Identify the purpose of the files in a Web Forms project

Estimated lesson time: 30 minutes
Creating a New Web Application

The first step in creating a Web Forms application is to start a new project in Visual Studio .NET. Visual Studio .NET provides templates for each type of application you can create. The template for a Web application is named ASP.NET Web Application. When you create a new project using this template, Visual Studio .NET creates a project file, a new, blank Web form, and other supporting files used by your application.

To create a Web application project from Visual Studio .NET, follow these steps:

  1. On the Visual Studio .NET Start Page, click New Project. Visual Studio .NET displays the New Project dialog box, as shown in Figure 2-1. Visual Studio .NET puts Web applications in the localhost virtual directory.

  2. In the New Project dialog box, select the ASP.NET Web Application template, type the name of the new project in the Location text box, and click OK.

When you create a Web application project in Visual Studio .NET, the programming environment creates a new folder and generates a set of files for the project. Visual Studio .NET gives the folder the same name as the project and puts the folder in the root folder of the default Web site that IIS hosts on your computer. This location is shown in the Location text box of the New Project dialog as http://localhost/projectname.

Figure 2-1 New Project dialog box
Organizing Your Projects with IIS

It' s important to realize that Web applications can exist only in a location that has been published by IIS as a virtual folder. A virtual folder is a shared resource identified by an alias that represents a physical location on a server. If you try to select a physical folder from the New Project dialog, such as c:\MyFiles, Visual Studio .NET disables the OK button. It' s not OK—you can' t create a Web application there!

The virtual folder named //localhost is the Web root folder on your computer. IIS determines the physical location of your Web root folder. By default, IIS installs it on your boot drive at \Inetpub\wwwroot. Rather than use this default and let it get cluttered with sample projects, production code, and other stuff, organize your projects by creating separate folders for samples, tests, and production code. Then, share those folders with the Web before you create new projects. Remember that Visual Studio .NET creates a new folder for each new project, so create only the folders to organize the types of projects you create.

Creating Virtual Folders to Organize Web Applications

Use IIS to create new virtual folders and to manage Web sites hosted on your computer. Creating a virtual folder for use with Visual Studio .NET requires two major tasks:

  • Creating the virtual folder. Virtual folders specify where your Web application projects are physically stored, so you use them to help organize your projects during development.

  • Adding the FrontPage Server Extensions to the virtual folder to create a subweb. A subweb is simply a virtual folder that contains a Web site. Adding the FrontPage Server Extensions to a virtual folder enables Visual Studio .NET to create and maintain Web applications in that folder.

Creating a Virtual Folder

To create a new virtual folder in IIS, follow these steps:

  1. Right-click the Default Web Site icon in IIS, and from the New menu click Virtual Directory, as shown in Figure 2-2.

    Figure 2-2 Create a new virtual folder

  2. IIS starts the Virtual Directory Creation Wizard to walk you through creating a new virtual folder. Click Next on the wizard title page to display the Virtual Directory Alias page, as shown in Figure 2-3.

    Figure 2-3 Virtual Directory Alias page

  3. Type an alias for the folder. The alias is name you will use to identify the resource in this folder. In Visual Studio .NET, this is the name you will use to specify the location of your project. Click Next. The wizard displays the Web Site Content Directory page, as shown in Figure 2-4.

    Figure 2-4 Web Site Content Directory page

  4. Type the path specification of the physical folder to associate with the virtual folder. This is the base folder where your project folders will be stored. Click Next. The wizard displays the Access Permissions page, as shown in Figure 2-5.

    Figure 2-5 Access Permissions page

  5. Keep the default permissions shown in Figure 2-5. Click Next and then click Finish to create the virtual folder and end the wizard.

Creating a Subweb

To add the FrontPage Server Extensions to a virtual folder, follow these steps:

  1. Right-click the localhost icon in IIS and select New Server Extensions Web from the pop-up menu.

  2. IIS starts the New Subweb Wizard to walk you through adding the FrontPage Server Extensions to your virtual folder. Click Next on the wizard title screen to display the Subweb Name page, as shown in Figure 2-6.

    Figure 2-6 Subweb Name page

  3. Type the name of the virtual folder in the Directory name text box. This name corresponds to the alias you entered in step 3 of the previous procedure. Type a description of the folder in the Title text box or leave it blank. Click Next. The wizard displays the Access Control page, as shown in Figure 2-7.

    Figure 2-7 Access Control page

  4. Accept the default access control settings by clicking Next, and then click Finish to create the subweb.

Creating a New Project in the Virtual Folder

After you create a new virtual folder and add the server extensions to it, you can use it within Visual Studio .NET to create new projects. To create a Web application project in your new virtual folder, choose New Project from the Visual Studio .NET File menu and specify the name of the virtual folder in the New Project dialog box, as shown in Figure 2-8.

The location you specify in the New Project dialog box takes the form http://servername/virtualfolder. The server name localhost indicates that the server is running on your development machine. The name virtualfolder is the alias you created for the virtual folder in the preceding tasks.

Figure 2-8 New Project dialog box
Adding Controls and Writing Code

When Visual Studio .NET creates a Web application, it displays a new Web form in the center window, as shown in Figure 2-9. Drag controls from the Toolbox to the Web form as you would with a Windows form.

Figure 2-9 A new Web form

To add code to respond to events on the Web form, double-click the control. Visual Studio .NET displays the Code window. Add your code to the event procedure provided for the control. For example, the following code displays “Hello, Web!” in Internet Explorer when you click a command button:

Visual Basic .NET
    Private Sub Button1_Click(ByVal sender As System.Object, _
         ByVal e As System.EventArgs) Handles Button1.Click
            Response.Write("Hello, Web!<br>")
    End Sub
Visual C#
    private void Button1_Click(object sender, System.EventArgs e)
    {
        Response.Write("Hello, Web!<br>");
    }

To run the application, press F5. Visual Studio .NET builds the application, starts the browser, and then displays the page in the browser. When you click Button1, the browser displays “Hello, Web!” as shown in Figure 2-10. Closing the browser ends the Web application.

Figure 2-10 The Web form

As you can see from this example, Web Forms applications are similar to Windows Forms applications. However, Web Forms applications have the following significant differences:

  • Tools. Web forms cannot use the standard Windows controls. Instead, they use server controls, HTML controls, user controls, or custom controls created specially for Web forms.

  • User interface. The appearance of a Web form is determined by the browser that displays it. Your Web application can appear in Internet Explorer, Netscape Communicator, or any other HTML-compliant browser. Different browsers (and different versions within browsers) support different HTML features, which can change the appearance and behavior of your Web forms. Don' t get too alarmed, though. Server controls handle most of these differences gracefully, as you' ll see in Chapter 4, “Creating a User Interface.”

  • Lifetime. Windows forms are instantiated, exist for as long as needed, and are destroyed. Web forms appear to behave this way, but in fact they are instantiated, sent to the browser, and destroyed immediately. This means that all of the variables and objects declared in a Web form are destroyed as soon as the Web form is displayed! To get anything interesting done, you need to save information in special state objects provided by ASP.NET. There' s more on state objects in Chapter 3, “Working with Web Objects.”

  • Execution. The executable portions of a Web application live on the Web server. In this way, Web applications are sort of the ultimate client/server application: The browser is the only software installed on the client side, and all of the user interface and business logic runs on the server. All communication between the client and the server occurs through HTML. This means that even sophisticated Web applications pose little security risk to clients and therefore pass through firewalls undisturbed. The physical structure of a Web application and where processing occurs are explained in more depth in Lesson 3.

The Files in a Web Forms Project

The Web form is only one of 11 files Visual Studio .NET generates when it creates a new Web forms project. Table 2-1 describes the purpose of each of these project files. Visual Studio .NET creates these different files for each new Microsoft Visual Basic Web application project. Only the files shown in bold are displayed in the Visual Studio .NET Solution Explorer.

Table 2-1 Web Forms Project Files
Filename | Contains
AssemblyInfo.AssemblyInfo.csvb | All of the build options for the project, including version, company name, GUID, compiler options, and so on.
Global.asax | The global events that occur in your Web application, such as when the application starts or ends. You can have only one Global.asax file per project and it exists in the root folder of the project.
Global.asax.vb Global.asax.cs | The code used in Global.asax. This file is not shown in the Solution Explorer.
Styles.css | The style definitions to use for the HTML generated by your project.
Web.config | The settings your Web server uses when processing this project. These settings determine how errors are reported, what type of user authentication to use, and so on. You can have only one Web.config file per project and it exists at the root folder of the project.
Projectname.disco | Descriptions of the Web Services that this project provides.
WebForm1.aspx | The visual description of a Web form.
WebForm1.aspx.vb WebForm1.aspx.cs | The code that responds to events on the Web form. By default, this file is not shown in the Solution Explorer.
WebForm1.aspx.resx | The XML resources used by the Web form. This file is not shown in the Solution Explorer.
Projectname.vbproj Projectname.csproj | The project file listing the files and settings used at design time. This file is not shown in the Solution Explorer.
Projectname .vbproj.webinfo Projectname .csproj.webinfo | The design-time Web preferences for the project. This file is not shown in the Solution Explorer.

In addition to the files listed in Table 2-1, Web application projects can contain any number of other file types. The primary types of files that you' ll add to a Web application project are shown in Table 2-2. Web application projects will often include these different types of source files.

Table 2-2 File Types for Web Forms Projects
File extension | Project item | Description
.aspx | Web form | Each Web form constitutes an ASP.NET Web page in your application. Applications can have one or many Web forms. Web forms have code files associated with them with the file extension .aspx.vb. Microsoft Visual C# forms have associated .aspx.cs files.
.htm | HTML Page | Web pages that don' t have server code can appear as HTML pages in your project.
.vb or .cs | Class or module | Code that defines objects in your application is stored in classes.
.ascx | Web user control | User controls that are built from other Web forms and server controls in Visual Studio .NET.
.xsd | DataSet | Creates an XML schema with the DataSet classes.
The Files in a Web Application

When you build a Web Forms project, Visual Studio .NET compiles all of the source code into an executable (DLL) and places that file in a /bin directory. The appearance portion of your application remains as .aspx and .html files. Figure 2-11 shows the files you deploy after you build your Web application.

Figure 2-11 Files in a Web application

In Figure 2-11, when a browser requests the start page (http://www.mysite.com/Hello.aspx) of the application, IIS starts the application' s executable (Hello.dll) and generates a response. In this case, the response is to display the page defined in Hello.aspx. If an event, such as a Button click, occurs on the page, it is sent back to the server, where it is handled by the application' s DLL.

The following two lessons cover the details of how events are handled and where processing occurs.


Lesson 2: Responding to Events

In this lesson, you will learn about events in the life cycle of a Web application and how this life cycle is different from the life cycle of a Windows application. Web application events occur at the application, page, and server control levels. The sequence of these events and how they are executed affect how you respond to them in code.

After this lesson, you will be able to

  • Understand the sequence of events in an application' s lifetime

  • Explain how the events in an application interact

  • List the events for each of the major objects in a Web application

  • Identify the three different types of server control events

  • Use state variables to preserve information in a Web application

Estimated lesson time: 35 minutes
Events in the Life Cycle of a Web Application

A Web application lives as long as it has active sessions, while Web forms live for barely a moment. The life of a Web application begins when a browser requests the start page of the application. (See Figure 2-12.) At that point, the Web server swings into action, starting the executable (DLL) that responds to that request. The executable creates an instance of the requested Web form, generates the HTML to respond to the request, and posts that response to the browser. It then destroys the instance of the Web form.

Figure 2-12 Life begins!

When the browser has the generated HTML, the user can type in text boxes, select option buttons, and perform other tasks until triggering a post-back event, such as a button click. Post-back events cause the browser to send the page' s data (view state) back to the server for event processing. When the server receives the view state, it creates a new instance of the Web form, fills in the data from the view state, and processes any events that occurred. (See Figure 2-13.) As soon as the server is done, it posts the resulting HTML back to the browser and destroys the instance of the Web form.

Figure 2-13 Life goes on.

When the user is all done and either closes the browser or goes off to some other Web site, the user' s session ends. (See Figure 2-14.) If there are no other sessions from other users, the application ends. This doesn' t always happen right away. ASP.NET manages memory using garbage collection rather than reference counting, as OLE did. Garbage collection means that the server periodically traces through the references between objects. When the server finds an object that is no longer used, it throws the object away and recovers the memory. This means that you don' t know exactly when an Application_End event will occur.

Figure 2-14 This is the end.
Preserving Data on a Web Form

Because Web forms have very short lifetimes, ASP.NET takes special steps to preserve the data entered in the controls on a Web form, as shown in Figure 2-15. Data entered in controls is sent with each request and restored to controls in Page_Init. The data in these controls is then available in the Page_Load event.

Figure 2-15 ASP.NET preserves Web form data

The data that ASP.NET preserves between requests is called the Web form' s view state. A Web form' s view state is available only within that Web form. To make data entered on a Web form available to other Web forms in an application, you need to save the data in a state variable in the Application or Session objects. These objects provide two levels of scope:

  • Application state variables. These are available to all users of an application. You can think of Application state as a multi-user global data. All sessions can read or write these variables.

  • Session state variables. These are available only to a single session (user). Session state is like global data in a standard Windows application. Only the current session has access to its Session state.

Application and Session state variables aren' t declared the way you declare regular variables. Rather, they are created on the fly in code. For example, the following code saves the number of button clicks in Session state:

Visual Basic .NET
Private Sub Button1_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles Button1.Click
    ' Save the number of clicks in Session state.
    Session("Clicks") = Session("Clicks") + 1
    ' Display the number of clicks.
    Response.Write("Number of clicks: " & Session("Clicks"))
End Sub
Visual C#
    // From Global.asax.cs
    protected void Session_Start(Object sender, EventArgs e)
    {
        // Initialize Clicks Session state variable.
        Session["Clicks"] = 0;
    }

    // From StateNEvents.asax.cs
    private void Button1_Click(object sender, System.EventArgs e)
    {
        // Increment click count.
        Session["Clicks"] = (int)Session["Clicks"] + 1;
        // Display the number of clicks.
        Response.Write("Number of clicks: " + Session["Clicks"] + "<br>");
    }

You can save any type of data in a state variable, from a simple integer to whole objects. Because state variables are global data, you need to develop strategies for working with them in your application. Managing state variables in your code is explained in depth in Chapter 3, “Working with Web Objects.”

important | Application state variables must be initialized in Visual C# before you perform most operations on them. For example, you need to assign a value to the Clicks state variable before performing the cast (int)Session["Clicks"]. Otherwise, you will receive the error “Value null was found where an instance of an object was required” at run time.
Application and Session Events

You can write code to respond to Application and Session events in the Global.asax file. Use Application events to initialize objects and data that you want to make available to all the current sessions of your Web application. Use Session events to initialize data that you want to keep throughout individual sessions, but that you don' t want to share between sessions. Table 2-3 lists each of the Application event handlers and describes when they occur.

Table 2-3 Application Event Handlers
Event handler name | Occurs when
Application_Start | The first user visits the start page of your Web application.
Application_End | There are no more users of the application.
Application_BeginRequest | At the beginning of each request to the server. A request happens every time a browser navigates to any of the pages in the application.
Application_EndRequest | At the end of each request to the server.
Session_Start | A new user visits the start page of your application.
Session_End | A user leaves your application, either by closing his or her browser or by timing out.

In Web forms, a session is a unique instance of the browser. A single user can have multiple instances of the browser running on his or her machine. If each instance visits your Web application, each instance has a unique session.

To see how Application and Session events occur, add the following code to the Global.asax file in a Web forms project:

Visual Basic .NET
    Sub Application_Start(ByVal Sender As Object, ByVal E As EventArgs)
        ' Record application start.
        Application("AppCount") = Application("AppCount") + 1
    End Sub

    Sub Session_Start(ByVal Sender As Object, ByVal E As EventArgs)
        ' Count sessions.
        Application("SessCount") = Application("SessCount") + 1
        ' Display session count.
        Response.Write("Number of applications: " & _
            Application("AppCount") & "<br>")
        ' Display session count.
        Response.Write("Number of sessions: " & _
            Application("SessCount") & "<br>")
    End Sub

    Sub Session_End(ByVal Sender As Object, ByVal E As EventArgs)
        ' Decrement sessions.
        Application("SessCount") = Application("SessCount") - 1
    End Sub
Visual C#
    protected void Application_Start(Object sender, EventArgs e)
    {
        // Create Application state variables.
        Application["AppCount"] = 0;
        Application["SessCount"] = 0;
        // Record application start.
        Application["AppCount"] = (int)Application["AppCount"] + 1;
    }

    protected void Session_Start(Object sender, EventArgs e)
    {
        // Count sessions.
        Application["SessCount"] = (int)Application["SessCount"] + 1;
        // Display session count.
        Response.Write("Number of applications: " +
          Application["AppCount"] + "<br>");
        // Display session count.
        Response.Write("Number of sessions: " +
          Application["SessCount"] + "<br>");
    }

    protected void Session_End(Object sender, EventArgs e)
    {
        // Decrement sessions.
        Application["SessCount"] = (int)Application["SessCount"] - 1;
    }

To demonstrate the events, run the preceding code, then start a new instance of the browser and navigate to the address. Each new instance of the browser increments the session count, but the application count stays at 1.

It' s important to realize that intrinsic objects such as Session and Response are not available at Application_Start. To use these objects, you have to wait until their creation event occurs.

Web Form Events

You use Web form events to process and maintain data used on a Web page, to respond to data binding, and to handle exceptions on the Web page. The events in Table 2-4 occur in the order shown. The Page_Load and Page_Error events are the event procedures you will most often deal with in code.

Table 2-4 Web Form Events
Event handler name | Occurs when
Page_Init | The server controls are loaded and initialized from the Web form' s view state. This is the first step in a Web form' s life cycle.
Page_Load | The server controls are loaded on the Page object. View state information is available at this point, so this is where you put code to change control settings or display text on the page.
Page_PreRender | The application is about to render the Page object.
Page_Unload | The page is unloaded from memory.
Page_Error | An unhandled exception occurs.
Page_AbortTransaction | A transaction is aborted.
Page_CommitTransaction | A transaction is accepted.
Page_DataBinding | A server control on the page binds to a data source.
Page_Disposed | The Page object is released from memory. This is the last event in the life of a Page object.

You can couple the Page_Load event with the IsPostback property to initialize data the first time a user visits a Web form. This is similar to a Session_Start event; however, it occurs at the page level rather than the application level. The following code initializes an object and stores it in the Session state the first time a page is viewed:

Visual Basic .NET
     ' Declare a new object.
    Dim FlashCard As New FlashCardClass()

    Private Sub Page_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
        ' If this is the first time the page is viewed...
        If Not (Page.IsPostBack) Then
            ' Shuffle the FlashCards.
            FlashCard.Shuffle()
            ' Store the object in a Session variable.
            Session("FlashCard") = FlashCard
        End If
        ' Get the Session FlashCard variable.
        FlashCard = Session("FlashCard")
        RefreshDisplay()
    End Sub
Visual C#
   // Declare a new object.
   FlashCardClass FlashCard = new FlashCardClass();

   private void Page_Load(object sender, System.EventArgs e)
   {
       if(!IsPostBack)
       {
                  // Shuffle the FlashCards.
               FlashCard.Shuffle();
               // Store the object in a Session variable.
               Session["FlashCard"] = FlashCard;
       }
      // Get the Session FlashCard variable.
       FlashCard = (FlashCardClass)Session["FlashCard"];
       RefreshDisplay();
   }

The other page events let you customize the appearance of the page and respond to data events. Data binding, transaction processing, and rendering are covered in greater detail in subsequent chapters.

Server Control Events

Server controls, such as a Button, TextBox, and DropDownList each have their own sets of events that occur in response to user actions. However, not all server control events are created equal. There are three types of server control events:

  • Post-back events. These events cause the Web page to be sent back to the server for immediate processing. Post-back events affect perceived performance because they trigger a round trip to the server.

  • Cached events. These events are saved in the page' s view state to be processed when a post-back event occurs.

  • Validation events. These events are handled on the page without posting back or caching. The validation server controls use these types of events.

Figure 2-16 shows the sequence of server control events on a Web form. The validations controls are evaluated before the page is posted back to the server. When the page is posted back, the Page_Init and Page_Load events are handled, then cached events are handled, and finally the event that caused the post-back is processed.

Figure 2-16 A Web form's sequence of events

The Button, Link Button, and Image Button controls all cause post-back events. The TextBox, DropDownList, ListBox, RadioButton, and CheckBox controls provide cached events; however, you can override this behavior by setting the AutoPostBack property to True.

To see how validation, cached, and post-back events interact, create a Web form with a TextBox, a RequiredFieldValidator, and a Button server control. Set the ControlToValidate property of the validator control to TextBox1, and then add the following code to the TextBox and Button event handlers:

Visual Basic .NET
    Private Sub Button1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Button1.Click
        Response.Write("Button Clicked!<br>")
    End Sub

    Private Sub TextBox1_TextChanged(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles TextBox1.TextChanged
        Response.Write("Text has changed!<br>")
    End Sub
Visual C#
    private void Button1_Click(object sender, System.EventArgs e)
    {
        Response.Write("Button Clicked!<br>");
    }

    private void TextBox1_TextChanged(object sender, System.EventArgs e)
    {
        Response.Write("Text has changed!<br>");
    }

If you leave the TextBox blank and click OK, the RequiredFieldValidator is displayed—no other events are processed and the page is not posted back to the server. If you type in the TextBox and click OK, the page is posted back and the TextChanged event occurs before the Click event.


Lesson 3: Where Does Processing Occur?

As you know from the previous lesson, Web applications run on a server. But Web applications aren' t like Windows applications, which start when the user invokes an executable file and end when the user closes the application. Instead, Web applications start when a browser requests a Web page and end when no more requests are made.

To handle this difference, Web applications use IIS to manage their processes on the server. In this lesson, you will learn how IIS determines which Web pages are part of a particular application, how the DLLs for applications are run on the server, and how IIS determines when to end an application.

After this lesson, you will be able to

  • Use folder structure to define the boundaries of an application

  • Understand the three options for how IIS runs your application

  • Explain how IIS settings affect performance and stability

  • Consider folder structure and processing options as issues in application development

  • Set the time-out value that indirectly determines when an application ends

Estimated lesson time: 15 minutes
Setting Application Boundaries

IIS defines a Web application as any file that is executed within a set of folders on your Web site. The boundaries of a Web application are determined by its folder structure. The application boundary starts in the folder containing the start page of the application and it ends at the last subordinate folder. Figure 2-17 shows several separate applications.

Figure 2-17 Web application boundaries in IIS

When Visual Studio .NET creates a new Web application project, it creates a new folder for the project and identifies the first Web form in the project (Webform1.aspx) as the start page. A start page is a page designated by Visual Studio as the first page to display in your application. All of the subordinate folders, such as /bin, are part of that application.

If your application design changes during development, or if you just want to quickly run the current page during debugging, you might want to change the start page of your application.

To set a different Web form or HTML page as the start page, follow these steps:

  1. In the Project window, select the Web form or HTML page you want to specify as the start page of your application.

  2. From the Project menu, choose Web Project, and then select Set As Start Page from the subordinate menu. Visual Studio .NET will now use the selected page as the first page it displays for the application.

Each project you create has its own start page, and is therefore a unique application. If you want to combine multiple projects into one Web application, you can organize them using IIS.

To include a subordinate Web application as part of a single, larger application, follow these steps:

  1. From IIS, select the subordinate Web application to include as part of the containing application.

  2. From the Action menu, choose Properties. IIS displays the application' s Properties dialog box, as shown in Figure 2-18.

    Figure 2-18 The IIS Properties dialog box

  3. In the Properties dialog box, click Remove. This removes the application' s starting point and therefore includes the application as part of the application that is the next level up in the folder hierarchy. Click OK to make the change and return to IIS.

Similarly, you might sometimes want to break large, multi-folder applications into smaller applications because of design changes.

To break a multi-folder application into smaller applications, follow these steps:

  1. From IIS, select the subordinate folder to specify as a new Web application.

  2. From the Action menu, choose Properties. IIS displays the application' s Properties dialog box.

  3. Click Create to make the selected folder an application starting point. Click OK to make the change and return to IIS.

Changing application boundaries can affect Application state variables. It' s important to realize that all of the DLLs within an application boundary have access to the same Application state. This might cause inadvertent interaction between DLLs if they are not developed with this in mind.

Managing Processes

Setting application boundaries in IIS is important because IIS uses these boundaries to determine how your application is run on the server. IIS gives you three options for how the server will run your Web application:

  • In-process with IIS (Inetinfo.exe). This option increases performance because all calls are made in-process; however, it offers no protection. If an application fails, it can corrupt memory and affect Inetinfo.exe, as well as other applications running in-process.

  • Pooled with other Web application processes in DLLHost.exe. This is the default option and it provides a balance between protection and performance. If an application fails, it can affect other applications in the pool, but it will not affect Inetinfo.exe.

  • Isolated in its own instance of DLLHost.exe. Isolated applications are protected from affecting or being affected by problems in other applications. However, calls to other applications must cross process boundaries, and this affects performance.

To set how IIS runs your application, follow these steps:

  1. In IIS, select the folder containing the application starting point.

  2. From the Action menu, choose Properties. IIS displays the application' s Properties dialog box, as shown previously in Figure 2-18.

  3. Select the appropriate setting from the Application Protection drop-down list. Click OK to make the change and return to IIS.

To ensure that your server is reliable, run your applications with Pooled or Isolated protection. The In-Process option is mostly an artifact of previous versions of IIS, which provided only one process model.

The Pooled option is the default for new applications, and this is the option you will use for most of your Web applications because it allows applications to access other components in the pool in-process rather than requiring calls to be marshaled for out-of-process access.

The Isolated option provides the most protection from errant applications; however, it requires calls to other applications to be marshaled, which harms performance. There is also a practical limit of 10 isolated processes per server, so use Isolated protection for applications that are critical to your business.

Determining When an Application Ends

When a user requests a page from a Web application for the first time, IIS starts the Web application, if it is not already running, and creates a session for that user. All of the subsequent requests made by that user are part of the same session. Sessions are important for two reasons:

  • They allow ASP.NET to keep user-specific data called the Session state. Session state and Application state are explained in Lesson 2.

  • They determine when the application ends. When the last session ends, IIS ends the application.

How long a session lasts is determined by a time-out value set in your project' s Web.config file. The default is 20 minutes. That means that 20 minutes after a user makes his or her last request to an application, the user' s session ends. When there are no more sessions, the application ends.

Remember that IIS defines a Web application as any file that is executed within a set of folders on your Web site. So if a user requests any of the .aspx or .htm files in that application' s set of folders within 20 minutes, that user' s session is maintained.

note | You have only indirect control over when an application ends through the session timeout. It is important to understand this because it controls the life cycle of the application on the server.

Reducing the session time-out value can free up resources on the server because session data expires more quickly. However, setting the value too low can cause sessions to time out while they are still being used, which will probably cause errors in the application.

To change the session time-out value, follow these steps:

  1. Open the project in Visual Studio .NET.

  2. Open the Web.config file by double-clicking it in the Solution Explorer window.

  3. Edit the time-out value (shown in bold) in the following line in the file:

    <sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424"
sqlConnectionString="data source=127.0.0.1;user id=sa;password="
cookieless="false" timeout="20" />

  4. Save the file.


Summary

  • Web applications use Web forms to create a user interface that is presented through an Internet browser on the user' s computer.

  • The code and resources that respond to events and perform useful tasks reside and run on the Web server hosting the application.

  • Because Web applications are distributed between a client and a server, there are four significant differences between programming for the Web and programming for Windows:

    • Web applications use server controls and HTML controls rather than Windows controls.

    • Web applications are displayed in a Web browser rather than in their own window.

    • Web forms are not persistent while displayed. You must preserve persistent data in a state variable during page and control events.

    • Processing occurs on the server and data is exchanged through a cycle of requests and responses.

  • Web applications are event-driven, and events occur at the application, page, and server control levels.

  • Server control events have three types, which occur in the following order:

    • Validation events occur before the page is returned to the server.

    • Cached events are collected while the page is displayed, and then processed once the page sends a request to the server.

    • Post-back events cause the page to send a request to the server, but their event procedure is processed last in order of events handled.

  • The boundaries of a Web application are determined by its folder structure.

  • Application boundaries affect the scope of data stored in Application state and they allow you to specify the process in which the server runs your application.

  • You use IIS to create root folders for your applications, set application boundaries, and determine the process in which IIS runs your application.


Lab: Building a Simple Web Application

In this lab, you will create the FlashCards application. FlashCards is a simple, one-form Web application that displays math problems and evaluates the entered result—just like the flash cards used in an elementary-school math class.

To complete the FlashCards application, you will create a Web form, add server controls to the form, and control those server controls from the Page_Load event procedure. The Web form will look like Figure 2-19 when complete.

Figure 2-19 The completed FlashCard Web form
Estimated lesson time: 20 minutes
Exercise 1: Creating the User Interface

In this exercise you will start the FlashCards project and create the user interface by adding server controls to the FlashCard Web form.

To create a new Web application project

  1. Open a new ASP.NET Web application project. In the New Project dialog box, enter the name FlashCards and click OK.

  2. In the Properties window, change the name of the WebForm1.aspx file to FlashCard.aspx.

To add the controls to the user interface

Add server controls to the Web form, as shown in Figure 2-19, and define the following properties:

Control | Control type | Property | Value
Label1 | Label | ID | lblFeedback
 |  | Font | Arial, Bold, XXL
 |  | Text | Flash Cards
Label2 | Label | ID | lblFirst
 |  | Font | Arial, Bold, XXL
Label3 | Label | ID | lblSecond
 |  | Font | Arial, Bold, XXL
<HR> | Horizontal Rule | Size | 4
 |  | Color | #000000
TextBox1 | Text Box | ID | txtAnswer
 |  | AutoPostBack | True
 |  | Font | Arial, Bold, XXL
tip | If you want to create several controls with similar properties, create the first control, set its properties as desired, and then copy and paste to create new controls with the same properties. In Web forms, this does not create a control array as it does in Windows forms.
Exercise 2: Create a Class Named FlashCardClass

In this exercise, you will create a class to contain the logic and data that the FlashCards application uses. A class is a definition of an object used to perform a task in a program. Chapter 3, “Working with Web Objects,” has more information about object-oriented programming.

The class FlashCardClass created here generates random math problems to display on the Web form. This is the logic that the application uses. In this application, and in most Web form applications, the logic (FlashCardClass) is separated from the user interface (the Web form).

To create a class

  1. From the Project menu, choose Add Class. Visual Studio .NET displays the Add New Item dialog box. Name the class FlashCardClass and click Open.

  2. In the Code window, add the following code:

    Visual Basic .NET
    Public Class FlashCardClass

    Dim mintFirstNumber, mintSecondNumber As Integer
    Dim mstrOp As String = "+"
    Dim mrndNumber As Random

    Public Sub New()
        ' Initialize the random number generator object.
        mrndNumber = New Random()
    End Sub

    Public Sub Shuffle(Optional ByVal Min As Integer = 1, _
            Optional ByVal Max As Integer = 12)
        ' Get random numbers.
        mintFirstNumber = mrndNumber.Next(Min, Max)
        mintSecondNumber = mrndNumber.Next(Min, Max)
    End Sub

    Public ReadOnly Property FirstNumber()
        Get
            FirstNumber = mintFirstNumber
        End Get
    End Property

    Public ReadOnly Property SecondNumber()
        Get
            SecondNumber = mintSecondNumber
        End Get
    End Property

    Public Property Operation() As String
        Get
            Operation = mstrOp
        End Get
        Set(ByVal Value As String)
            mstrOp = Value
        End Set
    End Property

     ' Calculates answer based on current operation.
    Public Function Answer() As Integer
        Select Case mstrOp
            Case "+"
                Answer = mintFirstNumber + mintSecondNumber
            Case "x", "*"
                Answer = mintFirstNumber * mintSecondNumber
            Case "-"
                Answer = mintFirstNumber - mintSecondNumber
        End Select
    End Function

End Class
    Visual C#
    public class FlashCardClass
{
    int mintFirstNumber, mintSecondNumber;
    string mstrOp = "+";
    Random mrndNumber;

    public FlashCardClass()
    {
          // Initialize the random number generator object.
    mrndNumber = new Random();
    }

    public void Shuffle(int Min, int Max)
    {
        // Get random numbers.
        mintFirstNumber = mrndNumber.Next(Min, Max);
        mintSecondNumber = mrndNumber.Next(Min, Max);
    }

    // Shuffle with no parameters defaults to Min = 0, Max = 12.
    public void Shuffle()
    {
        // Get random numbers.
        mintFirstNumber = mrndNumber.Next(0, 12);
        mintSecondNumber = mrndNumber.Next(0, 12);
    }

    public int FirstNumber
    {
        get
        {
            return mintFirstNumber;
        }
    }

    public int SecondNumber
    {
        get
        {
            return mintSecondNumber;
        }
    }

    public string Operation
    {
        get
        {
            return mstrOp;
        }
        set
        {
            mstrOp = value;
        }
    }

    // Calculates answer based on current operation.
public int Answer()
    {
        switch(mstrOp)
        {
            case "+":
                return mintFirstNumber + mintSecondNumber;
            case "x":
                return mintFirstNumber * mintSecondNumber;
            case "*":
                return mintFirstNumber * mintSecondNumber;
            case "-":
                return mintFirstNumber - mintSecondNumber;
            default :
                return 0;
        }
    }

}
Exercise 3: Store a FlashCardClass Object in Session State

In this exercise, you will add code to the Global.asax file to initialize a Session state variable containing the FlashCardClass object that your Web form will use. The Global.asax file contains the startup events that occur when your application starts and when new sessions start. The FlashCardClass is stored in a Session variable so it is retained during the user' s session.

To create a Session state variable

  1. In the Project window, double-click the Global.asax file and then click the “Click Here To Switch To Code View” hyperlink.

  2. In the Code View window, add the following Session_Start event procedure:

    Visual Basic .NET
    Sub Session_Start(ByVal Sender As Object, ByVal E As EventArgs)
       ' Create a new FlashCard object
       Dim FlashCard As New FlashCardClass()
       ' Store the object in a Session variable.
       Session("FlashCard") = FlashCard
   End Sub
    Visual C#
        protected void Session_Start(Object sender, EventArgs e)
    {
        // Create a new FlashCard object
            FlashCardClass FlashCard = new FlashCardClass();
        // Store the object in a Session variable.
        Session["FlashCard"] = FlashCard;
    }
Exercise 4: Use the FlashCardClass from Web Form Events

In this exercise, you will add code to the Web form to use the FlashCardClass object stored in the Session state variable. This links the logic (FlashCardClass) to the user interface (FlashCard.aspx).

To use the FlashCardClass object from event procedures in the Web form

  1. Open the Web form' s code module FlashCard.aspx.vb in the Code window by double-clicking anywhere on the Web form.

  2. Declare a module-level object variable to receive the object from the Session state.

    Visual Basic .NET
        Dim FlashCard As FlashCardClass
    Visual C#
    FlashCardClass FlashCard;

  3. In the Page_Load event procedure, add the following code to assign the Session state variable to the FlashCard variable you just declared:

    Visual Basic .NET
        Private Sub Page_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
        ' Get the Session FlashCard variable.
        FlashCard = Session("FlashCard")
        RefreshDisplay()
    End Sub
    Visual C#
        private void Page_Load(object sender, System.EventArgs e)
    {
        // Get the Session FlashCard variable.
        FlashCard = (FlashCardClass)Session["FlashCard"];
        RefreshDisplay();
    }

  4. Add the following code to the Text_Changed event procedure:

    Visual Basic .NET
        Private Sub txtAnswer_TextChanged(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles txtAnswer.TextChanged
        If txtAnswer.Text = FlashCard.Answer Then
            lblFeedback.Text = "Correct!"
            ' Get another set of numbers.
            FlashCard.Shuffle()
            ' Refresh display to show new numbers.
            RefreshDisplay()
            ' Clear answer
            txtAnswer.Text = ""
        Else
            lblFeedback.Text = "Oops! Try Again."
        End If
    End Sub
    Visual C#
        private void txtAnswer_TextChanged(object sender, System.EventArgs e)
    {
        if(txtAnswer.Text == FlashCard.Answer().ToString())
        {
            lblFeedback.Text = "Correct!";
            // Get another set of numbers.
            FlashCard.Shuffle();
            // Refresh display to show new numbers.
            RefreshDisplay();
            // Clear answer
            txtAnswer.Text = "";
        }
        else
        {
            lblFeedback.Text = "Oops! Try Again.";
        }
    }

  5. Write the following helper procedure to refresh the labels on the display:

    Visual Basic .NET
        Private Sub RefreshDisplay()
        lblFirst.Text = FlashCard.FirstNumber
        lblSecond.Text = FlashCard.Operation & _
          FlashCard.SecondNumber
    End Sub
    Visual C#
        private void RefreshDisplay()
    {
       lblFirst.Text = FlashCard.FirstNumber.ToString();
        lblSecond.Text = FlashCard.Operation +
          FlashCard.SecondNumber.ToString();
    }

  6. Run and test the application.

    tip

    Using a form-level variable to hold data stored in Session or Application state variables, as shown in steps 2 and 3, helps you catch errors. Visual Studio .NET does not require you to declare state variables. Misspelling a state variable name simply creates a new, empty state variable.


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. Explain where Visual Studio .NET stores Web application projects.

  2. List the four major differences between Web and Windows applications.

  3. Describe the life cycle of a Web application: When are Web forms instantiated and how long do they exist?

  4. How do you preserve persistent data, such as simple variables, in a Web application?

  5. What determines the boundaries of a Web application?


Working with Web Objects

Chapter 3
Working with Web Objects
Lesson 1: Namespace Fundamentals
Lesson 2: Namespaces in a Web Application
Lesson 3: Maintaining State Information
About This Chapter

In this chapter, you will learn how to create and organize objects in Microsoft Visual Studio .NET, use the objects provided by Microsoft ASP.NET, and save objects and data between requests to Web forms. Microsoft Visual Basic .NET, Microsoft Visual C#, and ASP.NET share a common framework, so all of the object-oriented programming techniques you learn here apply to all aspects of Web Forms application programming.

Before You Begin

To complete the lessons in this chapter, you must

  • Be familiar with the basics of the Visual Basic, C#, or C++ programming language. This should include familiarity with the concepts of variables, procedures, decision structures, and scope. See the “Language Changes in Visual Basic” or “C# Language Tour” help topics included with Visual Studio for introductions to these languages.

  • Understand events in the life cycle of a Web application as discussed in Lesson 2 of Chapter 2, “Creating Web Forms Applications.”


Lesson 1: Namespace Fundamentals

Visual Basic .NET and Visual C# provide a full set of object-oriented programming concepts, including abstract classes, interfaces, and overloading or overriding class members. These features are fundamental to the language and are used throughout the Microsoft .NET Framework. By learning how to use these features yourself, you will understand how the .NET Framework is implemented and you' ll be better able to use it in your applications.

After this lesson, you will be able to

  • Organize your code using namespaces

  • Create classes and control access to those classes

  • Create abstract and base classes and derive new classes from them

  • Understand how the members of an inherited class can be overloaded, overridden, or shadowed

  • Create interfaces for classes and explain how they are different from abstract classes

Estimated lesson time: 40 minutes
Understanding Namespaces

In the preceding chapter, you saw some generated code in the Global.asax file that looked like this:

Visual Basic .NET
Imports System
Imports System.Web
Visual C#
using System
using System.Web;

These statements permit you to use code from the ASP.NET namespaces System and System.Web without specifying their full names. Without these statements, a call to a simple method from the System namespace, for example, would look like this:

Visual Basic .NET
System.Array.Sort(strArray)
Visual C#
System.Array.Sort(strArray);

By including the System namespace at the beginning of the code, that Array method can be shortened to:

Visual Basic .NET
Array.Sort(strArray)
Visual C#
Array.Sort(strArray);

  • Namespaces are a way of organizing code. They provide protection from conflicting names, sometimes called namespace collisions. This protection is especially necessary in large projects where it is very easy for two items to accidentally have the same name. By organizing your code into namespaces, you reduce the chance of these conflicts.To create a namespace, enclose a Class or Module in a Namespace…End Namespace block.

  • To add more than one class or module to a namespace, specify the same namespace for each.

The following code (in bold) creates a namespace for the Strings module:

Visual Basic .NET
' Project name: Wombat.
Namespace Utils
    Public Module Strings
        Public Function Sort(ByVal strText As String, _
          Optional ByVal bAlphaOrder As Boolean = True) As String
            ' Declare and initialize a string array.
            Dim strArray As String() = {""}
            ' Convert the string to an array using System.String.
            strArray = strText.Split(" ")
            ' Use System.Array to sort.
            System.Array.Sort(strArray)
            ' If it's not alphabetic order, reverse the array.
            If Not bAlphaOrder Then
                ' Use System.Array to reverse.
                System.Array.Reverse(strArray)
            End If
            ' Return the a string.
            Sort = System.String.Join(" ", strArray)
        End Function
    End Module
End Namespace
Visual C#
// Project name: Wombat.
namespace Utils{
    class Strings
    {
        // Takes a string, sorts it, and returns a string.
        public static string Sort(string strText , bool bAlphaOrder)
        {
            // Declare and initialize a string array.
            string[] strArray = {""};
            char[] strSep = {' '};
            // Convert the string to an array using System.String.
            strArray = strText.Split(strSep);
            // Use System.Array to sort.
            System.Array.Sort(strArray);
            // If it's not alphabetic order, reverse the array.
            if (!bAlphaOrder)
            {
                // Use System.Array to reverse.
                System.Array.Reverse(strArray);
            }
            // Return the string.
            return System.String.Join(" ", strArray);
        }

        // Same method with one parameter.
        public static string Sort(string strText)
        {
            return Sort(strText, true);
        }

    }
}
important | The preceding example also uses two .NET namespaces: System.Array and System.String. The System namespaces are the new way to access functions you used to access in the Microsoft Windows API through Declare statements. This is even easier now because Visual Basic .NET, Visual C#, and the .NET Framework use the same scalar data types and default parameter passing conventions. Unlike the Windows API, the System namespaces provide an object-oriented interface to the Windows functions. This might take some getting used to if you are already familiar with the Windows API. However, the benefits of the common language runtime and managed code are enormous.

Because namespaces are an organization tool for code you want to use elsewhere, they are public by definition. When used in code, the reference takes the form:

ProjectName.NameSpace.ModuleName.MemberName

You can use code from a namespace in your application in one of two ways:

  • Use the fully qualified name of the member. The following code (in bold) calls the Sort function from the Wombat.Utils namespace:

    Visual Basic .NET
    Private Sub Button1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Button1.Click
        ' Sort the text.
        TextBox1.Text = Wombat.Utils.Strings.Sort(TextBox1.Text)
    End Sub
End Class
    Visual C#
    private void Button1_Click(object sender, System.EventArgs e)
    {
        TextBox1.Text = Wombat.Utils.Strings.Sort(TextBox1.Text);
    }

  • Add a Visual Basic .NET Imports or Visual C# using statement at the beginning of a class or module. The Imports or using statement provides a shortcut to the member name—you no longer have to use the full name in code. The following code (in bold) uses the namespace in the Wombat project' s Utils namespace:

    Visual Basic .NET
    ' Import the Utils namespace from the Wombat project.
Imports Wombat.Utils

Public Class WebForm1
   ' Declarations and initialization code omitted…

    Private Sub Button1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Button1.Click
        ' Call a helper function
TextBox1.Text = Strings.Sort(TextBox1.Text)
    End Sub
End Class
    Visual C#
    // Import the Utils namespace from the Wombat project.
using Wombat.Utils;
namespace csNamespaces
{
    public class WebForm1 : System.Web.UI.Page
    {
        // Declarations and initialization code omitted...

        private void Button1_Click(object sender, EventArgs e)
        {
            TextBox1.Text = Strings.Sort(TextBox1.Text);

        }
    }
}

Namespaces use dot notation (.) to specify hierarchy. Consider the following namespace declaration:

Visual Basic .NET
' Project name: Wombat
Namespace Utils
    Namespace Types
        Namespace Digits
            Public Enum Numeric
                Zero
                One
                Two
                Three
                Four
                Five
                Six
                Seven
                Eight
                Nine
            End Enum
        End Namespace
    End Namespace
End Namespace
Visual C#
// Project name: Wombat
namespace Utils
{
    namespace Types
    {
        class Digits
        {
            public enum Numeric
            {
                Zero, One, Two, Three, Four, Five,
                Six, Seven, Eight, Nine
            }
        }
    }
}

This declaration is equivalent to:

Visual Basic .NET
' Project name: Wombat
Namespace Utils.Types.Digits
               Public Enum Numeric
                Zero
                One
                Two
                Three
                Four
                Five
                Six
                Seven
                Eight
                Nine
            End Enum
End Namespace
Visual C#
// Project name: Wombat
namespace Utils.Types
{
    class Digits
    {
        public enum Numeric
        {
            Zero, One, Two, Three, Four, Five,
            Six, Seven, Eight, Nine
        }
    }
}

To use either of the preceding namespace declarations, you can use the fully qualified name, as shown here:

Visual Basic .NET
Dim numVar As Wombat.Utils.Types.Digits.Numeric
Visual C#
Wombat.Utils.Types.Digits.Numeric numVar;

Or, you can use an Imports or using statement, as shown here:

Visual Basic .NET
Imports Wombat.Utils.Types.Digits
Dim numVar As Numeric
Visual C#
using Wombat.Utils.Types;
Digits.Numeric numVar;
References vs. Imports

You add project references to use namespaces outside of the current project. Use the Imports statement to provide a shortcut to that namespace. The Imports statement simply provides an abbreviated way to refer to a namespace in code.

To add a reference to a project:

  1. From the Project menu, choose Add Reference. Visual Studio .NET displays the Add Reference dialog box, as shown in Figure 3-1.

  2. Select the reference to add from the within the .NET, COM, or Project component lists. Click OK.

  3. Visual Studio adds references to the Project window.

    Figure 3-1 Adding a project reference
Classes and Modules

Visual Studio projects store code in classes and modules. Previous versions of Visual Basic introduced the concept of classes and how they are unique from code modules. This concept is the same in Visual Basic .NET: You use classes for items that define their own storage; you use modules for code that does not have persistent data. Also, you instantiate classes to create objects before you use them, whereas you can simply call code modules directly.

In Visual C#, however, all code is contained in a class. If you want to create methods or properties that can be called without first creating an object, declare those items as static. The Utils class' s Sort method created in the preceding section demonstrates the Visual C# equivalent of a Visual Basic .NET module.

Visual Basic .NET and Visual C# use four key concepts for working with modules and classes. These concepts are new to Visual Basic .NET and, of course, Visual C#, since that is an entirely new language. However, C++ programmers should already be familiar with these concepts. Table 3-1 describes these key concepts in both languages.

Table 3-1 Key Object-Oriented Concepts
Concept | In Visual Basic .NET | In Visual C#
Definition | You define whether something is a class or a module by using Class…End Class or Module…End Module blocks. In previous versions, this was implicit with the file type and you could have only one Class or Module per file. | You define classes using the class keyword. All executable code is part of a class.
Access | There are five levels of access to classes, modules, and their members: Public, Protected, Friend, Protected Friend, and Private. Access is also explicit in the item' s definition, rather than hidden in file properties. | There are five levels of access to classes and their members: public, protected, internal, protected internal, and private.
Inheritance | Classes can inherit members from each other and override, shadow, or overload members of the inherited class. | Classes can inherit members from base classes and override or overload members of the inherited class.
Constructors and Destructors | Classes have New and Finalize methods that are called when an object based on the class is created or destroyed. | Classes have constructors and destructors that are called when an object based on the class is created or destroyed. Constructor methods have the same name as their class, and destructor methods use the class name preceded by a tilde (~).
Abstract Classes and Interfaces | You can create interfaces and abstract classes, methods, and properties. Interfaces define the member names and parameter lists for classes that use the interface. Abstract members provide the items to be inherited by classes derived from them. | You can define interfaces and abstract classes, methods, and properties. Interfaces define the member names and parameter lists for classes that use the interface. Abstract members provide the items to be inherited by classes derived from them.
Creating Classes/Modules and Providing Access

In Visual Basic .NET, use Class…End Class and Module…End Module blocks to define classes and modules. In Visual C#, use the class keyword to define classes. You can have one or more classes and/or modules per file. Use one of the access keywords shown in Table 3-2 to define which other classes and modules can use the members of the current class or module.

Table 3-2 Levels of Access for Classes and Modules
Visual Basic | Visual C# | Available to
Public | public | All members in all classes and projects.
Friend | internal | All members in the current project.
Protected | protected | All members in the current class and in derived classes. Can be used only in member definitions, not for class or module definitions.
Protected Friend | protected internal | All members in the current class and in derived classes in the current project. Can be used only in member definitions, not for class or module definitions.
Private | private | Available only in the current class.

For example, the following class is available only in the current project:

Visual Basic .NET
Friend Class Translator
    Private mstrText As String

    ' Controls access to the module-level
    ' variables.
    Public Property Text() As String
        Get
            Text = mstrText
        End Get
        Set(ByVal Value As String)
            mstrText = Value
        End Set
    End Property

    ' Translates the value in the Text property.
    Public Sub Translate()
        Dim strWord As String, intCount As Integer
        Dim arrWords() As String
        Dim bCaps As Boolean
        ' Convert the string to an array using System.String.
        arrWords = mstrText.Split(" ")
        For intCount = 0 To UBound(arrWords)
            ' Check if word is capitalized.
            If LCase(arrWords(intCount)) <> arrWords(intCount) Then
                bCaps = True
                arrWords(intCount) = LCase(arrWords(intCount))
            End If
            strWord = arrWords(intCount)
            ' Do translation.
            If strWord <> "" Then
                strWord = Right(strWord, Len(strWord) - 1) & _
                  Left(strWord, 1) & "ay"
                ' Recapitalize if necessary
                If bCaps Then
                    strWord = UCase(Left(strWord, 1)) & _
                      Right(strWord, Len(strWord) - 1)
                End If
            End If
            ' Store back in the array.
            arrWords(intCount) = strWord
            ' Reset caps flag.
            bCaps = False
        Next
        ' Rebuild string from array.
        mstrText = String.Join(" ", arrWords)
    End Sub
End Class
Visual C#
internal class Translator
    {
        string mstrText;

        // Controls access to class-level variables.
        public string Text
        {
            get
            {
                return mstrText;
            }
            set
            {
                mstrText = value;
            }
        }

        /// Translates the value in the Text property
        public void Translate()
        {
            string strWord;
            string[] arrWords;
            bool bCaps = false;

            // Convert the string into an array using System.String.
            arrWords = mstrText.Split(' ');
            for(int intCount = 0; intCount <= arrWords.GetUpperBound(0);
                 intCount++)
            {
                // Change to lowercase.
                strWord = arrWords[intCount].ToLower();
                // Check if word is capitalized.
                if(!arrWords[intCount].Equals(strWord))
                    bCaps = true;
                // Do translation.
                if(strWord != "")
                {
                    strWord = strWord.Substring(1,strWord.Length - 1) +
                      strWord.Substring(0,1) + "ay";
                    // Recapitalize if necessary.
                    if(bCaps)
                        strWord = strWord.Substring(0,1).ToUpper() +
                          strWord.Substring(1, strWord.Length - 1);
                }
                // Store the word back in the array.
                arrWords[intCount] = strWord;
                // Reset the caps flag.
                bCaps = false;
            }
            // Rebuild the string from the array.
            mstrText = String.Join(" ", arrWords);
        }
    }

You can access the Text property and Translate method of the preceding Translator class from within the project where it is included, but not from other projects. The following code (in bold) shows using the Text and Translate members from a Web form:

Visual Basic .NET
Private Sub butTranslate_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butTranslate.Click
     ' Create a new Translator object.

Dim TranslateText As New Translator()
    ' Put the source text into the translator.
    TranslateText.Text = txtInput.Text
    ' Turn the crank.
    TranslateText.Translate()
    ' Display result.
    txtOutput.Text = TranslateText.TextEnd Sub
Visual C#
private void butTranslate_Click(object sender, System.EventArgs e)
{
    // Create a new Translator object.
    Translator TranslateText = new Translator();
    // Put the source text into translator.
    TranslateText.Text = txtInput.Text;
    // Turn the crank.
    TranslateText.Translate();
    // Display the result.
    txtInput.Text = TranslateText.Text;
}

When you look at the code Visual Studio generates for a Web form, notice that the server controls on the Web form are declared as Protected:

Visual Basic .NET
Protected System.Web.UI.WebControls.TextBox txtInput
Protected System.Web.UI.WebControls.Button butTranslate
Visual C#
protected System.Web.UI.WebControls.TextBox txtInput;
protected System.Web.UI.WebControls.Button butTranslate;

By default, server control objects are set to be available only in the current Web form and in Web forms derived from that Web form.

Inheritance: Who' s Deriving?

Inheritance is the process of basing one class on another. In this process, a base class provides methods, properties, and other members to a derived class. The advantage of inheritance is that you can write and maintain code once it is in the base class and reuse it over and over in the derived classes. How familiar you are with inheritance usually depends on which programming language you have been using.

  • Visual Basic .NET introduces inheritance to Visual Basic programming. This is a huge step forward for the language and is the reason that many things had to change between all previous versions of Visual Basic and Visual Basic .NET. Windows Forms and data types are now derived from the .NET Framework, rather than being implemented through the “Ruby” engine that powered all previous versions of Visual Basic. You' ll now find a lot more agreement and consistency between Visual Basic .NET and Windows.

  • C# programmers who are familiar with C++ are probably very familiar with the concept of inheritance. However, Visual C# adds refinements not found in C++ syntax, including the interface, abstract, and virtual keywords.

How you specify inheritance depends on your programming language, too. In Visual Basic .NET, you use the Inherits statement to base a new class on an existing one. In Visual C#, no keyword is needed; instead, you use the class definition. The following lines show the different syntaxes for each language:

Visual Basic .NET
Public Class DerivedClass
    Inherits BaseClass
End Class
Visual C#
public class DerivedClass : BaseClass
{
}

Visual Studio uses the keywords described in Table 3-3 for creating base classes and deriving new classes from them. The sections that follow explain how to use each of these keywords.

Table 3-3 Overview of the Inheritance Keywords
Visual Basic | Visual C# | Use to
Inherits | derivedclass : baseclass | Base one class on another, inheriting members from the base class.
Overridable | virtual | Declare that a member of the base class can be overridden in a derived class.
Overrides | override | Declare that a member of a derived class overrides the member of the same name in the base class.
Shadows | new | Declare that a member of a derived class hides the member of the same name in the base class.
MustInherit | abstract | Declare that a class provides a template for derived classes. This type of class is called an abstract class, and it cannot be instantiated.
MustOverride | abstract | Declare that a member of a class provides a template for derived members. This type of member is called an abstract member and it cannot be invoked.
MyBase | base | Call a base class member from within the derived class.
Me | this | Call a member of the current instance of a class.
Interface | interface | Create an interface that defines the members a class must provide.
Implements | classname : interfacename | Use an interface definition in a class.

Before you learn about inheritance in more detail, you should understand the two things you can' t do with inheritance:

  • You can' t inherit from more than one base class in a single derived class definition. The concept of multiple inheritance exists in many object-oriented programming languages, but as a practical matter it is not widely used. Base classes can be derived from other base classes in a hierarchical fashion, however.

  • You can' t derive a Web form class from a base Web form. Even though Web forms are defined as classes, Visual Studio does not support deriving new Web forms from existing base Web forms.

Overriding, Overloading, and Shadowing Members

A derived class inherits the members of its base class. If the derived class defines a member with the same signature, the derived member overrides the base member. A member' s signature includes its name, parameter list, parameter types, and return type.

If a derived class defines a member with the same name, but a different parameter list, parameter type, or return type than the base member, the derived member either overloads or shadows the base member. A member overloads another member if the base member is still available. A member shadows another member if the derived member replaces the base member.

In the following example, the class Sphere is based on the class Circle. In Visual Basic .NET, members that can be overridden must be declared as Overridable (1). In Visual C#, members that can be overridden must be declared as virtual (1). The Circle class is shown here:

Visual Basic .NET
Public Class Circle
    Private sxCenter, syCenter As Single
    Private sRadius As Single

    Public Property Top() As Single
        Get
            Top = sxCenter - sRadius
        End Get
        Set(ByVal Value As Single)
            sxCenter = Value + sRadius
        End Set
    End Property

    Public Property Left() As Single
        Get
            Left = syCenter - sRadius
        End Get
        Set(ByVal Value As Single)
            syCenter = Value + sRadius
        End Set
    End Property

    Public Overridable Function Area() As Single       ' (1)
        Area = System.Math.PI * (sRadius ^ 2)
    End Function

    Public Function Perimeter() As Single
        Perimeter = 2 * sRadius * System.Math.PI
    End Function

    Public Property Radius() As Single
        Get
            Radius = sRadius
        End Get
        Set(ByVal Value As Single)
            sRadius = Value
        End Set
    End Property

    Public Overridable Sub Center(ByVal X As Single, _
      ByVal Y As Single) ' (1)
        sxCenter = X
        syCenter = Y
    End Sub
End Class
Visual C#
public class Circle
{
    float fxCenter, fyCenter, fRadius;
    // Constructor.
    public Circle()
    {
        // Initialize internal variables.
        fxCenter = 0;
        fyCenter = 0;
        fRadius = 0;
    }
    public float Top
    {
        get
        {
            return fxCenter - fRadius;
        }
        set
        {
            fxCenter = value + fRadius;
        }
    }
    public float Left
    {
        get
        {
            return fyCenter - fRadius;
        }
        set
        {
            fyCenter = value + fRadius;
        }
    }

    public virtual float Area()            // (1)
    {
        return (float)(System.Math.PI * Math.Pow((double)fRadius, 2));
    }

    public float Perimeter()
    {
        return 2 * fRadius * (float)System.Math.PI;
    }

    public float Radius
    {
        get
        {
            return fRadius;
        }
        set
        {
            fRadius = value;
        }
    }

    public virtual void Center(float X, float Y)        // (1)
    {
            fxCenter = X;
            fyCenter = Y;
    }
}

The Sphere class inherits all of the methods and properties defined in Circle (1). Sphere overrides the method for Area (2) because spheres use a different formula for this calculation. Sphere shadows the Center method (3) because spheres have an additional coordinate (z) and you wouldn' t want users to accidentally set X and Y coordinates without setting Z. Notice that Sphere uses the Visual Basic .NET MyBase or the Visual C# base keyword (4) to call the base class' s Center method within the shadowed method.

Visual Basic .NET
Public Class Sphere
    Inherits Circle                                     ' (1)
    Private sCenter As Single

    Public Overrides Function Area() As Single          ' (2)
        Area = 4 * System.Math.PI * (MyBase.Radius ^ 2)
    End Function

    Public Shadows Sub Center(ByVal X As Single, _
      ByVal Y As Single, ByVal Z As Single)             ' (3)
        MyBase.Center(X, Y)                             ' (4)
        sCenter = Z
    End Sub

    Public Function Volume() As Single
        Volume = (4 / 3) * System.Math.PI * (Radius ^ 3)
    End Function

    Public Property Front() As Single
        Get
            Depth = sCenter - Radius
        End Get
        Set(ByVal Value As Single)
            sCenter = Value + MyBase.Radius
        End Set
    End Property
End Class
Visual C#
public class Sphere : Circle                    // (1){
    float fCenter;

    // Constructor.
    public Sphere()
    {
        // Initialize internal variable.
        fCenter = 0;
    }

    public override float Area()                    // (2)
    {
        return (float)(4 * Math.PI * Math.Pow((double)base.Radius, 2));
    }

    public new void Center(float X, float Y)            // (3)
    {
        this.Center(X, Y, 0);
    }

    public void Center(float X, float Y, float Z)
    {
        base.Center(X, Y);                    //  (4)
        fCenter = Z;
    }

    public float Volume()
    {
        return (float)((4 / 3) * System.Math.PI *
           Math.Pow((double)base.Radius, 3));
    }

    public float Front
    {
        get
        {
            return fCenter - base.Radius;
        }
        set
        {
            fCenter = value + base.Radius;
        }
    }
}

To see how inheritance works, create a new object of each type and call each object' s properties and methods. The following code demonstrates the Circle and Sphere classes by displaying calculated values on a Web form:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    Dim MyCircle As New Circle()
    MyCircle.Radius = 2
    MyCircle.Center(10, 2)
    Response.Write("Circle area: " & MyCircle.Area & "<br>")
    Response.Write("Circle circumference: " & MyCircle.Perimeter _
      & "<br>")
    Dim MySphere As New Sphere()
    MySphere.Radius = 10
    MySphere.Center(10, 20, 25)
    Response.Write("Sphere top: " & MySphere.Top & "<br>")
    Response.Write("Sphere left: " & MySphere.Left & "<br>")
    Response.Write("Sphere front: " & MySphere.Front & "<br>")
    Response.Write("Sphere volume: " & MySphere.Volume & "<br>")
    Response.Write("Sphere surface area: " & MySphere.Area & _
      "<br>")
    Response.Write("Sphere circumference: " & _
      MySphere.Perimeter & "<br>")
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
    {
        Circle MyCircle = new Circle();
        MyCircle.Radius = 2;
        MyCircle.Center(10, 2);
        Response.Write("Circle area: " + MyCircle.Area() + "<br>");
        Response.Write("Circle circumference: " + MyCircle.Perimeter() +
           "<br>");
        Sphere MySphere = new Sphere();
        MySphere.Radius = 10;
        MySphere.Center(10,20,25);
        Response.Write("Sphere top: " + MySphere.Top + "<br>");
        Response.Write("Sphere left: " + MySphere.Left + "<br>");
        Response.Write("Sphere front: " + MySphere.Front + "<br>");
        Response.Write("Sphere volume: " + MySphere.Volume() + "<br>");
        Response.Write("Sphere surface area: " + MySphere.Area() + "<br>");
        Response.Write("Sphere circumference: " + MySphere.Perimeter() +
  "<br>");
    }
In the Abstract

Visual Studio also lets you define abstract classes. An abstract class is a class that defines an interface for derived classes. An abstract class is essentially a contract saying that all classes based on it will provide certain methods and properties. You can' t create objects from abstract classes—you can only derive new classes from them.

Abstract classes are declared with the Visual Basic .NET MustInherit or the Visual C# abstract keyword. Methods and properties that base classes must provide are declared as MustOverride in Visual Basic .NET or in abstract in Visual C#. The following Shape class demonstrates an abstract class:

Visual Basic .NET
' Definition of abstract class.
Public MustInherit Class Shape
    Public MustOverride Property Top() As Single
    Public MustOverride Property Left() As Single
    Public MustOverride Function Area() As Single
    Public MustOverride Function Perimeter() As Single
End Class
Visual C#
// Definition of abstract class.
public abstract class Shape
{
    public Shape()
    {
    }
    public abstract float Top
    {
        get;
        set;
    }
    public abstract float Left
    {
        get;
        set;
    }
    public abstract float Area();

    public abstract float Perimeter();

}

Notice that the Visual Basic .NET MustOverride or Visual C# abstract members are just definitions—there is no procedure body statement because these members will be defined (overridden) in the derived class.

The following Circle class demonstrates how an abstract class is inherited. In Visual Basic .NET, the Inherits statement (1) declares that this class is based on the abstract class Shape. The Overrides keywords (2) are required in each of the member definitions that override members of the abstract class. In Visual C#, the class definition (1) declares that this class is based on the abstract class Shape. The override keyword (2) is required in each of the member definitions that override members of the abstract class.

Visual Basic .NET
Public Class Circle
    Inherits Shape                             ' (1)
    Private sxCenter, syCenter As Single
    Private sRadius As Single

    Public Overrides Property Top() As Single  ' (2)
        Get
            Top = sxCenter - sRadius
        End Get
        Set(ByVal Value As Single)
            sxCenter = Value + sRadius
        End Set
    End Property

    Public Overrides Property Left() As Single
        Get
            Left = syCenter - sRadius
        End Get
        Set(ByVal Value As Single)
            syCenter = Value + sRadius
        End Set
    End Property

    Public Overrides Function Area() As Single
        Area = 2 * System.Math.PI * (sRadius ^ 2)
    End Function

    Public Overrides Function Perimeter() As Single
        Perimeter = 2 * sRadius * System.Math.PI
    End Function

    Public Property Radius() As Single
        Get
            Radius = sRadius
        End Get
        Set(ByVal Value As Single)
            sRadius = Value
        End Set
    End Property

    Public Overridable Sub Center(ByVal X As Single, ByVal Y As Single)
        sxCenter = X
        syCenter = Y
    End Sub
End Class
Visual C#
public class Circle : Shape                    // (1){
    float fxCenter, fyCenter, fRadius;

    // Constructor.
    public Circle()
    {
        // Initialize internal variables.
        fxCenter = 0;
        fyCenter = 0;
        fRadius = 0;
    }

    public override float Top                    // (2)
    {
        get
        {
            return fxCenter - fRadius;
        }
        set
        {
            fxCenter = value + fRadius;
        }
    }

    public override float Left
    {
        get
        {
            return fyCenter - fRadius;
        }
        set
        {
            fyCenter = value + fRadius;
        }
    }

    public override float Area()
    {
        return (float)(2 * System.Math.PI * Math.Pow((double)fRadius, 2));
    }
        public override float Perimeter()
    {
        return 2 * fRadius * (float)System.Math.PI;
    }
        public float Radius
    {
        get
        {
            return fRadius;
        }
        set
        {
            fRadius = value;
        }
    }

    public virtual void Center(float X, float Y)
    {
            fxCenter = X;
            fyCenter = Y;
    }
}
Interface-to-Face

Interfaces are similar to abstract classes in that they both provide a template that you can use to create new classes. The difference is that interfaces don' t provide any implementation of class members, whereas abstract classes can implement members that then become common to all the classes derived from them.

Interfaces are more like types than classes. When you implement a particular interface in a class, instances of that class can be used for any argument or variable declared as that interface. For example, the following code declares an interface for the shape objects created earlier in this lesson:

Visual Basic .NET
' Interface for all shapes.
Public Interface IFigure
    Property Top() As Single
    Property Left() As Single
    Function Area() As Single
    Function Perimeter() As Single
End Interface
Visual C#
// Interface for all shapes.
public interface IFigure
{
    float Top
    {
        get;
        set;
    }
        float Left
    {
        get;
        set;
    }
        float Area();
        float Perimeter();
}

To use the interface, implement it in a class as shown here in bold:

Visual Basic .NET
' Definition of abstract class.
Public MustInherit Class Shape
    Implements IFigure
    Public MustOverride Property Top() As Single Implements IFigure.Top
    Public MustOverride Property Left() As Single Implements IFigure.Left
    Public MustOverride Function Area() As Single Implements IFigure.Area
    Public MustOverride Function Perimeter() As Single Implements
       IFigure.Perimeter
End Class
Visual C#
public abstract class Shape : IFigure{
    // Constructor.
    public Shape()
    {
    }

    public abstract float Top
    {
        get;
        set;
    }
        public abstract float Left
    {
        get;
        set;
    }

    public abstract float Area();

    public abstract float Perimeter();

}

Because the Shape abstract class in this example implements the IFigure interface, all classes derived from Shape inherit the implementation of IFigure as well. This means that objects of the type Circle and Sphere, which derive from Shape, can be used as the arguments of the type IFigure, as shown in the following procedure calls:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Create a circle.
    Dim MyCircle As New Circle()
    MyCircle.Radius = 2
    MyCircle.Center(10, 2)
    ' Create a sphere.
    Dim MySphere As New Sphere()
    MySphere.Radius = 10
    MySphere.Center(10, 20, 25)
    ' Show info about the shapes.
    ShowShapeInfo(MySphere)
    ShowShapeInfo(MyCircle)
End Sub

' Display the shape info on the Web form.
Sub ShowShapeInfo(ByVal Shape As IFigure)
    Response.Write("Shape top: " & Shape.Top & "<br>")
    Response.Write("Shape left: " & Shape.Left & "<br>")
    Response.Write("Shape volume: " & Shape.Volume & "<br>")
    Response.Write("Shape surface area: " & Shape.Area & _
      "<br>")
    Response.Write("Shape perimeter: " & _
      Shape.Perimeter & "<br>")
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    // Create a circle.
    Circle MyCircle = new Circle();
    MyCircle.Radius = 2;
    MyCircle.Center(10, 2);
    // Create a sphere.
    Sphere MySphere = new Sphere();
    MySphere.Radius = 10;
    MySphere.Center(10,20,25);
    // Show info about each shape.
    ShowShapeInfo(MySphere);
    ShowShapeInfo(MySphere);
}

// Display the shape info on the Web form.
private void ShowShapeInfo(IFigure Shape)
{
    // Since Shape argument is IFigure, we know it has these members.
    Response.Write("Shape top: " + Shape.Top + "<br>");
    Response.Write("Shape left: " + Shape.Left + "<br>");
    Response.Write("Shape perimeter: " + Shape.Perimeter() + "<br>");
    Response.Write("Shape area: " + Shape.Area() + "<br>");
}

The key here is that all items defined in the interface must exist in any class that implements the interface. If you omit any member, Visual Studio generates a compile-time error.


Lesson 2: Namespaces in a Web Application

In this lesson, you will learn how to navigate the ASP.NET namespaces to find the objects you use to create a Web application. You will also see how to use the Application, Page, Request, and Response objects in code. These four objects form the core of Web application programming.

After this lesson, you will be able to

  • Know where to look in the .NET Framework to find objects for Web applications

  • Understand how objects relate to each other in the ASP.NET architecture

  • Get at the ASP.NET objects and control them in code

  • Navigate the object hierarchy to get subordinate ASP.NET objects such as the Browser and Cookies objects.

Estimated lesson time: 30 minutes
Overview of Web Namespaces

The class definitions for objects used in Web applications reside in the System.Web namespace. Table 3-4 shows the namespace hierarchy and describes the kinds of class definitions you will find in each namespace. Use this table as a guide to where to look for objects that perform specific tasks.

Table 3-4 The Namespace Hierarchy
Namespace | Contains classes for
System.Web | The Application, Browser, Cache, Cookies, Exception, Request, Response, Server, and Trace objects. Use these classes in most Web programming tasks. The Application object defined in Global.asax is based on the Application class.
System.Web.SessionState | The Session object. Use these classes to save and retrieve items saved in the Session state.
System.Web.Services | The WebService object. Use these classes to create and use Web services.
System.Web.UI | The Page and Control objects. Use these classes within a Web form to create and control an application' s user interface. Web forms are based on the Page class.
System.Web.UI.WebControls | All server controls objects. Use these classes within Web forms.
System.Web.UI.HTMLControls | All HTML control objects. Use these classes within Web forms.
System.Web.Caching | The Cache object. Use these classes to control server-side caching to improve application performance.
System.Web.Mail | The MailMessage, MailAttachment, and SmtpMail objects. Use these classes to send mail messages from your application.
System.Web.Security | Authentication objects and modules. Use these classes to authenticate users and provide security within your application.

When programming a Web application, you deal directly with two types of objects derived from classes in the Web namespaces.

  • The Application object. Derived from the HttpApplication class. In your application, this definition resides in the Global.asax file.

  • Web Form objects. Derived from the Page class. In your application, this definition resides in Web Form modules.

The following code shows the declarations that Visual Studio generates for the Global and WebForm1 classes. These classes are instantiated automatically by the ASP.NET run time.

Visual Basic .NET
Public Class Global
    Inherits System.Web.HttpApplication
End Class

Public Class WebForm1
    Inherits System.Web.UI.Page
End Class
Visual C#
public class Global : System.Web.HttpApplication
{
}

public class WebForm1 : System.Web.UI.Page
{
}

These Global and WebForm1 objects are the entry points you use to get at other Web objects in an application. Those objects, along with Request and Response, are the objects you will most commonly work with in code. The following sections explain how to use the Application, Page, Request, and Response objects to navigate among other objects.

Using the Application Object

The Application object is a top-level object in your Web application' s object hierarchy. Use the Application object to configure your application and to save state information. The Application object has properties and methods that provide access to other objects in your application, as shown in Figure 3-2.

Figure 3-2 The Application object

When ASP.NET starts your application, it automatically instantiates the Global object defined in Global.asax. Use the events in the Global object to configure your application and to initialize application-level state variables.

The HttpApplication base class provides the properties and methods to access the subordinate objects for the Global object. These properties and methods are shown in Table 3-5. Use these properties and methods to get at other objects from the Application object.

Table 3-5 Properties and Methods of the Application Object
Property/Method | Use to
Application | Save data items in the Application state.
Context | Get Trace, Cache, Error, and other objects for the current context.
Modules | Access HTTP modules.
Request | Read a request and get Browser, ClientCertificates, Cookies, and Files objects from the current request.
Response | Write text or data to a response and get Cache, Cookies, and Output objects from the current response.
Server | Process requests and responses. The Server object provides helper methods for URL encoding and decoding.
Session | Save data items in the Session state.
User | Get authentication information about the user who is making the current request. By default, Web applications allow anonymous access.

The following code uses the Request and Browser objects available from within the Application object to determine if a browser making a request is version 4.0 or later.

Visual Basic .NET
Sub Application_BeginRequest(ByVal sender As Object, _
  ByVal e As EventArgs)
    ' Fires at the beginning of each request
    If Request.Browser.MajorVersion < 4 Then
        ' Disable advanced features.
    End If
End Sub
Visual C#
protected void Application_BeginRequest(Object sender, EventArgs e)
{
    // Fires at the beginning of each request.
    if (Request.Browser.MajorVersion < 4)
    {
        // Disable advanced features.
    }
}
Using the Page Object

The Page object controls your application' s user interface. The Page object has properties and methods that provide access to other objects in the user interface, as shown in Figure 3-3.

Figure 3-3 The Page object

When a user requests a page from your application, ASP.NET automatically instantiates the Web form and displays the page. You add event procedures to the form to control the user interface and to interact with the user.

The Page base class provides the core methods and properties that you use most frequently when programming Web forms. These properties and methods are shown in Table 3-6.

Table 3-6 Properties and Methods of the Page Object
Property/Method | Use to
Application | Save data items in the Application state.
Cache | Control how responses are cached on the server.
Controls | Get at controls on the page.
Request | Read a request and get Browser, ClientCertificates, Cookies, and Files objects from the current request.
Response | Write text or data to a response and get Cache, Cookies, and Output objects from the current response.
Server | Process requests and responses. The Server object provides helper methods for URL encoding and decoding.
Session | Save data items in the Session state.
Trace | Turn tracing on or off and write to the trace log.

The following code adds a new control to the Web form at run time.

Visual Basic
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
        ' Create a new control.
        Dim txtNew As New TextBox()
        ' Set some text to display in the control.
        txtNew.Text = "Some new text"
        ' Add the control between the <form> </form> elements.
        FindControl("Form1").Controls.Add(txtNew)
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
        // Create a new control.
    TextBox txtNew = new TextBox();
        // Set some text to display in the control.
            txtNew.Text = "Some new text";
        // Add the control between the <form> </form> elements.
    FindControl["Form1"].Controls.Add(txtNew);
}
Using the Request Object

The Request object contains the information sent by the client browser when a page is requested from the application. The Request object has properties and methods that provide access to other objects that make up the request, as shown in Figure 3-4.

Figure 3-4 The Request object

The Request object provides the methods and properties to access the subordinate objects described in Table 3-7.

Table 3-7 Properties and Methods of the Request Object
Property/Method | Use to
Browser | Determine the capabilities of the browser making the request. Browser properties provide the browser version number, determine whether it is the AOL browser, determine whether the browser supports cookies, and supply other information.
ClientCertificates | Authenticate the client.
Cookies | Get information from the client in the form of cookies.
Files | Get files that are uploaded by the client.
InputStream | Read and write to the raw data sent in the request.

The following code uses the Request object to check if the browser supports cookies and if a particular cookie exists before saving the value of a cookie in the Session state.

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
     ByVal e As System.EventArgs) Handles MyBase.Load
      ' Run first time page is displayed.
        If Not IsPostBack Then
            ' Check if Browser supports cookies.
            If Request.Browser.Cookies Then
                ' Check if the UName cookie exists.
                If Not IsNothing(Request.Cookies("UName")) Then
                    ' Save the value of the cookie.
                    Session("User") = Request.Cookies("UName").Value
                End If
            End If
        End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    // Run first time page is displayed.
    if(!IsPostBack)
        // Check if Browser supports cookies.
        if(Request.Browser.Cookies)
            // Check if the UName cookie exists.
            if((Request.Cookies["UName"] != null))
                // Get the value of the cookie.
                Session["User"] = Request.Cookies["UName"].Value;
}
Using the Response Object

Use the Response object to form the response sent from the server to the client browser. The Response object has properties and methods that provide access to other objects that make up the request, as shown in Figure 3-5.

Figure 3-5 The Response object

The Response object provides the methods and properties to access the subordinate objects described in Table 3-8.

Table 3-8 Properties and Methods of the Response Object
Property/Method | Use to
Cache | Determine how the server caches responses before they are sent to the client
Cookies | Set the content of cookies to send to the client
Output | Get or set the raw data returned to the client as the response

The following code creates a cookie and sends it to the client as part of the response.

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
        ' Run the first time this page is displayed.
        If Not IsPostBack Then
            ' If the browser supports cookies.
            If Request.Browser.Cookies Then
                ' Create a cookie.
                Dim cookUname As New HttpCookie("UName")
                cookUname.Value = "Wombat"
                ' Add the cookie.
                Response.Cookies.Add(cookUname)
            End If
        End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    if(!IsPostBack)
        // If the browser supports cookies.
        if (Request.Browser.Cookies)
        {
            // Create a cookie.
            HttpCookie cookUname = new HttpCookie("UName");
            cookUname.Value = "Wombat";
            // Add the cookie.
            Response.Cookies.Add(cookUname);
        }
}


Lesson 3: Maintaining State Information

In this lesson, you will learn how to retain data on a Web form between requests. ASP.NET provides different mechanisms for saving form data, depending on the nature and scope of the information.

After this lesson, you will be able to

  • Choose from the different ways to save data on a Web form based on your needs

  • Transfer data between Web forms with query strings

  • Save items on the client as cookies

  • Store page data in a Web form' s ViewState property

  • Keep application-wide variables in Application and Session state

  • Structure access to Application and Session state variables to avoid programming errors

Estimated lesson time: 30 minutes
Overview of Levels of State

As explained in Chapter 2, “Creating Web Forms Applications,” Web forms are created and destroyed each time a client browser makes a request. Because of this characteristic, variables declared within a Web form do not retain their value after a page is displayed. To solve this problem, ASP.NET provides the following ways to retain variables between requests:

  • Query strings. Use these to pass information between requests and responses as part of the Web address. Query strings are visible to the user, so they should not contain secure information such as passwords.

  • Cookies. Use cookies to store small amounts of information on a client. Clients might refuse cookies, so your code has to anticipate that possibility.

  • View state. ASP.NET stores items added to a page' s ViewState property as hidden fields on the page.

  • Session state. Use Session state variables to store items that you want keep local to the current session (single user).

  • Application state. Use Application state variables to store items that you want be available to all users of the application.

Using Query Strings

Query strings allow you to send additional information along with an address. In HTML, query strings appear after a question mark in a hyperlink, as shown here:

<A HREF= "WebForm1.aspx?UName=Wombat">Query string sample.</A>

To send a query string in code, add it to the address of a Redirect method. The following Click event procedure is equivalent to the preceding HTML:

Visual Basic .NET
Private Sub Button1_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles Button1.Click
        ' Redisplay this page with a QueryString.
        Server.Redirect("WebForm1.aspx?UName=Wombat")
End Sub
Visual C#
private void Button1_Click(object sender, System.EventArgs e)
{
    // Redisplay this page with a QueryString
    Response.Redirect("Webform1.aspx?UName=Wombat");
}

To retrieve a query string in code, use the QueryString method of the Response object. The following code displays the UName item from the query string created in the preceding examples:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
        ' Display the query string.
        Response.Write(Request.QueryString("UName"))
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    // Display the query string.
    Response.Write(Request.QueryString["UName"]);
}
Using Cookies

Use cookies to store small amounts of information on the client' s machine. Web sites often use cookies to store user preferences or other information that is client-specific. Because cookies can be refused, it is important to check whether the browser allows them before you try to create them.

The following code checks whether a browser allows cookies, and then saves user preferences if it does.

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
        ' Run the first time this page is displayed.
        If Not IsPostBack Then
            ' If the browser supports cookies.
            If Request.Browser.Cookies Then
                ' Create a cookie.
                Dim cookUPrefs As New HttpCookie("UPrefs")
                cookUPrefs.Value = "English"
                ' Add the cookie.
                Response.Cookies.Add(cookUPrefs)
            End If
        End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    // Run the first time this page is displayed.
    if(!IsPostBack)
        // If the browser supports cookies.
        if(Request.Browser.Cookies)
        {
            // Create a cookie.
            HttpCookie cookUPrefs = new HttpCookie("UPrefs");
            cookUPrefs.Value = "English";
            // Add the cookie.
            Response.Cookies.Add(cookUPrefs);
        }
}

The following code checks for a cookie, and then gets the cookie if it is available:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
        ' Run first time page is displayed.
        If Not IsPostBack Then
            ' Check if Browser supports cookies.
            If Request.Browser.Cookies Then
                ' Check if the UPrefs cookie exists.
                If Not IsNothing(Request.Cookies("UPrefs")) Then
                    ' Save the value of the cookie.
                    Session("Lang") = Request.Cookies("UPrefs").Value
                End If
            End If
        End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    // Run the first time this page is displayed.
    if(!IsPostBack)
        // If the browser supports cookies.
        if(Request.Browser.Cookies)
            // Check if the UPrefs cookie exists
            if(Request.Cookies["UPrefs"] != null)
                // Save the value of the cookie.
                Session["Lang"] = Request.Cookies["UPrefs"].Value;
}
Using ViewState

Use the ViewState property to save data to a hidden field on a page. Because ViewState stores data on the page, it is limited to items that can be serialized. If you want to store more complex items in ViewState, you must to convert the items to and from a string.

For example, the following code adds text from a text box to cells in a table on the page. Because you can' t store objects directly in ViewState, you need to store the strings in the Button1_Click procedure, and then create the table row controls from the strings, as shown in the Page_Load procedure.

Visual Basic .NET
Private Sub Button1_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles Button1.Click
        ' Add text to ViewState
        ViewState.Add(ViewState.Count, TextBox1.Text)
End Sub

Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
        Dim iCount As Integer
        ' For each item in the ViewState
        For iCount = 0 To ViewState.Count
            Dim rowNew As New TableRow()
            Dim celNew As New TableCell()
            ' Set cell text.
            celNew.Text = ViewState(iCount)
            ' Add cell to row.
            rowNew.Cells.Add(celNew)
            ' Add row to table
            Table1.Rows.Add(rowNew)
        Next
End Sub
Visual C#
private void Button1_Click(object sender, System.EventArgs e)
{
    // Add text to the view state.
    ViewState.Add(ViewState.Count.ToString(), TextBox1.Text);
}

private void Page_Load(object sender, System.EventArgs e)
{
    if (IsPostBack)
        // For each item in the ViewState
        foreach(StateItem staItem in ViewState.Values)
        {
            TableRow rowNew = new TableRow();
            TableCell celNew = new TableCell();
            // Set cell text.
            celNew.Text = staItem.Value.ToString();
            // Add cell to row.
            rowNew.Cells.Add(celNew);
            // Add row to table
            Table1.Rows.Add(rowNew);
        }
}

ASP.NET encodes the hidden data that is stored on the page so it is not intelligible to users. If you add a few items to the table using the preceding code, and then choose View Source from the browser, the HTML for the hidden field looks something like this:

</HEAD>
<body MS_POSITIONING="GridLayout">
<form name="Form1" method="post" action="WebForm1.aspx" id="Form1">
<input type="hidden" name="__VIEWSTATE" value="dDwtMTMwNzIzMzU0Mzt0PHA8bDwwOzE7MjszOzQ7PjtsPFxlO1RoaXMgaXMgc29tZSB0ZXh
0O1RoaXMgaXMgc29tZSB0ZXh0O1RoaXMgaXMgc29tZSB0ZXh0O1RoaXMgaXMgc29tZSB0ZXh0Oz4+Ozs+Oz4=" />
Using Application and Session States

Use Application and Session states to store data that you want to keep for the lifetime of a session or for the lifetime of the application, respectively. You can store any type of data in the Application or Session state, including objects. However, you should consider the following points before using them:

  • Application and Session state variables are created on the fly, without variable name or type checking. You should limit your access points to these variables.

  • Maintaining Session state affects performance. It can be turned off at the application and page levels.

  • Application state variables are available throughout the current process, but not across processes. If an application is scaled to run on multiple servers or on multiple processors within a server, each process has its own Application state.

  • The application boundaries set by IIS determine the scope of Application state.

Structuring Access to State Variables

Application and Session state variables are powerful and therefore scary things. It is easy to introduce some old-style Basic errors in your code if you use these variables in an unstructured manner. For instance, the following code creates two different Application state variables:

Visual Basic .NET
Application("Uname") = "Wombat"
Response.Write(Application("Unamme"))
Visual C#
Application["Uname"] = "Wombat";
Response.Write(Application["Unamme"]);

The first line creates a variable and stores some text in it. The second line retrieves a new, empty variable and displays nothing, simply because the variable name is misspelled. The problem is obvious here, but if it occurred deep in a decision structure it would be very hard to find.

To ameliorate this problem, structure your access to Application and Session state variables. The easiest way to do this is to declare a page-level variable for each item you need, and then retrieve the Application or Session state value in the Page_Load event procedure and save the page-level variables back to state in the Page_Unload event procedure. You might want to make retrieving and saving state variables from a consistent location part of your coding conventions.

The following code demonstrates structuring access to state variables:

Visual Basic .NET
Public Class WebForm1

    ' Declare a page-level variable.
    Dim mstrUname As String

    Private Sub Page_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
        ' Get state variable.
        mstrUname = Application("Uname")
        ' Set variable
        mstrUname = "Wombat"
        ' Use variable.
        Response.Write(mstrUname)
    End Sub

    Private Sub Page_Unload(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles MyBase.Unload
        ' Save state variables back.
        Application("Uname") = mstrUname
    End Sub

End Class
Visual C#
string mstrUname = "";

private void Page_Load(object sender, System.EventArgs e)
{
    // Check if state variable exists.
    if(Application["Uname"] != null)
        // Get state variable.
        mstrUname = Application["Uname"].ToString();
    // Set variable
    mstrUname = "Wombat";
    // Use variable.
    Response.Write(mstrUname);
}

private void Page_UnLoad(object sender, System.EventArgs e)
{
    // Save the state variables back.
    Application["Uname"] = mstrUname;
}
important | In Visual C#, be sure to test whether a state variable is null before invoking any of its methods, such as ToString(). Otherwise, you will receive a run-time error if the state variable does not contain a value.
Turning Session State Off

ASP.NET maintains Session state for each page in your application by default. If a page does not require state information at the session level, you can turn this off to achieve better performance.

To turn Session state off for a Web form:

  • From the Web Form Properties window, set EnableSessionState to False.

    To turn Session state off for an entire application:

  • In the Web.config file, set the <sessionstate mode=> tag to False, as shown here:

    <sessionstate mode="False" …>


Summary

  • Namespaces organize your code and provide access to code in the .NET Framework. To use a namespace from outside of your project, establish a reference to it using the References dialog box from the Project menu. Add an Imports (Visual Basic .NET) or using (Visual C#) statement to the source file to provide a shortcut for referring to members of the namespace in code.

  • Classes define objects within a namespace. You can base one class on another using inheritance. When using inheritance, the base class provides its members to the derived class where they can be overridden, overloaded, or hidden.

  • Web applications use the System.Web and System.Web.UI namespaces. These namespaces define most of the objects used in a Web application, including the Application, Page, Request, and Response objects. These four objects provide access to most of the subordinate objects in a Web application.

  • Because ordinary variables defined in a Web form are not persistent, you need to save volatile data between requests. ASP.NET lets you save data items as query strings, cookies, ViewState, Session state, or Application state.


Lab: Using Objects and Saving Data

In this lab, you will create the Translator application. You will create a Web form and a Class module. You will create an instance of an object in the Global module, save that instance in Session state, and use Session state and view state from the Web form. The application will look like Figure 3-6 when complete.

Estimated lesson time: 20 minutes
Figure 3-6 The completed Translator application
Exercise 1: Create the User Interface

In this exercise, you will create the user interface by adding server controls to the Web form.

To create a new Web application project

  1. Open a new ASP.NET Web application project. In the New Project dialog box, enter the name Translator and click OK.

  2. In the Properties window, change the name of the WebForm1.aspx file to Translator.aspx.

To add the controls to the user interface

Add server controls to the Web form, as shown in Figure 3-6, and define the following properties:

Control | Control Type | Property | Value
Label1 | Label | Text | Universal Translator
 |  | Font | Bold, Large
TextBox1 | Text Box | ID | txtSource
 |  | TextMode | Multiline
Button1 | Button | ID | butTranslate
 |  | Text | Translate
Exercise 2: Create a Translator Class

In this exercise, you will create a Friend class to contain the logic and data that the Translator application uses.

To create a class

  1. From the Project menu, choose Add Class. Visual Studio displays the Add New Item dialog box. Name the class TranslatorClass.vb and click Open.

  2. In the code window, add the following code:

    Visual Basic .NET
    Friend Class TranslatorClass
    Private mstrText As String
    Private mstrOriginal As String

    ' Controls access to the module-level
    ' variables.
    Public Property Text() As String
        Get
            Text = mstrText
        End Get
        Set(ByVal Value As String)
            mstrText = Value
            ' Keep a copy of the original for Restore.
            mstrOriginal = Value
        End Set
    End Property

    ' Restores translated text back to the original.
    Public Sub Restore()
        mstrText = mstrOriginal
    End Sub

    ' Translates the value in the Text property.
    Public Sub Translate()
        Dim strWord As String, intCount As Integer
        Dim arrWords() As String
        Dim bCaps As Boolean
        ' Convert the string to an array using System.String.
        arrWords = mstrText.Split(" ")
        For intCount = 0 To UBound(arrWords)
            ' Check if word is capitalized.
            If LCase(arrWords(intCount)) <> arrWords(intCount) Then
                bCaps = True
                arrWords(intCount) = LCase(arrWords(intCount))
            End If
            strWord = arrWords(intCount)
            ' Do translation.
            If strWord <> "" Then
                strWord = Right(strWord, Len(strWord) - 1) & _
                  Left(strWord, 1) & "ay"
                ' Recapitalize if necessary
                If bCaps Then
                    strWord = UCase(Left(strWord, 1)) & _
                      Right(strWord, Len(strWord) - 1)
                End If
            End If
            ' Store back in the array.
            arrWords(intCount) = strWord
            ' Reset caps flag.
            bCaps = False
        Next
        ' Rebuild string from array.
        mstrText = String.Join(" ", arrWords)
    End Sub

End Class
    Visual C#
    internal class TranslatorClass
{

    string mstrText, mstrOriginal;

    // Controls access to class-level variables.
    public string Text
    {
        get
        {
            return mstrText;
        }
        set
        {
            mstrText = value;
                // Keep a copy of the original for Restore.
            mstrOriginal = value;
}
    }

    // Restores translated text back to the original.
    public void Restore()
    {
        mstrText = mstrOriginal;
    }

    // Translates the value in the Text property.
    public void Translate()
    {
        string strWord;
        string[] arrWords;
        bool bCaps = false;
        // Convert the string into an array using System.String.
        arrWords = mstrText.Split(' ');
        for(int intCount = 0; intCount <= arrWords.GetUpperBound(0);
  intCount++)
        {
            // Change to lowercase.
            strWord = arrWords[intCount].ToLower();
            // Check if word is capitalized.
            if(!arrWords[intCount].Equals(strWord))
                bCaps = true;
               // Do translation.
            if(strWord != "")
            {
                strWord = strWord.Substring(1,strWord.Length - 1) +
                    strWord.Substring(0,1) + "ay";
                // Recapitalize if necessary.
                if(bCaps)
                    strWord = strWord.Substring(0,1).ToUpper() +
                      strWord.Substring(1, strWord.Length - 1);
            }
            // Store the word back in the array.
            arrWords[intCount] = strWord;
            // Reset the caps flag.
            bCaps = false;
        }
        // Rebuild the string from the array.
        mstrText = String.Join(" ", arrWords);
    }
}
Exercise 3: Store a Translator Object in Session State

In this exercise, you will add code to the Global.asax file to initialize a Session state variable containing the TranslatorClass object that your Web form will use.

To create a Session state variable

  1. In the Project window, double-click the Global.asax file and then click the Click Here To Switch To Code View hyperlink.

  2. In the Code View window, add the following Session_Start event procedure:

    Visual Basic .NET
    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
    ' Create a new Translator object.
    Dim TransClass As New TranslatorClass()
    ' Store the object in a Session state variable.
    Session("TransClass") = TransClass
End Sub
    Visual C#
    protected void Session_Start(Object sender, EventArgs e)
{
    // Create a new Translator object.
    Translator TransClass = new TranslatorClass();
    // Store the object in a Session state variable.
    Session["TransClass"] = TransClass;
}
Exercise 4: Use the TransClass Object from Web Form Events

In this exercise, you will add code to the Web form to use the TransClass object stored in the Session state variable. You will also use a Boolean variable stored in the page' s view state to switch the function of the command button from Translate to Restore.

To use the TransClass object from event procedures in the Web form

  1. Open the Web form' s code module in the Code window.

  2. Declare an object variable to receive the object from the Session state.

    Visual Basic .NET
    Dim TransClass As TranslatorClass
    Visual C#
    TranslatorClass TransClass;

  3. In the Page_Load event procedure, add the following code to assign the Session state variable to the TransClass variable you just declared:

    Visual Basic .NET
    Private Sub Page_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
        ' Get the Session TransClass variable.
        TransClass = Session("TransClass")
    End Sub
    Visual C#
    private void Page_Load(object sender, System.EventArgs e)
    {
        // Make sure Session variable exists.
        if(Session["TransClass"] != null)
            // Get the Session TransClass variable.
            TransClass = (TranslatorClass)Session["TransClass"];
    }

  4. Add the following code to the butTranslate_Click event procedure:

    Visual Basic .NET
    Private Sub butTranslate_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles butTranslate.Click
    ' Declare a boolean switch.
    Dim bSwitch As Boolean
    ' Get the value from ViewState and switch it.
    bSwitch = Not Viewstate("bSwitch")
    ' Save the new value in ViewState.
    ViewState("bSwitch") = bSwitch
    ' Use the switch to either translate or restore
    ' the text in TextBox1.
    If bSwitch Then
        ' Get the text.
        TransClass.Text = txtSource.Text
        ' Translate it.
        TransClass.Translate()
        ' Display the text.
        txtSource.Text = TransClass.Text
        ' Change the Button text.
        butTranslate.Text = "Restore"
    Else
        ' Restore the original text.
        TransClass.Restore()
        ' Display the text.
        txtSource.Text = TransClass.Text
        ' Change the Button text.
        butTranslate.Text = "Translate"
    End If
End Sub
    Visual C#
    private void butTranslate_Click(object sender, System.EventArgs e)
{
    // Declare a boolean switch.
    bool bSwitch;
    // Check if ViewState variable exists.
    if(ViewState["bSwitch"] != null)
        // Get the value from ViewState and switch it.
        bSwitch = !(bool)ViewState["bSwitch"];
    else
        // Set the switch.
        bSwitch = true;
    // Save the new value in ViewState.
    ViewState["bSwitch"] = bSwitch;
    // Use the switch to either translate or restore
    // the text in txtSource.
    if (bSwitch)
    {
        // Get the text.
        TransClass.Text = txtSource.Text;
        // Translate it.
        TransClass.Translate();
        // Display the text.
        txtSource.Text = TransClass.Text;
        // Change the Button text.
        butTranslate.Text = "Restore";
    }
    else
    {
        // Restore the original text.
        TransClass.Restore();
        // Display the text.
        txtSource.Text = TransClass.Text;
        // Change the Button text.
        butTranslate.Text = "Translate";
    }
}

  5. Run and test the application.


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. How does the .NET Framework organize its classes?

  2. In Visual Basic .NET, what is difference between a class module and a code module?

  3. In Visual C#, how do you declare a method to make it available without having to first instantiate an object from the class?

  4. How do you call a member of a base class from within a derived class?

  5. What are the four main objects used in Web application programming?

  6. Where would you save the following data items so that they persist between requests to a Web form?

  • A control created at run time

  • An object that provides services to all users

  • User preferences


Creating a User Interface

Chapter 4
Creating a User Interface
Lesson 1: Using Controls
Lesson 2: Validating Data
Lesson 3: Navigating Between Forms
About This Chapter

In this chapter, you will learn how to use ASP.NET controls to create a user interface for a multi-page Web forms application. You will learn about different styles of Web forms, how to choose controls based on the tasks you want to perform, how to validate data fields, and how to navigate between the Web forms in your application.

Before You Begin

To complete the lessons in this chapter, you must

  • Be familiar with the basic elements of HTML.

  • Understand the life cycle of Web forms, as discussed in Chapter 2, “Creating Web Forms Applications.”


Lesson 1: Using Controls

Controls are the tools for all of the tasks you perform on a Web form. They define the appearance of the form and provide a way to get information and perform tasks on behalf of the user.

Microsoft Visual Studio .NET includes two different types of controls that you can use on a Web form and two different ways to position those controls on the form. In this lesson, you will learn about these differences and how they relate to major programming tasks.

After this lesson, you will be able to

  • Select a layout style for your Web form based on the type of application you are creating

  • Explain the differences between server controls and HTML controls and select the appropriate type of control for a particular programming task

  • Bind control values to data items, such as variables, in your application

  • Create templates for controls that contain repeating items

  • Write code to respond to user events on controls

  • Receive files uploaded from a client and store them on the server

Estimated lesson time: 60 minutes
Selecting a Layout

When you draw controls on a Web form, you have two choices for how those controls are arranged:

  • Grid layout. This is the default. Controls are placed exactly where you draw them and they have absolute positions on the page. Use grid layout for Windows-style applications, in which controls are not mixed with large amounts of text.

  • Flow layout. This places controls relative to other elements on the page. If you add elements at run time, the controls that occur after the new element move down. Use flow layout for document-style applications, in which text and controls are intermingled.

To set how controls are placed on a page:

  1. In the Design window, select the Web form.

  2. In the Properties window, select the DOCUMENT object.

  3. Set the DOCUMENT object' s pageLayout property to FlowLayout or GridLayout.

Figure 4-1 shows the difference between GridLayout and FlowLayout on a Web form. You use GridLayout for Web forms that have a fixed appearance. You use FlowLayout for Web forms that incorporate text and controls.

Figure 4-1 Selecting a layout

When you create controls with GridLayout, Visual Studio adds style attributes to each control that set the position of the control, as shown (in bold) by the following form definition:

<form action="webform1.aspx" method="post"
    enctype="multipart/form-data" runat="server" ID="Form1">
<h2>
Upload and View Files on Server
</h2>
<INPUT id="filUpload" style="Z-INDEX: 101; LEFT: 86px; WIDTH: 347px;
    POSITION: absolute; TOP: 20px; HEIGHT: 27px" runat="server"
    type="file" size="38">
    <asp:Button id="butUpload" style="Z-INDEX: 102; LEFT: 357px;
     POSITION: absolute; TOP: 60px" runat="server" Text="Upload"
     Width="74px" Height="31px"></asp:Button>
    <asp:ListBox id="lstServerFiles" style="Z-INDEX: 103; LEFT: 86px;
     POSITION: absolute; TOP: 79px" runat="server" Height="140px"
     Width="168px"></asp:ListBox>
    <asp:Button id="butView" style="Z-INDEX: 104; LEFT: 268px;
     POSITION: absolute; TOP: 178px" runat="server" Height="31px"
     Width="76px" Text="View"></asp:Button>
    <asp:Image id="imgView" style="Z-INDEX: 106; LEFT: 441px;
        POSITION: absolute; TOP: 24px" runat="server"
        Height="132px" Width="162px"></asp:Image>
</form>

When you create controls with FlowLayout, Visual Studio omits the style attribute. You can also create controls by double-clicking the control in the Toolbox, rather than by using the drag-and-drop operation. Using FlowLayout makes it easier to mix controls and text on a Web form, especially when editing the form' s HTML source, as shown in following form definition:

<form id="Form1" method="post" runat="server">
    <h2>
         File Manager
    </h2>
    <p>
    To create a new file, type the name of the file below and click New.
    </p>
    <asp:Literal id="litNoFile" runat="server"></asp:Literal>
    <asp:TextBox id="txtNewFile" runat="server" width="191px"> </asp:TextBox>
    <asp:Button id="butNew" runat="server" Width="62px" Height="29px"
        Text="New"></asp:Button>
    <p>
        To edit a file, select the file from the list below and click Edit.
        Click Delete to delete the selected file.
    </p>
    <asp:Literal id="litNoneSelected" runat="server"></asp:Literal>
    <asp:ListBox id="lstFiles" runat="server" Width="252px"
        Height="161px"></asp:ListBox>
    <br>
    <asp:Button id="butEdit" runat="server" Width="62" Height="29"
        Text="Edit"></asp:Button>
    &nbsp;&nbsp;
    <asp:Button id="butDelete" runat="server" Width="62px" Height="29px"
        Text="Delete"></asp:Button>
</form>
Choosing the Right Control

You can use server controls or HTML controls on a Web form. What' s the difference? Basically, server controls are a superset of HTML controls and offer the advantages shown in Table 4-1.

Table 4-1 Server Controls vs. HTML Controls
Feature | Server controls | HTML controls
Server events | Trigger control-specific events on the server. | Can trigger only page-level events on server (post-back)
State management | Data entered in a control is maintained across requests. | Data is not maintained; must be saved and restored using page-level scripts
Adaptation | Automatically detect browser and adapt display as appropriate. | No automatic adaptation; must detect browser in code or write for least common denominator
Properties | The .NET Framework provides a set of properties for each control. Properties allow you to change the control' s appearance and behavior within server-side code. | HTML attributes only

So why use anything else? Because HTML controls have a one-to-one correspondence with standard HTML elements, they provide more direct control over what appears on a page. You use HTML controls for the following reasons:

  • Migration from earlier versions of ASP. You can load an ASP application into Visual Studio and revise it gradually, rather than rewrite it completely. Previous versions of ASP supported only HTML elements, and these elements become HTML controls when you load the project in Visual Studio .NET.

  • Not all controls require server-side events or state management. This is particularly true when doing data binding. Bound items are usually refreshed from the data source with each request, so it is more efficient not to maintain state information for bound controls. This means you can use HTML controls or turn off state management for bound server controls.

  • You have complete control over what is rendered with HTML controls. ASP.NET adjusts the appearance of server controls based on the browser making the request. HTML controls are not adjusted, so you have direct control over their appearance.

Server and HTML controls provide overlapping functionality. In general, it is easier to work with server controls. Table 4-2 lists the server controls and HTML controls by programming task.

Table 4-2 Server Controls vs. HTML Controls
Task | Server controls | HTML controls
Display text | Label, TextBox, Literal | Label, Text Field, Text Area, Password Field
Display tables | Table, DataGrid | Table
Select from list | DropDownList, ListBox, DataList, Repeater | List Box, Dropdown
Perform commands | Button, LinkButton, ImageButton | Button, Reset Button, Submit Button
Set values | CheckBox, CheckBoxList, RadioButton, RadioButtonList | Checkbox, Radio Button
Display images | Image, ImageButton | Image
Navigation | Hyperlink | none (use <a> tags in text)
Group controls | Panel, Placeholder | Flow Layout, Grid Layout
Work with dates | Calendar | none
Display ads | AdRotator | none
Display horizontal rules | Literal | Horizontal Rule
Get filenames from client | none | File Field
Store data on page | (provided by state management) | Input Hidden
Validate data | RequiredFieldValidator, CompareValidator, RangeValidator, RegularExpressionValidator, CustomValidator,ValidationSummary | none (use page-level)

The following sections describe the major programming tasks you perform with controls to create a user interface.

Working with Text

There are a lot of ways to display text on a page. For read-only text, you can write directly to the Response object, as in Response.Write(“Some text”); you can use a Label control; you can use a TextBox control and set its ReadOnly property to True; or you can use a Literal control and compose the text in HTML as you would with the Response.Write method.

To display editable text, however, you want to use a TextBox server control. The TextBox control has the key properties listed in Table 4-3.

Table 4-3 TextBox Control Properties
Property | Use to
Text | Get or set the data in the TextBox.
TextMode | Display SingleLine, MultiLine (scrollable), or Password text. When set to Password, the text box displays dots rather than the characters typed.
ReadOnly | Prevent the user from changing the text.
AutoPostBack | When set to True, causes the TextBox to fire a TextChanged post-back event when the user leaves the TextBox after changing the contents. By default, this property is set to False and the TextChanged event is cached until some other post-back event occurs.

To see the text controls in action, perform the following steps to create a logon page:

  1. Start a new Web application with two Web forms named Webform1 and Webform2.

  2. On Webform1, draw Label, TextBox, and Button controls, and set their properties to the values shown in the following table:

    Control

    Property

    Setting

    Label1

    Text

    Username:

    Label2

    Text

    Password:

    TextBox1

    ID

    txtUser

    TextBox2

    ID

    txtPassword

    TextMode

    Password

    Button1

    ID

    butOK

    Text

    OK

  3. Add the following code to the butOK_Click event procedure:

    Visual Basic .NET
    Private Sub butOK_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles butOK.Click
        If txtUser.Text = "Guest" And txtPassWord.Text = "Moondog" Then
            Response.Redirect("Webform2.aspx")
        Else
            txtPassword.Text = ""
        End If
    End Sub
    Visual C#
    private void butOK_Click(object sender, System.EventArgs e)
    {
        if ((txtUser.Text == "Guest") &&
              (txtPassword.Text == "Moondog"))
            Response.Redirect("Webform2.aspx");
        else
            txtPassword.Text = "";
    }

  4. Run the application. When you enter the username and password specified in the code, Webform2 is displayed, as shown in Figure 4-2. Notice that setting the TextMode property to Password causes each character entered to be replaced with a symbol.

    Figure 4-2 WebForm1 and WebForm2

Of course, it' s usually not a good idea to put passwords and usernames in code. Instructions on how to save and retrieve passwords from a secure file on the server are included in Chapter 8, “Maintaining Security.”

Working with Tables and Lists

Text displayed in Labels and TextBoxes is arranged in a single block. To arrange text into rows and columns, you need to use one of the list or table controls described in Table 4-4. Use the ListBox, DropDownList, and Table controls for simple dynamic tables and lists. Use the DataGrid, DataList, and Repeater controls for complex tables and lists that contain other controls or are bound to data.

Table 4-4 ASP.NET List and Table Controls
Control | Use to
ListBox | Display read-only text in a simple scrollable list format.
DropDownList | Display read-only text in a simple drop-down list format.
Table | Display text and controls in columns and rows. Table controls allow you to dynamically build tables in code using TableRows and TableCells collections.
DataGrid | Display text and controls in columns and rows using a template to control appearance. DataGrid controls have built-in formatting, sorting, and paging capabilities.
DataList | Display rows of text and controls using a template to control appearance. DataList controls have built-in formatting and selection capabilities.
Repeater | Display rows of other controls using a template to control appearance. Repeater controls do not include the built-in capabilities found in the DataGrid and DataList controls.
Adding Items to a List or Table at Design Time

The ListBox, DropDownList, and Table controls allow you to add static items at design time using the Collection Editor dialog box, as shown in Figure 4-3.

Figure 4-3 The Collection Editor

You use the Collection Editor to add static items to a ListBox, DropDownList, or Table control:

  • To add static items to a ListBox or DropDownList control, select the Items property in the Properties window and click the button next to (Collection).

  • To add static items to a Table control, select the Rows property in the Properties window and click (Collection).

Adding Items to a List or Table at Run Time

To add items to a list at run time, use the Add method on the control' s Items collection. For example, the following code adds items entered in a TextBox to a ListBox and DropDownList control:

Visual Basic .NET
Private Sub butAdd_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butAdd.Click
    ListBox1.Items.Add(txtSource.Text)
    DropDownList1.Items.Add(txtSource.Text)
End Sub
Visual C#
private void butAdd_Click(object sender,
      System.EventArgs e)
{
    ListBox1.Items.Add(txtSource.Text);
    DropDownList1.Items.Add(txtSource.Text);
}

Both the ListBox and the DropDown list automatically store the items you add to them at run time. The Table control, however, will automatically store data only for the table cells created at design time in the Collection Editor. To create additional table rows and cells at run time, you need to rebuild the table from information stored in a state variable. For example, the following code displays comma-delimited items entered in a text box as cells in a table, adding rows to the table each time the user clicks the Add button:

Visual Basic .NET
Private Sub butAdd_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles butAdd.Click
        ' Add text to the page's ViewState.
        ViewState.Add(Viewstate.Count, txtSource.Text)
        ' Rebuild the table
        RebuildTable()
    End Sub

Private Sub RebuildTable()
    Dim iCount1, iCount2 As Integer
    Dim arrWords As String()
    Dim strWords As String
    ' For each string saved in ViewState.
    For iCount1 = 0 To ViewState.Count - 1
        ' Create a new table row.
        Dim rowNew As New TableRow()
        ' Get the string from ViewState.
        strWords = ViewState(iCount1)
        ' Break item list into an array.
        arrWords = Split(strWords, ",")
        ' For each item in the array.
        For iCount2 = 0 To UBound(arrWords)
            ' Create a new table cell.
            Dim celNew As New TableCell()
            ' Set the text to display in the cell.
            celNew.Text = arrWords(iCount2)
            ' Add the cell to the table row.
            rowNew.Cells.Add(celNew)
        Next
        ' Add the row to the table.
        Table1.Rows.Add(rowNew)
    Next
End Sub
Visual C#
private void butAdd_Click(object sender,
      System.EventArgs e)
{
    ListBox1.Items.Add(txtSource.Text);
    DropDownList1.Items.Add(txtSource.Text);
    // Add text to the page's ViewState.
    ViewState.Add(ViewState.Count, txtSource.Text);
    RebuildTable();
}

private void RebuildTable()
{
    string[] arrWords;
    string strWords;
    TableRow rowNew;
    TableCell celNew;
    // For each string saved in ViewState.
    for (int iCount1 = 0; iCount1 < ViewState.Count;
            iCount1++)
    {
        // Create a new table row.
        rowNew = new TableRow();
        // Get the string from ViewState.
        strWords = ViewState(iCount1);
        // Break the item list into an array.
        arrWords = Split(strWords, ",");
        // For each item in the array.
        for (int iCount2 = 0;
                 iCount <= arrWords.GetUpperBound; iCount2++)
        {
            // Create a new table cell.
            celNew = new TableCell();
            // Set the text to display in the cell.
            celNew.Text = arrWords[iCount2];
            // Add the cell to the table row.
            rowNew.Cells.Add(celNew);
        }
        // Add the row to the table.
        Table1.Rows.Add(rowNew);
    }
}
Getting the Selected Item from a List

Use the SelectedItem property to get the current selection from a list. For example, the following code displays the items selected from a list box in a label on a Web form:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Test if there is a selected item.
    If Not IsNothing(ListBox1.SelectedItem) Then
        ' Display the selected item.
        Label1.Text = "The selected item is: " _
          & ListBox1.SelectedItem.Text
    Else
        Label1.Text = "No item is selected."
    End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    // Test is an item is selected.
    if (ListBox1.SelectedItem == null)
        Label1.Text = "No item is selected.";
    else
        // Display the selected item.
        Label1.Text = "The selected item is: " +
            ListBox1.SelectedItem.Text;
}

The SelectedItem property returns an object, so be sure to test if it exists before trying to use it.

Using Simple Data Binding with Lists

Controls can get their values from any data source in your application. Data sources can be any public data—whether it is a database table, an array, a property of an object, or an expression combining several items. At its simplest level, this provides an easy way to initialize the values of ListBoxes and DropDownLists on a Web form.

To see how simple data binding works, follow these steps:

  1. Create a Web form with a DropDownList and the following code:

    Visual Basic .NET
    ' Public data item for DropDownList.
Public arrData As String() = {"This", "that", "and", _
  "the", "other"}

Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Bind data to controls on this page.
    Page.DataBind()
End Sub
    Visual C#
    public string[] arrData= {"This", "that", "and",
                      "the",  "other"};

private void Page_Load(object sender, System.EventArgs e)
{
    Page.DataBind()
}

  2. Select the DropDownList and click the button in the DataBinding property in the Properties window. Visual Studio displays the DataBindings dialog box, as shown in Figure 4-4. You use the DataBindings dialog box to load items from any public data source to list or table controls.

    Figure 4-4 The DataBindings dialog box

  3. Select the DataSource property in the Bindable Properties list, then select the Custom Binding Expression option button and type arrData in the expression text box. When you run the application, the items in the arrData array are displayed in the DropDownList, as shown in Figure 4-5.

    Figure 4-5 Binding a list control to an array

When you use data binding with a server control, you can turn off state management for that control. This improves performance because the DataBind method replaces the automatic view state management provided by ASP.NET.

To turn off state management for a server control, set the control' s EnableViewState property to False.

Adding Items to DataGrid, DataList, and Repeater Controls

Use data binding to add items to the DataGrid, DataList, and Repeater controls. These three controls use templates to define their appearance at run time. A template is a set of HTML elements or server controls, or both, that will be repeated for each data item in the control.

To add items to a DataGrid, DataList, or Repeater control, follow these steps:

  1. Define the data source.

  2. Draw the DataGrid, DataList, or Repeater control and bind to the data source.

  3. Edit the templates in the control to add HTML elements or server controls that will be repeated within the list or grid.

  4. Set the properties of the server controls contained in the list or grid to bind to data items in the container' s data source.

The following example shows how to add template columns to a DataGrid and how to bind the controls in those columns to a simple data source:

  1. Create a public data source in your application as you did in the previous steps. For example, the following code creates an array to demonstrate a simple data source:

    Visual Basic .NET
    ' Public data item for DataGrid.
    Public arrData As String() = {"This", "that", "and", _
      "the", "other"}
    Visual C#
    // Public data item for DataGrid.
    public string[] arrData = new string[] {"This", "that",
        "and", "the", "other"};

  2. Draw a DataGrid control on a Web form.

  3. Add template columns to the control by selecting the DataGrid control and then clicking the Property Builder link below the Property Sheet in the Properties window. Visual Studio displays the Properties dialog box, as shown in Figure 4-6.

    Figure 4-6 Properties dialog box

  4. In the Properties dialog box, select Columns, then select Template Column in the Columns list and click the Add (>) button to add a template column to the DataGrid control. For this example, add two template columns and click OK.

  5. In the Properties window, select the DataSource property and specify the data source for the control. For this example, type in arrData, the array created in step 1.

  6. Create the template used to display data in the control: right-click the DataGrid control, select Edit Template, and then select Column(0) from the pop-up menu. The control' s appearance changes to Edit mode (Figure 4-7).

    Figure 4-7 Adding controls to DataGrid

  7. Draw other controls on the Web form, and then drag them on to the template to add them to the DataGrid. For this example, draw a TextBox and drag it on to column 0.

  8. In the Properties window, select the DataBindings property of the control you just added to the template and click the button that appears. Visual Studio displays the DataBindings dialog box, as shown in Figure 4-8.

    Figure 4-8 DataBindings dialog box

  9. In the Bindable Properties list, select the property to receive the data item. For this example, select Text. Select Simple Binding, and then click Container and DataItem to specify which data item to put into the selected property. Click OK to close the dialog box.

  10. Edit the second template column. To do this, right-click the DataGrid control, select Edit Template, and then select Column(1) from the pop-up menu.

  11. Repeat steps 4 through 7 for Column(1). For this example, draw a Button and drag it on to the Column(1) template in the DataGrid.

  12. Close the template when you are done. To do this, right-click the template and select End Template Editing from the pop-up menu. Visual Studio displays the contained controls, as shown in Figure 4-9. To change any of the contained control' s properties, edit the template as we did in the preceding steps.

    Figure 4-9 The bound controls
Performing Commands

The Button, LinkButton, and ImageButton server controls all trigger post-back events to perform commands. A post-back event begins a request from the browser, causing the server to process the page' s events. The Click event procedure for a button control is processed after any validation or cached events on a page.

To see the order of page events, place a TextBox, a ListBox, and a Button control on a Web form and add the following code:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
        Response.Write("Page load.<br>")
' Add items to list box the first time the page loads.
If Not IsPostBack Then
ListBox1.Items.Add("This")
ListBox1.Items.Add("That")
ListBox1.Items.Add("The other")
End If
End Sub

Private Sub TextBox1_TextChanged(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles TextBox1.TextChanged
    Response.Write("Text changed.<br>")
End Sub

Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged
    Response.Write("Item selected.<br>")
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles Button1.Click
    Response.Write("Button clicked.<br>")
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    Response.Write("Page load.<br>");
    // Add items to list box the first time the page loads.
    if (!IsPostBack)
    {
        ListBox1.Items.Add("This");
        ListBox1.Items.Add("That");
        ListBox1.Items.Add("The other");
    }
}

private void TextBox1_TextChanged(object sender,
    System.EventArgs e)
{
    Response.Write("Text changed.<br>");
}

private void ListBox1_SelectedIndexChanged(object sender,
    System.EventArgs e)
{
    Response.Write("Item selected.<br>");
}
private void Button1_Click(object sender, System.EventArgs e)
{
    Response.Write("Page load.<br>");
}

When you run the preceding code, the page displays the event order, as shown in Figure 4-10. The Click event procedure is the last control event processed on a page.

Figure 4-10 Event order

Using the Button and LinkButton Click event procedure is straightforward. The ImageButton control provides an additional capability. The Click event argument for the ImageButton control includes the X and Y coordinates for where the user clicked on the control. The image response depends on where it was clicked, as shown in Figure 4-11. Images that respond to clicks in this way are called image maps.

Figure 4-11 ImageButton control

The following code uses the ImageButton' s e.X and e.Y arguments to calculate which circle was clicked in Figure 4-11:

Visual Basic .NET
Private Sub ImageButton1_Click(ByVal sender As System.Object, _
    ByVal e As System.Web.UI.ImageClickEventArgs) Handles ImageButton1.Click
      Dim xOffset, yOffset, X, Y, Radius As Single
      Dim strMessage As String
      ' Calculate the radius of the click from X, Y.
      xOffset = CSng(sender.width.value) / 2
      yOffset = CSng(sender.height.value) / 2
      X = System.Math.Abs(CSng(e.X) - xOffset)
      Y = System.Math.Abs(CSng(e.Y) - yOffset)
      Radius = System.Math.Sqrt((X ^ 2) + (Y ^ 2))
      ' Set the message to display.
      Select Case CInt(Radius)
          Case 0 To 21
              strMessage = "on center circle."
          Case 22 To 42
              strMessage = "on second circle."
          Case 43 To 64
              strMessage = "on third circle."
          Case 65 To 86
              strMessage = "on fourth circle."
          Case Else
              strMessage = "outside of circle."
      End Select
      ' Display the message.
      Label1.Text = "You clicked " & strMessage
End Sub
Visual C#
private void ImageButton1_Click(System.Object sender,
       System.Web.UI.ImageClickEventArgs e)
{
    double xOffset, yOffset, X, Y, Radius;
    string strMessage;
    // Calculate the radius of the click from X, Y.
    xOffset = Convert.ToDouble(ImageButton1.Width.Value) / 2;
    yOffset = Convert.ToDouble(ImageButton1.Height.Value) / 2;
    X = Math.Abs(Convert.ToDouble(e.X) - xOffset);
    Y = Math.Abs(Convert.ToDouble(e.Y) - yOffset);
    Radius = Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2));
    if (Radius < 22)
    {
        strMessage = "on center circle.";
    }
    else if ((Radius >= 22) && (Radius <= 42))
    {
        strMessage = "on second circle.";
    }
    else if ((Radius > 42) && (Radius <= 64))
    {
        strMessage = "on third circle.";
    }
    else if ((Radius > 64) && (Radius <= 86))
    {
        strMessage = "on fourth circle.";
    }
    else
    {
        strMessage = "outside of circle.";
    }
    // Display the message.
    Label1.Text = "You clicked " + strMessage;
}
Getting and Setting Values

Use the RadioButton, RadioButtonList, CheckBox, or CheckBoxList controls to get Boolean value settings from the user. As with the ListBox and DropDownList, use the Collection Editor to add items to a RadioButtonList or a CheckBoxList. Click the control' s Items property to display the Collection Editor for that control.

Use the Checked property to get the setting from a CheckBox or a RadioButton. For example, the following code displays whether the CheckBox1 control is selected:

Visual Basic .NET
Private Sub Button1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Button1.Click
    Response.Write("Checkbox1 is " & CheckBox1.Checked.ToString)
End Sub
Visual C#
private void Button1_Click(object sender,
              System.EventArgs e)
{
    Response.Write("Checkbox1 is " + CheckBox1.Checked.ToString());
}

When you first draw a RadioButton on a Web form, it doesn' t automatically interact with the other RadioButtons on the form the way OptionButton controls do on a Windows form. To get the controls to interact, you need to set the GroupName property for each RadioButton. Figure 4-12 shows three RadioButtons with the same GroupName. Because these RadioButtons have the same GroupName, selecting one clears any other selected control.

Figure 4-12 Grouped RadioButtons

To get or set the values from a CheckBoxList or RadioButtonList control, use a For Each loop to check each control in the list. The controls contained in a CheckBoxList are not CheckBox controls, as you might expect. Instead, the CheckBoxList and RadioButtonList controls contain ListControls. To determine the setting of a ListControl, use the Selected property, as shown in the following code:

Visual Basic .NET
Private Sub Button1_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles Button1.Click
    Dim lstItem As ListItem
    For Each lstItem In RadioButtonList1.Items
        If lstItem.Selected Then
            Response.Write(lstItem.Text & " is selected.<br>")
        End If
    Next
End Sub
Visual C#
private void Button1_Click(object sender,
          System.EventArgs e)
{
    foreach (ListItem lstItem in RadioButtonList1.Items)
    {
        if (lstItem.Selected)
            Response.Write(lstItem.Text +
                        " is selected.<br>");
    }
}
Displaying Graphics and Advertisements

There are many ways to display graphics on a Web form:

  • As a background. Use the Background property of the Web form to display an image on the entire page. Use the BackImageUrl property of the Panel control to display a background image in one region of a page, rather than over the entire page.

  • As a foreground. Use the Image control to display images in the foreground.

  • As a button. Use the ImageButton control to display images that respond to user events. See the preceding section, “Performing Commands,” for an example of responding to click events on an image.

  • As an ad. Use the AdRotator control to display images from a list of ads. Ads displayed in the AdRotator control contain hyperlinks to the advertiser' s Web site.

The Image control does not respond to user events, but it allows you to display graphics dynamically based on input from other controls. To display an image in an Image control at run time, use the ImageUrl property. For example, the following code displays a picture of Venice, Italy, when the user clicks a button.

Visual Basic .NET
Private Sub Button1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Button1.Click
    Image1.ImageUrl = "gondola.jpg"
End Sub
Visual C#
private void Button1_Click(object sender,
              System.EventArgs e)
{
    Image1.ImageUrl = "gondola.jpg";
}

A common use of graphics is for advertisements—most Internet ads are stored as .gif files. Visual Studio includes the AdRotator control to handle the tasks associated with displaying these types of ads. The AdRotator control uses an XML file to schedule the ads that are displayed. The XML file stores the URL of the advertisement' s image, the URL of the page to display if the user clicks the ad, the priority of the ad, and other properties of the ad. At run time, the AdRotator selects one of the ads listed in its XML file and displays it on the page.

To use the AdRotator control, follow these steps:

  1. Draw an AdRotator control on a Web form.

  2. Add an XML file to the current project and open it.

  3. In the Properties window, select the TargetSchema property, and then select AdRotator Schedule File from the drop-down list of property settings. Visual Studio adds the following lines to the XML file:

    <Advertisements xmlns="http://schemas.microsoft.com/AspNet/AdRotator-Schedule-File">

</Advertisements>

  4. Add <Ad> tags to the <Advertisements> section of the XML file for each ad you want to display.

  5. Save the XML file and switch back to the Web form in Design view.

  6. Select the AdRotator control and set the AdvertisementsFile property to the XML file you just created.

Now edit the XML file to display the desired information. For example, the following XML file displays two different ads:

<?xml version="1.0" encoding="utf-8" ?>
<Advertisements xmlns="http://schemas.microsoft.com/AspNet/AdRotator-Schedule-File">
    <Ad>
        <ImageUrl>../ads/sponsorad.gif</ImageUrl>
        <NavigateUrl>/ads/sponsorad.htm</NavigateUrl>
        <AlternateText>Click here to visit our sponsor.</AlternateText>
        <Keyword></Keyword>
        <Impressions>1</Impressions>
    </Ad>
    <Ad>
        <ImageUrl>../ads/net.gif</ImageUrl>
        <NavigateUrl>//www.gotdotnet.com</NavigateUrl>
        <AlternateText> ASP.NET tutorials and more.</AlternateText>
        <Keyword>ASP</Keyword>
        <Keyword>.NET</Keyword>
        <Impressions>5</Impressions>
    </Ad>
</Advertisements>

The tags in the AdRotator XML schema are defined in Table 4-5.

Table 4-5 The AdRotator Schema
Tag | Meaning
<Ad> | Begins an ad.
<ImageUrl> | The address of the ad to display.
<NavigateUrl> | The address to navigate to if the user clicks the ad.
<AlternateText> | The text to display in a ToolTip popup if the user pauses the mouse over the ad. Also, if the ImageUrl cannot be displayed, this text appears in its place.
<Keyword> | A category name to use for filtering the ads to display.
<Impressions> | A number representing the likelihood that an ad will be displayed. Ads with higher numbers are more likely to display.
Grouping Controls

Place controls in a group when you want to manipulate a particular region of your Web form in code. For example, you might want to allow users to log on to a system from a region of a page, and then hide or disable that region once they are logged on.

Use the Panel control to group controls on a Web form. Using the Panel control on a Web form is different from using the Panel control on a Windows form. For one thing, you cannot draw controls onto a Panel control. You must first draw your controls on the Web form, and then drag them on to the Panel control. For another thing, the Web Forms Panel control uses flow layout rather than grid layout. This means you cannot drag a control to an exact position on a panel.

note | You do not have to place RadioButton controls in a panel to get them to work together. Instead, you enter a name in their GroupName property. See the section, “Getting and Setting Values,” for more information.

To position controls on a Panel control, follow these steps:

  1. Draw the controls on the Web form.

  2. Drag the controls on to the Panel control in the order you want them displayed. Visual Studio will place each control immediately after the next in sequence.

  3. Use carriage returns and spaces to place the controls where you want them to appear.

Figure 4-13 shows a group of controls that allow you to log on to a Web site. After the user logs on, the panel is hidden and a welcome message is displayed.

Figure 4-13 Positioning controls in a panel

The following code hides the panel after the user is logged on:

Visual Basic .NET
Private Sub butOK_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butOK.Click
    ' If name/password match, hide log on panel and show message.
    If txtName.Text = "Guest" And txtPassword.Text = "Wombat" Then
        pnlLogon.Visible = False
        lblWelcome.Text = "Welcome " & txtName.Text
    Else
        txtPassword.Text = ""
        vldPassword.Validate()
    End If
End Sub
Visual C#
private void butOK_Click(object sender, System.EventArgs e)
{
    // If name/password match, hide the log on panel
    // and show message.
    if ((txtName.Text == "Guest") &&
           (txtPassword.Text == "Wombat"))
    {
        pnlLogon.Visible = False;
        lblWelcome.Text = "Welcome " + txtName.Text;

    }
    else
    {
        txtPassword.Text = "";
        vldPassword.Validate();
    }
}
Getting Dates

Use the Calendar control to get or display date information. The Calendar control provides properties to control the appearance of elements on the calendar, as shown in Figure 4-14.

Figure 4-14 Calendar control properties

To get or set dates selected on the Calendar control, use the SelectionChanged event procedure and the SelectedDate or SelectedDates properties. SelectionChanged is a post-back event, so the following code displays the selected date or dates as soon as the selection changes:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
    ' Display current date in the label to start.
    lblDate.Text = "Current date: " & Date.Now.Date
End Sub

Private Sub calSource_SelectionChanged(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles calSource.SelectionChanged
    If calSource.SelectedDates.Count = 1 Then
        ' If one date is selected, display it in a label.
        lblDate.Text = "Selected date: " & calSource.SelectedDate
    Else
        ' If multiple dates are selected, display them.
        lblDate.Text = "Selected dates: " & calSource.SelectedDates(0) & _
        " to " & _
        calSource.SelectedDates(calSource.SelectedDates.Count - 1)
    End If
End Sub
Visual C#
private void Page_Load(object sender,
           System.EventArgs e)
{
    // Display the current date.
    lblDate.Text = "Current date: " +
            calSource.TodaysDate;
}

private void calSource_SelectionChanged(object sender,
      System.EventArgs e)
{
    // Display the current date.
    lblDate.Text = "Current date: " +
            calSource.TodaysDate;
    if (calSource.SelectedDates.Count == 1)
        // If one date is selected, display it.
        lblDate.Text = "Selected date: " +
                  calSource.SelectedDate;
    else
        // If multiple dates are selected,
                //display them.
        lblDate.Text = "Selected dates: " +
                  calSource.SelectedDates[0] + " to " +
                  calSource.SelectedDates
                  [calSource.SelectedDates.Count - 1];

}
Getting Files from the Client

Use the File Field HTML control to upload files from the client to the server. The File Field HTML control is actually a Text Field HTML control and a Submit Button HTML control bound together. Clicking the Browse button runs a built-in script that displays the Windows Choose File dialog box on the client' s computer, as shown in Figure 4-15.

Figure 4-15 Windows Choose File dialog box

To receive the selected client file on the server, follow these steps:

  1. Draw a File Field HTML control on a Web form.

  2. Right-click the control and select Run As Server Control from the pop-up menu.

  3. Right-click the Web form and select View HTML Source. Visual Studio displays the HTML code for the Web form.

  4. Add an enctype attribute to the <form> tag in the Web form' s HTML. The enctype attribute sets the MIME type of the form, and uploading a file requires both a MIME type of multipart/form-data and a form method of post, as shown here in bold:

    <form action="webform1.aspx" method="post" enctype="multipart/form-data" runat="server" ID="Form1">

  5. Right-click the Web form and select View Design from the pop-up menu. Visual Studio displays the Web form Designer.

  6. Add a Button control and a Click event procedure to the Web form to get and save the selected file. For example, the following event procedure saves the file to the server:

    Visual Basic .NET
    Private Sub butUpload_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles butUpload.Click
    Dim strFilename As String()
    ' Break the file specification into parts.
    strFilename = filUpload.PostedFile.FileName.Split("\")
    ' Save uploaded file to server using the file base name.
    filUpload.PostedFile.SaveAs_
      (Request.MapPath(Request.ApplicationPath) & "\" & _
      strFilename(UBound(strFilename)))
    ' Add file to list of server files.
    lstServerFiles.Items.Add(strFilename(UBound(strFilename)))
    ' Select the first item in the list box.
    lstServerFiles.SelectedIndex = 0
End Sub
    Visual C#
    private void butUpload_Click(object sender,
      System.EventArgs e)
{
    // Get the full file name.
    string strFilename = filUpload.PostedFile.FileName;
          // Get the base name.
    strFilename =
            System.IO.Path.GetFileName(strFilename);
    // Save file as base name on server.
    filUpload.PostedFile.SaveAs(Request.MapPath
            (Request.ApplicationPath) +  "\\" + strFilename);
    // Add file to list of server files.
    lstServerFiles.Items.Add(strFilename);
    // Select the first item in the list.
    lstServerFiles.SelectedIndex = 0;
}

    7. The PostedFile method of the File Field HTML control represents the file being uploaded. You can use the FileName property and SaveAs method of the returned object to save the file on the server, as shown in the preceding code. The following code shows one way to retrieve the file from the server. In this case, the file is displayed in the browser.

    Visual Basic .NET
    Private Sub butView_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles butView.Click
    Dim strFilename As String
    ' Get selected file name.
    strFilename = lstServerFiles.SelectedItem.ToString
    ' Display as Web page.
    Response.Redirect(Request.ApplicationPath & "/" & strFilename)
End Sub
    Visual C#
    private void butView_Click(object sender,
      System.EventArgs e)
{
    // Get selected file name.
    string strFilename =
            lstServerFiles.SelectedItem.ToString();
    // Display as web page.
    Response.Redirect(Request.ApplicationPath +
            "/" + strFilename);
}


Lesson 2: Validating Data

One of the most important steps in getting data entries from a user is ensuring that the data is valid. Validity is determined by a number of criteria: Did the user enter anything? Is the entry the right kind of data (a telephone number, for example)? Is the data within a required range?

ASP.NET provides validation controls to help you check Web form data entries against these and other criteria before the data items are accepted. This lesson teaches you how to use the validation controls to catch invalid data and direct the user to fix the problems that occur.

After this lesson, you will be able to

  • Explain how ASP.NET checks validity, including why it checks validity both on the client side and on the server side

  • Validate data entries in a TextBox using one or more criteria

  • Display error messages to help users correct data validation problems on a Web form

  • Cancel validation to allow the user to exit from a Web form that has validation errors

  • Create custom validation criteria to be evaluated on the server or client, or both

Estimated lesson time: 40 minutes
Using Validation

The validation controls check the validity of data entered in associated server controls on the client before the page is posted back to the server. This is an important improvement to previous validation schemes—most validity problems can be caught and corrected by the user without a round trip to the server.

Client-side validation is provided by a JScript library named WebUIValidation.js, which is downloaded separately to the client. Though JScript is widely supported, the Document Object Model (DOM) that the library relies on is available only in Microsoft Internet Explorer version 4.0 and later. Therefore, validation controls also automatically provide server-side validation. Server-side validation is always performed, whether or not client-side validation has occurred. This double-checking ensures that custom validations are performed correctly and that client-side validation has not been circumvented.

The validation controls check the value of the server control specified in their ControlToValidate property. Table 4-6 describes the six different validation controls.

Table 4-6 ASP.NET Validation Controls
Validation control | Use to
RequiredFieldValidator | Check if a control contains data
CompareValidator | Check if an entered item matches an entry in another control
RangeValidator | Check if an entered item is between two values
RegularExpressionValidator | Check if an entered item matches a specified format
CustomValidator | Check the validity of an entered item using a client-side script or a server-side code, or both
ValidationSummary | Display validation errors in a central location or display a general validation error description

To use the validation controls, follow these steps:

  1. Draw a validation control on a Web form and set its ControlToValidate property to the control you want to validate. If you' re using the CompareValidator control, you also need to specify the ControlToCompare property.

  2. Set the validation control' s ErrorMessage property to the error message you want displayed if the control' s data is not valid.

  3. Set the validation control' s Text property if you want the validation control to display a message other than the message in the ErrorMessage property when an error occurs. Setting the Text property lets you briefly indicate where the error occurred on the form and display the longer ErrorMessage property in a ValidationSummary control.

  4. Draw a ValidationSummary control on the Web form to display the error messages from the validation controls in one place.

  5. Provide a control that triggers a post-back event. Although validation occurs on the client-side, validation doesn' t start until a post-back is requested.

Figure 4-16 shows simple validation being performed on a Web form.

Figure 4-16 Validation on a Web form

Figure 4-17 shows validation using a ValidationSummary control. Notice that the individual validators display their Text properties, while the longer ErrorMessage property is displayed in the ValidationSummary control.

Figure 4-17 Using the ValidationSummary control

To display validation errors as a dialog box, set the ValidationSummary control' s ShowMessage property to True. You can see the result in Figure 4-18.

Figure 4-18 Validation errors dialog box
Combining Validations

A server control can have multiple validators. For instance, a TextBox for a telephone number might be both a required field and a regular expression, so the TextBox would be checked by two validation controls, as shown in Figure 4-19.

To write meaningful error messages when more than one validation control checks a single control, you need to understand the ASP.NET validation rules. These rules might not make obvious sense, but they make it possible to write error messages that address specific problems.

For example, all validation controls except the RequireFieldValidator are considered valid if they are blank. This makes it possible to provide one error message if the control is blank, using the RequiredFieldValidator, and a different error message if the control is out of range or not in the right format, as shown in the telephone number example in Figure 4-19.

Figure 4-19 Multiple validators

The rules listed in Table 4-7 apply to data conversion for the CompareValidator.

Table 4-7 CompareValidator Validation Rules
If the control | The result is…
Specified in the ControlToValidate property cannot be converted to the appropriate data type | Invalid
Specified in the ControlToCompare property cannot be converted to the appropriate data type | Valid

In the second case in Table 4-7, you should provide a separate validation control for the control specified in the ControlToCompare property to make sure it is the appropriate data type.

Canceling Validation

Because validation occurs before the server processes the page, a user can become trapped by validation unless you provide a way to cancel validation without posting back.

To let the user cancel validation, provide a Submit HTML control that sets the Page_ValidationActive attribute, as shown (in bold) by the following HTML code:

<INPUT id="butCancel" onclick="Page_ValidationActive=false;" style="Z INDEX:
114; LEFT: 386px; WIDTH: 62px; POSITION: absolute; TOP: 130px; HEIGHT: 30px"
type="submit" value="Cancel">

The preceding button definition cancels validation and posts the page back to the server. You can tell if the user has cancelled the operation by checking the Page object' s IsValid property in the Page_Load event procedure. You have to revalidate the page because canceling validation sets IsValid to True. The following code shows how to check whether the user cancelled validation:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Validate in case user cancelled validation.
    If Page.IsPostBack Then Page.Validate()
    ' Check if page is valid.
    If Not Page.IsValid Then
        ' User cancelled operation, return home.
        Response.Redirect("default.htm")
    End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    // Validate in case user cancelled validation.
    if (Page.IsPostBack)
    {
        Page.Validate();
        if (!Page.IsValid)
            // User cancelled validation.
            Response.Redirect("default.htm");
    }
}
Customizing Validation

To perform complex types of validation not provided by the standard validation control, use a CustomValidator control and write code to perform the validation on the server side and (optionally) on the client side.

On the server side, place your validation code in the ServerValidate event procedure. The arguments to this procedure provide access to the control to validate. The following event procedure validates that an entered number is prime:

Visual Basic .NET
' Validate a prime number.
Private Sub CustomValidator1_ServerValidate _
  (ByVal source As System.Object, ByVal args As _
  System.Web.UI.WebControls.ServerValidateEventArgs) _
  Handles CustomValidator1.ServerValidate
    Try
        Dim iPrime, iCount As Integer
        ' Get value from ControlToValidate (passed as args)
        iPrime = Integer.Parse(args.Value)
        For iCount = 2 To iPrime \ 2
            ' If number is evenly divisible, it's
            ' not prime, return False.
            If iPrime Mod iCount = 0 Then
                args.IsValid = False
                Return
            End If
        Next
        ' Number is Prime, return True.
        args.IsValid = True
        Return
    Catch ex As Exception
        ' If there was an error parsing, return False.
        args.IsValid = False
       Return
    End Try
End Sub
Visual C#
private void vldtxtPrime_ServerValidate(object source,
      System.Web.UI.WebControls.ServerValidateEventArgs args)
{
    try
    {
        // Get value from ControlToValidate
                // (passed as args).
        int iPrime = Int32.Parse(args.Value);
        for (int iCount = 2; iCount <= (iPrime / 2);
                  iCount++)
        {
            // If number is evenly divisible, it's
            // not prime,return False.
            if((iPrime % iCount) == 0)
            {
                args.IsValid = false;
                return;
            }
            // Number is prime, return True.
            args.IsValid = true;
            return;
        }
    }
    catch(Exception e)
    {
        // If there was an error parsing, return False.
        args.IsValid = false;
        return;
    }
}

To provide client-side validation, specify a validation script in the CustomValidator control' s ClientValidationFunction property. Client-side validation is optional, and if you provide it, you should maintain similar validation code in both places. The following script provides a client-side version of the prime number validation performed on the server:

VBScript
<script language="vbscript">
        Sub ClientValidate(source, arguments)
            For iCount = 2 To arguments.Value \ 2
                ' If number is evenly divisible, it's
                ' not prime, return False.
                If arguments.Value Mod iCount = 0 Then
                    arguments.IsValid = False
                    Exit Sub
                End If
            Next
            arguments.IsValid = True
        End Sub
</script>
JScript
<script language="jscript">
        function ClientValidate(source, arguments)
        {
            for (var iCount = 2; iCount <= arguments.Value / 2;
                    iCount++)
            {
                // If number is evenly divisible,
                // it's not prime. Return false.
                if ((arguments.Value % iCount) == 0)
                {
                    arguments.IsValid = false;
                    return false;
                }
            }
            // Number is prime, return True.
            arguments.IsValid = true;
            return true;
        }
</script>


Lesson 3: Navigating Between Forms

Linking information is the essence of the Web. In a Web Forms application, hyperlinks and the navigation methods are what link multiple Web forms together into a cohesive application. ASP.NET provides several ways to navigate between pages in your application, and each of these techniques yields different effects in terms of how the page is displayed and how data is exchanged between pages.

In this lesson, you will learn how to use each of the different navigation techniques in code and in HTML.

After this lesson, you will be able to

  • List different ways to navigate between pages in a Web forms application

  • Choose the appropriate navigation technique based upon your programming needs

  • Retain form ViewState data across a request

  • Process a second Web form without leaving the current Web form

  • Display a page in a new browser window and control that window from server code

Estimated lesson time: 35 minutes
Ways to Navigate

ASP.NET provides five distinct ways to navigate between pages in your application, as shown in Table 4-8.

Table 4-8 Navigating Between Pages
Navigation method | Use to
Hyperlink control | Navigate to another page.
Response.Redirect method | Navigate to another page from code. This is equivalent to clicking a hyperlink.
Server.Transfer method | End the current Web form and begin executing a new Web form. This method works only when navigating to a Web Forms page (.aspx).
Server.Execute method | Begin executing a new Web form while still displaying the current Web form. The contents of both forms are combined. This method works only when navigating to a Web Forms page (.aspx).
Window.Open script method | Display a page in a new browser window on the client.
Using Hyperlinks and Redirection

Hyperlink server controls respond to user click events by displaying the page specified in the control' s NavigateURL property. The Hyperlink control does not expose any server-side user events; if you want to intercept a click event in code, use the LinkButton control or the ImageButton server control.

To navigate from a LinkButton or ImageButton, use the Response object' s Redirect method, as shown here:

Visual Basic .NET
Private Sub LinkButton1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles LinkButton1.Click
    ' Display next page.
    Response.Redirect("NextPage.aspx")
End Sub
Visual C#
private void LinkButton1_Click(object sender, System.EventArgs e)
{
    // Display next page.
    Response.Redirect("NextPage.aspx");
}
Using the Transfer Method

Using the Transfer method is very similar to executing a hyperlink or using the Redirect method, with one difference: Transfer can retain some information from the source page across requests. Setting the Transfer method' s preserveForm argument to True makes the form' s QueryString, ViewState, and event procedure information available in the destination form.

To use the Transfer method with preserveForm set to True, you must first set the EnableViewStateMac attribute in the Web form' s Page directive to False. By default, ASP.NET hashes ViewState information, and setting this attribute to False disables that hashing so the information can be read on the subsequent Web form. The following line shows how to disable hashing so that a page' s ViewState can be used from another page:

Visual Basic .NET
<%@ Page language="vb" EnableViewStateMac="false"
Codebehind="RedirectNTransfer.aspx.vb"
Inherits="vbNavigateSnippet.WebForm1" %>
Visual C#
<%@ Page language="c#" EnableViewStateMac="false"
Codebehind="RedirectNTransfer.aspx.cs" AutoEventWireup="false"
Inherits="csNavigateSnippet.WebForm1" %>

The following event procedure for an ImageButton shows how information can be passed between forms with the Transfer method:

Visual Basic .NET
' Webform1.aspx
Private Sub imgTransfer_Click(ByVal sender As System.Object, _
  ByVal e As System.Web.UI.ImageClickEventArgs) _
  Handles imgTransfer.Click
    ' Transfer to another form, retaining ViewState.
    Server.Transfer("Webform2.aspx", True)
End Sub
Visual C#
// Webform1.aspx
private void ImageButton1_Click(object sender,
    System.Web.UI.ImageClickEventArgs e)
{
    // Transfer to another form, retaining ViewState.
    Server.Transfer("Webform2.aspx", true);
}

Use the Request object' s Form method to retrieve the ViewState information from the source Web form. The following code displays the setting of a TextBox control and the X, Y coordinates from the ImageButton click event from Webform1.aspx on Webform2.aspx:

Visual Basic .NET
' Webform2.aspx
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    Dim colForm As _
      System.Collections.Specialized.NameValueCollection
    ' Get data from source Web form.
    colForm = Request.Form
    ' Display the value from Webform1's TextBox.
    Response.Write("TextBox1.Text: " & _
      colForm.Item("TextBox1") & "<br>")
    ' Display the x, y coordinates of where the click occurred.
    Response.Write("ImageButton X, Y coords: " & _
      colForm.Item("imgTransfer.x") & ", " & _
      colForm.Item("imgTransfer.y") & "<br>")
End Sub
Visual C#
// Webform2.aspx
    private void Page_Load(object sender, System.EventArgs e)
    {
        System.Collections.Specialized.NameValueCollection colForm;
        // Get data from the source Web form.
        colForm = Request.Form;
        // Display the value from Webform1's TextBox.
        Response.Write("TextBox1.Text: " + colForm["TextBox1"]
             + "<br>");
        // Display the X, Y coordinated of where the click occurred.
        Response.Write("ImageButton X, Y coords: " +
             colForm["imgTransfer.x"] + ", " + colForm["imgTransfer.y"]
            + "<br>");
    }

You can use the Server object' s Transfer method to link control values across requests, as shown in Figure 4-20.

Figure 4-20 The Server object’s Transfer method
note | The Server object' s Transfer and Execute methods work exclusively with Web forms. Trying to navigate to an HTML page using one of these methods results in a run-time error.
Using the Execute Method

Use the Server object' s Execute method to process a second Web form without leaving the first Web form. This lets you direct the results from a Web form to a region on the current page. As with the Transfer method, Execute requires that the Web form' s EnableViewstateMac attribute be set to False to disable ViewState hashing.

For example, the following code executes the Table.aspx Web form and displays it in a Literal control on the current page:

Visual Basic .NET
Private Sub butExecute_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butExecute.Click
    Dim swrTarget As New System.IO.StringWriter()
    ' Execute a Web form, store the results.
    Server.Execute("Table.aspx", swrTarget)
    ' Display the result in a Literal control.
    litTable.Text = "<h2>Table Results</h2>" & swrTarget.ToString
End Sub
Visual C#
private void butExecute_Click(object sender, System.EventArgs e)
    {
        System.IO.StringWriter swrTarget = new
             System.IO.StringWriter();
        // Execute a Web form, store the results.
        Server.Execute("Table.aspx", swrTarget);
        // Display the result in a literal control.
        litTarget.Text = "<h2>Table Results</h2>" +
             swrTarget.ToString();
    }
}

The Execute method' s second argument is optional. If you omit it, the result is written to the current page. The result is additive—the content of both pages is displayed at once and server controls on both pages can respond to user events, as shown in Figure 4-21.

Figure 4-21 The Server object' s Execute method

When you combine Web forms using the Execute method, be aware that any post-back events occurring on the second Web form will clear the first Web form. For this reason, combining Web forms is mainly useful when the second Web form does not contain controls that trigger post-back events.

Displaying a Page in a New Browser Window

To start a new instance of the browser, use the client-side Window object' s Open method. You can do this only as part of a client script because the new window is created on the client. However, there are ways to control the content and appearance of the new browser window from the server.

In its simplest form, the Window.Open method takes the form shown (in bold) for the following Button HTML control definition:

<INPUT style="Z-INDEX:
102; LEFT: 55px; WIDTH: 81px; POSITION: absolute; TOP: 156px; HEIGHT:
24px" onclick="window.open('webform2.aspx')" type="submit"
value="New Window">

To use a variable as the target URL, replace webform2.aspx with a data tag:

<INPUT style="Z-INDEX:
102; LEFT: 55px; WIDTH: 81px; POSITION: absolute; TOP: 156px; HEIGHT:
24px" onclick="window.open('<%# urlTarget %>')" type="submit"
value="New Window">

To update the target URL from server code, use a Public variable and data binding. The following Page_Load event procedure sets the target URL and updates it with data binding when the page loads:

Visual Basic .NET
Public urlTarget As String
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    urlTarget = "webform2.aspx"
    Page.DataBind()
End Sub
Visual C#
public string urlTarget;
private void Page_Load(object sender, System.EventArgs e)
{
    urlTarget = "webform2.aspx";
        Page.DataBind();
}

Because the Window.Open method takes many different arguments to control the various aspects of the new browser window, you might want to create a class to handle all the various settings. Classes allow you to encapsulate all the possible settings so they can be used in an object-oriented manner. The following Page_Load event procedure and class definition demonstrate how to control the size, location, and URL of the new window from server-side code using a class named BrowserWindow:

Visual Basic .NET
Public urlTarget As New BrowserWindow()

Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    With urlTarget
        ' Set URL for new window
        .URL = "HTMLPage1.htm"
        .Top = 100
        .Left = 100
        .Width = 400
        .Height = 400
    End With
    ' Update HTML Button.
    Page.DataBind()
End Sub

' Class to control new browser windows created in scripts.
' Default settings shown here are the same as browser defaults.
Public Class BrowserWindow
    ' String settings: default is blank.
    Public URL As String = "about:blank"
    ' Integer settings: 0 invokes default.
    Public Height As Integer = 0
    Public Width As Integer = 0
    Public Top As Integer = 0
    Public Left As Integer = 0
    ' Boolean-like settings: 0 is "no" 1, is "yes"
    Public ChannelMode As Integer = 0
    Public Directories As Integer = 1
    Public FullScreen As Integer = 0
    Public Location As Integer = 1
    Public Resizable As Integer = 1
    Public ScrollBars As Integer = 1
    Public Status As Integer = 1
    Public TitleBar As Integer = 1
    Public ToolBar As Integer = 1
    Public MenuBar As Integer = 1
End Class
Visual C#
public BrowserWindow urlTarget = new BrowserWindow();

private void Page_Load(object sender, System.EventArgs e)
{
    urlTarget.URL = "Execute.aspx";
    urlTarget.Top = 100;
    urlTarget.Left = 100;
    urlTarget.Width = 400;
    urlTarget.Height = 400;
    // Update HTML Button.
    Page.DataBind();
}

// Class to control new browser window create in scripts.
// Default settings shown here are the same as browser defaults.
public class BrowserWindow
{
    // String settings: default is blank.
    public string URL = "about:blank";
    // Integer settings: 0 invoked default.
    public int Height = 0;
    public int Width = 0;
    public int Top = 0;
    public int Left = 0;
    // Boolean-like settings: 0 is "no", 1 is "yes".
    public int ChannelMode = 0;
    public int Directories = 1;
    public int FullScreen = 0;
    public int Location = 1;
    public int Resizable = 1;
    public int ScrollBars = 1;
    public int Status = 1;
    public int TitleBar = 1;
    public int ToolBar = 1;
    public int MenuBar = 1;
}

The HTML code for a button that uses these settings would look like the following example. (The server-supplied variable is shown in bold.)

<INPUT style="Z-
INDEX: 103; LEFT: 25px; WIDTH: 126px; POSITION: absolute; TOP: 60px; HEIGHT:
33px" type="button" value="Show New Window"
onclick="window.open('<%# urlTarget.URL %>', null, 'heig
ht=<%# urlTarget.Height %>, width=<%# urlTarget.Width %>, top=<%# urlTarget.Top %>, left=<%# urlTa
rget.Left %>, channelmode=<%# urlTarget.ChannelMode %>,directories=<%# urlTar
get.Directories %>,fullscreen=<%# urlTarget.FullScreen %>,location=<%# urlTar
get.Location %>,menubar=<%# urlTarget.MenuBar %>,resi
zable=<%# urlTarget.Resizable %>,scrollbars=<%# urlTarget.ScrollBars %>,status=<%# urlTarget.Status
%>,titlebar=<%# urlTarget.TitleBar %>,toolbar=<%# urlTarget.ToolBar %>')">


Summary

  • Server controls provide properties, methods, and events that can be used in server code.

  • HTML controls provide attributes and events that can be used in scripts that run on the client. To access properties of an HTML control in server code, add a runat=server attribute to the control' s HTML definition.

  • ASP.NET performs control validation on the client side immediately before posting the Web form back to the server. Once client-side validation succeeds, the Web form is validated again on the server side before the Page_Load event occurs. This ensures that the Web form has not been modified on the client to bypass validation and it also guarantees that validation will work with pre-Internet Explorer 4.0 browsers.

  • Use multiple validation controls to check multiple conditions on a single data field. For instance, a TextBox that requires a telephone number should be validated by both a RequiredFieldValidator and a RegularExpressionValidator control.

  • To navigate between pages in a Web forms application, use one of the following techniques. (The two Server object methods work only with Web forms.)

    • Hyperlinks

    • The Response.Redirect method

    • The Server.Transfer method

    • The Server.Execute method

    • Client script procedures

  • To perform an action in the client browser, you must use a client script. For example, to open a page in a new window, you would use a Button HTML control with an onclick="window.open()” attribute. This is because client scripts are the only executable code in a Web forms application that runs on the client.


Lab: Building a Web-Based Text Editor

In this lab, you will create a multi-page Web forms application that allows users to create and edit text files stored on a server. The application uses the user' s sign-on name to determine where files are stored. It does not do rigorous password checking—in fact, the password is always Guest. Password authentication is covered in Chapter 8, “Maintaining Security.”

Estimated lesson time: 45 minutes
important | To create and delete files, the ASPNET user account must have write and modify privileges in the application' s root folder. By default, the ASPNET user account has only read and execute privileges. See Chapter 9, “Building and Deploying Web Applications,” for information about how to assign security privileges in the application folder.
Exercise 1: Create the Sign-On Form

In this exercise, you will create the Web form that allows users to sign on to the text editor. The Web form created here uses validation, flow layout, navigation to other pages, and the System.IO namespace for directory services. When you are done creating the Web form, it will appear as shown in Figure 4-22.

Figure 4-22 The SignOn.aspx Web form
To start the application and create the Sign-On Web form

  1. Open a new ASP.NET Web application project. In the New Project dialog box, enter the name WebTextEditor.

  2. In the Properties window, change the name of the Webform1.aspx file to SignOn.aspx.

  3. In the Properties window, select the DOCUMENT object and set the pageLayout property to FlowLayout.

    4. After you' ve set pageLayout to FlowLayout, you can add controls to the page by double-clicking the controls in the Toolbox. If you create the controls in the order they appear on the page, this process goes quickly. The following table shows the controls you should create in the order you create them, along with their property settings.

    Control | Property | Value
Literal | ID | litNoAccount
TextBox | ID | txtName
RequiredFieldValidator | ID | vldtxtName
 | ControlToValidate | txtName
 | ErrorMessage | Please enter a name.
TextBox | ID | txtPassword
RequiredFieldValidator | ID | vldtxtPassword
 | ControlToValidate | txtPassword
 | ErrorMessage | Please enter the password.
RangeValidator | ID | vldtxtPasswordGuest
 | ControlToValidate | txtPassword
 | ErrorMessage | The password is “Guest”.
 | MinimumValue | Guest
 | MaximumValue | Guest
Button | ID | butSignOn
 | Text | Sign On

    After you create the controls and set their properties, switch the Designer to view the HTML source for the page, and then edit the page in HTML mode, adding text and HTML elements directly to the page.

To edit the Web form in HTML mode

  1. Right-click the Web form and select View HTML Source from the pop-up menu.

  2. Edit the Web form by adding the following text and HTML tags shown in bold:

    <form id="Form1" method="post" runat="server">
<h2>
Welcome to the Web-Based Text Editor
</h2><asp:Literal id="litNoAccount" runat="server"></asp:Literal>
<p>
Please sign on:
</p>
Name:<asp:TextBox id="txtName" runat="server" Width="165px"></asp:TextBox>
<asp:RequiredFieldValidator id="vldtxtName" runat="server" ErrorMessage="Please enter a name."
ControlToValidate="txtName"></asp:RequiredFieldValidator>
<br>
<br>
Password:<asp:TextBox id="txtPassword" runat="server" Width="163px" Height="21px" TextMode="Password"></asp:TextBox>
<asp:RequiredFieldValidator id="vldtxtPassword" runat="server" ErrorMessage="Please enter a password."
ControlToValidate="txtPassword"></asp:RequiredFieldValidator>
<br><asp:RangeValidator id="vldtxtPasswordGuest" runat="server" ControlToValidate="txtPassword" ErrorMessage='The password is "Guest".'
MaximumValue="Guest" MinimumValue="Guest"></asp:RangeValidator>
<br>
<br><asp:Button id="butSignOn" runat="server" Text="Sign On" Width="61px" Height="28px"></asp:Button>
<p>
New users click <a href="NewAccount.aspx">here</a> to set up an account.
</p></form>

  3. To view the results of the changes, right-click the Web form and select View Design from the pop-up menu.

  4. When you are done editing the Web form, switch to Design view and double-click Sign On to display the code window. Place the following Imports or using statement before the Web form' s class definition:

Visual Basic .NET
Imports System.IO
Visual C#
using System.IO;

Add the following code to the butClick event procedure:

Visual Basic .NET
Private Sub butSignOn_Click(ByVal sender As System.Object, _
     ByVal e As System.EventArgs) Handles butSignOn.Click
    Dim strPath As String
    'If the user exists, there is a directory of the same name.
    strPath = Server.MapPath(Request.ApplicationPath) & "\" & txtName.Text
    If Directory.Exists(strPath) Then
        ' Set session variables.
        Session("Path") = strPath
        Server.Transfer("FileManager.aspx")
    Else
        ' Otherwise, report that user wasn't found.
        litNoAccount.Text = ("<p>The name " & txtName.Text & _
        " wasn't found." & _
        "Check the name, or click <A href='NewAccount.aspx'>here</A>" & _
        " if you are a new user.</p>")
    End If
End Sub
Visual C#
private void butSignOn_Click(object sender, System.EventArgs e)
{
    string strPath;
    // If the user exists, there is a directory of the same name.
    strPath = Server.MapPath(Request.ApplicationPath) + "\\"
         + txtName.Text;
    if(Directory.Exists(strPath))
    {
        // Set session variables.
        Session["Path"] = strPath;
        Server.Transfer("FileManager.aspx");
    }
    else
        // Otherwise, report that user wasn't found.
        litNoAccount.Text = "<p>The name " + txtName.Text +
          " wasn't found. Check the name, or click " +
               "<A href='NewAccount.aspx'>here</A> if you " +
               "are a new user.</p>";
}
Exercise 2: Create the NewAccount Form

In this exercise, you will create the Web form that creates an account for new users. In this application, an “account” consists of a directory in the application' s root that bears the user' s name. Again, there is no security provided in this exercise; it is covered in Chapter 8, “Maintaining Security.”

The Web form created here uses validation, flow layout, navigation, HTML controls, and the System.IO namespace for directory services. The form uses the username to create a directory for the user' s files in the application' s server root. When you are done creating the Web form, it will appear as shown in Figure 4-23.

Figure 4-23 The NewAccount.aspx Web form
To create the NewAccount Web form

  1. Create a new Web form named NewAccount.aspx.

  2. In the Properties window, select the DOCUMENT object and set its pageLayout property to FlowLayout.

  3. Add controls and set their properties as shown in the following table:

    4. Control | Property | Value
Literal | ID | litNameExists
TextBox | ID | txtName
RequiredFieldValidator | ID | vldtxtName
 | ControlToValidate | txtName
 | ErrorMessage | Please enter a name.
Button | ID | butCreate
 | Text | Create
Button HTML control | id | butCancel
 | value | Cancel

  4. Switch the Web form to HTML view and add the following text and HTML tags shown in bold:

    <form id="Form1" method="post" runat="server">
<h2>
Create an Account
</h2><asp:Literal id="litNameExists" runat="server"></asp:Literal>
<p>
You do not currently have an account on the server.
<br>
Type the name you would like to use to sign on below, then click Create to
create the account.
</p><asp:TextBox id="txtName" runat="server" Width="153px"
Height="24px"></asp:TextBox>
<asp:RequiredFieldValidator id="vldtxtName" runat="server" ErrorMessage="Please enter a name."
ControlToValidate="txtName"></asp:RequiredFieldValidator>
<p>
</p><asp:Button id="butCreate" runat="server" Text="Create" Width="67" Height="24"></asp:Button>
&nbsp;&nbsp; <INPUT type="button" onclick="history.back()" value="Cancel"></BUTTON>
</form>

    5. The last edit in the preceding HTML code, onclick="history.back()", enables the user to cancel the operation without causing validation. Using an HTML control here is important because you don' t want server-side processing—you just want to go back to the SignOn Web form.

  5. When you are done editing the Web form, switch back to Design view and double-click Create to display the Code window. Place the Imports or using statement before the Web form' s Class definition:

Visual Basic .NET
Imports System.IO
Visual C#
using System.IO;

Add the following code to the butCreate_Click event procedure:

Visual Basic .NET
Private Sub butCreate_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butCreate.Click
    Dim strPath As String
    'Check if directory exists.
    strPath = Server.MapPath(Request.ApplicationPath) & "\" & txtName.Text
    If Directory.Exists(strPath) Then
        ' Tell the user to choose another name.
        litNameExists.Text = "<p>The name " & txtName.Text & _
          " already exists. Please choose a different one.</p>"
        Return
    Else
        Try
            ' Create the directory.
            Directory.CreateDirectory(strPath)
            ' Set the session variable.
            Session("Path") = strPath
            ' Go to file manager.
            Server.Transfer("FileManager.aspx")
        Catch ex As System.UnauthorizedAccessException
            Server.Transfer("NotAuthorized.aspx")
      End Try
     End If
End Sub
Visual C#
private void butCreate_Click(object sender, System.EventArgs e)
{
        string strPath;
    //Check if directory exists.
    strPath = Server.MapPath(Request.ApplicationPath) + "\\"
      + txtName.Text;
      if (Directory.Exists(strPath))
      {
          // Tell the user to choose another name.
          litNameExists.Text = "<p>The name " + txtName.Text +
            " already exists. Please choose a different one.</p>";
          return;
      }
      else
      {
        try
        {
            // Create the directory.
            Directory.CreateDirectory(strPath);
            // Set the session variable.
            Session["Path"] = strPath;
            // Go to file manager.
            Server.Transfer("FileManager.aspx");
        }
            catch (System.UnauthorizedAccessException ex)
        {
            Server.Transfer("NotAuthorized.aspx");
        }
    }
}
Exercise 3: Create the File Manager Form

In this exercise, you will create the Web form that lets users create, open, and delete files in their accounts on the server. The Web form created here uses data binding, flow layout, navigation, QueryStrings, and the System.IO namespace for file access. When you are done creating the Web form, it will appear as shown in Figure 4-24.

Figure 4-24 The FileManager.aspx Web form
To create the FileManager Web form

  1. Create a new Web form named FileManager.aspx.

  2. In the Properties window, select the DOCUMENT object and set its pageLayout property to FlowLayout.

  3. Add controls and set their properties as shown in the following table:

    4. Control | Property | Value
Literal | ID | litNoFile
TextBox | ID | txtNewFile
Button | ID | butNew
 | Text | New
Literal | ID | litNoneSelected
ListBox | ID | lstFiles
 | DataSource | strFiles
Button | ID | butEdit
 | Text | Edit
Button | ID | butDelete
 | Text | Delete

  4. Switch the Web form to HTML view and add the following text and HTML tags shown in bold.

    <form id="Form1" method="post" runat="server">
<h2>
File Manager
</h2>
<p>
To create a new file, type the name of the file below and click New.
</p><asp:Literal id="litNoFile" runat="server"></asp:Literal>
<asp:TextBox id="txtNewFile" runat="server" Width="191px"></asp:TextBox>
<asp:Button id="butNew" runat="server" Width="62px" Height="29px" Text="New"></asp:Button>
<p>
To edit a file, select the file from the list below and click Edit. Click
Delete to delete the selected file.
</p><asp:Literal id="litNoneSelected" runat="server"></asp:Literal>
<asp:listbox id="lstFiles" runat="server" Width="252px" Height="161px" DataSource="<%# strFiles %>"></asp:listbox>
<br><asp:Button id="butEdit" runat="server" Width="62" Height="29" Text="Edit"></asp:Button>
&nbsp;&nbsp;
<asp:Button id="butDelete" runat="server" Width="62px" Height="29px" Text="Delete"></asp:Button>
</form>

  5. When you are done editing the Web form, switch back to Design view and double-click New to display the Code window. Place the Imports or using statement before the Web form' s Class definition.

    Visual Basic .NET
    Imports System.IO
    Visual C#
    using System.IO;

  6. Add the following code to the form event procedures:

    Visual Basic .NET
    Public strPath As String, strFiles As String()
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Get path.
    strPath = Session("Path")
    ' If this is not a post-back event.
    If Not IsPostBack Then
        Dim iCount As Integer
        'Get list of files in the current directory.
        strFiles = Directory.GetFiles(strPath)
        ' Get the short names for the files.
        For iCount = 0 To UBound(strFiles)
            strFiles(iCount) = Path.GetFileName(strFiles(iCount))
        Next
    End If
    ' Bind lstFiles to file array.
    lstFiles.DataBind()
End Sub

Private Sub butNew_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butNew.Click
    ' If there is a file name, then start the editor.
    If txtNewFile.Text <> "" Then
        Response.Redirect("EditFile.aspx?file=" & txtNewFile.Text)
    Else
        ' Otherwise, display a message.
        litNoFile.Text = "<p>You must enter the name of a file" & _
                         " to create.<p>"
    End If
End Sub

Private Sub butEdit_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butEdit.Click
    ' If there is a file name, then start the editor.
    If Not IsNothing(lstFiles.SelectedItem) Then
        Response.Redirect("EditFile.aspx?file=" & _
          lstFiles.SelectedItem.ToString)
    Else
        ' Otherwise, display a message.
        litNoneSelected.Text = "<p>You must select a file.<p>"
    End If
End Sub
Private Sub butDelete_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles butDelete.Click
    ' If there is a file name, then start the editor.
    If Not IsNothing(lstFiles.SelectedItem) Then
            Try
                Dim filToDelete As File
    filToDelete.Delete(strPath & "\" & _
      lstFiles.SelectedItem.ToString)
                lstFiles.Items.Remove(lstFiles.SelectedItem)
            Catch ex As System.Security.SecurityException
                Server.Transfer("NotAuthorized.aspx")
            End Try
    Else
        ' Otherwise, display a message.
        litNoneSelected.Text = "<p>You must select a file.<p>"
    End If
End Sub
    Visual C#
    string strPath;
public string[] strFiles;

private void Page_Load(object sender, System.EventArgs e)
{
    // Get path.
    strPath = Session["Path"].ToString();
    // If this is not a post-back event.
    if(!Page.IsPostBack)
    {
        // Get list of files in the current directory.
        strFiles = Directory.GetFiles(strPath);
        // Get the short names for the files.
        for (int iCount = 0; iCount <= strFiles.GetUpperBound(0);
               iCount++)
            strFiles[iCount] =
                     Path.GetFileName(strFiles[iCount]);
    }
// Bind lstFiles to file array.
lstFiles.DataBind();
}
private void butNew_Click(object sender, System.EventArgs e)
{
    //If there is a file name, then start the editor.
    if (!(txtNewFile.Text == ""))
        Response.Redirect("EditFile.aspx?file=" +
               txtNewFile.Text);
else
    // Otherwise, display a message.
    litNoFile.Text = "<p>You must enter the name of a file " +
      " to create.<p>";
}

private void butEdit_Click(object sender, System.EventArgs e)
{
  // If there is a file name, then start the editor.
  if (lstFiles.SelectedItem == null)
      Response.Redirect("EditFile.aspx?file=" +
        lstFiles.SelectedItem.ToString());
  else
      // Otherwise, display a message.
      litNoneSelected.Text = "<p>You must select a file.<p>";
}

private void butDelete_Click(object sender, System.EventArgs e)
{
// If there is a file name, then start the editor.
if (lstFiles.SelectedItem != null)
    try
    {
        File.Delete(strPath + "\\" +
          lstFiles.SelectedItem.ToString());
        lstFiles.Items.Remove(lstFiles.SelectedItem);
    }
    catch(System.UnauthorizedAccessException ex)
    {
        Server.Transfer("NotAuthorized.aspx");
    }
else
    // Otherwise, display a message.
    litNoneSelected.Text = "<p>You must select a file.<p>";
}
Exercise 4: Create the File Editor Form

In this exercise, you will create the Web form that edits files stored on the server in the user' s account. The Web form created here uses the same layout and programming techniques as the previous Web form in Exercise 3. When you are done creating the Web form, it will appear as shown in Figure 4-25.

Figure 4-25 The EditFile.aspx Web form
To create the File Editor Web form

  1. Create a new Web form named EditFile.aspx.

  2. In the Properties window, select the DOCUMENT object and set its pageLayout property to FlowLayout.

  3. Add controls and set their properties as shown in the following table:

    4. Control | Property | Value
TextBox | ID | txtEditFile
 | TextMode | Multiline
Button | ID | butSave
 | Text | Save
HTML Button | id | butCancel
 | value | Cancel
Button | ID | butExit
 | Text | Exit

  4. Switch the Web form to HTML view and add the following text and HTML tags shown in bold.

    <form id="Form1" method="post" runat="server">
<h2>
File Editor
</h2>
<p>
Edit the file below. Click Save to save your changes or Cancel to abandon
changes.
</p>
<asp:TextBox id="txtEditFile" runat="server" Width="448px" Height="253px"
TextMode="MultiLine"></asp:TextBox>
<p>
</p>
<asp:Button id="butSave" runat="server" Width="65" Height="30"
Text="Save"></asp:Button>
&nbsp;&nbsp; <INPUT id="butCancel" onclick="history.back()" type="button"
value="Cancel" style="WIDTH: 65px; HEIGHT: 30px">
&nbsp;&nbsp;
<asp:Button id="butExit" runat="server" Width="65" Height="30"
Text="Exit"></asp:Button>
</form>

  5. When you are done editing the Web form, switch back to Design view and double-click New to display the Code window. Place the Imports or using statement before the Web form' s Class definition.

    Visual Basic .NET
    Imports System.IO
    Visual C#
    using System.IO;

Add the following code to the form event procedures:

Visual Basic .NET
Dim strPath, strFile As String
Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
    ' Get the path and file names.
    strPath = Session("Path")
    strFile = Request.QueryString("file")
    ' If this is not a post-back event.
    If Not IsPostBack Then
            Try
                ' Open the file.
                strmEditFile = File.OpenText(strPath & "\" & _
                  strFile)
                ' Read its text.
                txtEditFile.Text = strmEditFile.ReadToEnd.ToString
                ' Close the file.
                strmEditFile.Close()
            Catch ex As System.IO.FileNotFoundException
                ' If it doesn't exist, create it.
                File.CreateText(strPath + "\\" + strFile).Close()
                ' Clear text box.
                txtEditFile.Text = ""
            Catch ex As System.UnauthorizedAccessException
                Server.Transfer("NotAuthorized.aspx")
            End Try
    End If
   End Sub

Private Sub butExit_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butExit.Click
    If viewstate("changed") Then
        SaveFile()
    End If
    ' Return to File Manager.
    Response.Redirect("FileManager.aspx")
End Sub

Private Sub txtEditFile_TextChanged(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles txtEditFile.TextChanged
    ' Set changed flag.
    viewstate("changed") = True
End Sub

Private Sub butSave_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butSave.Click
    SaveFile()
End Sub

Private Sub SaveFile()
    Dim strmEditWrite As StreamWriter
    ' Delete the file.
    File.Delete(strPath & strFile)
    ' Create the file with new text.
    strmEditWrite = File.CreateText(strPath & "\" & strFile)
    strmEditWrite.Write(txtEditFile.Text)
    strmEditWrite.Close()
    ' Reset changed flag.
    viewstate("changed") = False
End Sub
Visual C#
// Place the Imports statement before the Web form's Class definition.
using System.IO;
string strPath;
string strFile;

private void Page_Load(object sender, System.EventArgs e)
{
    // Get the path and file names.
    Session["Path"] = "c:";
    strPath = Session["Path"].ToString();
    strFile = Request.QueryString["file"];
    // If this is not a post-back event.
    if (! Page.IsPostBack)
    {
    StreamReader strmEditFile;
    try
    {
        // Open the file.
        strmEditFile = File.OpenText(strPath + "\\" +
          strFile);
        // Read its text.
        txtEditFile.Text =
          strmEditFile.ReadToEnd().ToString();
        // Close the file.
        strmEditFile.Close();
    }
        catch(FileNotFoundException ex)
    {
        // If it doesn't exist, create it.
        File.CreateText(strPath + "\\" +
          strFile).Close();
        // Clear text box.
        txtEditFile.Text = "";
    }
    catch(System.UnauthorizedAccessException ex)
    {
        Server.Transfer("NotAuthorized.aspx");
    }
}

private void butExit_Click(object sender, System.EventArgs e)
{
    if (ViewState["Changed"].ToString() == "true")
        SaveFile();
    // Return to File Manager.
    Response.Redirect("FileManager.aspx");
}

private void txtEditFile_TextChanged(object sender,
    System.EventArgs e)
{
    // Set changed flag.
    ViewState["Changed"] = true.ToString();
}

private void butSave_Click(object sender, System.EventArgs e)
{
    SaveFile();
}
void SaveFile()
{
    StreamWriter strmEditWrite;
    // Delete the file.
    File.Delete(strPath + strFile);
    // Create the file with new text.
    strmEditWrite = File.CreateText(strPath + "\\" + strFile);
    strmEditWrite.Write(txtEditFile.Text);
    strmEditWrite.Close();
    // Reset changed flag.
    ViewState["Changed"] = false.ToString();
}
Exercise 5: Create the Not Authorized Web Form

As mentioned at the beginning of this lab, you must authorize the ASPNET user account to create and delete files in the application' s root folder. If you don' t grant these permissions, all attempts to create, save, or delete files result in a System.UnauthorizedAccessException error.

The code in the preceding exercises handles this error by using Try…Catch/try…catch exception handling blocks. If the error occurs, the Web Text Editor displays a Web form with instructions for how to fix the error, as shown in Figure 4-26.

Figure 4-26 The NotAuthorized.aspx Web form
To create the Not Authorized Web form

  1. Add a new Web form to the project named NotAuthorized.aspx.

  2. Switch to HTML mode and add the following HTML to the Web form:

    <h2>You are not authorized for this request.</h2>
<p>
This error occurs when ASPNET user account is not authorized to access,
create, or modify&nbsp;a folder or file on the server. Use Windows
Explorer to set the permissions for the project folder to allow the ASPNET
user account to modify, read, and write files to that folder.
</p>
<a href="SignOn.aspx">Return to sign-on.</a>


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. What is the main difference between the Button server control and the Button HTML control?

  2. How do you get several RadioButton controls to interoperate on a Web form so that only one of the RadioButtons can be selected at once?

  3. Why does ASP.NET perform validation on both the client and the server?

  4. What types of validation would you use to verify that a user entered a valid customer number?

  5. What is wrong with the following line of code?

    Visual Basic .NET
    Server.Transfer("Default.htm")
    Visual C#
    Server.Transfer("Default.htm");

  6. Why can' t you open a new browser window from within server code?


Storing and Retrieving Data with ADO.NET

Chapter 5
Storing and Retrieving Data with ADO.NET
Lesson 1: Accessing Data with ADO.NET
Lesson 2: Using Data Sets on Web Forms
Lesson 3: Processing Transactions
About This Chapter

In this chapter, you will learn how to use ADO.NET to access and modify data stored in SQL and OLE databases from Web forms. You will learn about the data tools included with Microsoft Visual Studio .NET and how to use them to create connections to, get records from, and perform commands on databases. You will also learn how to display data on a Web form using either data binding or code. Finally, you will learn how to use transactions to maintain the integrity of a database.

Before You Begin

To complete the lessons in this chapter, you must

  • Have Microsoft Data Engine (MSDE) or Microsoft SQL Server 2000 installed on your computer. Visual Studio .NET installs MSDE as part of the default setup.

  • Install the Contacts SQL database (Contacts.mdf) from the book' s companion CD-ROM onto your computer. This sample SQL database is used throughout examples in this chapter.

  • Be familiar with the basic elements of the Transact-SQL language. To find information about the SQL language, see the “Transact-SQL Language Reference” help topic in Microsoft SQL Server Books Online.

    tip

    A free evaluation copy of Microsoft SQL Server 2000 is available for download online from the Microsoft Web site at http://www.microsoft.com/sql.


Lesson 1: Accessing Data with ADO.NET

Visual Studio .NET provides access to databases through the set of tools and namespaces collectively referred to as Microsoft ADO.NET. Data access in ADO.NET is standardized to be mostly independent of the source of the data—once you' ve established a connection to a database, you use a consistent set of objects, properties, and methods, regardless of the type of database you are using.

In this lesson, you will learn how to use ADO.NET to connect to a database and how to read, modify, and delete records from that database. The lessons in this chapter use the Contacts database (Contacts.mdf) included on the companion CD-ROM for all the examples. Be sure to install that database before trying to work with the examples.

After this lesson, you will be able to

  • Understand the structure and steps involved in accessing data through ADO.NET

  • Connect to a database and create a data set at design time

  • Explain how ADO.NET refers to data items using type information

  • Add, delete, and modify records in a data set

  • Update a database from a data set

  • Connect to a database and create a data set at run time

Estimated lesson time: 35 minutes
Understanding ADO.NET

There are three layers to data access in ADO.NET:

  • The physical data store. This can be an OLE database, a SQL database, or an XML file.

  • The data provider. This consists of the Connection object and command objects that create the in-memory representation of the data.

  • The data set. This is the in-memory representation of the tables and relationships that you work with in your application.

The data provider layer provides abstraction between the physical data store and the data set you work with in code. After you' ve created the data set, it doesn' t matter where it comes from or where it is stored. This architecture is referred to as disconnected because the data set is independent of the data store.

Figure 5-1 shows the ADO.NET object model in action.

Figure 5-1 The Connection and command objects

There are two types of database connection in ADO.NET:

  • Use an OleDbConnection object to connect to a local database. OLE database connections use the OleDbDataAdapter object to perform commands and return data.

  • Use a SqlDbConnection object to connect to a server database. SQL database connections use the SqlDbDataAdapter object to perform commands and return data.

In addition to these database connections, you can access XML files directly from data sets using the DataSet object' s ReadXML and WriteXML methods. XML files are static representations of data sets. ADO.NET uses XML for all data transfer across the Internet.

ADO.NET provides its objects, properties, and methods through the three name-spaces described in Table 5-1. The System.Data.SqlClient and System.Data.OleDb namespaces listed in the table provide equivalent features for SQL and OLE databases, respectively.

Table 5-1 ADO.NET Namespaces
Namespace | Provides
System.Data | Classes, types, and services for creating and accessing data sets and their subordinate objects
System.Data.SqlClient | Classes and types for accessing Microsoft SQL Server databases
System.Data.OleDb | Classes and types for accessing OLE databases

When working with databases in code, you need to add the following Imports or using declarations at the top of your code module:

Visual Basic .NET
Imports System.Data
' For SQL database connections.
Imports System.Data.SqlClient
' For OLE DB database connections.
Imports System.Data.OleDb
Visual C#
using System.Data
// For SQL database connections.
using System.Data.SqlClient
// For OLE DB database connections.
using System.Data.OleDb

To access a database through ADO.NET, follow these steps:

  1. Create a connection to the database using a connection object.

  2. Invoke a command to create a DataSet using an adapter object.

  3. Use the DataSet object in code to display data or to change items in the database.

  4. Invoke a command to update the database from the DataSet using an adapter object.

  5. Close the database connection if you explicitly opened it in step 2 using the Open method. Invoking commands without first invoking the Open method implicitly opens and closes the connection with each request.

The following sections discuss each of these steps in more detail.

Connecting to a Database

Use the Server Explorer to connect to a database in Visual Studio.

To connect to a database in the Visual Studio design environment, follow these steps:

  1. From the View menu, choose Server Explorer. Visual Studio displays the Server Explorer window.

  2. In the Server Explorer, click Connect To Database Toolbar. Visual Studio .NET displays the DataLink Properties dialog box, as shown in Figure 5-2.

    Figure 5-2 The DataLink properties dialog box

  3. Click the dialog' s Provider tab to select the type of database for the connection. By default the database provider is Microsoft OLE DB Provider for SQL Server. That is the correct selection if your database is running under Microsoft SQL Server. To access a different type of database, select the appropriate provider. For instance, to access a Microsoft Access 2000 database directly, select Microsoft Jet 4.0 OLE DB Provider.

  4. Click the Connection tab to specify the database to connect to. Changing the database provider in step 3 changes the connection settings, as shown in Figure 5-3.

    Figure 5-3 The Connection tab

  5. Enter the connection settings and click Test Connection to make sure your setting are correct. Click OK when the connection succeeds. Visual Studio adds the data connection to the Server Explorer, as shown in Figure 5-4.

    Figure 5-4 The Server Explorer

  6. Clicking the plus signs in the Server Explorer expands items. To view the tables in a data connection, expand the items under the data connection and then expand the items under Tables. Visual Studio displays the tables, as shown in Figure 5-5.

    Figure 5-5 Expanded items in the Server Explorer

  7. To add a data item to your application, drag-and-drop the item from the Server Explorer to your Web form. For instance, drag the Contacts table shown in Figure 5-5 to the Web form. When you drop the table onto the Web form, Visual Studio creates connection and adapter objects with the appropriate settings, as shown in Figure 5-6.

    Figure 5-6 Connection and adapter objects
    important

    Web applications run using the ASPNET user account. The SQL database administrator will have to set up this account and grant it permissions before your Web application will have access to a SQL database. For Access databases, you must grant permissions on the database file to the ASPNET user account using Windows file security settings.
Creating a Data Set

Use the data connection and adapter objects created in step 7 of the preceding section to create a data set.

To create a data set in Design mode, follow these steps:

  1. Right-click the data adapter object and select Generate Dataset from the pop-up menu. Visual Studio displays the Generate Dataset dialog box, as shown in Figure 5-7.

    Figure 5-7 The Generate Dataset dialog box

  2. Select the tables to add to the data set and click OK. Visual Studio creates a new DataSet object and adds it to the Web form.

To view the data in the data set in Design mode, right-click the DataSet object, and then select View Schema from the pop-up menu. Visual Studio displays the data set in the XML Designer window, as shown in Figure 5-8.

Figure 5-8 Data set schema in Design mode
Displaying a Data Set

To display the data set on the Web form at run time, follow these steps:

  1. Add a control to the Web form to display the data. For instance, add a DataGrid control to the Web form.

  2. Set the data set as the data source for the control. For instance, for the DataGrid control, click the Property Builder link on the Properties Window, set the DataSource property to the DataSet object, and set the DataMember property to a table in the data set, as shown in Figure 5-9.

    Figure 5-9 Data binding on a DataGrid

  3. Set the columns to display in the control. For the DataGrid control, click the Columns item in the Properties dialog box, deselect the Create Columns Automatically At Run Time check box, and then add the columns to display from the Available Columns list, as shown in Figure 5-10. Click OK when you are done.

    Figure 5-10 Adding columns to display in the DataGrid

  4. Add code to the Web form' s Page_Load event procedure to fill the data set from the data adapter and to bind the data from the DataSet object to the control. For example, the following code displays the data in the DataGrid control created in the previous steps:

    Visual Basic .NET
    Private Sub Page_Load(ByVal sender As System.Object,
      ByVal e As System.EventArgs) Handles MyBase.Load
        ' Fill the data set.
        SqlDataAdapter1.Fill(DataSet1)
        ' Update the DataGrid.
        DataGrid1.DataBind()
    End Sub
    Visual C#
    private void Page_Load(object sender, System.EventArgs e)
   {
         // Fill the data set.
         sqlDataAdapter1.Fill(DataSet1);
         // Update the DataGrid.
         DataGrid1.DataBind();
   }

  5. Run the application. When the page loads, the data set is displayed, as shown in Figure 5-11.

    Figure 5-11 The DataGrid at run time
    tip

    Because you are filling the data set and binding the data to the DataGrid control in the Page_Load event, you don' t need to maintain state information for the DataGrid control. Turning off state information improves performance because the data on the DataGrid does not have to be saved to the page' s ViewState between requests. Set the DataGrid' s EnableViewState property to False to turn off state maintenance for the control.
Changing Records in a Database

The DataSet is the central object in ADO.NET. Any additions, deletions, or changes to records in a database are generally done through a DataSet object.

To change records through a DataSet object, follow these steps:

  1. Get a DataSet object as described in the preceding sections.

  2. Modify the DataSet.

  3. Update the database from the DataSet by calling the data adapter' s Update method.

You use the Tables, Rows, and Columns collections to get to data items in a DataSet, as shown in Figure 5-12.

Figure 5-12 DataSet collections
How ADO.NET Refers to Objects

When you create connection, adapter, and data set objects in Design mode, you enable data typing for those objects. This means that you can use the specific names from the database schema to identify tables, rows, and fields. This is a big change from ADO, which provided only untyped references to data objects.

The following equivalent lines of code show a typed reference vs. an untyped reference to an object from a database:

Visual Basic .NET
' Typed reference to the Contacts table's HomePhone column.
DataSet1.Contacts.HomePhoneColumn.Caption = "@Home"
' Untyped reference to the Contacts table's HomePhone column.
DataSet1.Tables("Contacts").Columns("HomePhone").Caption = "@Home"
Visual C#
// Typed reference to the Contacts table's HomePhone column.
DataSet1.Contacts.HomePhoneColumn.Caption = "@Home";
// Untyped reference to the Contacts table's HomePhone column.
DataSet1.Tables["Contacts"].Columns["HomePhone"].Caption = "@Home";

The first line in the preceding code is not only easier to type and to read, but also much less error prone, since Visual Studio checks data types as you work, flagging any typos as unrecognized references.

In general, you should use typed references when working with data objects. The exception to this rule occurs when you don' t know the specific object you are working with. Usually, this situation occurs when the data source is supplied at run time rather than at design time.

Type information for data objects comes from the XML Schema that Visual Studio generates when you create a data set in Design mode.

To view type information for a data set, right-click the data set in the Design window and select View Schema from the pop-up menu. Visual Studio displays the data set in the XML Designer, as shown in Figure 5-13.

Figure 5-13 Viewing type information

You can use the XML Designer to add elements, specify unique keys, and view or change the data types of elements in a data set.

Adding, Changing, and Deleting Rows

Use the Rows collection to add, change, or delete rows in the DataSet' s Table object. To add a record to a data set, create a new Row object and add it to the DataSet' s Rows collection, as shown by the following code:

Visual Basic .NET
' Uses connection, adapter, and data set created in Design mode.
    Private Sub butAddRow_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles butAddRow.Click
        ' Create a new row object for the Contacts table.
        Dim rowNew As DataSet1.ContactsRow = DataSet1.Contacts.NewRow
        ' Add data to the columns in the row.
        rowNew.ContactID = 42
        rowNew.FirstName = "Danielle"
        rowNew.LastName = "Tiedt"
        rowNew.WorkPhone = "(111) 555-1212"
        ' Add the row to the data set.
        DataSet1.Contacts.Rows.Add(rowNew)
    End Sub
Visual C#
// Uses connection, adapter, and data set created in Design mode.
private void butAddRow_Click(object sender, System.EventArgs e)
{
    // Create a new row object for the Contacts table.
    DataSet1.ContactsRow rowNew =
        (DataSet1.ContactsRow)DataSet1.Contacts.NewRow();
    // Add data to the columns in the row.
    rowNew.ContactID = 42;
    rowNew.FirstName = "Danielle";
    rowNew.LastName = "Tiedt";
    rowNew.WorkPhone = "(111) 555-1212";
}

To change a row in a data set, get a Row object from the table using the FindBy method, and then make changes to the fields in the row, as shown by the following code:

Visual Basic .NET
' Uses connection, adapter, and data set created in Design mode.
    Private Sub butChangeRow_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles butChangeRow.Click
        ' Declare a row object.
        Dim rowChange As DataSet1.ContactsRow
        ' Get the row to change using the primary key.
        rowChange = DataSet1.Contacts.FindByContactID(42)
        ' Change a field in the row.
        rowChange.WorkPhone = "(111) 555-9000"
    End Sub
Visual C#
// Uses connection, adapter, and data set created in Design mode.
   private void butChangeRow_Click(object sender, System.EventArgs e)
   {
         // Declare a row object.
         DataSet1.ContactsRow rowChange;
         // Get the row to change using the primary key.
         rowChange = DataSet1.Contacts.FindByContactID(42);
         // Change a field in the row.
         rowChange.WorkPhone = "(111) 555-9000";
   }

To delete a row in a data set, get a Row object from the Table using the FindBy method, and then delete the row using the Row object' s Delete method, as shown here:

Visual Basic .NET
' Uses connection, adapter, and data set created in Design mode.
Private Sub butDeleteRow_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butDeleteRow.Click
    ' Declare a row object.
    Dim rowDelete As DataSet1.ContactsRow
    ' Get the row to delete
    rowDelete = DataSet1.Contacts.FindByContactID(42)
    ' Delete the row.
    rowDelete.Delete()
End Sub
Visual C#
// Uses connection, adapter, and data set created in Design mode.
private void butDeleteRow_Click(object sender, System.EventArgs e)
{
     // Declare a row object.
     DataSet1.ContactsRow rowDelete;
     // Get the row to delete
     rowDelete = DataSet1.Contacts.FindByContactID(42);
     // Delete the row.
     rowDelete.Delete();
}
Updating the Database from the DataSet

Use the data adapter' s Update method to update the database with changes from a DataSet object. You will usually want to do this after all of the control events on a page have been processed; therefore, the Update method is usually called from the Page_PreRender event, as shown here:

Visual Basic .NET
' Uses connection, adapter, and data set created in Design mode.
Private Sub Page_PreRender(ByVal sender As Object, _
  ByVal e As System.EventArgs) Handles MyBase.PreRender
    ' Update the database with changes from the data set.
    SqlDataAdapter1.Update(DataSet1)
End Sub
Visual C#
// Uses connection, adapter, and data set created in Design mode.
   private void Page_PreRender(object sender, System.EventArgs e)
   {
         // Update the database with changes from the data set.
         sqlDataAdapter1.Update(DataSet1);
   }

When you update a database from a data set, ADO.NET follows these steps:

  1. Determines the changes to the data set by checking each DataRow object' s RowState property. Possible values are Added, Deleted, Modified, Unchanged, or Detached.

  2. Invokes the adapter object' s InsertCommand, DeleteCommand, or UpdateCommand properties to make the required changes in the database.

  3. Resets the updated DataRow objects' RowState properties to Unchanged.

    The adapter object' s InsertCommand, DeleteCommand, and UpdateCommand properties are generated automatically from the adapter object' s SelectCommand when you create a data set from a data adapter in Design mode. Each of these properties represents an OleCommand or SqlCommand object. Command objects have the following properties that determine how the command is executed:

    • The CommandText property contains the SQL statement or the stored procedure name to perform the command.

    • The CommandType property determines how the command is performed by the following possible settings:

      • The Text setting executes the command as a SQL statement. (This is the default.)

      • The StoredProcedure setting executes the command as a procedure stored in the database.

      • The TableDirect setting returns the entire table. This setting applies only to OLE DB .NET data providers.

    You can change the contents of the data set and how the data set is updated by changing the SQL statements used by these command objects.

    To view or modify these commands, follow these steps:

    1. In the Properties window, double-click the command property you want to change. This expands the list of properties that apply to the command object, as shown in Figure 5-14.

      Figure 5-14 Viewing and modifying SQL commands

    2. Select the CommandText property for the command, and then click the ellipsis to the right of the property setting. Visual Studio displays the Query Builder dialog box, as shown in Figure 5-15.

      Figure 5-15 The Query Builder dialog box

    3. Select the columns to include in the command by selecting the check boxes next to the data field names in the table pane of the Query Builder dialog box, or type the SQL statement directly in the command pane of the Query Builder. When you are done, click OK to close the dialog box.

Creating a Database Connection at Run Time

Creating data objects in Design mode is a great way to learn about data access in ADO.NET because Visual Studio generates the connection and adapter object property settings for you. Some of the property settings, such as ConnectionString, can be difficult and confusing to construct without the help of Design mode. After you' ve created a connection in Design mode, you can cut and paste the property settings to create similar connections in code.

Using data-access objects in code follows the same sequence of events as it does in Design mode:

  1. Create the data connection object.

  2. Create a data adapter object.

  3. Create a data set object.

  4. Invoke methods on the adapter object to fill or update the data set.

  5. Use data binding or another technique to display the data from the data set.

For example, the following code creates data objects and displays data from a SQL data provider:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles MyBase.Load
    ' (1) Create the data connection.
    Dim sqlconnectContactMgmt As New SqlConnection _
      ("server=(local);database=Contacts;Trusted_Connection=yes")
    ' (2) Create a data adapter.
    Dim sqladaptContactMgmt As New _
      SqlDataAdapter("select * from Contacts", sqlconnectContactMgmt)
    ' (3) Create a data set.
    Dim dsContacts As New DataSet()
    ' (4) Fill the data set.
    sqladaptContactMgmt.Fill(dsContacts, "Contacts")
    ' (5) Display the table in a data grid using data binding.
    DataGrid1.DataSource = dsContacts.Tables("Contacts").DefaultView
    DataGrid1.DataBind()
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
     // (1) Create the data connection.
     SqlConnection sqlconnectContactMgmt = new
       SqlConnection("server=(local);
       database=Contacts;Trusted_Connection=yes");
     // (2) Create a data adapter.
     SqlDataAdapter sqladaptContactMgmt = new
       SqlDataAdapter("select * from Contacts", sqlconnectContactMgmt);
     // (3) Create a data set.
     DataSet dsContacts = new DataSet();
     // (4) Fill the data set.
     sqladaptContactMgmt.Fill(dsContacts, "Contacts");
     // (5) Display the table in a data grid using data binding.
     DataGrid1.DataSource = dsContacts.Tables["Contacts"].DefaultView;
     DataGrid1.DataBind();
}
Updating a Run-Time Database Connection

As mentioned earlier in this section, ADO.NET uses the adapter object' s InsertCommand, DeleteCommand, and UpdateCommand properties to update the database from the data set. When you create a data adapter at run time, you need to generate these properties before you can call the adapter object' s Update method.

To generate InsertCommand, DeleteCommand, and UpdateCommand properties at run time, follow these steps:

  1. Set the adapter object' s SelectCommand. ADO.NET used the SelectCommand' s CommandText property to generate the settings for the InsertCommand, DeleteCommand, and UpdateCommand properties.

  2. Create a command builder object for the adapter object. The command builder object is dependent on the data provider, so it has two forms: OleDbCommandBuilder and SqlCommandBuilder.

The following code connects to a database (1), creates a data set (2), generates commands (3), modifies the data set (4), and then updates the database based on the changes (5):

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' (1) Create the data connection.
    Dim ContactMgmt As New SqlConnection _
      ("server=(local);database=Contacts;Trusted_Connection=yes")
    ' Create an adapter.
    Dim adptContactMgmt As New _
      SqlDataAdapter("select * from Contacts", ContactMgmt)
    ' (2) Create a data set.
    Dim dsContacts As New DataSet()
    ' Set select command.
    adptContactMgmt.SelectCommand.CommandText = _
      "SELECT * FROM Contacts"
    ' (3) Create insert, delete, and update commands automatically.
    Dim cmdContactMgmt As SqlCommandBuilder = New _
      SqlCommandBuilder(adptContactMgmt)
    ' (4) Create a new row.
    Dim rowInsert As DataRow = dsContacts.Tables("Contacts").NewRow
    ' Add data to fields in the row.
    rowInsert("ContactID") = 42
    rowInsert("FirstName") = "Danielle"
    rowInsert("LastName") = "Tiedt"
    rowInsert("WorkPhone") = "(555) 555-1212"
    ' Add the row to the data set.
    dsContacts.Tables("Contacts").Rows.Add(rowInsert)
    ' (5) Update the database.
    adptContactMgmt.Update(dsContacts.Tables("Contacts"))
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
   // (1) Create the data connection.
   SqlConnection ContactMgmt = new
   SqlConnection("server=(local);database=Contacts;Trusted_Connection=yes");
   // Create a data adapter.
   SqlDataAdapter adptContactMgmt =
      new SqlDataAdapter("select * from
      Contacts", ContactMgmt);
   // (2) Create a data set.
   DataSet dsContacts = new DataSet();
   // Set select command.
   adptContactMgmt.SelectCommand.CommandText =
      "SELECT * FROM Contacts";
   // (3) Create insert, delete, and update commands automatically.
   SqlCommandBuilder cmdContactMgmt = new
      SqlCommandBuilder(adptContactMgmt);
   // (4) Create a new row.
   DataRow rowInsert = dsContacts.Tables["Contacts"].NewRow();
   // Add data to fields in the row.
   rowInsert["ContactID"] = 42;
   rowInsert["FirstName"] = "Danielle";
   rowInsert["LastName"] = "Tiedt";
   rowInsert["WorkPhone"] = "(222) 555-1212";
   // Add the row to the data set.
   dsContacts.Tables["Contacts"].Rows.Add(rowInsert);
   // (5) Update the database.
   adptContactMgmt.Update(dsContacts.Tables["Contacts"]);
}

The Update method in the preceding code requires that you create the SqlCommandBuilder object first. Creating that object provides the settings for the InsertCommand property that the adapter object uses to add the new row to the database.


Lesson 2: Using Data Sets on Web Forms

Most ASP.NET server controls support data binding, which is a way to link data in your application, such as data sets, to properties of a control. In particular, the DataGrid and DataList server controls are specifically designed to make displaying data sets on a Web form easy and efficient.

In this lesson, you will learn how to display a data set on a Web form using the DataGrid, DataList, and other server controls. You will learn how to select records from a data set on a Web form and how to perform SQL commands on a database from control event procedures.

After this lesson, you will be able to

  • Quickly display a data set on a Web form through data binding

  • Format the data displayed in a DataGrid, DataList, or other data-bound server control

  • Display data from a data set in a server control using code rather than data binding

  • Get records from a data set displayed on a Web form

  • Perform SQL commands on a database to return values or to modify data directly

Estimated lesson time: 30 minutes
Displaying a Data Set in a DataGrid

The simplest way to display a data set on a Web form is through a DataGrid control using data binding.

To display a data set in this way, follow these steps:

  1. Create the database connection, adapter, and data set objects as described in the previous lesson.

  2. Add a DataGrid control to the Web form.

  3. Set the DataSource property of the DataGrid to the name of the data set.

  4. Add code to the Web form' s Page_Load event to fill the data set from the adapter and to bind the data from the data set to the DataGrid.

For example, the following event procedure creates a data set from the Contacts database' s Contacts table and then displays the data in a DataGrid control:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
        ' Fill the data set.
        adptContacts.Fill(dsContacts)
        ' Bind the data to the DataGrid control.
        grdContacts.DataBind()
    End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
   {
         // Fill the data set.
         adptContacts.Fill(dsContacts);
         // Bind the data to the DataGrid control.
         grdContacts.DataBind();
   }

The preceding procedure and code displays all of the columns in the data set without formatting, as shown in Figure 5-16.

Figure 5-16 The DataGrid without formatting

This is generally not what you want. For instance, the Birthdate column (not shown in Figure 5-16) includes a time as well as the date.

To select the columns you want to display and to format the data in the DataGrid, follow these steps:

  1. Click the Property Builder link in the Properties window. Visual Studio displays the Properties dialog box for the DataGrid.

  2. Select the Columns item on the left side of the dialog box to select and format the columns of data displayed from the data set. Visual Studio displays the columns' properties, as shown in Figure 5-17.

    Figure 5-17 Specifying columns and formatting for a DataGrid

To display specific columns from the data set, follow these steps:

  1. Deselect the Create Columns Automatically At Run Time check box. This limits the columns in the DataGrid to only the ones you select.

  2. Select the column in the Available Columns list and click the Add (>) button to add it to the Selected Columns list.

To format data in a column, follow these steps:

  1. Select the column to format in the Selected Columns list.

  2. Type the format expression in the Data Formatting Expression text box. Data formatting expressions take the form:

{0:formattingexpression}

where formattingexpression is one of the predefined Microsoft .NET Framework format expressions, as described in the “Date and Time Format Strings,” “Numeric Format Strings,” or “Custom Format Strings” topics in the Visual Studio online Help.

You use the DataGrid Properties dialog box to determine all aspects of the appearance and behavior of the DataGrid, including formatting the column headings, determining how the grid scrolls (pages) through records, whether borders or rules are displayed, and much more.

Figure 5-18 shows the Contacts data set displayed in a DataGrid with formatting applied. The DataGrid displays only four columns from the data set and formats the Birthdate column as a short date ({0:d}).

Figure 5-18 Selected columns from a data set with formatting applied
Displaying a Data Set in a DataList

Use the DataList control to display information from a data set as a list of rows, rather than rows and columns as in a DataGrid.

To display a data set in a DataList control, follow these steps:

  1. Create the database connection, adapter, and data set objects as described in the previous lesson.

  2. Add a DataList control to the Web form.

  3. Set the DataSource property of the DataGrid to the name of the data set.

  4. Add code to the Web form' s Page_Load event to fill the data set from the adapter and to bind the data from the data set to the DataGrid.

  5. Edit the DataList control' s header, item, and separator templates to create the appearance of the DataList.

The DataList control uses templates to determine the content of its headers, footers, and rows. You edit these templates to add literals and controls that bind to items in the data set. The DataList provides three different categories of templates to control the different aspects of its appearance:

  • Header and Footer Templates. These include the title and rule lines that appear at the top and the bottom of the DataList.

  • Items Templates. These determine the contents of the rows in the data list and allow you to alternate the appearance of odd, even, selected, or edit rows.

  • Separator Templates. These add rule lines or other separators between rows.

To edit a DataList template:

  1. Right-click the DataList control and select Edit Template from the pop-up menu, then select the template to edit from the menu list.

  2. The DataList control changes its appearance when it is in Edit mode, as shown in Figure 5-19. Type the text you want to appear in the template and add controls as desired.

    Figure 5-19 DataList control in template Edit mode

  3. To display an item from the data set in the template, add a control to the template, and then click the DataBindings property for the control. Click the ellipsis to the right of the DataBindings property. Visual Studio displays the DataBindings dialog box for the control, as shown in Figure 5-20.

    Figure 5-20 DataBindings dialog box

  4. When you are done editing the DataList control' s templates, right-click the DataList control and select End Template Editing from the pop-up menu.

Data binding for controls in the DataList control' s Items Template can be a simple binding to an item from the container, as shown in Figure 5-20, or it can be a more complex expression using the Eval method. For example, the following custom binding expression combines first name and last name information in a single control:

Visual Basic .NET
DataBinder.Eval(Container, "DataItem.FirstName") & " " & _
  DataBinder.Eval(Container, "DataItem.LastName")
Visual C#
DataBinder.Eval(Container, "DataItem.FirstName") + " " +
  DataBinder.Eval(Container, "DataItem.LastName");

The DataBinder object' s Eval method is actually what is generated by Visual Studio in the Web form' s HTML when you create a DataList control template with data binding. It is sometimes easier to edit templates directly in HTML than through the edit template procedure described previously.

To edit a DataList template directly in HTML, right-click the Web form and select View HTML Source from the pop-up menu. Visual Studio displays the Web form in HTML mode.

For example, the following HTML displays a DataList control that contains name, birthday, and telephone information from the Contacts data set.

<asp:DataList id="dlstContacts" runat="server" DataSource="<%# dsContacts %>">
<HeaderTemplate>
<h2>
    Contact Information
   <HR width="100%" SIZE="1">
   </h2>
</HeaderTemplate>

<FooterTemplate>
<HR>
</FooterTemplate>

<ItemTemplate>
    <asp:Label id="lblName" runat="server"
        Text='<%# DataBinder.Eval(Container, "DataItem.FirstName") + " "         + DataBinder.Eval(Container,"DataItem.LastName") %>'>
    </asp:Label>
    &nbsp;Birthdate:
    <asp:Label id="lblBirthdate" runat="server"
        Text='<%# DataBinder.Eval(Container, "DataItem.Birthdate",
    "{0:d}") %>'>
    </asp:Label>
    &nbsp;Work phone:
    <asp:Label id="lblWorkPhone" runat="server"
        Text='<%# DataBinder.Eval(Container, "DataItem.WorkPhone") %>'>
    </asp:Label>
</ItemTemplate>

<SeparatorTemplate>
   <HR>
</SeparatorTemplate>
</asp:DataList>

To display the data set in the preceding control, fill the data set from the data adapter and bind to the DataList control, as shown by the following Page_Load event procedure:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Fill the data set.
    adptContacts.Fill(dsContacts)
    ' Bind the data to the DataList control
    dlstContacts.DataBind()
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
   // Fill the data set.
   adptContacts.Fill(dsContacts);
   // Bind the data to the DataList control.
   dlstContacts.DataBind();
}

At run time, the preceding DataList control displays the header, items, and separators, as shown in Figure 5-21.

Figure 5-21 DataList control with data binding at run time
Displaying Data Items in Other List Controls

Of course you can bind the items from a data set to any list control on a Web form. The DataGrid and DataList controls are just two of the most versatile ways to display a data set.

To display items from a data set in a ListBox, DropDownList, CheckBoxList, or RadioButtonList control, follow these steps:

  1. Set the control' s DataSource property to the name of the data set.

  2. Set the control' s DataText property to the data set member to display as Text property of the list item.

  3. Set the control' s DataValue property to the data set member to return as the Value property of the list item.

  4. In code, fill the data set from data adapter and bind to the control.

The following HTML shows the property settings for a DropDownList control that is bound to the Contacts data set:

<asp:DropDownList id="drpContacts" runat="server" Width="384px" Height="22px" DataSource="<%# dsContacts %>" DataTextField='LastName' DataValueField="ContactID"></asp:DropDownList>

The following code fills the data set and binds the data to the DropDownList:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Fill the data set.
    adptContacts.Fill(dsContacts)
    ' Bind the data to the control.
    drpContacts.DataBind()
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
   // Fill the data set.
   adptContacts.Fill(dsContacts);
   // Bind the data to the DataList control.
   dlstContacts.DataBind();
}

At run time, the DropDownList displays a list of the last names from the Contacts table, and the SelectedItem.Value property returns the ContactID, which is useful for looking up other information about the individual.

The limitation here is that you can include only one value for the list control' s DataText and DataValue properties. If you want to include the first and last names of the contact in the DropDownList, you have to do it from code. The following Page_Load event procedure adds the first and last names to the DropDownList and includes the ContactID as the Value property for each list item:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Run first time page is displayed.
    If Not IsPostBack Then
        ' Fill the Contacts data set.
        adptContacts.Fill(dsContacts)
        ' For each row in the table...
        Dim drowItem As dsContacts.ContactsRow
        For Each drowItem In dsContacts.Contacts
            ' Create a new list item.
            Dim lstNew As New ListItem()
            lstNew.Text = drowItem.FirstName & " " & drowItem.LastName
            lstNew.Value = drowItem.ContactID
            ' Add the list item to the drop-down list.
            drpContacts.Items.Add(lstNew)
        Next
    End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
   {
         // Run first time page is displayed.
         if (!IsPostBack)
         {
               // Fill the Contacts data set.
               adptContacts.Fill(dsContacts);
               // For each row in the table...
               foreach (dsContacts.ContactsRow drowItem in
                 dsContacts.Contacts)
               {
                    // Create a new list item.
                     ListItem lstNew = new ListItem();
                     lstNew.Text = drowItem.FirstName + " " +
                       drowItem.LastName;
                     lstNew.Value = drowItem.ContactID.ToString();
                     // Add the list item to the drop-down list.
                     drpContacts.Items.Add(lstNew);
               }
         }
   }
Selecting Specific Records

The DropDownList created in the preceding section is useful for selecting items from the Calls table in the Contacts database. Because ContactID is a unique key in the database, you can use it to select the Call records for a specific contact. The following code builds a data set containing the calls for the contact selected from the DropDownList:

Visual Basic .NET
Private Sub drpContacts_SelectedIndexChanged(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles drpContacts.SelectedIndexChanged
    ' When a contact is selected, display the contact's calls.
    adptCalls.SelectCommand.CommandText = "SELECT * FROM Calls" & _
         " WHERE ContactID =" & drpContacts.SelectedItem.Value
    ' Clear the data set.
    dsCalls.Clear
    adptCalls.Fill(dsCalls)
    ' Display the results in a data grid.
    grdCalls.DataBind()
    End Sub
Visual C#
private void drpContacts_SelectedIndexChanged(object sender,
  System.EventArgs e)
{
     // When a contact is selected, display the contact's calls.
     adptCalls.SelectCommand.CommandText = "SELECT * FROM Calls" +
       " WHERE ContactID =" + drpContacts.SelectedItem.Value;
     // Clear the data set.
     dsCalls.Clear();
     adptCalls.Fill(dsCalls);
     // Display the results in a data grid.
     grdCalls.DataBind();
}

At run time, the Web form displays a list of the calls for the contact selected in the DropDownList, as shown in Figure 5-22.

Figure 5-22 DropDownList
Executing Commands on a Database

In addition to working with data sets, you can perform commands directly on a database connection. The database connection object provides these three command methods:

  • ExecuteScalar. Performs query commands that return a single value, such as counting the number of records in a table.

  • ExecuteNonQuery. Performs commands that change the database but do not return a specific value. This includes adding and deleting items from a database. The ExecuteNonQuery method returns the number of rows affected by the command.

  • ExecuteReader. Reads records sequentially from the database.

To use these methods, follows these steps:

  1. Create a connection to the database.

  2. Open the connection.

  3. Create a command object containing the SQL command or stored procedure to execute.

  4. Execute the method on the command object.

  5. Close the database connection.

Returning a Value From a Database

Lesson 1 showed you how to add and delete rows from a data set, and then how to update the database from the data set, but it glossed over an important detail: you often need to retrieve information from the database before you can add a record. The most common example of this is when a record contains a unique key field, such as the ContactID in the Contacts database. You must obtain a new unique value for each record before you can add the record to the database

There are several ways to get a new unique key value from a database. Perhaps the most straightforward way is to create a command object and use the ExecuteScalar method to return a value. There are two types of command objects:

  • SqlCommand objects execute commands on SQL database connections.

  • OleDBCommand objects execute commands on OLE database connections.

The following code uses a SqlCommand object to retrieve the highest number used for ContactID, then increments the value and uses the number as the unique key for a new Contact table row:

Visual Basic .NET
' Uses connection, adapter, and data set created in Design mode.
Private Sub butAddRow_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butAddRow.Click
    ' Get new unique ContactID.
    Dim intID As Integer
    Dim cmdNewID As New SqlCommand("SELECT MAX(ContactID)" & _
      " FROM Contacts", ContactMgmt)
    ' Open the database connection.
    ContactMgmt.Open()
    intID = CInt(cmdNewID.ExecuteScalar())
    ' Create a new row object for the Contacts table.
    Dim rowNew As dsContacts.ContactsRow = dsContacts.Contacts.NewRow
    ' Add data to the fields in the row.
    rowNew.ContactID = intID + 1
    rowNew.FirstName = "Max"
    rowNew.LastName = "Benson"
    rowNew.WorkPhone = "(111) 555-1212"
    ' Add the row to the data set.
    dsContacts.Contacts.AddContactsRow(rowNew)
    ' Close the database connection.
    ContactMgmt.Close()
End Sub
Visual C#
private void butAddRow_Click(object sender, System.EventArgs e)
{
     // Get new unique ContactID.
     int intID;
     SqlCommand cmdNewID = new SqlCommand("SELECT MAX(ContactID)" +
       " FROM Contacts", ContactMgmt);
     // Open the database connection.
     ContactMgmt.Open();
     intID = (int)sqlcommNewID.ExecuteScalar();
     // Create a new row object for the Contacts table.
     dsContacts.ContactsRow rowNew = (dsContacts.ContactsRow)
     dsContacts.Contacts.NewRow();
     // Add data to the fields in the row.
     rowNew.ContactID = intID + 1;
     rowNew.FirstName = "Max";
     rowNew.LastName = "Benson";
     rowNew.WorkPhone = "(111) 555-1212";
     // Add the row to the data set.
     dsContacts.Contacts.AddContactsRow(rowNew);
     // Close the database connection.
     ContactMgmt.Close();
}

The ExecuteScalar method can be used with any SQL statement that returns a single value. To execute other types of SQL statements, use the ExecuteNonQuery or ExecuteReader method.

Changing Records Directly in a Database

The ExecuteNonQuery method performs commands that do not return a data set, such as SQL INSERT, DELETE, or UPDATE. For example, the following code uses ExecuteNonQuery to delete a row directly from the database:

Visual Basic .NET
Private Sub butDelete_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles butDelete.Click
    Dim intRows As Integer
    ' Create a command object
    Dim cmdDeleteRow As New SqlCommand("DELETE ROW" & _
      " FROM Contacts WHERE ContactID=1", ContactMgmt)
    ' Open the database connection.
    ContactMgmt.Open()
    ' Execute the command
    intRows = cmdDeleteRow.ExecuteNonQuery()
    ' Display the number of rows deleted.
    Response.Write("Records deleted: " & intRows)
    ' Close the connection.
    ContactMgmt.Close()
End Sub
Visual C#
private void butDeleteRow_Click(object sender, System.EventArgs e)
{
     int intRows;
     // Create a command object
         SqlCommand cmdDeleteRow = new SqlCommand("DELETE ROW" +
     " FROM Contacts WHERE ContactID=1", ContactMgmt);
     // Open the database connection.
     ContactMgmt.Open();
     // Execute the command
     intRows = cmdDeleteRow.ExecuteNonQuery();
     // Display the number of rows deleted.
     Response.Write("Records deleted: " + intRows.ToString());
     // Close the connection.
     ContactMgmt.Close();
}

The ExecuteNonQuery method acts directly on the database connection. It does not go through a data adapter or a data set. If you make changes to a table in the database through ExecuteNonQuery, you must update any data sets affected by those changes by calling the Fill method on the data adapter.

Retrieving Records Directly from the Database

The ExecuteReader method performs commands that return records, such as SQL SELECT. Each record is returned as a data reader object, which is sort of a read-only version of a data set. Because the ExecuteReader method acts directly on the database connection, there are two versions of the data reader object: OleDbDataReader and SqlDataReader.

Using ExecuteReader to create data reader objects provides better performance than creating a data set from a data adapter object, but it doesn' t provide you with much flexibility. Data reader objects are read-only and they only read forward, one record at a time. Data sets allow you to get records in any order and, more important, allow you to write changes back to the database.

The following code gets all the calls for a contact and displays the subject lines for each call on the Web form using a data reader object and the Execute reader command:

Visual Basic .NET
Private Sub butShowCalls_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles butShowCalls.Click
    ' Create a command object
    Dim cmdGetCalls As New SqlCommand("SELECT * FROM Calls" & _
      " WHERE ContactID=3", ContactMgmt)
    ' Open the database connection.
    ContactMgmt.Open()
    ' Create a data reader object.
    Dim dreadCalls As SqlDataReader
    ' Execute the command
    dreadCalls = cmdGetCalls.ExecuteReader()
    ' Display the records.
    Do While dreadCalls.Read()
        Response.Write(dreadCalls.GetString(4))
    Loop
    ' Close the reader
    dreadCalls.Close()
    ' Close the database connection.
    ContactMgmt.Close()
End Sub
Visual C#
private void butShowCalls_Click(object sender, System.EventArgs e)
{
     // Create a command object
     SqlCommand cmdGetCalls = new SqlCommand("SELECT * FROM Calls" +
     " WHERE ContactID=3", ContactMgmt);
     // Open the database connection.
     ContactMgmt.Open();
     // Create a data reader object.
     SqlDataReader dreadCalls;
     // Execute the command
     dreadCalls = cmdGetCalls.ExecuteReader();
     // Display the records.
     while (dreadCalls.Read())
     Response.Write(dreadCalls.GetString(4));
     // Close the reader
     dreadCalls.Close();
     // Close the database connection.
     ContactMgmt.Close();
}

The data reader object is a read-forward set of records, so the Read method reads each subsequent line until it reaches the end of the record set. A reader object locks the database connection while it is executing, so you should call the reader object' s Close method when you are done getting records, as shown in the preceding code.


Lesson 3: Processing Transactions

ADO.NET lets you group database operations into transactions. A transaction is a group of commands that change the data stored in a database. The transaction, which is treated as a single unit, assures that the commands are handled in an all-or-nothing fashion—if one of the commands fails, all of the commands fail, and any data that was written to the database by the commands is backed out. In this way, transactions maintain the integrity of data in a database.

In this lesson, you will learn how to implement transaction processing in a Web application for data sets and for databases.

After this lesson, you will be able to

  • Understand the importance of transaction processing for Web applications that use data access

  • Explain the levels of transaction processing that ADO.NET supports

  • Manage changes to a data set as a transaction

  • Create transaction objects for a database

  • Track commands performed on a database through a transaction object

  • Commit or roll back (undo) changes to a database using the transaction object

Estimated lesson time: 30 minutes
Understanding Transactions

As stated earlier, a transaction is a group of database commands that are treated as a single unit. Database programmers determine what database commands belong in a transaction by using the ACID test: commands must be atomic, consistent, isolated, and durable. Commands belong in a transaction if they are:

  • Atomic. In other words, they make up a single unit of work. For example, if a customer moves, you want your data entry operator to change all of the customer' s address fields as a single unit, rather than changing street, then city, then state, and so on.

  • Consistent. All the relationships between data in a database are maintained correctly. For example, if customer information uses a tax rate from a state tax table, the state entered for the customer must exist in the state tax table.

  • Isolated. Changes made by other clients can' t affect the current changes. For example, if two data entry operators try to make a change to the same customer at the same time, one of two things occurs: either one operator' s changes are accepted and the other is notified that the changes weren' t made, or both operators are notified that their changes were not made. In either case, the customer data is not left in an indeterminate state.

  • Durable. Once a change is made, it is permanent. If a system error or power failure occurs before a set of commands is complete, those commands are undone and the data is restored to its original state once the system begins running again.

Transaction processing is particularly important for Web applications that use data access, since Web applications are distributed among many different clients. In a Web application, databases are a shared resource, and having many different clients distributed over a wide area can present these key problems:

  • Contention for resources. Several clients might try to change the same record at the same time. This problem gets worse the more clients you have.

  • Unexpected failures. The Internet is not the most reliable network around, even if your Web application and Web server are 100 percent reliable. Clients can be unexpectedly disconnected by their service providers, by their modems, or by power failures.

  • Web application life cycle. Web applications don' t follow the same life cycle as Windows applications—Web forms live for only an instant, and a client can leave your application at any point by simply typing a new address in his or her browser.

Transaction processing follows these steps:

  1. Begin a transaction.

  2. Process database commands.

  3. Check for errors.

  4. If errors occurred, restore the database to its state at the beginning of the transaction. If no errors occurred, commit the transaction to the database.

In ADO.NET, transactions are handled in different ways, depending on the level you are working at:

  • Data sets provide transaction processing through the RejectChanges and Update methods. Data sets also provide an AcceptChanges method that resets the state of records in a data set to unchanged.

  • Database connection objects provide transaction processing through the transaction object. Transaction objects track commands performed on a database and provide the Rollback, Commit, and Save methods to restore database state, commit changes, or create a save point within a transaction, respectively.

  • The System.EnterpriseServices namespace provides enterprise-level transactions through the ContextUtil class. Enterprise-level transactions use the Microsoft Distributed Transaction Coordinator (DTC) provided with Microsoft SQL Server 2000 to track transactions across multiple Web forms and across multiple COM+ components.

Data Set Transactions

Data sets provide an implicit transaction processing, since changes to a data set are not made in the database until you invoke the Update method on the data adapter object. This lets you perform a set of commands on the data, then choose a point at which to make the changes permanent in the database.

If an error occurs during the Update method, none of the changes from the data set is made in the database. At that point, you can either attempt to correct the error and try the Update method again, or you can undo the changes pending in the data set using the data set' s RejectChanges method. For example, the following code displays the Contacts list in a DataGrid control and allows the user to delete rows by clicking Delete in the DataGrid:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
     ByVal e As System.EventArgs) Handles MyBase.Load
    ' Check if this is the first time page is displayed.
    If Not IsPostBack Then
        ' On first display:
        '   Fill the data set.
        adptContacts.Fill(dsContacts)
        '   Save data set as state variable.
        Session("dsContacts") = dsContacts
    Else
        ' On subsequent displays:
        '   Get the data set from the state variable.
        dsContacts = Session("dsContacts")
    End If
    ' Bind to data set.
    grdContacts.DataBind()
End Sub

Private Sub grdContacts_ItemCommand(ByVal source As Object, _
  ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
  Handles grdContacts.ItemCommand
    ' If the Delete button was clicked.
   If e.CommandName = "Delete" Then
        Dim intContactID
        ' Get selected row's ContactID.
        intContactID = _
          CInt(grdContacts.Items(e.Item.ItemIndex).Cells(3).Text)
        Dim rowDelete As dsContacts.ContactsRow
        ' Get the row to delete from the data set.
        rowDelete = dsContacts.Contacts.FindByContactID(intContactID)
        ' Delete the row.
        rowDelete.Delete()
        ' Refresh the data grid.
        grdContacts.DataBind()
    End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
     // Check if this is the first time page is displayed.
     if (!IsPostBack)
     {
           // On first display:
           //   Fill the data set.
           adptContacts.Fill(dsContacts);
           //   Save data set as state variable.
           Session["dsContacts"] = dsContacts;
     }
     else
           // On subsequent displays:
           //   Get the data set from the state variable.
           dsContacts = (dsContacts)Session["dsContacts"];
     // Bind to data set.
     grdContacts.DataBind();
}

private void grdContacts_ItemCommand(object sender,
  System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
    // If the Delete button was clicked.
      if (e.CommandName == "Delete")
      {
               int intContactID;
               // Get selected row's ContactID.
                  intContactID = Convert.ToInt16
                  (grdContacts.Items[e.Item.ItemIndex].Cells[3].Text);
                  dsContacts.ContactsRow rowDelete;
            // Get the row to delete from the data set.
            rowDelete = dsContacts.Contacts.FindByContactID(intContactID);
            // Delete the row.
            rowDelete.Delete();
            // Refresh the data grid.
            grdContacts.DataBind();
      }
}

The following event procedures for the Restore and Commit buttons let the user restore the data set to its previous state, or, alternatively, update the database with the deletions made in the preceding code:

Visual Basic .NET
Private Sub butRestore_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles butRestore.Click
    ' Restore the data set to its original state.
    dsContacts.RejectChanges()
    ' Refresh the data grid.
    grdContacts.DataBind()
End Sub

Private Sub butCommit_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butCommit.Click
    ' Update the database from the data set.
    adptContacts.Update(dsContacts)
    ' Save changes to state variable.
    Session("dsContacts") = dsContacts
    ' Refresh the data grid.
    grdContacts.DataBind()
End Sub
Visual C#
private void butRestore_Click(object sender, System.EventArgs e)
{
     // Restore the data set to its original state.
     dsContacts.RejectChanges();
     // Refresh the data grid.
     grdContacts.DataBind();
}

private void butCommit_Click(object sender, System.EventArgs e)
{
     int intRows;
     // Update the database from the data set.
     intRows = adptContacts.Update(dsContacts);
     // Save changes to state variable.
     Session["dsContacts"] = dsContacts;
     // Refresh the data grid.
     grdContacts.DataBind();
}

The RejectChanges method in the preceding butRestore_Click event procedure returns the data set to its state before the row was deleted. The data set' s AcceptChanges method is the inverse to RejectChanges—it resets the DataRowState property of all of the changed rows in a data set to Unchanged and removes any deleted rows.

The AcceptChanges method prevents the Update method from making those changes in the database, however, because Update uses the rows' DataRowState property to determine which rows to modify in the database. For this reason, the AcceptChanges method is useful only when you do not intend to update a database from the data set.

Database Transactions

You can manage transactions at the database level through a transaction object. Since there are two types of database connections in ADO.NET, there are also two types of transaction object: SqlTransaction and OleDbTransaction.

To use either type of transaction object, follow these steps:

  1. Open a database connection.

  2. Create the transaction object using the database connection object' s BeginTransaction method.

  3. Create command objects to track with this transaction, assigning the Transaction property of each command object to the name of the transaction created in step 2.

  4. Execute the commands. Because the purpose of transaction processing is to detect and correct errors before data is written to the database, this is usually done as part of an error-handling structure.

  5. Commit the changes to the database or restore the database state, depending on the success of the commands.

  6. Close the database connection.

The following code uses a DataGrid to display a list of contacts from the Contacts database. The DataGrid includes a column of buttons that allow the user to delete contacts. The DeleteContact function uses a transaction to ensure that a contact' s calls are deleted if the contact is deleted. This helps ensure the integrity of the database.

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
    ' Fill the data set.
    adptContacts.Fill(dsContacts)
    ' Bind to data set.
    grdContacts.DataBind()
End Sub

Private Sub grdContacts_ItemCommand(ByVal source As Object, _
  ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
  Handles grdContacts.ItemCommand
    ' If the Delete button was clicked.
    If e.CommandName = "Delete" Then
        Dim intContactID
        ' Get selected row's ContactID.
        intContactID = _
          CInt(grdContacts.Items(e.Item.ItemIndex).Cells(3).Text)
        ' Delete the contact information.
        lblStatus.Text = DeleteContact(intContactID)
    End If
    ' Refresh the data set.
    adptContacts.Fill(dsContacts)
    ' Refresh the DataGrid.
    grdContacts.DataBind()
End Sub

Function DeleteContact(ByVal intContactID As Integer) As String
    ' Open the database connection.
    ContactMgmt.Open()
    ' Declare a transaction object.
    Dim transDelete As SqlTransaction
    ' Create the tranasction.
    transDelete = ContactMgmt.BeginTransaction _
     (IsolationLevel.ReadCommitted)
    ' Create the command to delete from Contacts table.
    Dim cmdDelete As New SqlCommand("DELETE FROM Contacts" & _
      " WHERE ContactID=" & intContactID, _
      ContactMgmt, transDelete)
    ' Execute the commands
    Try
        Dim intRows As Integer
        ' Delete row from Contacts table.
        intRows = cmdDelete.ExecuteNonQuery()
        ' Delete Calls for this ContactID.
        cmdDelete.CommandText = "DELETE FROM Calls WHERE " & _
          " ContactID=" & intContactID
        intRows = intRows + cmdDelete.ExecuteNonQuery()
        ' Commit the transaction.
        transDelete.Commit()
        ' Return success message.
        Return intRows & " deleted."
    Catch
        ' Restore the database state if there was an error.
        transDelete.Rollback()
        ' Return error message.
        Return "Contact could not be deleted."
    Finally
        ' Close the database.
        ContactMgmt.Close()
    End Try
End Function
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    // Fill the data set.
     adptContacts.Fill(dsContacts);
         // Bind to the data set.
         grdContacts.DataBind();
}

   private void grdContacts_ItemCommand(object sender,
      System.Web.UI.WebControls.DataGridCommandEventArgs e)
   {
         // If the Delete button was clicked.
         if (e.CommandName == "Delete")
         {
               int intContactID;
               // Get selected row's ContactID.
               intContactID = Convert.ToInt16(grdContacts.Items
                  [e.Item.ItemIndex].Cells[3].Text);
               // Delete the contact information.
               lblStatus.Text = DeleteContact(intContactID);
         }
         // Refresh the data set.
         adptContacts.Fill(dsContacts);
         // Refresh the data grid.
         grdContacts.DataBind();
   }

   string DeleteContact(int intContactID)
   {
         // Open the database connection.
         ContactMgmt.Open();
         // Declare a transaction object.
         SqlTransaction transDelete;
         // Create the tranasction.
         transDelete = ContactMgmt.BeginTransaction
           (IsolationLevel.ReadCommitted);
         // Create the command to delete from Contacts table.
         SqlCommand cmdDelete = new SqlCommand("DELETE FROM Contacts" +
           " WHERE ContactID=" + intContactID.ToString(),
           ContactMgmt, transDelete);
         // Execute the commands
         try
         {
               int intRows;
               // Delete row from Contacts table.
               intRows = cmdDelete.ExecuteNonQuery();
               // Delete Calls for this ContactID.
               cmdDelete.CommandText = "DELETE FROM Calls WHERE " +
                 " ContactID=" + intContactID.ToString();
               intRows = intRows + cmdDelete.ExecuteNonQuery();
               // Commit the transaction.
               transDelete.Commit();
               // Return success message.
               return intRows.ToString() + " deleted.";
         }
         catch
         {
               // Restore the database state if there was an error.
               transDelete.Rollback();
               // Return error message.
               return "Contact could not be deleted.";
         }
         finally
         {
               // Close the database.
               ContactMgmt.Close();
         }
  }

The transaction object determines how concurrent changes to a database are handled through the IsolationLevel property. The level of protection varies, as specified by the settings described in Table 5-2.

Table 5-2 Isolation Level Settings
Isolation level | Behavior
ReadUncommitted | Does not lock the records being read. This means that an uncommitted change can be read and then rolled back by another client, resulting in a local copy of a record that is not consistent with what is stored in the database. This is called a dirty read because the data is inconsistent.
Chaos | Behaves the same way as ReadUncommitted, but checks the isolation level of other pending transactions during a write operation so that transactions with more restrictive isolation levels are not overwritten.
ReadCommitted | Locks the records being read and immediately frees the lock as soon as the records are read. This prevents any changes from being read before they are committed, but it does not prevent records from being added, deleted, or changed by other clients during the transaction. This is the default isolation level.
ReapeatableRead | Locks the records being read and keeps the lock until the transaction completes. This ensures that the data being read does not change during the transaction.
Serializable | Locks the entire data set being read and keeps the lock until the transaction completes. This ensures that the data and its order within the database do not change during the transaction.

SQL database connections provide one transaction capability that is unavailable for OLE database connections: the ability to create save points within a transaction. Save points let you restore the database state to a specific position within the current transaction. To set a save point within a SQL transaction, use the Save method:

Visual Basic .NET
transDelete.Save("FirstStep")
Visual C#
transDelete.Save("FirstStep");

To restore a SQL transaction to a save point, specify the name of the save point in the Rollback method:

Visual Basic .NET
transDelete.Rollback("FirstStep")
Visual C#
transDelete.Rollback("FirstStep");
Enterprise Transactions

Because transactions can span multiple Web forms, or even multiple components within a distributed application, ASP.NET provides a way for Web forms to work with the DTC.

To use DTC from a Web form, follow these steps:

  1. Start a new transaction or continue an existing transaction by setting the document element' s transaction attribute. For example, the following @Page directive starts a new transaction:

    <%@ Page Language="vb" AutoEventWireup="false"
Codebehind="PageTransactions.aspx.vb"
Inherits="vbTransactionSnippet.PageTransactions"
Transaction="RequiresNew"%>

  2. Add a reference to the System.EnterpriseServices namespace to your project.

  3. Use the ContextUtil class' s SetAbort and SetComplete methods to change the status of the transaction as required.

  4. Use the Page class' s CommitTransaction and AbortTransaction events to respond to changes in the transaction' s status.

See the Visual Studio .NET online Help for more information about DTC.


Summary

  • ADO.NET includes three key components used for most data access: database connection, data adapter, and data set.

  • To access a database, follow these steps:

    • Create a connection to the database.

    • Create a data set using an adapter object.

    • Use the data set to display data or to change items in the database.

    • Update the database from the data set.

    • Close the database connection.

  • ADO.NET provides typed data sets. Typed data sets use explicit names and data types that help prevent errors during programming.

  • Use data binding to quickly display data sets on Web forms through the DataGrid, DataList, or other list controls.

  • Create command objects to execute SQL commands directly on a database. Command objects may return a single value (ExecuteScalar), modify the database (ExecuteNonQuery), or return data (ExecuteReader).

  • Transactions group database commands so that they succeed or fail in an all-or-nothing fashion. This ensures that changes are not partially made, thus preserving the integrity of the database.

  • To determine which commands should be included in a transaction, use the ACID test. A transaction must be atomic, consistent, isolated, and durable.

  • To use a transaction, follow these steps:

    • Begin the transaction.

    • Perform commands and make changes that are part of the transaction.

    • Check for errors.

    • If errors occurred, undo (roll back) the changes. If no errors occurred, commit the changes. This ends the transaction.

  • Manage data set transactions using the Update method to commit changes and the RejectChanges method to undo (or roll back) changes.

  • Manage database transactions using the transaction object' s Commit and Rollback methods.


Lab: Building the Contact Management Application

In this lab, you will create a multi-page Web application that uses a database to store contact and call information. The application lets you add contacts, view and add calls made by a contact, and add new contact types. This application uses the Contacts database (Contacts.mdb) that is included with this book' s CD-ROM.

The Contact Management Application demonstrates these key concepts:

  • Navigation. Users navigate from a central switchboard page to specific task pages. Once users are done with a task, they navigate back to the switchboard page.

  • Central data connection. The database connection, data adapter, and data set components reside in the Global.asax file and are initialized once per user. These objects are shared between Web forms using session state variables.

  • Data set transactions. All changes to the database are processed through data sets, and all updates are contained within error-handling structures. If an update fails, the user can try again or cancel the operation without damaging the integrity of the database.

  • Bound and unbound controls. Calls are displayed using a DataList control containing data-bound label controls. Contacts are displayed in a drop-down list control using a code rather than data binding.

  • Database commands. The application gets unique key values by executing commands on the database.

Estimated lesson time: 90 minutes
Exercise 1: Start a Project and Create the SwitchBoard Form

In this exercise, you start a new project and create the Web form used to navigate to the other Web forms in the application. This form is very simple and does not include any executable code.

When you are done creating the SwitchBoard form, it will appear as shown in Figure 5-23.

Figure 5-23 The SwitchBoard form
To start the new application and create the SwitchBoard Web form:

  1. Create a new ASP.NET Web application project named ContactManagement.

  2. Change the name of the Webform1.aspx file to SwitchBoard.aspx and display the Web form in HTML mode.

  3. Type the following HTML between the <form> and </form> tags.

    <h2>Contact Management Switchboard</h2>
   <P>What would you like to do? </P>
   <P><a href="Calls.aspx">View/add calls.</a></P>
   <P><a href="AddContact.aspx">Add a contact.</a></P>
   <P><a href="DeleteContact.aspx">Delete a contact.</a></P>
   <P><a href="ContactTypes.aspx">Add a contact type.</a></P>
Exercise 2: Adding Database Components

The Contact Management application performs data access from several different Web forms. The database connection, adapter, and data set components are added to the Global.asax file and saved as session state variables so they can be accessed from the respective Web forms.

All the Web forms, and all users of the Web forms, share a single database connection. This conserves resources and makes it easier to maintain connection and adapter settings while developing the application because the database components are all located in one place: the Global.asax file.

To add the database components for the Contact Management application

  1. Open the Global.asax file and click the Server Explorer link. Visual Studio displays the Server Explorer.

  2. Click the Connect To A Database button on the Server Explorer window. Visual Studio displays the DataLink Properties dialog box.

  3. Select the name of the SQL server where the Contacts sample database is installed, choose the Use Windows NT integrated security option, and then type Contacts in the Select database on the Server text box. Click Test Connection to verify your settings, then click OK when you are done. Visual Studio adds the database connection to the Server Explorer.

  4. In the Server Explorer window, expand the items under the data connection you created by clicking on the plus signs to the left of the Contacts.dbo data connection and then do the same to the Tables sub item. Drag the Calls, ContactTypes, and Contacts tables from the Solution Explorer to the Global.asax page. Visual Studio adds a database connection component and data adapter components for each of the tables.

  5. Each data adapter represents one of the tables you dragged onto the page. Right-click one of the data adapters and select Generate Data Set from the pop-up menu. Visual Studio displays the Generate DataSet dialog box.

  6. Type the name of the data set to create in the New text box. Name the data set after the table it represents (for example, dsCalls for the Calls table), then select the Add This Dataset To The Designer check box and click OK. Visual Studio adds the data set to the Global.asax page.

    7. note | When Visual Studio adds a data set, it appends a number to the name you provide. You need to change the name of the data set in the properties window to remove the appended number.

  7. Repeat steps 5 and 6 for each of the data adapters.

  8. Double-click the Globabl.asax page in the Code window to display the code for the page. Add the following Session_Start event procedure to create the session state variables that you will use from subsequent Web forms to access the database components:

    Visual Basic .NET
    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
    ' Database connection.
    Session("ContactMgmt") = SqlConnection1
    ' Data adapters.
    Session("adptCalls") = SqlDataAdapter1
    Session("adptContactTypes") = SqlDataAdapter2
    Session("adptContacts") = SqlDataAdapter3
    ' Data sets.
    Session("dsCalls") = dsCalls
    Session("dsContactTypes") = dsContactTypes
    Session("dsContacts") = dsContacts
End Sub
    Visual C#
    protected void Session_Start(Object sender, EventArgs e)
{
     // Database connection.
     Session["ContactMgmt"] = SqlConnection1;
     // Data adapters.
     Session["adptCalls"] = SqlDataAdapter1;
     Session["adptContactTypes"] = SqlDataAdapter2;
     Session["adptContacts"] = SqlDataAdapter3;
     // Data sets.
     Session["dsCalls"] = dsCalls;
     Session["dsContactTypes"] = dsContactTypes;
     Session["dsContacts"] = dsContacts;
}
Exercise 3: Create the AddContact Form

The Contact Management application allows users to add information about new contacts through the AddContact Web form. The AddContact Web form is a straightforward data entry form with text boxes for each of the data items in a Contact table record. When complete, the AddContact Web form will appear as shown in Figure 5-24.

Figure 5-24 The AddContact Web form
To create the AddContact data entry Web form

  1. Add a new Web form to the project named AddContact.aspx.

  2. In the Properties window, select the DOCUMENT object and set the pageLayout property to FlowLayout.

  3. Add controls to the Web form with the property settings shown in the following table:

    4. Control | Property | Setting
TextBox | ID | txtFirstName
TextBox | ID | txtLastName
TextBox | ID | txtAddress
 | TextMode | MultiLine
TextBox | ID | txtCity
DropDownList | ID | drpStates
 | DataSource | arrState
TextBox | ID | txtZIP
TextBox | ID | txtHomePhone
TextBox | ID | txtWorkPhone
TextBox | ID | txtNotes
DropDownList | ID | drpContactTypes
Button | ID | butAdd
 | Text | Add
Button | ID | butCancel
 | Text | Cancel
Literal | ID | litStatus
Hyperlink | Text | Return to SwitchBoard
 | NavigateUrl | SwitchBoard.aspx

  4. Type text to identify the fields directly on the Web form. Use carriage returns to start new lines.

  5. Double-click the Web form to display the Code window, and then add the following Imports or using statement to the top of the module:

    Visual Basic .NET
    Imports System.Data.SqlClient
    Visual C#
    using System.Data.SqlClient;

  6. The AddContact Web form uses the database components created in Exercise 2 on the Global.asax file. To use these components in the Web form, declare class-level variables, and then retrieve the references to the components from the state variables where they are stored. The following code demonstrates how to do this:

    Visual Basic .NET
    Dim ContactMgmt As SqlConnection
Dim adptContactTypes As SqlDataAdapter
Dim adptContacts As SqlDataAdapter
Dim dsContacts As New dsContacts()
' These variables are public for data binding.
Public dsContactTypes As New dsContactTypes()
Public arrState As String() = {"AL", "AK", "AR", "AZ", _
  "CA", "CO", "CT", "DE", "FL", "GA", "HI", "ID", "IL", _
  "IN", "IA", "KS", "KY", "LA", "MA", "ME", "MD", "MI", _
  "MN", "MO", "MS", "MT", "NC", "ND", "NE", "NH", "NJ", _
  "NM", "NV", "NY", "OH", "OK", "OR", "PA", "RI", "SC", _
  "SD", "TN", "TX", "UT", "VA", "VT", "WA", "WI", "WY"}

Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Get the connection and adapters from Session state.
    ContactMgmt = Session("ContactMgmt")
    adptContactTypes = Session("adptContactTypes")
    adptContacts = Session("adptContacts")
    ' Fill the ContactType data set.
    adptContactTypes.Fill(dsContactTypes)
    ' Fill the Contacts data set.
    adptContacts.Fill(dsContacts)
    ' Bind to data -- populates the drpContactTypes and drpState lists.
    drpContactTypes.DataBind()
    drpStates.DataBind()
End Sub
    Visual C#
    SqlConnection ContactMgmt;
SqlDataAdapter adptContactTypes;
SqlDataAdapter adptContacts;
dsContacts dsContacts = new dsContacts();
// These variables are public for data binding.
public dsContactTypes dsContactTypes = new dsContactTypes();
public string[] arrState = {"AL", "AK", "AR", "AZ",
  "CA", "CO", "CT", "DE", "FL", "GA", "HI", "ID", "IL",
  "IN", "IA", "KS", "KY", "LA", "MA", "ME", "MD", "MI",
  "MN", "MO", "MS", "MT", "NC", "ND", "NE", "NH", "NJ",
  "NM", "NV", "NY", "OH", "OK", "OR", "PA", "RI", "SC",
  "SD", "TN", "TX", "UT", "VA", "VT", "WA", "WI", "WY"};

private void Page_Load(object sender, System.EventArgs e)
{
     // Get the connection and adapters from Session state.
     ContactMgmt = (SqlConnection)Session["ContactMgmt"];
     adptContactTypes = (SqlDataAdapter)Session["adptContactTypes"];
     adptContacts = (SqlDataAdapter)Session["adptContacts"];
     // Fill the ContactType data set.
     adptContactTypes.Fill(dsContactTypes, "Contact_Types");
     // Fill the Contacts data set.
     adptContacts.Fill(dsContacts);
     // Bind to data -- populates the drpContactTypes and drpState lists.
     drpContactTypes.DataBind();
     drpStates.DataBind();
}

  7. Each contact in the Contacts table is identified by a ContactID. This is the primary key for the table, so it must be unique for each contact. Therefore, before you can add a contact to the Contacts data set, you must obtain a new, unique ContactID from the database. Add the following helper function to the Web form class to get this unique ID before adding the contact:

    Visual Basic .NET
    ' Returns a new, unique ContactID.
Private Function GetNewContactID() As Integer
    Dim cmdNewID As New SqlCommand("SELECT MAX(ContactID)" & _
      " FROM Contacts", ContactMgmt)
    ' Open the database connection.
    ContactMgmt.Open()
    ' Get the highest ID.
    Dim intID As Integer
    ' Make sure records exist.
    If Not (cmdNewID.ExecuteScalar() Is DBNull.Value) Then
    ' Get highest number.
        intID = CInt(cmdNewID.ExecuteScalar())
    Else
        ' Otherwise, set intID to 0.
        intID = 0
    End If
    ' Close the connection
    ContactMgmt.Close()
    ' Return a new ID (one more than highest ID).
    Return intID + 1
End Function
    Visual C#
    // Returns a new, unique ContactID.
int GetNewContactID()
{
     SqlCommand cmdNewID = new SqlCommand("SELECT " +
     "MAX(ContactID) FROM Contacts", ContactMgmt);
     // Open the database connection.
     ContactMgmt.Open();
     // Get the highest ID.
     int intID;
     // Make sure records exist.
     if (cmdNewID.ExecuteScalar() != DBNull.Value)
         // Get highest number.
         intID = (int)cmdNewID.ExecuteScalar();
     else
        // Otherwise, set intID to 0.
        intID = 0;
     // Close the connection
     ContactMgmt.Close();
     // Return a new ID (one more than highest ID).
     return intID + 1;
}

  8. Add the following butAdd_Click event procedure to add the new contact information to the database:

    Visual Basic .NET
    Private Sub butAdd_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butAdd.Click
    ' Create a new row for the data set.
    Dim rowNew As dsContacts.ContactsRow
    rowNew = dsContacts.Contacts.NewContactsRow()
    ' Add data to the row.
    rowNew.ContactID = GetNewContactID()
    rowNew.FirstName = txtFirstName.Text
    rowNew.LastName = txtLastName.Text
    rowNew.Address = txtAddress.Text
    rowNew.City = txtCity.Text
    rowNew.StateOrProvince = drpStates.SelectedItem.Text
    rowNew.PostalCode = txtZip.Text
    rowNew.HomePhone = txtHomePhone.Text
    rowNew.WorkPhone = txtWorkPhone.Text
    rowNew.Notes = txtNotes.Text
    rowNew.ContactTypeID = drpContactTypes.SelectedItem.Value
    ' Add the row to the data set.
    dsContacts.Contacts.AddContactsRow(rowNew)
    Try
        ' Modify the database.
        adptContacts.Update(dsContacts)
        ' Show success.
        litStatus.Text = rowNew.FirstName & " " & rowNew.LastName & _
          " added successfully.<br>"
        ' Redisplay page to clear fields.
        Server.Transfer("AddContact.aspx")
    Catch ex As Exception
        litStatus.Text = "The following error occurred:<br>" & _
          ex.Message & "<br>" & _
          "Correct the error and click Add to add the contact " & _
          "or click Cancel to abort.<br>"
    End Try
End Sub
    Visual C#
    private void butAdd_Click(object sender, System.EventArgs e)
{
     // Create a new row for the data set.
     dsContacts.ContactsRow rowNew;
     rowNew = dsContacts.Contacts.NewContactsRow();
     // Add data to the row.
     rowNew.ContactID = GetNewContactID();
     rowNew.FirstName = txtFirstName.Text;
     rowNew.LastName = txtLastName.Text;
     rowNew.Address = txtAddress.Text;
     rowNew.City = txtCity.Text;
     rowNew.StateOrProvince = drpStates.SelectedItem.Text;
     rowNew.PostalCode = txtZip.Text;
     rowNew.HomePhone = txtHomePhone.Text;
     rowNew.WorkPhone = txtWorkPhone.Text;
     rowNew.Notes = txtNotes.Text;
     rowNew.ContactTypeID = Convert.ToInt16
       (drpContactTypes.SelectedItem.Value);
     // Add the row to the data set.
     dsContacts.Contacts.AddContactsRow(rowNew);
     try
     {
        // Modify the database.
        adptContacts.Update(dsContacts);
        // Show success.
        litStatus.Text = rowNew.FirstName + " " + rowNew.LastName +
          " added successfully.<br>";
        // Redisplay page to clear fields.
        Server.Transfer("AddContact.aspx");
     }
     catch (Exception ex)
     {
        litStatus.Text = "The following error occurred:<br>" +
        ex.Message + "<br>" +
        "Correct the error and click Add to add the contact " +
              "or click Cancel to abort.<br>";
     }
}

  9. Add the butCancel_Click event procedure to allow the user to cancel the operation and clear the text boxes on the form:

    Visual Basic .NET
    Private Sub butCancel_ServerClick(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butCancel.ServerClick
    ' Redisplay the page to clear text boxes.
     Server.Transfer("AddContact.aspx")
End Sub
    Visual C#
    private void butCancel_ServerClick(object sender, System.EventArgs e)
{
     // Redisplay the page to clear text boxes.
     Server.Transfer("AddContact.aspx");
}
Exercise 4: Create the Calls Form

The Calls Web form lets users enter and view telephone calls from a contact. It uses a DataList control to view the telephone calls for a contact selected in a DropDownList control, and contains the controls that add a call in a Panel control so they can be hidden and displayed when the user clicks Add.

When completed, the Calls Web form appears as shown in Figure 5-25.

Figure 5-25 The Calls form
To create the Calls Web form

  1. Add a new Web form to the project named Calls.aspx.

  2. Add controls to the Web form with the property settings shown in the following table. Place the controls shown after the Panel control inside the panel so that you can easily control their display at run time.

    3. Control | Property | Setting
DropDownList | ID | drpContacts
Button | ID | butView
 | Text | View Call
Button | ID | butAddCall
 | Text | Add Call
Panel | ID | pnlAdd
TextBox | ID | txtDate
TextBox | ID | txtTime
TextBox | ID | txtSubject
TextBox | ID | txtNotes
 | TextMode | Multiline
Button | ID | butOKAdd
Button | ID | butCancelAdd
Literal | ID | litError

  3. Open the Data tab in the Toolbox and drag a DataSet component onto the Web form. Visual Studio displays the Add Dataset dialog box with the Typed Dataset option selected.

  4. In the Name dropdown box, select the typed data set named ContactManagement.dsCalls and click OK. Visual Studio adds the data set to the Web form.

  5. From the Web Forms tab in the Toolbox, add a DataList control to the Web form below the panel. Set its properties and edit its templates as shown in the following table:

    6. Control | Property | Setting
DataList | ID | dlstCalls
 | DataSource | dsCalls
DataList Header Template |  |
Regular text | none | Calls
HTML Rule | none | <HR>
DataList Footer Template |  |
HTML Rule | none | <HR>
DataList Item Template |  |
Label | ID | lblDate
 | (DataBindings) | Using the DataBinding dialog box, choose Container.DataItem.CallDate from the Simple Binding tree. Choose format {0:d}.
Label | ID | lblTime
 | (DataBindings) | Using the DataBinding dialog box, choose Container.DataItem.CallTime from the Simple Binding tree. Choose format {0:t}.
Regular text | none | Subject:
Label | ID | lblSubject
 | (DataBindings) | Using the DataBinding dialog box, choose Container.DataItem.Subject from the Simple Binding tree.
Regular text | none | Notes:
Label | ID | lblNotes
 | (DataBindings) | Using the DataBinding dialog box, choose Container.DataItem.Notes from the Simple Binding tree.
Separator Template |  |
HTML Rule | none | <HR>

  6. Copy the Hyperlink control from the bottom of the AddContact form to the bottom of the Calls form. To copy the control, select the control and press Ctrl+C. To paste the copy on to the Calls form, click the form in Design mode and press Ctrl+V.

  7. Double-click the Calls Web form to display the Code window, and add the following Imports or using statements to the top of the module:

    Visual Basic .NET
    Imports System.Data.SqlClient
    Visual C#
    using System.Data.SqlClient;

  8. The Calls Web form uses the data components from the Global.asax file, so you need to retrieve references to those components from the state variables created in Exercise 2. The following declarations and Page_Load event procedure retrieve references to the data components and make them available to the form code:

    Visual Basic .NET
    Dim ContactMgmt As SqlConnection
Dim adptContacts As SqlDataAdapter
Dim adptCalls As SqlDataAdapter
Dim dsContacts As New dsContacts()

Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Get session state variables.
    ContactMgmt = Session("ContactMgmt")
    adptContacts = Session("adptContacts")
    adptCalls = Session("adptCalls")
    dsCalls = Session("dsCalls")
    dsContacts = Session("dsContacts")
    ' Run the first time page is displayed.
    If Not IsPostBack Then
        ' Fill the Contacts data set.
        adptContacts.Fill(dsContacts)
        ' For each row in the table...
        Dim rowNext As dsContacts.ContactsRow
        For Each rowNext In dsContacts.Contacts
            ' Create a new list item.
            Dim lstNew As New ListItem()
            lstNew.Text = rowNext.FirstName & " " & rowNext.LastName
            lstNew.Value = rowNext.ContactID
            ' Add the list item to the drop-down list.
            drpContacts.Items.Add(lstNew)
        Next
        ' Select the first item in the list.
        drpContacts.SelectedIndex = 0
    End If
End Sub
    Visual C#
    SqlConnection ContactMgmt;
SqlDataAdapter adptContacts;
SqlDataAdapter adptCalls;
dsContacts dsContacts = new dsContacts();

private void Page_Load(object sender, System.EventArgs e)
{
     // Get session state variables.
     ContactMgmt = (SqlConnection)Session["ContactMgmt"];
     adptContacts = (SqlDataAdapter)Session["adptContacts"];
     adptCalls = (SqlDataAdapter)Session["adptCalls"];
     dsCalls = (dsCalls)Session["dsCalls"];
     dsContacts = (dsContacts)Session["dsContacts"];
     // Run the first time page is displayed.
     if (!IsPostBack)
     {
        // Fill the Contacts data set.
        adptContacts.Fill(dsContacts);
        // For each row in the table...
        foreach (dsContacts.ContactsRow rowNext in dsContacts.Contacts)
        {
              // Create a new list item.
              ListItem lstNew = new ListItem();
              lstNew.Text = rowNext.FirstName + " " + rowNext.LastName;
              lstNew.Value = rowNext.ContactID.ToString();
              // Add the list item to the drop-down list.
              drpContacts.Items.Add(lstNew);
        }
        // Select the first item in the list.
        drpContacts.SelectedIndex = 0;
     }
}

  9. The Calls Web form modifies the Calls data set at run time. You need to add the following code to the Page_PreRender event procedure to store the data set back to the state variable after the page is finished processing. (The PreRender event is the last event processed before the page is displayed.)

    Visual Basic .NET
    Private Sub Page_PreRender(ByVal sender As Object, _
  ByVal e As System.EventArgs) Handles MyBase.PreRender
    ' Save the data set to the session state.
    Session("dsCalls") = dsCalls
End Sub
    Visual C#
    private void Page_PreRender(object sender, System.EventArgs e)
{
        // Save the data set to the session state.
        Session["dsCalls"] = dsCalls;
}

  10. Add the following code for the View and Add button Click event procedures:

    Visual Basic .NET
    Private Sub butView_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butView.Click
    ' Set the Select command for the data set.
    adptCalls.SelectCommand.CommandText = "SELECT * FROM Calls " & _
      "WHERE ContactID=" & drpContacts.SelectedItem.Value
    ' Clear the data set.
     dsCalls.Clear
    ' Fill the calls data set.
    adptCalls.Fill(dsCalls)
    ' Bind to the DataList control
    dlstCalls.DataBind()
End Sub

Private Sub butAddCall_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butAddCall.Click
    ' Set the initial date and time values.
    txtDate.Text = Now.ToShortDateString()
    txtTime.Text = Now.ToShortTimeString()
    ' Make add call panel visible
    pnlAdd.Visible = True
    ' Disable buttons/DropDown list.
    butView.Enabled = False
    butAddCall.Enabled = False
    drpContacts.Enabled = False
End Sub
    Visual C#
    private void butView_Click(object sender, System.EventArgs e)
{
     // Set the Select command for the data set.
     adptCalls.SelectCommand.CommandText = "SELECT * FROM Calls " +
       "WHERE ContactID=" + drpContacts.SelectedItem.Value;
     // Clear the data set.
     dsCalls.Clear();
     // Fill the calls data set.
     adptCalls.Fill(dsCalls);
     // Bind to the DataList control
     dlstCalls.DataBind();
}

private void butAddCall_Click(object sender, System.EventArgs e)
{
     // Set the initial date and time values.
     txtDate.Text = System.DateTime.Now.ToShortDateString();
     txtTime.Text = System.DateTime.Now.ToShortTimeString();
     // Make add call panel visible
     pnlAdd.Visible = true;
     // Disable buttons/DropDown list.
     butView.Enabled = false;
     butAddCall.Enabled = false;
     drpContacts.Enabled = false;
}

  11. Add the following helper function to get a new CallID value. Because CallID is the primary key in the Calls table, you need a value that is unique within the database, as shown here:

    Visual Basic .NET
    ' Returns a new, unique CallID.
Private Function GetCallID() As Integer
    Dim cmdNewID As New SqlCommand("SELECT MAX(CallID)" & _
      " FROM Calls", ContactMgmt)
    ' Open the database connection.
    ContactMgmt.Open()
    ' Get the highest ID.
    Dim intID As Integer
    ' Make sure records exist.
    If Not (cmdNewID.ExecuteScalar() Is DBNull.Value) Then
        ' Get highest number.
        intID = CInt(cmdNewID.ExecuteScalar())
    Else
        ' Otherwise, set intID to 0.
        intID = 0
    End If
    ' Close the connection
    ContactMgmt.Close()
    ' Return a new ID (one more than highest ID).
    Return intID + 1
End Sub
    Visual C#
    int GetCallID()
{
     SqlCommand cmdNewID = new SqlCommand("SELECT MAX(CallID)" +
       " FROM Calls", ContactMgmt);
     // Open the database connection.
     ContactMgmt.Open();
     // Get the highest ID.
     int intID;
     // Make sure records exist.
     if (cmdNewID.ExecuteScalar() != DBNull.Value)
        // Get highest number.
        intID = (int)cmdNewID.ExecuteScalar();
     else
        // Otherwise, set intID to 0.
        intID = 0;
     // Close the connection
     ContactMgmt.Close();
     // Return a new ID (one more than highest ID).
     return intID + 1;
}

  12. Add the following code to the OK and Cancel button event procedures. These event procedures are very similar to the ones used to add or cancel the adding of records in the Contacts table in the previous exercise.

    Visual Basic .NET
    Private Sub butOKAdd_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butOKAdd.Click
    Dim rowNew As dsCalls.CallsRow
    rowNew = dsCalls.Calls.NewCallsRow
    rowNew.ContactID = drpContacts.SelectedItem.Value
    rowNew.CallID = GetCallID()
    rowNew.CallDate = txtDate.Text
    rowNew.CallTime = txtDate.Text & " " & txtTime.Text
    rowNew.Subject = txtSubject.Text
    rowNew.Notes = txtNotes.Text
    ' Add row to calls data set.
    dsCalls.Calls.AddCallsRow(rowNew)
    Try
        adptCalls.Update(dsCalls)
        ' Hide the add call panel.
        pnlAdd.Visible = False
        ' Clear the fields.
        txtSubject.Text = ""
        txtNotes.Text = ""
        ' Enable the other controls.
        butView.Enabled = True
        butAddCall.Enabled = True
        drpContacts.Enabled = True
    Catch ex As Exception
        ' Display error.
        litError.Text = "The following error occurred while adding " & _
            "the call:<br>" & _
            ex.Message & "<br>" & _
            "Correct the error and try again, or click Cancel to " & _
            "abort.<br>"
    End Try
End Sub

Private Sub butCancelAdd_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butCancelAdd.Click
    ' Hide the add call panel.
    pnlAdd.Visible = False
    ' Clear the fields.
    txtSubject.Text = ""
    txtNotes.Text = ""
    ' Enable the other controls.
    butAddCall.Enabled = True
    drpContacts.Enabled = True
End Sub
    Visual C#
    private void butOKAdd_Click(object sender, System.EventArgs e)
{
     dsCalls.CallsRow rowNew;
     rowNew = dsCalls.Calls.NewCallsRow();
     rowNew.ContactID = Convert.ToInt16(drpContacts.SelectedItem.Value);
     rowNew.CallID = GetCallID();
     rowNew.CallDate = Convert.ToDateTime(txtDate.Text);
     rowNew.CallTime = Convert.ToDateTime(txtDate.Text + " "
        + txtTime.Text);
     rowNew.Subject = txtSubject.Text;
     rowNew.Notes = txtNotes.Text;
     // Add row to calls data set.
     dsCalls.Calls.AddCallsRow(rowNew);
     try
     {
        adptCalls.Update(dsCalls);
        // Hide the add call panel.
        pnlAdd.Visible = false;
    // Clear the fields.
    txtSubject.Text = "";
    txtNotes.Text = "";
        // Enable the other controls.
        butView.Enabled = true;
        butAddCall.Enabled = true;
        drpContacts.Enabled = true;
     }
     catch (Exception ex)
     {
        // Display error.
        litError.Text = "The following error occurred while adding " +
        "the call:<br>" +
        ex.Message + "<br>" +
        "Correct the error and try again, or click Cancel to " +
        "abort.<br>";
     }
}

private void butCancelAdd_Click(object sender, System.EventArgs e)
{
     // Hide the add call panel.
     pnlAdd.Visible = false;
    // Clear the fields.
    txtSubject.Text = "";
    txtNotes.Text = "";
     // Enable the other controls.
     butAddCall.Enabled = true;
     drpContacts.Enabled = true;
}
Exercise 5: Create the DeleteContact and ContactTypes Forms

So far this lab has shown you how to create Web forms in a step-by-step fashion. Now it' s time to strike out on your own! Create the DeleteContact and ContactTypes Web forms by yourself. These Web forms should perform the following tasks:

  • Use the data components from the Global.asax file.

  • Perform operations on the database. The DeleteContact form should allow users to delete contacts from the database. The ContactTypes form should allow users to view and add new contact types.

  • Use data set transactions, as demonstrated by the AddContact and Calls forms.

  • Provide navigation back to the SwitchBoard form.

When you are done, compare your results to the Contact Management application included on the companion CD-ROM. Good luck!


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. What steps would you follow and what objects would you use to quickly find the number of records in a database table?

  2. How do typed data sets differ from untyped data sets, and what are the advantages of typed data sets?

  3. How do you reuse a database connection component throughout a Web application?

  4. Explain the difference between handling transactions at the data set level and at the database level.


Catching and Correcting Errors

Chapter 6
Catching and Correcting Errors
Lesson 1: Using Exception Handling
Lesson 2: Using Error Pages
Lesson 3: Logging Exceptions
About This Chapter

In this chapter, you will learn how to deal with problems that can occur in your application as a result of external circumstances. This chapter will help you anticipate, identify, and handle these types of problems effectively within your application.

Lessons 1 and 2 cover the two major techniques for dealing with these types of programming problems within ASP.NET Web applications. Lesson 3 explains how to monitor an application during testing or after it is deployed to ensure problems are being handled effectively.

Before You Begin

To complete the lessons in this chapter, you must

  • Be familiar with the basics of the Microsoft Visual Basic or Microsoft Visual C# programming language. This should include familiarity with the concepts of variables, procedures, decision structures, and scope. See the help topics “Language Changes in Visual Basic” or “C# Language Tour” included with Microsoft Visual Studio .NET for introductions to these languages.

  • Understand how to create a basic Web application using the Web forms, server controls, and event procedures that were introduced in Chapter 2, “Creating Web Forms Applications.”

  • Have completed the Contact Management application discussed in Chapter 5, “Storing and Retrieving Data with ADO.NET.” The lab at the end of this chapter extends the exception handling in that sample.


Lesson 1: Using Exception Handling

Exceptions are unusual occurrences that happen within the logic of an application. The terms exception and error are often used interchangeably in the Visual Studio .NET documentation because you use the same programming techniques to handle either situation.

In this lesson, you will learn the different programming techniques that you can use to handle exceptions in a Web application. This lesson discusses only the modern techniques provided by Visual Studio .NET and ASP.NET. It does not cover earlier exception-handling syntax, such as On Error, which is still supported by Visual Basic .NET.

After this lesson, you will be able to

  • Explain the two main exception-handling techniques used in Web applications

  • Create an exception-handling block to catch and handle exceptions within a procedure

  • Cause exceptions to occur in response to unexpected occurrences

  • Define new exceptions that your application can use to describe exception conditions

  • Handle exceptions from within error events

Estimated lesson time: 30 minutes
Exception-Handling Techniques

When an unanticipated problem occurs in a Web application, it is immediately apparent to the user. Figure 6-1 shows what the user sees if a Web application tries to open a file that doesn' t exist on the server.

Figure 6-1 An unhandled exception

When this happens, the user is stuck—he or she can only click Back on the browser and try again. As a programmer, you are responsible for anticipating and dealing with these situations in your code before they occur. The process of dealing with these types of situations is called exception handling. Errors that are not dealt with in code are called unhandled exceptions, and they appear to the user as shown in Figure 6-1.

There are two different approaches to handling exceptions in a Web application:

  • Use exception-handling blocks to deal with exceptions within the scope of a procedure. This technique is called structured exception handling (SEH) in the Visual Studio .NET documentation.

  • Use error events to deal with exceptions within the scope of an object.

You can combine these two approaches to effectively handle all possible exceptions within your application, as explained in the following sections.

Using Exception-Handling Blocks

Use exception-handling blocks to enclose statements that might fail because of external conditions. A common example of this is file access. When you try to save a file to a server, a variety of problems can occur: the disk might be full, the filename might already be in use, or the path to save the file to might not exist.

To deal with these potential problems, enclose the file access statements in an exception-handling block. Table 6-1 describes the keywords used to create an exception-handling block.

Table 6-1 Exception-Handling Keywords
Visual Basic .NET keyword | Visual C# keyword | Use to
Try | try | Begin an error-handling structure. If a statement that follows this keyword causes an exception, control flow passes immediately to the next Catch/catch statement.
Catch | catch | Retrieve any exceptions that occurred and handle them. When control flow passes to a Catch/catch block, the statements contained in the block are processed to correct the error or otherwise handle the exception.
Finally | finally | Process any statements that should run as part of the exception-handling block, whether or not an exception has occurred.
End Try | N/A | End an exception-handling block.
Throw | throw | Cause an exception to occur. Use this keyword within your exception-handling block to immediately pass control flow to the Catch/catch statement.

The easiest way to understand how to use these keywords together is with an example. Figure 6-2 shows a Web form that allows the user to select a file from his or her machine to upload to the server. When the file is uploaded, it is added to the list box.

Figure 6-2 File upload sample

When the user enters a filename on the Web form and clicks Upload, a number of exception conditions can occur:

  • The file might not exist on the client' s computer.

  • The Internet connection might fail during the file upload.

  • The file might be too large to transfer over the Internet.

  • The server might not have enough available disk space for the file.

To handle these and other unforeseen conditions, enclose the statements that might cause these conditions in an exception-handling block, as shown by the following code:

Visual Basic .NET
Private Sub butUpload_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butUpload.Click
    Dim strFilename As String = fileUpload.PostedFile.Filename
    ' Break the file specification into parts.
    strFilename = System.IO.Path.GetFilename(strFilename)
    Try
        ' (1) Check if file is zero length (file does not exist).
        If filUpload.PostedFile.ContentLength = 0 Then
            Throw New System.IO.FileNotFoundException()
        End If
        ' (2) Save uploaded file to server using the file base name.
        filUpload.PostedFile.SaveAs _
          (Request.MapPath(Request.ApplicationPath) & "\" & _
          strFilename)
        ' Add file to list of server files.
        lstFiles.Items.Add(strFilename)
        ' Select the first item in the list box.
        lstFiles.SelectedIndex = 0
     ' (3) Handle possible exceptions.
    Catch ex As Exception
        litError.Text = "<p>The following error occurred:</p>" & _
          ex.Message & "<br>"
     ' (4) Perform any cleanup.
    Finally
        ' Reset File Field.
        filUpload.Value = ""
    End Try
End Sub
Visual C#
private void butUpload_Click(object sender, System.EventArgs e)
{
    string strFilename = filUpload.PostedFile.FileName;
    // Break the file specification into parts.
    strFilename = System.IO.Path.GetFileName(strFilename);
    try
    {
    // (1) Check if file is zero length (file does not exist).
    if (filUpload.PostedFile.ContentLength == 0)
        throw new System.IO.FileNotFoundException();
    // (2) Save uploaded file to server using the file base name.
    filUpload.PostedFile.SaveAs
        (Request.MapPath(Request.ApplicationPath) + "\\" +
        strFilename);
    // Add file to list of server files.
    lstFiles.Items.Add(strFilename);
    // Select the first item in the list box.
    lstFiles.SelectedIndex = 0;
    }
    // (3) Handle possible exceptions.
    catch (Exception ex)
    {
    litError.Text = "<p>The following error occurred:</p>" +
        ex.Message + "<br>";
    }
    // (4) Perform any cleanup.
    finally
    {

    // Reset File Field.
    filUpload.Value = "";
    }
}

As the preceding code executes, it follows these steps:

  1. Checks whether the file exists on the client machine. If the entered file has zero length, the file does not exist, so the code causes the System.IO.FileNotFoundException, and control flow passes immediately to step 3.

  2. Saves the file to the server. If saving succeeds, control flow continues by adding the filename to the ListBox. If saving fails, control flow passes to step 3, skipping the ListBox.

  3. Handles exceptions. In this case, exceptions are handled simply by displaying them in a Literal control on the Web form. If no exceptions occurred, control flow skips step 3 and goes directly to step 4.

  4. Performs final cleanup. These statements are the last step in an exception-handling block.

The Catch/catch keyword can intercept general exceptions, as shown in the preceding example, or it can intercept and respond differently to specific types of exceptions, as shown in the following code fragment:

Visual Basic .NET
Try
    ' Some statements...
Catch ex As System.IO.FileNotFoundException
    litError.Text = "<p>The file does not exist.</p>"
Catch ex As System.IO.IOException
    litError.Text = "<p>The file could not be saved " & _
  "to the server.</p>"
Catch ex As System.Net.WebException
    litError.Text = "<p>An Internet error occurred while " & _
  "uploading the file.</p>"
Catch ex As Exception
    litError.Text = "<p>The following error occurred:</p>" & _
  ex.Message & "<br>"
End Try
Visual C#
 try
 {
     // Some statements...
 }
     catch (System.IO.FileNotFoundException ex)
 {
     litError.Text = "<p>The file does not exist.</p>";
 }
 catch (System.IO.IOException ex)
 {
     litError.Text = "<p>The file could not be saved " +
       "to the server.</p>";
 }
 catch (System.Net.WebException ex)
 {
     litError.Text = "<p>An Internet error occurred while" +
         "uploading the file.</p>";
 }
 catch (Exception ex)
 {
     litError.Text = "<p>The following error occurred:</p>" +
         ex.Message + "<br>";
 }

The preceding code executes three different statements, depending on whether the exception is a FileNotFoundException, a WebException, or some other exception type.

Because Catch statements are processed in sequence, you should put your Catch statements in order, from the most specific exception to most general exception. For example, the following code fragment will never execute the statements for System.IO.IOException, System.IO.FileNotFound, or System.Net.WebException because those errors are caught first by the general Exception type:

Visual Basic .NET
Catch ex As Exception
    litError.Text = "<p>The following error occurred:</p>" & _
      ex.Message & "<br>"
Catch ex As System.IO.IOExcecption
    litError.Text = "<p>The file could not be saved " & _
      "to the server.</p>"
Catch ex As System.IO.FileNotFoundException
    litError.Text = "<p>The file does not exist.</p>"
Catch ex As System.Net.WebException
    litError.Text = "<p>An Internet error occurred while " & _
      "uploading the file.</p>"
Visual C#
 catch (Exception ex)
 {
     litError.Text = "<p>The following error occurred:</p>" +
       ex.Message + "<br>";
 }
 catch (System.IO.IOException ex)
 {
     litError.Text = "<p>The file could not be saved " +
       "to the server.</p>";
 }
 catch (System.IO.FileNotFoundException ex)
 {
     litError.Text = "<p>The file does not exist.</p>";
 }
 catch (System.Net.WebException ex)
 {
     litError.Text = "<p>An Internet error occurred while" +
         "uploading the file.</p>";
 }

The Microsoft .NET Framework includes specific exception types with the individual Framework classes. This means there is no single, comprehensive list of exception types in the .NET documentation. Instead, you must use the Visual Studio .NET Exceptions dialog box to view specific exception types, as described in the following procedure.

To view the exception types that the .NET Framework provides, follow these steps:

  1. From the Debug menu, choose Exceptions. Visual Studio .NET displays the Exceptions dialog box, as shown in Figure 6-3.

    Figure 6-3 The Exceptions dialog box

  2. Click the plus signs to the left of items in the Exceptions list to see the exceptions each provides.

Causing Exceptions

As you saw in the preceding section, you use the Throw/throw keyword to cause specific exceptions to occur. This technique is useful to illustrate how exception handling works, but it begs the question, “Why would you want to cause an exception in a real-world application?”

Exception handling is a way to implement a certain kind of logic in your application. You are simply specifying a set of occurrences that are not normally part of the application' s course of events, and then saying, “These are the exceptions.” That lets you set aside the complexity of dealing with those exceptions, thereby simplifying the main logic of your application.

For example, in the File Upload sample that follows, it is reasonable to cause the FileNotFoundException if the uploaded file has a length of 0. Otherwise, it is very difficult to determine whether a file exists on a client' s machine before uploading. The FileNotFoundException is a standard exception in the System.IO namespace, and it is descriptive of what happened.

Visual Basic .NET
Private Sub butUpload_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butUpload.Click
    Dim strFilename As String = filUpload.PostedFile.Filename
    ' Break the file specification into parts.
    strFilename = System.IO.Path.GetFilename(strFilename)
    Try
        ' Check if file is zero-length (file does not exist).
        If filUpload.PostedFile.ContentLength = 0 Then
            Throw New System.IO.FileNotFoundException()
        End If
        ' Save uploaded file to server using the file base name.
        filUpload.PostedFile.SaveAs _
          (Request.MapPath(Request.ApplicationPath) & "\" & _
          strFilename)
        ' Add file to list of server files.
        lstFiles.Items.Add(strFilename)
        ' Select the first item in the list box.
        lstFiles.SelectedIndex = 0
        ' Handle possible exceptions.
   Catch ex As System.IO.FileNotFoundException
      litError.Text = "<p>The file does not exist.</p>"
   Catch ex As System.IO.IOExcecption
      litError.Text = "<p>The file could not be saved " & _
     "to the server.</p>"
   Catch ex As System.Net.WebException
      litError.Text = "<p>An Internet error occurred while " & _
     "uploading the file.</p>"
   Catch ex As Exception
      litError.Text = "<p>The following error occurred:</p>" & _
     ex.Message & "<br>"
     ' Perform any clean up.
    Finally
        ' Reset File Field.
        filUpload.Value = ""
    End Try
End Sub
Visual C#
private void butUpload_Click(object sender, System.EventArgs e)
{
    string strFilename = filUpload.PostedFile.FileName;
    // Break the file specification into parts.
    strFilename = System.IO.Path.GetFileName(strFilename);
    try
    {
        // (1) Check if file is zero-length (file does not exist).
        if (filUpload.PostedFile.ContentLength == 0)
            throw new System.IO.FileNotFoundException();
        // (2) Save uploaded file to server using the file base name.
        filUpload.PostedFile.SaveAs
          (Request.MapPath(Request.ApplicationPath) + "\\" +
          strFilename);
        // Add file to list of server files.
        lstFiles.Items.Add(strFilename);
        // Select the first item in the list box.
        lstFiles.SelectedIndex = 0;
    }
    // (3) Handle possible exceptions.
    catch (System.IO.FileNotFoundException ex)
    {
        litError.Text = "<p>The file does not exist.</p>";
    }
    catch (System.IO.IOException ex)
    {
        litError.Text = "<p>The file could not be saved " +
          "to the server.</p>";
    }
    catch (System.Net.WebException ex)
    {
        litError.Text = "<p>An Internet error occurred while" +
          "uploading the file.</p>";
    }
    catch (Exception ex)
    {
        litError.Text = "<p>The following error occurred:</p>" +
          ex.Message + "<br>";
    }
    // (4) Perform any clean up.
    finally
    {
        // Reset File Field.
        filUpload.Value = "";
    }
}
Defining New Exceptions

In some cases, you will want to cause an exception in your application that doesn' t have an effective, descriptive equivalent in the .NET Framework. In those cases, you should create a new exception of the type ApplicationException. For example, the following code fragment causes an exception indicating that the user is already logged on:

Visual Basic .NET
Throw New ApplicationException("User is already logged on.")
Visual C#
throw new ApplicationException("User is already logged on.");

To handle this exception, use the following Catch/catch statement:

Visual Basic .NET
Catch ex As ApplicationException
Visual C#
catch (ApplicationException ex)

The ApplicationException class provides the same features as the standard Exception class. It simply provides a way to differentiate between those exceptions defined in the .NET Framework and those defined in your application.

If you are creating a large application or if you are creating components that are used by other applications, you might want to define your own exception classes based on the ApplicationException class. For example, the following code defines a class for the UserLoggedOnException:

Visual Basic .NET
Public Class UserLoggedOnException
   Inherits ApplicationException

   ' Exception constructor.
   Sub New(Optional ByVal Message As String = "The user is already " & _
     "logged on to the server.", _
     Optional ByVal Inner As Exception = Nothing)
         MyBase.New(Message, Inner)
   End Sub

End Class
Visual C#
public class UserLoggedOnException : System.ApplicationException
{
    // Exception constructor (overloaded).
    public UserLoggedOnException() :
  this("The user is already logged on to the server", null)
    {
    }

    public UserLoggedOnException(string message) : this(message, null)
    {
    }

    public UserLoggedOnException(string message, Exception inner) :
  base(message, inner)
    {
    }

}

The preceding UserLoggedOnException class inherits its properties and methods from the ApplicationException base class. The new exception class provides only its own constructor to set the default message to display. This is a standard practice.

Using Error Events

Another way to handle exceptions is through the Web objects' built-in error events. When an unhandled exception occurs in a Web application, ASP.NET fires the error events described in Table 6-2.

Table 6-2 Exception-Handling Events
Event procedure | Occurs when
Page_Error | An unhandled exception occurs on the page. This event procedure resides in the Web form.
Global_Error | An unhandled exception occurs in the application. This event procedure resides in the Global.asax file.
Application_Error | An unhandled exception occurs in the application. This event procedure resides in the Global.asax file.

Error events let you handle exceptions for an entire object in a single, centralized location—the error event procedure. This is different from using exception-handling blocks, in which exceptions are handled within the procedure where they occurred. You can use error events in the following ways:

  • As a substitute for exception-handling blocks. Because error events occur outside the scope of the procedure where the error occurred, you have less information about the steps leading up to the exception and therefore less ability to correct the exception condition for the user. However, this is fine for tasks where you might not be able to correct the exception in code.

  • As an adjunct to exception-handling blocks. Error events can provide a centralized “backstop” against exceptions that were not foreseen or handled elsewhere. Using the two exception-handling techniques together lets you catch all exceptions before the user sees them, display a reasonable message, and even record the exception to a log as part of an ongoing effort to improve your application.

When handling exceptions in error events, use the Server object to get information about the exception that occurred. The Server object provides the methods described in Table 6-3 for working with exceptions.

Table 6-3 The Server Object' s Exception-Handling Events
Server method | Use to
GetLastError() | Get the last exception that occurred on the server.
ClearError() | Clear the last exception that occurred on the server. Invoking ClearError handles the exception so it does not trigger subsequent error events or appear to the user in the browser.

To handle an exception in an error event, follow these steps:

  1. In the Page_Error, Global_Error, or Application_Error event procedure, get the exception that occurred using the GetLastError method.

  2. Do something with the exception, such as display a message to the user, take steps to correct the problem, or write to an error log.

  3. Clear the exception using the ClearError method.

  4. Redisplay the page. Web form processing stops immediately when an exception occurs, so server controls and other items on the page might not be displayed after the exception is cleared.

The following code demonstrates these steps:

Visual Basic .NET
Private Sub Page_Error(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles MyBase.Error
     ' Get error.
     Dim ex As Exception = Server.GetLastError()
     ' Store the error message.
     Session("Error") = ex.Message()
     ' Clear error.
     Server.ClearError()
     ' Redirect back to this page.
     Response.Redirect("ErrorEvents.aspx")
End Sub
Visual C#
private void Page_Error(object sender, System.EventArgs e)
{
    // Get the error.
    Exception ex = Server.GetLastError();
    // Store the message.
    Session["Error"] = ex.Message;
    // Clear the error.
    Server.ClearError();
    // Redisplay this page.
    Response.Redirect("ErrorEvent.aspx");
}

The preceding code stores the exception message as a Session state variable before clearing the exception so that the message can be displayed when the page is reloaded by the Redirect method. The following code displays the saved exception message when the page is redisplayed:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load
     ' Display error. if any.
     If Session("Error") <> Nothing Then
         litError.Text = "<p>The following error occurred:</p>" & _
           Session("Error") & "<br>"
        ' Clear the Session state variable.
         Session("Error") = Nothing
     End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    // Display error. if any.
    if(Session["Error"] != null)
    {
        litError.Text = "<p>The following error occurred:</p>" +
          Session["Error"].ToString();
        // Clear the Session state variable.
        Session["Error"] = null;
    }
}


Lesson 2: Using Error Pages

Lesson 1 provided techniques for handling exceptions that occur on the server running your Web application. However, because Web applications run over the Internet, there is a whole class of exceptions that cannot be detected from within code. To intercept these errors and provide the best possible response, you need to use error pages.

In this lesson, you will learn how to use error pages in ASP.NET to intercept and respond to HTTP errors at the application and page levels.

After this lesson, you will be able to

  • Identify types of exceptions that cannot be detected from within application code

  • Respond to HTTP exceptions at the application and page levels

  • Specify error pages for specific HTTP errors using the Web.config file

Estimated lesson time: 15 minutes
Handling Internet-Related Exceptions

When a user runs a Web application from his or her browser, a variety of exceptions can occur outside the scope of the actual application. For example, server errors can occur when a server times out waiting for a response from a user, or the page requested by a user isn' t found on the server.

When these types of events happen, ASP.NET displays an error page like the one shown in Figure 6-4.

Figure 6-4 Page Not Found error page

Error pages are .htm or .aspx pages on the server that the user is redirected to if an unhandled exception occurs. ASP.NET lets you define error pages at two levels:

  • Specify application-wide error page settings in the customErrors section of the Web.config file. These settings determine the page to display for specific HTTP errors.

  • Specify an error page for a specific Web form in the ErrorPage attribute of the Web form' s Page directive. This setting determines the error page to display if the Web form encounters an unhandled exception.

The following sections describe these levels in more detail.

Using Application-Wide Error Pages

Microsoft Internet Information Service (IIS) defines the error pages that are displayed by default for specific HTTP responses. To see or change the error page settings for an application in IIS, follow these steps:

  1. Right-click the Web application folder and select Properties from the pop-up menu. IIS displays the application' s Properties dialog box.

  2. Click the Custom Errors tab on the Properties dialog box. IIS displays the error page settings for specific HTTP response codes, as shown in Figure 6-5.

    Figure 6-5 IIS Custom Errors settings

  3. Select the response code you want to change the error page for in the Error Messages For HTTP Errors list, and then click Edit Properties to change the setting. IIS displays the Error Mapping Properties dialog box, as shown in Figure 6-6.

    Figure 6-6 IIS Error Mapping Properties dialog box

  4. Click Browse to replace the default error page with a page from your own application folder. Click OK when done.

Using IIS to change application-wide error pages makes the changes on the server where the application is deployed. If you redeploy your application, you will have to repeat those changes for the new server using IIS. Alternatively, you can make application-wide error page settings part of your application using the project' s Web.config file.

Use the customErrors section in the Web.config file to specify pages to display if specific, unhandled HTTP errors occur in a Web application. HTTP errors are identified by status codes defined in the HTTP 1.1 specification. ASP.NET lists these status codes in the HTTPStatusCode enumeration. Some of the common status codes are listed in Table 6-4.

Table 6-4 Common HTTP Status Codes
Status code value | HTTPStatusCode member | Indicates
200 | OK | The request succeeded.
204 | NoContent | The request succeeded, but the response is intentionally blank.
301 | Moved, MovedPermanently | The request is being redirected to another address.
302 | Found, Redirect | The request is being redirected to another address.
400 | BadRequest | The request could not be understood by the server.
401 | Unauthorized | The requested resource requires authorization, which was not provided with the request.
403 | Forbidden | The server refuses to fulfill the request.
404 | NotFound | The requested resource does not exist on the server.
408 | RequestTimeOut | The client did not send a request before the server' s request timeout occurred.
500 | InternalServerError | A generic error occurred on the server.
503 | ServiceUnavailable | The server is temporarily unavailable.
505 | HttpVersionNotSupported | The requested HTTP version is not supported by the server.

To display a specific page in response to one or more of these status codes, include an <error> tag in the customErrors section of your application' s Web.config file. For example, the following customErrors section specifies a default error page to display, along with three different error pages for specific HTTP response codes that indicate errors:

    <customErrors mode="On" defaultRedirect="ErrDefault.aspx">
        <error statusCode="401" redirect="ErrUnauthorized.aspx" />
        <error statusCode="404" redirect="ErrPageNotFound.aspx" />
        <error statusCode="500" redirect="ErrServer.htm" />
    </customErrors>

The customErrors mode field must equal On to view the error pages while debugging the application on your local machine. Setting the mode to RemoteOnly (the default) will display the designated error pages when the application is accessed from client computers, but not when the application is accessed locally.

Using Page-Level Error Pages

Use the Page object' s ErrorPage attribute to display a specific page when an unhandled exception occurs on a Web form. You can use the Server object' s GetLastError and ClearError methods from the error page, just as shown earlier for Error events.

For example, the following HTML sets the ErrorPage attribute for a Web form:

Visual Basic .NET
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm1.aspx.vb"
Inherits="vbExceptionSnippet.WebForm1" errorPage="errDefault.aspx"%>
Visual C#
<%@ Page language="c#" Codebehind="ErrorPage.aspx.cs"
    AutoEventWireup="false" Inherits="csErrorPageSnippet.WebForm1"
    errorPage="ErrDefault.aspx"%>

The following code then causes an exception when the user clicks butError:

Visual Basic .NET
' Webform1.aspx
Private Sub butError_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butError.Click
      ' Cause exception.
      Throw New System.Net.WebException()
End Sub
Visual C#
// Webform1.aspx
private void butError_Click(object sender, System.EventArgs e)
{
   // Cause exception.
   throw new System.Net.WebException();
}

When the exception occurs, the ErrDefault.aspx page is displayed, which describes the error to the user by using the following code:

Visual Basic .NET
' ErrDefault.aspx
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Display the error that occurred.
    litError.Text = "<p>The following error occurred:</p>" & _
      Server.GetLastError.Message
    ' Clear the error.
    Server.ClearError()
End Sub
Visual C#
// ErrDefault.aspx
 private void Page_Load(object sender, System.EventArgs e)
 {
     // Display the error that occurred.
     litError.Text = "<p>The following error occurred:</p>" +
       Server.GetLastError().Message;
     // Clear the error.
     Server.ClearError();
 }


Lesson 3: Logging Exceptions

As you develop your application, it is a good idea to use exception logging to provide a means to track exceptions. An exception log is a list of handled exceptions that occur while your application is running. Reviewing the exception log periodically helps you verify that exceptions are being handled correctly, are not occurring too frequently, and are not preventing users from accomplishing tasks with your application.

In this lesson, you will learn how to use the ASP.NET tracing features to implement exception logging in your Web application.

After this lesson, you will be able to

  • List the advantages of tracing as a way to record exceptions

  • Turn tracing on or off for an application or a Web form

  • Write messages to the trace log

  • View the trace log

  • Clear the trace log and get a new set of tracing information

Estimated lesson time: 20 minutes
Using Tracing to Log Errors

Tracing is a technique for recording events, such as exceptions, in an application. There have always been ways to record errors in an application—usually by opening a file and writing error messages to it—but tracing offers these significant advantages:

  • Standardization. Building tracing into the .NET Framework ensures that programming techniques are the same across all the applications you develop with the .NET Framework.

  • Built-in Web support. ASP.NET extends the .NET Framework tools by including information related to the performance and behavior of Web requests.

  • Configuration. You can turn tracing on and off using settings in your application' s configuration file. You don' t have to recompile your application to enable or disable tracing.

  • Performance. While disabled, tracing statements do not affect application performance.

To use tracing in a Web application, follow these steps:

  1. Turn tracing on.

  2. Write to the trace log.

  3. Read the trace log.

The following sections provide details on these three steps.

Turning Tracing On and Off

Tracing can be turned on or off for an entire Web application or for an individual page in the application:

  • To turn tracing on for an entire application, in the application' s Web.config file, set the <trace> element' s Enabled attribute to True.

  • To turn tracing on for a single page, set the DOCUMENT object' s Trace property to False.

By default, trace output is displayed at the end of each Web page, as shown by Figure 6-7.

Figure 6-7 Tracing output

While this is fine for debugging purposes, you' ll generally want to write trace output to a log file when you start testing your completed application. To write trace messages to a log file for an entire application, in the application' s Web.config file, set the <trace> element' s PageOutput attribute to False. ASP.NET then writes trace output to the Trace.axd file in your application' s root folder.

The <trace> element also includes a RequestLimit attribute to determine how many page requests to write to the trace log. For example, the following line from a Web.config file turns on tracing for the application and writes the first 20 requests to the Trace.axd file:

<trace enabled="true" requestLimit="20" pageOutput="false"
traceMode="SortByTime" localOnly="true" />

Writing trace messages to a log file does not affect tracing set at the Page level. When a DOCUMENT object' s Trace property is set to True, all trace messages for that page are displayed on the page.

To write trace messages to a log file for only selected pages in an application, follow these steps:

  1. In the application' s Web.config file, set the <trace> element' s Enabled attribute to True and PageOutput attribute to False.

  2. For each Web page you want exclude from tracing, set the DOCUMENT object' s Trace property to False.

When you have set a DOCUMENT object' s Trace property to True or False, you can' t restore the default setting from the Properties window in Visual Studio .NET. Instead, you must edit the Web form' s HTML to remove the trace field from the Page element. The following HTML shows the text to delete in bold:

Visual Basic .NET
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="Trace.aspx.vb"
    Inherits="vbErrorLogSnippet.WebForm1" trace="True" %>
Visual C#
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false"
    Inherits="csTracing.WebForm1" trace="True"%>
Writing Messages to the Trace Log

The Trace object provides the Write and Warn methods to allow you to write messages to a request' s trace information. The two methods are identical with one exception: messages written with Write are displayed in black, while messages written with Warn are displayed in red.

For example, the following code writes information about unhandled exceptions to the trace log using the Warn method, so they are displayed in red:

Visual Basic .NET
Private Sub Page_Error(ByVal sender As Object, _
  ByVal e As System.EventArgs) Handles MyBase.Error
    ' Write a message to the trace log.
    Trace.Warn("Error", "", Server.GetLastError())
    ' Clear the error so the application can continue.
    Server.ClearError()
    ' Redisplay the page.
    Response.Redirect("Trace.aspx")
End Sub
Visual C#
private void Page_Error(object sender, System.EventArgs e)
{
    // Write a message to the trace log.
    Trace.Warn("Error", "", Server.GetLastError());
    // Clear the error so the application can continue.
    Server.ClearError();
    // Redisplay the page.
    Response.Redirect("Trace.aspx");
}

The following code causes an unhandled exception when the user clicks butError:

Visual Basic .NET
Private Sub butError_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butError.Click
    Throw New System.IO.FileNotFoundException()
End Sub
Visual C#
private void butError_Click(object sender, System.EventArgs e)
{
    throw new System.IO.FileNotFoundException();
}

In some cases, you might want to take additional actions while tracing is enabled. For example, you might want to get specific user information to add to the trace message. The Trace object' s IsEnabled property lets you run this code conditionally, so that it does not affect performance while tracing is disabled. For example, the following code adds browser information to the trace message if tracing is enabled:

Visual Basic .NET
Private Sub Page_Error(ByVal sender As Object, _
  ByVal e As System.EventArgs) Handles MyBase.Error
    If Trace.IsEnabled Then
        Dim strMessage As String
        If Request.Browser.AOL Then
            strMessage = "AOL Browser"
        Else
            strMessage = "Non-AOL Browser"
        End If
        Trace.Warn("Error", strMessage, Server.GetLastError())
    End If
    Server.ClearError()
    Response.Redirect("Trace.aspx")
End Sub
Visual C#
private void Page_Error(object sender, System.EventArgs e)
{
    if (Trace.IsEnabled)
    {
        string strMessage;
        if (Request.Browser.AOL)
            strMessage = "AOL Browser";
        else
            strMessage = "Non-AOL Browser";
        // Write a message to the trace log.
        Trace.Warn("Error", strMessage, Server.GetLastError());
    }
    // Clear the error so the application can continue.
    Server.ClearError();
    // Redisplay the page.
    Response.Redirect("Trace.aspx");
}
Reading the Trace Log

By default, trace output is displayed at the bottom of each Web page for which tracing is enabled. As mentioned earlier, if the <trace> element' s PageOutput attribute is set to False in the Web.config file, trace output is written instead to the Trace.axd file in your application' s root directory. You can view this file by simply navigating to it in your browser. For example, you could use the following hyperlink to open the log:

<A href="Trace.axd">View trace log.</A>

By default, you can view Trace.axd only from the local server running the application. If you want to view the trace log from a remote machine, such as when debugging remotely, set the <trace> element' s LocalOnly attribute to False in the Web.config file, as shown here:

<trace enabled="true" requestLimit="20" pageOutput="false"
    traceMode="SortByTime" localOnly="false" />

When you navigate to Trace.axd in a browser, the trace log appears as shown in Figure 6-8.

Figure 6-8 The Trace.axd log file

ASP.NET tracing stops after the server receives the number of HTTP requests entered in the <trace> element' s RequestLimit attribute. To clear the list and start tracing again, click the Clear Current Trace link in the upper right corner of the Trace.axd page. The Clear Current Trace link redisplays Trace.axd and passes the query string ”clear=1” as shown by the following HTML:

<a href="Trace.axd?clear=1">Clear trace log.</a>


Summary

  • Exception handling provides a way to deal with unusual occurrences in your program. Each of the different exception-handling techniques provides a way to set aside the complexity of dealing with these unusual occurrences so that they do not obscure the main logic of your application.

  • Exception-handling blocks use the Try, Catch, Finally (Visual Basic .NET) or try, catch, finally (Visual C#) keywords to handle exceptions within a procedure.

  • Catch exceptions in order from most the most specific exception type to the most general exception type.

  • Use the Throw (Visual Basic .NET) or throw (Visual C#) keyword to intentionally cause an exception.

  • Error events handle exceptions in a separate event procedure. Use the Server object' s GetLastError and ClearError to handle exceptions within error events.

  • Error pages handle exceptions that occur outside of the Web application because of Internet-related problems.

  • Monitor exceptions that are handled in your application by writing messages to the trace log using the Trace object' s Write and Warn methods.

  • Enable application-level tracing by setting the <trace> element' s attributes in the Web.config file.

  • Enable page-level tracing by setting the Trace attribute in a Web form' s Page directive.


Lab: Adding Exception Handling to an Application

In this lab, you will add exception-handling features to the pre-existing Contact Management data application. When the Contact Management application was introduced in Chapter 5, “Storing and Retrieving Data with ADO.NET,” it used some simple exception-handling blocks to deal with problems that might occur while updating records in a database. This lesson extends those exception-handling blocks to deal with specific types of data error and implements tracing to view how errors are handled.

Estimated lesson time: 30 minutes
Exercise 1: Extending Exception Handling

The Contact Management application deals with database exceptions in a very simple way: If an exception is encountered, the application displays the error and then lets the user cancel the operation. The following code fragment shows this approach as implemented in the butAdd_Click event procedure of the AddContact Web form:

Visual Basic .NET
Try
' Modify the database.
    adptContacts.Update(dsContacts)
    ' Show success.
    litStatus.Text = rowNew.FirstName & " " & rowNew.LastName & _
    " added successfully.<br>"
Catch ex As Exception
    litStatus.Text = "The following error occurred:<br>" & _
    ex.Message & "<br>" & _
    "Correct the error and click Add to add the contact " & _
    "or click Cancel to abort.<br>"
End Try
Visual C#
try
{
    // Modify the database.
    adptContacts.Update(dsContacts);
    // Show success.
    litStatus.Text = rowNew.FirstName + " " + rowNew.LastName +
      " added successfully.<br>";
    // Toggle ViewState to clear fields.
    Page.EnableViewState = false;
    Page.EnableViewState = true;
}
catch (Exception ex)
{
    litStatus.Text = "The following error occurred:<br>" +
    ex.Message + "<br>" +
    "Correct the error and click Add to add the contact " +
      "or click Cancel to abort.<br>";
}

To create more detailed handling for data exceptions, edit the Catch/catch statements in the application to handle the following possible exceptions:

  • ConstraintException. This exception occurs if the dataset' s unique key field constraint is violated. In practice, this can happen if two users are adding a contact at nearly the same time and they both get the same ContactID. When the second user tries to update the database, the ConstraintException occurs because the first user has already added a Contact with that ContactID. To handle this situation, get another ContactID and try the update again, as shown by the following code:

    Visual Basic .NET
    ' Occurs if ContactID is not unique.
Catch ex As ConstraintException
      ' Get another contact ID.
   rowNew.ContactID = GetNewContactID()
' Try again.
Try
      adptContacts.Update(dsContacts)
   ' Show success.
   litStatus.Text = rowNew.FirstName & " " & rowNew.LastName & _
     " added successfully.<br>"
Catch
   litStatus.Text = "The record could not be added. " & _
     "Click Add to try again or click Cancel to abort."
End Try
    Visual C#
    // Occurs if ContactID is not unique.
catch (ConstraintException ex)
{
    // Get another contact ID.
    rowNew.ContactID = GetNewContactID();
    // Try again.
    try
    {
        adptContacts.Update(dsContacts);
        // Show success.
        litStatus.Text = rowNew.FirstName + " " + rowNew.LastName +
          " added successfully.<br>";
    }
    catch
    {
        litStatus.Text = "The record could not be added. " +
        "Click Add to try again or click Cancel to abort.";
    }
}

  • DBConcurrencyException. This exception can occur if a user tries to update the database while another user has exclusive access. In this case, it is best to notify the first user that the update was not made and allow that user to resubmit the change, as shown by the following code:

    Visual Basic .NET
    ' The database may be locked by another session,
' so let the user try again.
Catch ex As DBConcurrencyException
    litStatus.Text = "The database is currently locked. Wait " & _
      "a few seconds and then click Add again."
    Visual C#
    // The database may be locked by another session,
// so let the user try again.
catch (DBConcurrencyException ex)
{
    litStatus.Text = "The database is currently locked. Wait " +
      "a few seconds and then click Add again.";
}

  • DataException. This is the general exception for data-related tasks. Handle this exception as a way to intercept problems you might not have thought of. The following code displays a message to the user, then records the exception for viewing in the trace log as a diagnostic tool:

    Visual Basic .NET
    ' General data exception, so report it.
Catch ex As DataException
    litStatus.Text = "The following database error occurred:<br>" & _
      ex.Message & "<br>" & _
      "Correct the error and click Add to add the contact " & _
      "or click Cancel to abort.<br>"
    Trace.Warn("Error", "Data exception", ex)
End Try
    Visual C#
    // General data exception, so report it.
catch(DataException ex)
{
    litStatus.Text = "The following database error occurred:<br>" +
      ex.Message + "<br>" +
      "Correct the error and click Add to add the contact " +
      "or click Cancel to abort.<br>";
Trace.Warn("Error", "Data exception", ex);
}

The preceding changes to the butAdd_Click event procedure leave non-data related exceptions unhandled. For the purpose of this lab, we' ll handle those possible exceptions in Error events in the following exercise.

Exercise 2: Adding an Error Event

After handling the possible data exceptions in the butAdd_Click event procedure of the Contacts Web form, you should provide a way to handle and record unanticipated exceptions through the form' s Error event procedure. This serves two purposes:

  • It prevents the user from seeing technical error messages and replaces them with more helpful information.

  • It keeps information about unanticipated errors in a trace log that you can use as a diagnostic tool to help you determine if you need to handle other exceptions.

To handle unanticipated exceptions using the Error event of the AddContact Web form:

  1. Add a Page_Error event procedure to the Web form to deal with all other exceptions on the page. The following code saves information about the exception to the trace log, creates an error message to display, and clears the error so the application can continue:

    Visual Basic .NET
    Private Sub Page_Error(ByVal sender As Object, ByVal e As _
  System.EventArgs) Handles MyBase.Error
    ' Write the error to the trace log.
    Trace.Warn("Error", "Error event", Server.GetLastError())
    ' Save a message to display as a Session state variable.
    Session("Error") = "<p>An unexpected error occurred. " & _
         "Try your task again.</p>"
    ' Clear the error
    Server.ClearError()
    ' Redisplay this page.
    Response.Redirect("AddContact.aspx")
End Sub
    Visual C#
    private void Page_Error(object sender, System.EventArgs e)
{
    // Write the error to the trace log.
    Trace.Warn("Error", "Error event", Server.GetLastError());
    // Save a message to display as a Session state variable.
    Session["Error"] = "<p>An unexpected error occurred. " +
      "Try your task again.</p>";
    // Clear the error
    Server.ClearError();
    // Redisplay this page.
    Response.Redirect("AddContact.aspx");
}

  2. Modify the Page_Load event procedure to display the error message that was saved as a Session state variable in the preceding step. The following code shows the modified Page_Load procedure with the additions in bold:

    Visual Basic .NET
    Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Get the connection and adapters from Session state.
    oledbContactMgmt = Session("oledbContactMgmt")
    adptContactTypes = Session("adptContactTypes")
    adptContacts = Session("adptContacts")
    ' Fill the ContactType data set.
    adptContactTypes.Fill(dsContactTypes)
    ' Fill the Contacts data set.
    adptContacts.Fill(dsContacts)
    ' Bind to data -- populates the drpContactTypes and drpState lists.
    drpContactTypes.DataBind()
    drpStates.DataBind()
    ' If there was an unhandled error, display it.
    If Session("Error") <> "" Then
        litStatus.Text = Session("Error")
        ' Reset the error message.
        Session("Error") = Nothing
    End IfEnd Sub
    Visual C#
    private void Page_Load(object sender, System.EventArgs e)
{
    // Get the connection and adapters from Session state.
    oledbContactMgmt = (OleDbConnection)Session["oledbContactMgmt"];
    adptContactTypes = (OleDbDataAdapter)Session["adptContactTypes"];
    adptContacts = (OleDbDataAdapter)Session["adptContacts"];
    // Fill the ContactType data set.
    adptContactTypes.Fill(dsContactTypes);
    // Fill the Contacts data set.
    adptContacts.Fill(dsContacts);
    // Bind to data -- populates the drpContactTypes and drpState lists.
    drpContactTypes.DataBind();
    drpStates.DataBind();
    // If there was an unhandled error, display it.
    if(Session["Error"] != null)
    {
        litStatus.Text = Session["Error"].ToString();
        // Reset the error message.
        Session["Error"] = null;
    }}
Exercise 3: Creating and Viewing the Trace Log

Exercises 1 and 2 wrote messages to the trace log to help you catch unanticipated errors when people start using your application. To make these messages viewable, enable tracing and add a link within the application to display the trace log, as described in the following procedure.

To create viewable messages:

  1. Modify the Web.config file to enable tracing. The following <trace> element enables tracing, saves the output as Trace.axd, increases the number of requests recorded to 20, and enables the trace log to be viewed remotely:

    <trace enabled="true" requestLimit="20" pageOutput="false"
    traceMode="SortByTime" localOnly="false" />

  2. Add a link to the SwitchBoard Web form to conditionally display the trace log. The following HTML creates the link as a Hyperlink server control so that you can make the link visible or invisible at run time, depending on the <trace> element setting:

    <asp:HyperLink NavigateUrl="Trace.axd" ID="hypTrace" runat="server"
    text="View trace log." Visible="false"></asp:HyperLink>

  3. Add the following code to the SwitchBoard Web form' s Page_Load event procedure to control the display of the trace log link:

    Visual Basic .NET
    Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Display the trace link if tracing is enabled.
    If Trace.IsEnabled Then
        hypTrace.Visible = True
    End If
End Sub
    Visual C#
    private void Page_Load(object sender, System.EventArgs e)
{
    // Display the trace link if tracing is enabled.
    if (Trace.IsEnabled)
        hypTrace.Visible = true;
}
Exercise 4: Extend Exception Handling to Other Web Forms

So far this lab has shown you how to add exception handling in a step-by-step fashion. Now it' s time to strike out on your own! Extend the exception handling on the Calls, DeleteContact, and ContactTypes Web forms by yourself. These Web forms should perform the following tasks:

  • Handle possible data exceptions using exception-handling blocks, as demonstrated in Exercise 1.

  • Add Error events to capture and record unanticipated exceptions, as demonstrated in Exercise 2.

When you are done, compare your results to the Contact Management application included on the book' s companion CD. Good luck!


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. Explain why exception handling is important to a completed application.

  2. List two different exception-handling approaches in ASP.NET Web applications.

  3. Describe the purpose of error pages and why they are needed.

  4. Explain why tracing helps with exception handling.


Advanced Web Forms Programming

Chapter 7
Advanced Web Forms Programming
Lesson 1: Saving and Retrieving User Information
Lesson 2: Sending Mail
Lesson 3: Using Frames
Lesson 4: Using Client-Side Scripts
Lesson 5: Consuming XML Web Services
About This Chapter

In this chapter, you will learn how to perform some advanced programming tasks that don' t fit neatly into any other category. Although you can successfully create Web applications without knowing the topics covered in this chapter, the lessons found here are an important part of mainstream Web programming and they will help complete your skill set.

Before You Begin

To complete the lessons in this chapter, you must

  • Be comfortable creating Web applications using server controls and server-side code.

  • Have some experience with a scripting language, such as Microsoft Visual Basic Scripting Edition (VBScript) or Microsoft JScript. If you' ve never used a scripting language, you should review the VBScript User' s Guide or JScript User' s Guide topics in the Microsoft Visual Studio online Help before beginning Lesson 4.


Lesson 1: Saving and Retrieving User Information

Web applications that require users to register information can retrieve that information whenever a particular user revisits the Web site. For example, a shopping Web site might record the user' s shipping and billing information and then retrieve that information to fill in address fields whenever that user places a new order.

After this lesson, you will be able to

  • Explain how cookies make it possible to identify users and to store user information

  • Store simple or complex information on a user' s computer through a cookie

  • Remove a cookie from a user' s computer

  • Create a unique identifier for a user and store it on his or her computer

  • Use an XML file to store and retrieve information about a user based on that user' s unique identifier

Estimated lesson time: 35 minutes
Identifying Web Application Users

Web applications can identify users by requiring them to enter a user name and password whenever they visit the Web site. Web applications can also identify users through information stored on their computers in the form of cookies. Cookies are small files that a Web application can write to the client' s computer.

The advantage of using cookies is that the interaction happens invisibly to the user: Users don' t have to log on every time they visit your Web site; their information just appears automatically when needed.

The disadvantage of using cookies is that users can set their browsers not to accept cookies. Some users don' t like the idea of Web sites storing information on their computers and possibly using that information to track their movements on the Internet. Therefore, you should always check to see if a client accepts cookies before attempting to use them.

You can use two approaches when storing and retrieving user information through cookies:

  • Store all the user information as a cookie on the client' s machine. This approach is useful when the user information is simple and is not required on the server for any other tasks.

  • Store an identification key on the client' s machine, and then retrieve user information from a data source on the server using that identification key. This is the best approach for storing more extensive information.

The following sections describe how to save and retrieve user information using each of these approaches.

Storing User Information on the Client

To store a cookie on the client' s machine, follow these steps:

  1. Check whether the client supports cookies by using the Browser object' s Cookies property.

  2. If the client supports cookies, check whether the cookie already exists by using the Request object' s Cookies collection.

  3. If the cookie does not already exist, create a new cookie object using the HttpCookie class.

  4. Set the cookie object' s Value and Expiration properties.

  5. Add the cookie object to the Response object' s Cookies collection.

The following Page_Load event procedure demonstrates the preceding steps by creating a cookie that tracks the last time a user viewed a Web page. The code checks whether the user accepts cookies and either adds a cookie if it is the user' s first visit to the page or updates the cookie if the user has visited the page before.

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
        ' (1) Check if browser accepts cookies.
        If Request.Browser.Cookies Then
            ' (2) If the cookie does not exist...
            If Request.Cookies("LastVisit") Is Nothing Then
                ' (3) Create cookie.
                Dim cookLastVisit As New HttpCookie("LastVisit", _
                  Now.ToString())
                ' (4) Set the expiration to tommorrow.
                cookLastVisit.Expires = DateTime.Now.AddDays(1)
                ' (5) Add to cookies collection.
                Response.Cookies.Add(cookLastVisit)
                ' Display message.
                Response.Write("This is your first visit.")
            Else
                ' Get the cookie.
                Dim cookLastVisit As HttpCookie = _
                Request.Cookies("LastVisit")
                ' Display a message showing time of last visit.
                Response.Write("You last visited this page: " & _
                  cookLastVisit.Value)
                ' Update the cookie on the client.
                Response.Cookies("LastVisit").Value = Now.ToString()
                Response.Cookies("LastVisit").Expires = _
                  DateTime.Now.AddDays(1)
            End If
        Else
            Response.Write("Your browser does not accept cookies.")
        End If
    End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
      // (1) Check if browser accepts cookies.
      if (Request.Browser.Cookies)
      {
           // (2) If the cookie does not exist...
           if (Request.Cookies["LastVisit"] == null)
           {
              // (3) Create cookie.
               HttpCookie cookLastVisit = new HttpCookie("LastVisit",
               DateTime.Now.ToString());
               // (4) Set the expiration to tommorrow.
               cookLastVisit.Expires = DateTime.Now.AddDays(1);
               // (5) Add to cookies collection.
               Response.Cookies.Add(cookLastVisit);
               // Display message.
               Response.Write("This is your first visit.");
           }
           else
           {
               // Get the cookie.
               HttpCookie cookLastVisit = Request.Cookies["LastVisit1"];
               // Display a message showing time of last visit.
               Response.Write("You last visited this page: " +
                 cookLastVisit.Value);
               // Update the cookie on the client.
               Response.Cookies["LastVisit"].Value =
                 DateTime.Now.ToString();
               Response.Cookies["LastVisit"].Expires =
                 DateTime.Now.AddDays(1);
           }
}
else
{
           Response.Write("Your browser does not accept cookies.");
      }
}
Facts about Cookies

Cookies are case sensitive. For example, LastVisit is not the same cookie as Lastvisit. The Expires property determines when the client' s machine can discard the cookie. By default, cookies expire when the user' s session ends. Setting Expires to the DateTime.MaxValue means that the cookie never expires.

You can remove the cookie from the client' s machine by resetting the Expires property to the current time. For example, the following code removes the LastVisit cookie from the client' s machine:

Visual Basic .NET
Private Sub butRemoveCookie_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butRemoveCookie.Click
     ' Set cookie to expire immediately.
     Response.Cookies("LastVisit").Expires = DateTime.Now
End Sub
Visual C#
private void butRemoveCookie_Click(object sender, System.EventArgs e)
{
      // Set cookie to expire immediately.
      Response.Cookies["LastVisit"].Expires = DateTime.Now;
}
Using Keys within Cookies

You can save up to 4096 bytes of information in a single cookie, and you can identify information within a cookie using keys. For example, the following code saves user name and address information as a cookie with individual keys:

Visual Basic .NET
Private Sub butOK_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butOK.Click
    ' Create a cookie cookie.
    Dim cookUserInfo As New HttpCookie("UserInfo")
    ' Fill in the keys from the form data.
    cookUserInfo("FirstName") = txtFirstName.Text
    cookUserInfo("LastName") = txtLastName.Text
    cookUserInfo("Street") = txtStreet.Text
    cookUserInfo("City") = txtStreet.Text
    cookUserInfo("State") = drpState.SelectedItem.Value
    cookUserInfo("ZIP") = txtZIP.Text
    ' Set the expiration.
    cookUserInfo.Expires = DateTime.Now.AddDays(30)
    ' Add the cookie.
    Response.Cookies.Add(cookUserInfo)
End Sub
Visual C#
private void butOK_Click(object sender, System.EventArgs e)
{
      // Create a cookie cookie.
      HttpCookie cookUserInfo = new HttpCookie("UserInfo");
      // Fill in the keys from the form data.
      cookUserInfo["FirstName"] = txtFirstName.Text;
      cookUserInfo["LastName"] = txtLastName.Text;
      cookUserInfo["Street"] = txtStreet.Text;
      cookUserInfo["City"] = txtStreet.Text;
      cookUserInfo["State"] = drpState.SelectedItem.Value;
      cookUserInfo["ZIP"] = txtZIP.Text;
      // Set the expiration.
      cookUserInfo.Expires = DateTime.Now.AddDays(30);
      // Add the cookie.
      Response.Cookies.Add(cookUserInfo);
}

To get the values from keys stored in a cookie, simply use the key' s name, as shown in the following code:

Visual Basic .NET
Private Sub butGetData_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butGetData.Click
    ' Get the cookie.
    Dim cookUserInfo As HttpCookie = Request.Cookies("UserInfo")
    ' Fill in the fields.
    txtFirstName.Text = cookUserInfo("FirstName")
    txtLastName.Text = cookUserInfo("LastName")
    txtStreet.Text = cookUserInfo("Street")
    txtCity.Text = cookUserInfo("City")
    drpState.SelectedItem.Value = cookUserInfo("State")
    txtZIP.Text = cookUserInfo("ZIP")
End Sub
Visual C#
private void butGetData_Click(object sender, System.EventArgs e)
{
      // Get the cookie.
      HttpCookie cookUserInfo  = Request.Cookies["UserInfo"];
      // Fill in the fields.
      txtFirstName.Text = cookUserInfo["FirstName"];
      txtLastName.Text = cookUserInfo["LastName"];
      txtStreet.Text = cookUserInfo["Street"];
      txtCity.Text = cookUserInfo["City"];
      drpState.SelectedItem.Value = cookUserInfo["State"];
      txtZIP.Text = cookUserInfo["ZIP"];
}
Storing User Information on the Server

To store user information on the server instead of on the client' s machine, simply use cookies as an identification device to store and retrieve user information on the server. You can store user information on the server using a database, an XML file, or some other type of data store.

To store user information on the server, follow these steps:

  1. Create a unique key to identify the user.

  2. Save the unique key as a cookie on the user' s computer.

  3. Create a file on the server to store user information.

  4. Save the user information on the server using the unique key as an index.

The follow sections describe these steps in greater detail.

Creating Unique Keys to Identify Users

The Microsoft .NET Framework provides the System.Guid namespace for creating globally unique identifiers (GUIDs). A GUID is a 128-bit integer that serves as a unique identifier across networks. You can use GUIDs as unique keys to identify all sorts of things, including users.

The following code creates a GUID and stores it as a cookie on the client' s machine to later identify the user:

Visual Basic .NET
Private Sub butOK_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butOK.Click
    ' Get the request cookie.
    Dim cookUserID As HttpCookie = Request.Cookies("UserID")
    ' If it doesn't exist, create it.
    If cookUserID Is Nothing Then
        ' Create a new cookie with a new GUID.
        cookUserID = New HttpCookie("UserID", _
          System.Guid.NewGuid().ToString())
        cookUserID.Name = "UserID"
    End If
    ' Set the expiration.
    cookUserID.Expires = DateTime.Now.AddDays(30)
    ' Add the cookie to the response.
    Response.Cookies.Add(cookUserID)
    ' Save the user info from the form data.
    SetUserInfo(cookUserID.Value)
End Sub
Visual C#
private void butOK_Click(object sender, System.EventArgs e)
{
      // Get the request cookie.
      HttpCookie cookUserID  = Request.Cookies["UserID"];
      // If it doesn't exist, create it.
      if (cookUserID == null)
            // Create a new cookie with a new GUID.
            cookUserID = new HttpCookie("UserID",
              System.Guid.NewGuid().ToString());
      // Set the expiration.
      cookUserID.Expires = DateTime.Now.AddDays(30);
      // Add the cookie to the response.
      Response.Cookies.Add(cookUserID);
      // Save the user info from the form data.
      SetUserInfo(cookUserID.Value);
}
Creating a File to Store User Information

Part of the point of storing user information is to be able to retrieve that information quickly the next time the user visits your Web application. Because these users are already identified by a unique key (the GUID created in the preceding section), it makes sense that you will want to access their data through a data set. Data sets provide the Find method to retrieve rows of data by unique keys.

Using a data set means that the file you create on the server must be either a database or an XML file. XML provides a way to record structured data to a file without the complexity or overhead of a database, so for something as simple as user name and address information you will probably want to use XML.

To create an XML file to store user information, follow these steps:

  1. Create an XML file in Visual Studio containing test data for each of the data fields you want to record about the user.

  2. Generate an XML schema from the XML file. The XML schema enables the data set to refer to data in the XML file by name.

  3. Specify a key field within the XML schema. This will let you find records using that field with the data set' s Find method.

  4. Read the XML schema and XML file into a data set.

The following sections describe these steps in more detail.

Creating an XML File

To create an XML file in Visual Studio, choose Add New Item from the Project menu and then select XML File from the Templates list.

XML files look a lot like HTML files; however, XML elements and attributes are case sensitive and use a strict syntax. You use <element> and </element> tags to identify the data elements in an XML file. The following sample shows an XML file for storing user name and address information:

<?xml version="1.0" standalone="yes"?>
<USERS xmlns="http://tempuri.org/UserPrefs.xsd">
  <USER>
    <FIRSTNAME>Joan</FIRSTNAME>
    <LASTNAME>Reddington</LASTNAME>
    <STREET>436 Atlantic Ave.</STREET>
    <CITY>Melbourne Beach</CITY>
    <STATE>FL</STATE>
    <ZIP>32401</ZIP>
       <ID>79844302-6d86-4520-ac64-c8c3240e21a9</ID>
  </USER>
</USERS>
Creating an XML Schema

To use an XML file with a data set, you must first create an XML schema for the data set. An XML schema is a description of the data elements contained in the XML file. It provides the names of the elements, their types, whether they are key fields, and other information.

To create an XML schema from an XML file, select Create Schema from the XML menu while the XML file is displayed in the Design window. Visual Studio creates a schema file describing the XML file, as shown in Figure 7-1.

Figure 7-1 An XML schema
Specifying a Key Field

You add a primary key to the XML schema to enable searching. The schema shown in Figure 7-1 includes a key field that enables the data set to search for users by UserID.

To add a key field to the XML schema, follow these steps:

  1. Right-click the element to create as a key, select Add, and then select New Key from the pop-up menu. Visual Studio displays the Edit Key dialog box, as shown in Figure 7-2.

    Figure 7-2 The Edit Key dialog box

  2. Select the Dataset Primary Key check box and click OK to make the element a primary key within the data set. This enables you to use the Dataset object' s Find method to retrieve rows from the data set using values for the element.

Reading the XML into a Data Set

When you' ve created an XML file and an XML schema, you can read those files into a data set using the ReadXmlSchema and ReadXml methods, respectively. The following code shows a helper function that returns a data set created from the UserInfo XML file:

Visual Basic .NET
Function GetUserData() As DataSet
    ' Set the path of the XML file and XML schema.
    Dim strPath As String = Server.MapPath(Request.ApplicationPath)
    ' Declare a data set.
    Dim dsUsers As New DataSet()
    ' Apply the XML schema to data set.
    dsUsers.ReadXmlSchema(strPath & "\UserInfo.xsd")
    ' Read the XML into the data set.
    dsUsers.ReadXml(strPath & "\UserInfo.xml")
    Return dsUsers
End Function
Visual C#
private DataSet GetUserData()
{
      // Set the path of the XML file and XML schema.
      string strPath  = Server.MapPath(Request.ApplicationPath);
      // Declare a data set.
      DataSet dsUsers = new DataSet();
      // Apply the XML schema to data set.
      dsUsers.ReadXmlSchema(strPath + "\\UserInfo.xsd");
      // Read the XML into the data set.
      dsUsers.ReadXml(strPath + "\\UserInfo.xml");
      return dsUsers;
}
Saving User Information on the Server

To save user information on the server as an XML file, use the data set object' s SaveXML method. The following SetUserInfo procedure uses the GetUserData procedure described in the preceding section to get a data set from the XML file, and then uses the Find method to check if the UserID exists within that data set. If the UserID is not found, SetUserInfo adds a row for the user before setting the values for the fields in that row from server controls on a Web form. Finally, SetUserInfo writes the whole data set back to the server.

Visual Basic .NET
Sub SetUserInfo(ByVal UserID As String)
    ' Set the path of the XML file and XML schema.
    Dim strPath As String = Server.MapPath(Request.ApplicationPath())
    ' Get the Users data set.
    Dim dsUsers As DataSet = GetUserData()
    ' Find the row in the data set.
    Dim rowUser As DataRow = dsUsers.Tables("User").Rows.Find(UserID)
    ' If the row is not found, then create a new row.
    If rowUser Is Nothing Then
        rowUser = dsUsers.Tables("User").NewRow
        dsUsers.Tables("User").Rows.Add(rowUser)
    End If
    ' Save data from form fields.
    rowUser("FirstName") = txtFirstName.Text
    rowUser("LastName") = txtLastName.Text
    rowUser("Street") = txtStreet.Text
    rowUser("City") = txtCity.Text
    rowUser("State") = drpState.SelectedItem.Text
    rowUser("ZIP") = txtZIP.Text
    rowUser("ID") = UserID
    ' Write the XML from the data set.
    dsUsers.WriteXml(strPath & "\UserInfo.xml")
End Sub
Visual C#
private void SetUserInfo(string UserID)
{
      // Set the path of the XML file and XML schema.
      string strPath  = Server.MapPath(Request.ApplicationPath);
      // Get the Users data set.
      DataSet dsUsers  = GetUserData();
      // Find the row in the data set.
      DataRow rowUser = dsUsers.Tables["User"].Rows.Find(UserID);
      // If the row is not found, then create a new row.
      if (rowUser == null)
      {
            rowUser = dsUsers.Tables["User"].NewRow();
            dsUsers.Tables["User"].Rows.Add(rowUser);
      }
      // Save data from form fields.
      rowUser["FirstName"] = txtFirstName.Text;
      rowUser["LastName"] = txtLastName.Text;
      rowUser["Street"] = txtStreet.Text;
      rowUser["City"] = txtCity.Text;
      rowUser["State"] = drpState.SelectedItem.Text;
      rowUser["ZIP"] = txtZIP.Text;
      rowUser["ID"] = UserID;
      // Write the XML from the data set.
      dsUsers.WriteXml(strPath + "\\UserInfo.xml");
}
Retrieving User Information from the Data Set

To retrieve user information from the XML file, use the GetUserData procedure to create a data set from the XML file, and then use the Find method to retrieve the row that corresponds to the UserID. The following GetUserInfo procedure retrieves user information from the data set and uses it to fill in server controls on a Web form.

Visual Basic .NET
Sub GetUserInfo(ByVal UserID As String)
    ' Get the Users data set.
    Dim dsUsers As Data.DataSet = GetUserData()
    ' Find the row in the data set.
    Dim rowUser As Data.DataRow = dsUsers.Tables("User").Rows.Find(UserID)
    ' If user wasn't found, exit.
    If rowUser Is Nothing Then Exit Sub
    ' Add data to form fields.
    txtFirstName.Text = rowUser.Item("FirstName")
    txtLastName.Text = rowUser.Item("LastName")
    txtStreet.Text = rowUser.Item("Street")
    txtCity.Text = rowUser.Item("City")
    drpState.SelectedItem.Text = rowUser.Item("State")
    txtZIP.Text = rowUser.Item("ZIP")
End Sub
Visual C#
void GetUserInfo(string UserID)
{
      // Get the Users data set.
      DataSet dsUsers  = GetUserData();
      // Find the row in the data set.
      DataRow rowUser  = dsUsers.Tables["User"].Rows.Find(UserID);
      // If user wasn't found, exit.
      if (rowUser == null) return;
      // Add data to form fields.
      txtFirstName.Text = rowUser["FirstName"].ToString();
      txtLastName.Text = rowUser["LastName"].ToString();
      txtStreet.Text = rowUser["Street"].ToString();
      txtCity.Text = rowUser["City"].ToString();
      drpState.SelectedItem.Text = rowUser["State"].ToString();
      txtZIP.Text = rowUser["ZIP"].ToString();
}


Lesson 2: Sending Mail

You can send mail from a Web application two different ways, depending on where you want to send the mail from:

  • Use the mailto: protocol to send mail from the client. This technique uses the client' s mail system and is useful for gathering feedback for a Web site, providing technical support, or other tasks that require a response to the user' s mail.

  • Use the MailMessage and SmtpMail classes to send mail from the server. This technique uses the server' s mail system, which is useful for automated error reporting, creating anonymous mail, or other tasks that do not necessarily require a client' s return mail address.

In this lesson, you will learn how to send mail using both these techniques.

After this lesson, you will be able to

  • Choose the best technique for sending mail, based on your needs

  • Create a hyperlink that starts the user' s mail system

  • Compose a mail message using either the user' s mail system or the server' s mail system

  • Send a mail message from the server

Estimated lesson time: 20 minutes
Sending Mail from a Client

To send mail using the client' s mail system, create a hyperlink using the mailto protocol. For example, the following HTML shows a hyperlink that creates a new message with a subject line and a short message body:

<A href="mailto:someone@microsoft.com?SUBJECT=Sending from a client&BODY=Some
message text.">

When the use clicks the preceding hyperlink, the client' s browser starts the client' s mail application and creates a message, as shown in Figure 7-3.

You can use the mailto protocol in place of any destination URL in a server control or an HTML control. For instance, the following HTML defines a Hyperlink server control that sends a mail message:

<asp:HyperLink ID="hypMail" NavigateUrl="mailto:someone@microsoft.com?
SUBJECT=Mailing a Webform&amp;BODY=Some message text."
Runat="server">Send mail.</asp:HyperLink>
Figure 7-3 Creating a mail message from a hyperlink

You can also use the mailto protocol as part of a Redirect method in code, but that has the undesired side effect of displaying a blank page in the browser as well as creating a mail message.

You specify the parts of the mail message by using the keys that the mailto protocol supports. These keys are included as a query string and are separated by ampersands (&), as shown here and described in Table 7-1:

mailto:address[?key=setting][&key=setting] …

Table 7-1 Keys Supported by the mailto Protocol
Key | Specifies
SUBJECT | The text to appear in the message' s subject line.
CC | A list of addresses to copy the message to. Multiple addresses are separated by semicolons (;).
BCC | A list of addresses to send blind copies to. Multiple addresses are separated by semicolons (;).
BODY | The text of the message.

The mailto protocol does not provide a key for adding attachments; however, the user can attach files to the message manually while the message window is open. If you want the user to attach a file (such as an error log or some other report), provide instructions on how to do this in the message body.

Sending Mail from the Server

When you send mail from the server, ASP.NET does not start the client' s mail system to allow the user to compose the message to send, to add attachments, or to validate addresses. Instead, you compose the message in code and send it one of two ways:

  • You can send a simple message using the SmtpMail class' s Send method.

  • You can create a more complex message using the MailMessage class, and then send that message using the SmtpMail class' s Send method.

Both the SmtpMail class and the MailMessage class are part of the System.Web.Mail namespace in the .NET Framework, so add the following statement at the beginning of your module to simplify references to those classes in code:

Visual Basic .NET
Imports System.Web.Mail
Visual C#
using System.Web.Mail;

You do not need to create an instance of the SmtpMail class before using it. To send a simple message, just use the Send method, as shown here:

Visual Basic .NET
SmtpMail.Send ("someone@microsoft.com", "jesse@contoso.com",
               "Subject line", _
               "Message text.")
Visual C#
SmtpMail.Send("someone@microsoft.com", "jesse@contoso.com", "Subject line",
              "Message text.");

The preceding line of code immediately sends a message from someone@microsoft.com to jesse@contoso.com. Both the from and to addresses are required, though they are not validated by the Send method. The SmtpMail class sends mail using your local SMTP server by default. To use a different server, set the SmtpServer property.

Using the Send method alone is fine for simple text messages, but to send a formatted message or a message containing an attachment, you need to create an object based on the MailMessage class, and then use the SmtpMail class' s Send method to send the message, as shown by the following code:

Visual Basic .NET
Private Sub butMail_Click(ByVal sender As System.Object, +
      ByVal e As System.EventArgs) Handles butMail.Click
    ' Create the message.
    Dim mailNew As New MailMessage()
    ' Set the message properties.
    mailNew.From = "someone@microsoft.com"
    mailNew.To = "jesse@contoso.com"
    mailNew.Subject = "This is the subject text."
    mailNew.Body = "This is the message text."
    ' Create an attachment.
    Dim atcItem As New _
    MailAttachment(Server.MapPath(Request.ApplicationPath) & "\joey.jpg")
    ' Attach it to the message.
    mailNew.Attachments.Add(atcItem)
    ' Send the message.
    SmtpMail.Send(mailNew)
End Sub
Visual C#
private void butSendMail_Click(object sender, System.EventArgs e)
{
      // Create the message.
      MailMessage mailNew = new MailMessage();
      // Set the message properties.
      mailNew.From = "someone@microsoft.com";
      mailNew.To = "jesse@contoso.com";
      mailNew.Subject = "This is the subject text.";
      mailNew.Body = "This is the message text.";
      // Create an attachment.
      MailAttachment atcItem = new MailAttachment
       (Server.MapPath(Request.ApplicationPath) +
       "\\joey.jpg");
      // Attach it to the message.
      mailNew.Attachments.Add(atcItem);
      // Send the message.
      SmtpMail.Send(mailNew);
}

The preceding code creates a message, sets the message properties, attaches a file to the message, and sends the message. As with the previous Send method example, the From and To properties are required, but are not validated.


Lesson 3: Using Frames

Frames are regions of a Web page that you can use to display other Web pages. You use frames to display multiple regions that scroll and behave independently of each other. In this lesson, you will learn how to create frames in Visual Studio and how to control one frame from another.

After this lesson, you will be able to

  • Create a frameset that defines separate display regions for your Web application

  • Check the user' s browser to make sure it supports frames

  • Display Web forms or HTML pages within the regions defined by a frameset

  • Control a main frame from a contents frame using hyperlinks and client-side scripts

Estimated lesson time: 15 minutes
Creating Frames in Visual Studio

To create frames in Visual Studio, follow these steps:

  1. Create a frameset to display the frames.

  2. Create Web pages to display in the frames.

  3. Set the Web page to display in each of the frames in the frameset.

The following procedures describe these steps in more detail.

Creating a Frameset

To create a frameset in Visual Studio, follow these steps:

  1. From the Project menu, choose Add New Item. Visual Studio displays the Add New Item dialog box, as shown in Figure 7-4.

    Figure 7-4 Add New Item dialog box

  2. From the Add New Item dialog box, select UI or Web from the Categories list and Frameset from the Templates list, type the name of the Web page to create in the Name text box, and then click Open. Visual Studio displays the Select A Frameset Template dialog box, as shown in Figure 7-5.

    Figure 7-5 Select A Frameset Template dialog box

  3. Select the type of frames to create from the Framesets list and click OK. Visual Studio creates Web page as an .htm file and opens it in the Designer, as shown in Figure 7-6.

    Figure 7-6 A new, empty frameset
Creating Pages and Adding Them to a Frameset

After you' ve created the frameset, you can display HTML pages or Web forms in each of the frames in the frameset. While framesets are HTML files, the pages you display within the frames can be HTML files or Web forms.

After you create the pages to display in the frames, associate each of the frames with an HTML page or a Web form by following these steps:

  1. Right-click the frame to associate with a page and select Set Page For Frame in the pop-up menu. Visual Studio displays the Select Page dialog box, as shown in Figure 7-7.

    Figure 7-7 Select Page dialog box

  2. Select the page to display from the Contents list and click OK. Visual Studio adds the selected page to the frame.

  3. Repeat steps 1 and 2 for each frame in the frameset. When you' re done, the frameset will appear similar to the one in Figure 7-8.

    Figure 7-8 A completed frameset in Design mode

Visual Studio can' t display the Web form in the frameset in Design mode correctly—the frames will appear either with error messages or with strange controls, as shown in Figure 7-8. The frameset will be correctly displayed at run time, however.

You can display the frameset at run time in two ways:

  • Set the frameset as the Web application' s start page and run the application.

  • Navigate to the frameset at run time from a link or by using the Redirect method in code.

At run time, the frameset appears as shown in Figure 7-9.

Figure 7-9 A completed frameset at run time
Checking Browser Support for Frames

Before you start using frames extensively in your application, you should be aware that early versions of some browsers did not support frames. If a client running one of these browsers requests a page containing frames, the browser will either display a blank page or lock up.

To detect whether a browser supports frames before displaying a frameset, use the Browser object' s Frames property. For example, the following code displays either a frameset or a plain HTML page, depending on the client' s frame support:

Visual Basic .NET
Private Sub butDisplayPage_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butDisplayPage.Click
    ' If the client's browser supports frames, display the frameset.
    If Request.Browser.Frames Then
        Server.Transfer("frames.htm")
' Otherwise, display a plain HTML version.
    Else
        Server.Transfer("noframes.htm")
    End If
End Sub
Visual C#
private void butDisplayPage_Click(object sender, System.EventArgs e)
{
      // If the client's browser supports frames, display the frameset.
      if (Request.Browser.Frames)
            Server.Transfer("frames.htm");
      // Otherwise, display a plain HTML version.
      else
            Server.Transfer("noframes.htm");
}

You can also create alternative content within HTML based on frame support by using the <NOFRAMES> element. The <NOFRAMES> element is supported in Internet Explorer version 3.0 and later and is part of the HTML 4.0 specification.

When you create a frameset using Visual Studio, it automatically generates the HTML containing a <NOFRAMES> element, as shown here:

<frameset rows="64,*">
      <frame name="banner" src="Banner.aspx" scrolling="no" noresize>
      <frameset cols="150,*">
            <frame name="contents" src="Contents.aspx">
            <frame name="main" src="Body.aspx">
      </frameset>
      <noframes>
            <p>
            This HTML frameset displays multiple Web pages. To view this
            frameset, use a Web browser that supports HTML 4.0 and later.
            </p>
      </noframes>
</frameset>

You can modify the text to provide alternative content or links to a non-frame version of the page.

Targeting Frames with Links

Each frame in a frameset acts as a separate browser window. Once a page is displayed in a frame, subsequent links from that page are displayed within that same frame.

To link between pages displayed in frames, add a TARGET attribute to the link. For example, the following HTML displays a list of links that target the main frame in a frameset:

<h3>Contents</h3>
<P><A href="Body1.aspx" target="main">Body1</A></P>
<P><A href="Body2.aspx" target="main">Body2</A></P>
<P><A href="Body3.aspx" target="main">Body3</A></P>

When a user clicks one of these links, the content of the frame named “main” changes, as shown in Figure 7-10.

Figure 7-10 Linking between pages

You can' t target a frame using the Redirect, Transfer, or Execute methods in code. You can only use HTML hyperlinks or Hyperlink server controls to navigate between frames.


Lesson 4: Using Client-Side Scripts

ASP.NET provides a full set of programming tools for creating Web applications that run on servers, so why should you worry about adding scripts to run on the client' s computer? Because scripts have direct access to the client' s browser, they make it possible to do many things you can' t do from server-side code. For example, you can use client-side scripts to

  • Control the browser window. You can' t control the browser from server-side code. To open new windows, set focus within a window, navigate between frames, navigate within history, and perform other browser-related tasks, you must use client-side scripts.

  • Respond immediately to mouse-over events. Server-side code can respond only to events after the page is posted back to the server; client-side code can respond to page events as they occur.

  • Start the client' s mail system. See Lesson 2 in this chapter for more information about this topic.

In this lesson, you will learn how to add scripts to Web forms and HTML pages in Visual Studio to create code that executes on the client' s computer.

After this lesson, you will be able to

  • Test a browser to see if it can run scripts

  • Choose a scripting language that suits your needs

  • Use objects provided by the Document Object Model (DOM) to control the browser window and the documents it contains

  • Create scripts that run inline as the page is interpreted by the browser

  • Create scripts that respond to events in the browser, such as mouse clicks and mouse move events

  • Get input from the user and display a response using scripts

Estimated lesson time: 30 minutes
Before You Use Scripts

Client-side scripts pose three issues you should be aware of:

  • Not all browsers support scripting.

  • Only Internet Explorer supports VBScript.

  • Scripting poses security concerns.

Because not all browsers support scripts, you should check the browser capabilities before displaying pages that contain client-side scripts. The following Page_Load event procedure checks whether a browser supports scripts and redirects users to a page recommending they download a new version if their browser does not support scripts:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load
    If Request.Browser.VBScript Then
        Response.Redirect("VBScripts.htm")
        Exit Sub
    ElseIf Request.Browser.JavaScript Then
        Response.Redirect ("JScripts.htm")
    Else
        Response.Redirect("NoScripts.htm")
    End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
      if (Request.Browser.VBScript)
            Response.Redirect("VBScripts.htm");
      else if (Request.Browser.JavaScript)
            Response.Redirect("JScripts.htm");
      else
            Response.Redirect("NoScripts.htm");
}

Another concern with client-side scripts is security. There are two aspects to this issue. First, your scripts are not secure—they can be viewed or copied by anyone visiting your application. Second, scripts can potentially spread viruses; therefore, some users set their browser security settings to disable scripts.

The Browser object' s VBScript and JavaScript properties will return True, even if scripting has been disabled. To check if scripting is enabled, you need to be a little clever. The following HTML tests if scripting is enabled by trying to run a script. If scripting is enabled, the user is redirected to another page and he or she never sees the message that follows the script:

<html>
   <script>
      window.navigate("scripts.aspx")
   </script>
   <!-- If scripting is enabled, the following is never displayed -->
   <head>
   <title>Scripts</title>
   </head>
   <body MS_POSITIONING="FlowLayout">
   <h2>Scripting is turned off.</h2>
   <p>Your Internet security options specify that your browser will not run
   scripts, therefore you cannot view the page you requested. To turn
   scripting on, reset your browser's Internet security options to Medium, or   enable active scripting in the custom security settings.</p>
   <p><a href="CheckBrowser.htm">Click here</a> to try again.</p>
   </body>
</html>
Choosing a Scripting Language

Client-side scripts can be written in either VBScript or JScript. Scripting languages have the following differences from compiled languages, such as Visual Basic .NET or Visual C#:

  • Script languages are not compiled. Instead, they are interpreted by the browser at run time.

  • Script keywords are a “safe subset” of their parent languages. Scripts don' t allow you to create or delete files to the user' s disk or to perform tasks that obviously risk crashing the user' s computer.

Both VBScript and JScript are functionally equivalent—they differ only in syntax and keywords. In general, Visual C# programmers are more comfortable using JScript because it follows the C conventions; Visual Basic programmers are more comfortable using VBScript because it is closely related to Visual Basic. However, only Internet Explorer supports VBScript, so if your application needs to run on other browsers, you should use JScript.

If you are a Visual Basic programmer, you will probably not like the fact that JScript is case sensitive. In JScript, all keywords, variables, object references, methods, and properties must be correctly capitalized in order to work.

For more information on the scripting languages, see the following Visual Studio online Help topics:

  • JScript User' s Guide

  • JScript Language Reference

  • VBScript User' s Guide

  • VBScript Language Reference

Understanding the DOM

The Document Object Model (DOM) is made up of the objects that the browser provides for scripting. These objects let you control the browser window, the current page (or document), and objects on the page. Figure 7-11 shows the objects provided by the DOM.

Figure 7-11 The DOM object hierarchy

Explaining all the objects in the DOM is outside the scope of this book. However, a great deal of information is available in the Visual Studio online Help in the DHTML References topic. That topic and the references it links to are part of the Web Workshop online Help.

Adding Inline Scripts

Web pages can include scripts inline as part of their content or as procedures that run in response to events. Inline scripts run the moment the browser encounters them. The example in the preceding section shows how this works by redirecting the browser to a new page if the browser allows scripting:

<html>
<script>
window.navigate("scripts.aspx")
</script>
   <!-- If scripting is enabled, the markup that follows
        this code is never displayed -->
   <head>
         <title>Scripts</title>
   </head>
   <body MS_POSITIONING="FlowLayout" onunload="return window_onunload()">
         <h2>Scripting is turned off.</h2>
         <p>Your Internet security options specify that your browser will
         not run scripts, therefore you cannot view the page you requested.
         To turn scripting on, reset your browser's Internet security
         options to Medium, or enable active scripting in the custom security
         settings.</p>
         <p><a href="CheckBrowser.htm">Click here</a> to try again.</p>
   </body>
</html>

If the script successfully runs, the user never sees the message that scripting is turned off because the script runs before any page content. If an inline script refers to an element on the page, that element must appear before the script refers to it. For example, the following HTML displays two text boxes and uses a script to move the cursor to the second text box:

VBScript
<HTML>
   <body language="vbscript">
         <form id="Form1" method="post" runat="server">
               <P>
               <asp:TextBox id="txtFirstName" Runat="server"></asp:TextBox>
               </P>
               <P>
               <asp:TextBox id="txtLastName" Runat="server"></asp:TextBox>
               </P>
               <script>window.document.all("txtLastName").focus()</script>
         </form>
   </body>
</HTML>
JScript
<HTML>
   <body language="javascript">
         <form id="Form1" method="post" runat="server">
               <P>
               <asp:TextBox id="txtFirstName" Runat="server"></asp:TextBox>
               </P>
               <P>
               <asp:TextBox id="txtLastName" Runat="server"></asp:TextBox>
               </P>
               <script>window.document.all["txtLastName"].focus()</script>
         </form>
   </body>
</HTML>

Inline scripts can also be used as attributes of HTML elements that perform actions, such as the HTML Button control. For example, the following HTML creates a button that opens a Help page in a new window:

<button id="butHelp" onclick="window.open('help.aspx', 'help',
'height=200,width=300')">Help</button>

Notice that the inline script above is language-neutral. In other words, it will work whether the language attribute of the <body> element is set to VBScript or JScript. To use a scripting language other that the one set in the <body> element, you need to set the <script> element' s language attribute. For example, the following scripts display a list of heading levels:

VBScript
<HTML>
   <script language="vbscript">
   For i = 1 to 6
         document.write("<h" & i & ">")
         document.write("H" & i)
         document.write("</h" & i & "><br>")
   Next
   </script>
</HTML>
JScript
<HTML>
   <script language="javascript">
   for (i = 1; i <= 6; i++)
   {
         document.write("<h" + i + ">");
         document.write("H" + i);
         document.write("</h" + i + "><br>");
   }
   </script>
</HTML>

The preceding code displays the seven HTML heading levels, as shown in Figure 7-12.

Figure 7-12 Inline scripts
Responding to Events with Script Procedures

Scripts also define procedures that respond to page events, such as the window loading, the buttons being clicked, and the mouse passing over objects.

To create a script event procedure, follow these steps:

  1. Using Visual Studio, create or open the page for which you want to include the client-side scripts. The page can be a Web form (.aspx) or an HTML page (.htm).

  2. Switch to the HTML view of the page.

  3. Using the drop-down list at the top left of the page, select the object for which you want to add the event procedure, as shown in Figure 7-13.

    Figure 7-13 Selecting the object

  4. Using the drop-down list at the top right of the page, select the event that you want the code to respond to, as shown in Figure 7-14.

    Figure 7-14 Selecting the event

  5. Visual Studio creates a script block containing an empty event procedure, as shown by the following code:

    VBScript
    <script id=clientEventHandlersVBS language=vbscript>
<!--

Sub butRunScript_onmouseover

End Sub

-->
</script>
    JScript
    <script id=clientEventHandlersJS language=javascript>
<!--

function butRunScript_onmouseover() {

}

//-->
</script>

Any code you add to the event procedure will run when that page event occurs. For example, the following code switches the mouse cursor to the hand symbol when the user moves the mouse over the button:

VBScript
Sub butRunScript_onmouseover
   butRunScript.style.cursor = "hand"
End Sub
JScript
function butRunScript_onmouseover() {
   // Display the Hand cursor when the mouse moves over the button.
   butRunScript.style.cursor = "hand";
}

Client-side event procedures can detect a wide variety of events, including keypresses, mouse-over events, clicks, loads, and unloads. Unlike server-side events, client-side events do not provide arguments directly. Instead, you use the window object' s event method to get values. For example, the following mousemove event procedure displays the coordinates of the cursor in the browser' s status bar:

VBScript
Sub document_onmousemove
   ' Display X, Y coordinates as the mouse moves across the browser window.
   window.status = window.event.clientX & ", " & window.event.clientY
End Sub
JScript
function window_onmousemove() {
   // Display X, Y coordinates as the mouse moves across the browser window.
   window.status = window.event.clientX + ", " + window.event.clientY;
}

The code in client-side event procedures can get and set the values displayed in server and HTML controls on the page. This means you can get values from the user and provide responses without posting the page back to the server. For example, the following HTML code defines a page that plays a simple game with the user—all without posting back to the server:

VBScript
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
   <head>
   <title>EventScript</title>
         <meta name="vs_defaultClientScript" content="VBScript">
         <meta name="vs_targetSchema"
           content="http://schemas.microsoft.com/intellisense/ie5">
         <meta name="GENERATOR" content="Microsoft Visual Studio.NET 7.0">
         <meta name="ProgId" content="VisualStudio.HTML">
         <meta name="Originator" content="Microsoft Visual Studio.NET 7.0">
<script id="clientEventHandlersVBS" language="vbscript">

Sub butRunScript_onclick
   ' Display a message in a text area.
   sMessage = "I am thinking of a number between 0 and 9. "
   sMessage = sMessage & "Press a number key to take a guess."
   txtMessage.value = sMessage
   ' Initialize the random number generator.
   Randomize
   ' Get a random number and store it in a hidden field.
   hidNumber.value = Int(9 * Rnd)
End Sub

Sub document_onkeypress
   ' If the keypress matches the number, display a success message.
   if (window.event.keyCode - 48) = CInt(hidNumber.value) then
         txtMessage.value = "You guessed it!"
   ' Otherwise tell the correct answer.
   else
         txtMessage.value = "You didn't guess it. It was: " & hidNumber.value
   end if
End Sub

</script>
   </head>
   <body MS_POSITIONING="FlowLayout">
   <p>
   <textarea id="txtMessage"></textarea>
   </p>
   <P>
   <INPUT type="button" id="butRunScript" value="Run Script"
     NAME="butRunScript">
   </P>
   <INPUT id="hidNumber" type="hidden" name="Hidden1">
</body>
</html>
JScript
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
   <HEAD>
         <META NAME="GENERATOR" Content="Microsoft Visual Studio 7.0">
         <TITLE></TITLE>
         <meta name="vs_defaultClientScript" content="JavaScript">
         <meta name="vs_targetSchema"
            content="http://schemas.microsoft.com/intellisense/ie5">
         <meta name="GENERATOR" content="Microsoft Visual Studio.NET 7.0">
         <meta name="ProgId" content="VisualStudio.HTML">
         <meta name="Originator" content="Microsoft Visual Studio.NET 7.0">
   <script id=clientEventHandlersJS language=javascript>
<!--

function window_onkeypress() {
   // If the keypress matches the number, display a success message.
   if ((window.event.keyCode - 48) == parseInt(hidNumber.value))
         txtMessage.value = "You guessed it!";
   // Otherwise tell the correct answer.
   else
         txtMessage.value = "You didn't guess it. It was: "
         + hidNumber.value;
}

function butRunScript_onclick() {
   // Display a message in a text area.
   sMessage = "I am thinking of a number between 0 and 9. "
     + "Press a number key to take a guess.";
   txtMessage.value = sMessage;
   // Get a random integer.
   hidNumber.value = Math.round(9 * Math.random());
}

//-->
</script>
</HEAD>
<BODY language=javascript onmousemove="return window_onmousemove()"
  onkeypress="return window_onkeypress()">
         <p>
         <textarea id="txtMessage" style="WIDTH: 200px; HEIGHT: 88px"
         rows="5" cols="22" NAME="txtMessage"></textarea>
         </p>
         <P>
         <INPUT type="button" id="butRunScript" value="Run Script"
           NAME="butRunScript" language=javascript onmouseover="return
           butRunScript_onmouseover()" onclick="return
           butRunScript_onclick()">
         </P>
         <P><INPUT id="hidNumber" type="hidden" name="Hidden1"></P>
   </BODY>
</HTML>

At run time, the preceding code displays a page that plays a guessing game, as shown in Figure 7-15.

Figure 7-15 The guessing game


Lesson 5: Consuming XML Web Services

XML Web services are business logic components that can be accessed over the Internet. Consuming a Web service simply means using that service from within your application. In this lesson, you will learn how to locate, reference, and use XML Web services from a Web application.

After this lesson, you will be able to

  • Search for XML Web services by company name

  • Add a reference to an XML Web service from a Visual Studio .NET project

  • Use an XML Web service from within a Web application

  • Use an XML Web service from client-side scripts

Estimated lesson time: 10 minutes
Finding XML Web Services

XML Web services are made public over the Web using a Universal Description, Discovery, and Integration (UDDI) registry. Currently, Microsoft and IBM manage the two UDDI registry nodes available for locating XML Web services. Businesses register their Web Services on these nodes so that customers (you and me) can locate the Web Services they want to use.

There are many different ways to search for XML Web services on the Internet. The easiest way is just to select XML Web services from the Start page in Visual Studio .NET, as shown by Figure 7-16.

Figure 7-16 Finding XML Web services

The XML Web services item on the Start page lets you locate XML Web services by general category of task that the service performs, such as calendar, financial, math, or weather. The Search In option buttons above the Category box let you search for XML Web services that have been put into production (presumably these have been debugged and tested) or are still under development.

Using an XML Web Service

Using an XML Web service is much the same as using a .NET or COM component: You establish a reference to the class, create an instance of an object from the class, and then use the object' s properties and methods within your code. There are several ways to establish a reference to an XML Web service from within Visual Studio .NET, perhaps the easiest is to do so from the Start page.

To reference XML Web services from the Visual Studio .NET Start page, follow these steps:

  1. Find the XML Web service you want to use by clicking the XML Web Services link on the Start page.

  2. Click on the Add As Web Reference To Your Current Project hyperlink underneath the XML Web service description. Visual Studio adds a Web reference to the Web References section of the Solution Explorer, as shown in Figure 7-17.

    Figure 7-17 Web references

To use the reference in code, follow these steps:

  1. Create a new object from the XML Web service class. The XML Web service class name is displayed in the Web References folder of the Solution Explorer.

  2. Use the XML Web service' s properties and methods from the new object. XML Web service classes work with Visual Studio' s autocomplete and Object Browser features, so using XML Web service properties and methods is the same as for any other object.

note | The following example depends on the availability of a third party Web site. At the time this book was written, this Web site was available. However, its future availability cannot be guaranteed.

For example, the following code uses the CDYNE Credit Card Checker Web service to validate a credit card number entered in a text box:

Visual Basic .NET
Private Sub butCheck_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butCheck.Click
        Dim wsCheck As New com.cdyne.secure.LUHNChecker()
        Dim wsValid As com.cdyne.secure.ReturnIndicator
        wsValid = wsCheck.CheckCC(txtNumber.Text)
        lblMsg.Text = wsValid.CardType & " " & wsValid.CardValid.ToString()
End Sub
Visual C#
private void butCheck_Click(object sender, System.EventArgs e)
{
   com.cdyne.secure.LUHNChecker wsCheck = new com.cdyne.secure.LUHNChecker();
   com.cdyne.secure.ReturnIndicator wsValid;
   wsValid = wsCheck.CheckCC(txtNumber.Text);
   lblMsg.Text = wsValid.CardType + " " + wsValid.CardValid.ToString();
}
Using XML Web Services from Client-Side Scripts

In some cases, it makes more sense to call an XML Web service from client-side scripts than from server code. An XML Web service might take a long time to respond—there' s no point in making the server wait for the response when the server is just going to pass that response on to the client anyway.

To use an XML Web service from client-side scripts, follow these steps:

  1. Create a style class for the WebService behavior (Webservice.htc).

  2. Add an HTML element to the page that uses the class you created in step 1.

  3. Write script procedures to initialize and call methods on the XML Web service using the XML Web service behavior.

The following HTML creates a webservice style class, initializes, and calls a Web service to display a daily quote when the user clicks Show Quote:

VBScript
<HTML>
   <HEAD>
         <title>WebForm2</title>
         <style>
               .webservice { BEHAVIOR:url(webservice.htc) }
         </style>
         <script language="VBScript">
Dim iCallID

Sub init(control, wsAddress, name)
   control.useService wsAddress, name
End Sub

Function getResult()
    if window.event.result.error And (iCallID = window.event.result.id) Then
      Dim xfaultcode, xfaultstring, xfaultsoap
      xfaultcode = window.event.result.errorDetail.code
      xfaultstring = window.event.result.errorDetail.string
      xfaultsoap = window.event.result.errorDetail.raw
      ' Display error information.
      alert("Error " & xfautlcode & " │ " & xfaultstring & " │ " & xfaultSoap)
   Else
      getResult = window.event.result.value
   End If
End Function

Sub getQuote()
   ' Initialize Web service on the selected control.
   init ws, "http://webservice.effective-web.net/globalself/
globalselfDailyThought.WSDL", "DailyQuote"
   ' Call a method within the Web service
   iCallID = ws.DailyQuote.callService("getTodaysQuote")
   ' Result is displayed in the ws div element by onresult.
End Sub
         </script>
   </HEAD>
   <body>
         <form id="WebForm2" method="post" runat="server">
               <h2>Using Web Services from Client-Side Code</h2>
               <div id="ws" class="webservice"
               onresult="ws.innerText = getResult()"></div>
               <br>
               <input type="button" onclick="getQuote()" value="Get Quote">
         </form>
         </DIV>
   </body>
</HTML>
JScript
<HTML>
   <HEAD>
         <title>WebForm2</title>
         <style>
               .webservice { BEHAVIOR:url(webservice.htc) }
         </style>
         <script language="JScript">
var iCallID;

function init(control, wsAddress, name)
{
   control.useService(wsAddress, name);
}

function getResult()
{
    if((event.result.error)&&(iCallID==event.result.id))
   {
      var xfaultcode = event.result.errorDetail.code;
      var xfaultstring = event.result.errorDetail.string;
      var xfaultsoap = event.result.errorDetail.raw;
      // Display error information.
      alert("Error " + xfautlcode + " │ " + xfaultstring + " │ " + xfaultSoap);
   }
   else
   {
      return event.result.value;
   }
}

function getQuote()
{
   // Initialize Web service on the selected control.
   init(ws, "http://webservice.effective-web.net/globalself/
globalselfGlobalSelf.WSDL", "DailyQuote");
   // Call a method within the Web service
   iCallID = ws.DailyQuote.callService("getTodaysQuote");
   // Result is displayed in the ws div element by onresult.
}
         </script>
   </HEAD>
   <body>
         <form id="WebForm2" method="post" runat="server">
               <h2>Using Web Services from Client-Side Code</h2>
               <div id="ws" class="webservice"
               onresult="ws.innerText = getResult();"></div>
               <br>
               <input type="button" onclick="getQuote()" value="Get Quote">
         </form>
         </DIV>
   </body>
</HTML>

For more information about using XML Web services from client-side scripts, search for “WebService Behavior” in the Visual Studio online Help.


Summary

  • Cookies enable you to invisibly save information about a particular user. Cookies can be used to save the information directly on the user' s computer or indirectly through a unique identifier, which is then referenced to retrieve user information from a data set stored on the server.

  • Use the Browser object' s Cookies property to test whether a browser accepts cookies before you attempt to create them.

  • Use the Request object' s Cookies collection to retrieve a cookie from the user. Use the Response object' s Cookies collection to create or modify a cookie on the user' s computer.

  • Set the Cookie object' s Expires property to Now to delete a cookie from the user' s computer.

  • Use the mailto: protocol to create a message that will be sent from the user' s mail system. The mailto: protocol is used as part of a hyperlink.

  • Use the MailMessage and SmtpMail classes to compose and send messages from the server' s mail system.

  • Frames allow you to display multiple pages in a single browser window. The display and scrolling of each frame is controlled independently.

  • Use the Browser object' s Frames property to test whether a browser supports frames before displaying them.

  • Hyperlinks on one frame can display a page in another frame by using the target attribute. You can control frames only from hyperlinks or client-side scripts.

  • Client-side scripts let you control the browser window, respond immediately to non-post-back events, and perform other tasks that are not possible from server-side code.

  • Use the Browser object' s VBScript or JavaScript properties to test if the browser can run scripts. Use an inline test script that redirects the users to another page to test whether a user has disabled scripting through his or her browser security settings.

  • Add script event procedures to a page' s HTML to respond to events that occur on the client side rather than the server side.


Lab: Using Advanced Features

In this lab, you will create an application that displays multiple pages using frames. This application lets users choose a background color for the application, compose and send mail from the server, and play a simple game. The Advanced Features application demonstrates these key features:

  • Testing browser capabilities to make sure a user' s browser supports frames, cookies, and scripts

  • Saving cookies on the user' s computer and then retrieving those cookies to set the background color for the application

  • Sending mail from the server using the MailMessage and SmtpMail objects

  • Displaying pages in frames and controlling the display of those frames from a Contents page

When complete, the Advanced Features application will appear as shown in Figure 7-18.

Figure 7-18 The Advanced Features application
Estimated lesson time: 45 minutes
Exercise 1: Check Advanced Feature Support

In this exercise, you will create a default page for an application that checks to see if the user' s browser accepts cookies, can display frames, and runs scripts. If any of these features are not available, an appropriate message is displayed.

A default page is a page that IIS displays if the user navigates to the Web application directory without specifying a particular page to view. IIS uses the names Default.htm and Default.aspx for the default page unless you change them for the application through IIS.

To create a default page that checks the user' s browser for advanced feature support:

  1. Add a Web form to your application named Default.aspx and make that page the start page for your application.

  2. Add the following Page_Load event procedure to the Web form:

    Visual Basic .NET
    Private Sub Page_Load(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles MyBase.Load
    ' Create a flag to track if any advanced features are disabled.
    Dim bProceed As Boolean = True
    ' Check if browser accepts cookies.
    If Request.Browser.Cookies = False Then
        Response.Write("Your browser does not accept cookies.<br>" & _
          "Change your browser's security settings allow cookies." & _
          "<br><br>")
        bProceed = False
    End If
    ' Check if browser supports scripting.
    If Request.Browser.JavaScript = False Then
        Response.Write("Your browser does not support scripts.<br>" & _
        "Update your browser software to the latest version.<br><br>")
        bProceed = False
    End If
    ' Check if browser supports VBScript.
    If Request.Browser.VBScript = False Then
        Response.Write("Your browser does not support VBScript.<br>" & _
          "Install the latest version of Internet Explorer.<br><br>")
        bProceed = False
    End If
    ' If browser passes other tests, see if scripting is enabled by
    ' trying a script.
    If bProceed Then
        ' If scripts are disabled, the following line is ignored.
        ' Otherwise, it displays the frameset.
        Response.Write("<script>window.navigate('frameset.htm')</script>")
        Response.Write("Scripting is disabled due to your browser " & _
          "security settings.<br> Change your browser's security " & _
          "settings allow scripting.<br><br>")
    End If
End Sub
    Visual C#
    private void Page_Load(object sender, System.EventArgs e)
{
      // Create a flag to track if any advanced features are disabled.
      bool bProceed = true;
      // Check if browser accepts cookies.
      if (!Request.Browser.Cookies)
      {
            Response.Write("Your browser does not accept cookies.<br>" +
              "Change your browser's security settings allow cookies." +
              "<br><br>");
            bProceed = false;
      }
      // Check if browser supports scripting.
      if (!Request.Browser.JavaScript)
      {
            Response.Write("Your browser does not support scripts.<br>" +
              "Update your browser software to the latest " +
              "version.<br><br>");
            bProceed = false;
      }
      // Check if browser supports VBScript.
      if (!Request.Browser.VBScript)
      {
            Response.Write("Your browser does not support VBScript.<br>" +
            "Install the latest version of Internet Explorer.<br><br>");
            bProceed = false;
      }
      // If browser passes other tests, see if scripting is enabled by
      // trying a script.
      if (bProceed)
      {
            // If scripts are disabled, the following line is ignored.
            // Otherwise, it displays the frameset.
            Response.Write("<script>window.navigate('frameset.htm')" +
              "</script>");
            Response.Write("Scripting is disabled due to your browser " +
              "security settings.<br> Change your browser's security " +
              "settings allow scripting.<br><br>");
      }
}

  3. Add the following HTML content to the Web form:

    <p>This Web application uses features that are either not available in
your browser or are disabled through your browser's security settings.
You cannot run this application until you update your browser as described
above. Then click <a href="default.aspx">here</a> to try again.</p>

When the default page you created with the preceding steps loads, it checks each of the advanced features that a browser might or might not support. If there are problems using any of those features, the page displays a warning message, as shown in Figure 7-19.

Figure 7-19 Warning message

If no problems are encountered, the user never sees the default page. Instead, he or she is redirected immediately to the frameset, which displays the other pages in the application.

Exercise 2: Store User Information

In this exercise, you will create a Web form that allows the user to select a background color. The form “remembers” the user' s choice between sessions by saving the background in a cookie on the user' s machine. When complete, the Web form will appear as shown in Figure 7-20.

Figure 7-20 The Background.aspx Web form
To create the Background Web form:

  1. Create a new Web form named Background.aspx.

  2. Add a DropDownList server control to the Web form containing list items for some different background colors. This step is probably easiest to do in HTML rather than in Design mode, because you can use cut and paste to create the list items quickly in HTML. The following HTML shows the definition for the DropDownList and its list items:

    <asp:dropdownlist id="drpBackground" AutoPostBack="True" Runat="server" Width="128px">
   <asp:ListItem Value="White">White</asp:ListItem>
   <asp:ListItem Value="Silver">Grey</asp:ListItem>
   <asp:ListItem Value="LightSteelBlue">Light steel blue</asp:ListItem>
   <asp:ListItem Value="Honeydew">Honeydew</asp:ListItem>
   <asp:ListItem Value="Azure">Azure</asp:ListItem>
   <asp:ListItem Value="Linen">Linen</asp:ListItem>
   <asp:ListItem Value="MintCream">Mint cream</asp:ListItem>
</asp:dropdownlist>

  3. Modify the Web form' s <body> element to specify a background color using data binding from the DropDownList. The following HTML shows the changes to the <body> element:

    <body bgColor="<%# drpBackground.SelectedItem.Value %>">

  4. Add the following code to the Page_Load event procedure to check for a cookie, create the cookie if it does not exist, or set the background color from the cookie if it does exist. The Page_Load event procedure also performs data binding for the page to update the background color.

    Visual Basic .NET
    Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' On first display
    If IsPostBack = False Then
        ' Check if cookie doesn't exist, create it.
        If Request.Cookies("BackgroundColor") Is Nothing Then
            ' Create a cookie with the default background color.
            Dim cookBackground As New HttpCookie("BackgroundColor", "0")
            ' Set the cookie to expire in one day.
            cookBackground.Expires = DateTime.Now.AddDays(1)
            ' Add the cookie to the response object.
            Response.Cookies.Add(cookBackground)
        Else
            ' Get the user's background color from the cookie.
            drpBackground.SelectedIndex = _
              CInt(Request.Cookies("BackgroundColor").Value)
        End If
    End If
    ' Update bound field to set background color
    Page.DataBind()
End Sub
    Visual C#
    private void Page_Load(object sender, System.EventArgs e)
{
      // On first display
      if (!IsPostBack)
      {
            // Check if cookie doesn't exist, create it.
            if (Request.Cookies["BackgroundColor"] == null)
            {
                  // Create a cookie with the default background color.
                  HttpCookie cookBackground = new
                  HttpCookie("BackgroundColor", "0");
                  // Set the cookie to expire in one day.
                  cookBackground.Expires = DateTime.Now.AddDays(1);
                  // Add the cookie to the response object.
                  Response.Cookies.Add(cookBackground);
            }
            else
            {
                // Get the user's background color from the cookie.
                drpBackground.SelectedIndex =
                 Convert.ToInt16(Request.Cookies["BackgroundColor"]
                 .Value);
            }
      }
      // Update bound field to set background color
      Page.DataBind();
}

  5. Add the following code to update the cookie through the Response object when the user selects a background color from the DropDownList:

    Visual Basic .NET
    Private Sub drpBackground_SelectedIndexChanged(ByVal sender As _
  System.Object, ByVal e As System.EventArgs) Handles _
  drpBackground.SelectedIndexChanged
    ' Record color selection in a cookie.
    Dim cookBackground As New HttpCookie("BackgroundColor")
    ' Set the cookie's value.
    cookBackground.Value = drpBackground.SelectedIndex
    ' Set the cookie's expiration.
    cookBackground.Expires = DateTime.Now.AddDays(1)
    ' Add the cookie to the Response object.
    Response.Cookies.Add(cookBackground)
End Sub
    Visual C#
    private void drpBackground_SelectedIndexChanged(object sender,
  System.EventArgs e)
{
      // Record color selection in a cookie.
      HttpCookie cookBackground = new HttpCookie("BackgroundColor");
      // Set the cookie's value.
      cookBackground.Value = drpBackground.SelectedIndex.ToString();
      // Set the cookie's expiration.
      cookBackground.Expires = DateTime.Now.AddDays(1);
      // Add the cookie to the Response object.
      Response.Cookies.Add(cookBackground);

}
Exercise 3: Create a Mail Web Form

In this exercise, you will create a Web form that allows you to compose and send a mail message from the server. When complete, the Mail Web form will appear as shown in Figure 7-21.

Figure 7-21 The Mail.aspx Web form
To create the Mail Web form:

  1. Create a new Web form named Mail.aspx.

  2. Add text and server controls to the Web form, as shown by the following HTML:

    <P>
From:&nbsp;&nbsp;&nbsp;
<asp:TextBox ID="txtFrom" Runat="server" Width="376px"></asp:TextBox>
<asp:RequiredFieldValidator ID="vldtxtFrom" ControlToValidate="txtFrom" Runat="server" ErrorMessage="Required field.">
</asp:RequiredFieldValidator>
<br>
To:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<asp:TextBox ID="txtTo" Runat="server" Width="377px"></asp:TextBox>
<asp:RequiredFieldValidator ID="vldTo" ControlToValidate="txtTo" Runat="server" ErrorMessage="Required field.">
</asp:RequiredFieldValidator>
<br>
Subject:
<asp:TextBox ID="txtSubject" Runat="server" Width="376px"></asp:TextBox>
</P>
<asp:TextBox ID="txtMessage" Runat="server" TextMode="MultiLine" Width="432px" Height="208px"></asp:TextBox>
<br>
<asp:Button ID="butSend" Runat="server" Text="Send"></asp:Button>
<input type="reset" id="butReset" value="Cancel">
<br>
<asp:literal id="litStatus" runat="server"></asp:literal>

    The RequiredFieldValidator controls in the preceding HTML are needed because the From and To properties of the MailMessage object must be set before the message can be sent.

  3. Add the following Imports or using statement to the beginning of the Web form' s code module so that you can use short names for members of the System.Web.Mail namespace:

    Visual Basic .NET
    Imports System.Web.Mail
    Visual C#
    using System.Web.Mail;

  4. Add the following code to the butSend_Click event procedure to create a MailMessage object and to send the message from the server:

    Visual Basic .NET
    Private Sub butSend_Click(ByVal sender As System.Object, +
  ByVal e As System.EventArgs) Handles butSend.Click
    ' Create message.
    Dim msgMail As New MailMessage()
    ' Set message properties.
    msgMail.From = txtFrom.Text
    msgMail.To = txtTo.Text
    msgMail.Subject = txtSubject.Text
    msgMail.Body = txtMessage.Text
    ' Send message.
    SmtpMail.Send(msgMail)
    ' Clear To, Subject, and Message fields.
    txtTo.Text = ""
    txtSubject.Text = ""
    txtMessage.Text = ""
    ' Show success.
    litStatus.Text = "<p>Message sent.</p>"
End Sub
    Visual C#
    private void butSend_Click(object sender, System.EventArgs e)
{
      // Create message.
      MailMessage msgMail = new MailMessage();
      // Set message properties.
      msgMail.From = txtFrom.Text;
      msgMail.To = txtTo.Text;
      msgMail.Subject = txtSubject.Text;
      msgMail.Body = txtMessage.Text;
      // Send message.
      SmtpMail.Send(msgMail);
      // Clear To, Subject, and Message fields.
      txtTo.Text = "";
      txtSubject.Text = "";
      txtMessage.Text = "";
      // Show success.
      litStatus.Text = "<p>Message sent.</p>";
}

  5. Add the following code to the Page_Load event procedure to clear the status message if the user cancels sending a message after successfully sending an earlier message:

    Visual Basic .NET
    Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    ' Clear status message.
    litStatus.Text = ""
End Sub
    Visual C#
    private void Page_Load(object sender, System.EventArgs e)
{
      // Clear status message.
      litStatus.Text = "";
}
Exercise 4: Create a Frame-Based User Interface

In this exercise, you will create a frameset containing regions for a table of contents, a banner, and a body. You will create a table of contents page that displays selected pages in the body frame and updates the banner frame using a script. When complete, the frameset will appear as shown in Figure 7-22.

Figure 7-22 The completed frameset at run time
To create the frame-based user interface:

  1. Create a new HTML page named Contents.htm.

  2. Add the following hyperlinks to the Contents page:

    <a href="Background.aspx" target="main" onclick="ShowBanner('Choose Background')">Choose Background</a>
<br>
<a href="Mail.aspx" target="main" onclick="ShowBanner('Send Mail From Server')">Send Mail</a>
<br>
<a href="Game.htm" target="main" onclick="ShowBanner('Play Game')">Play a
Game</a>
<br>

    The hyperlinks shown in the preceding HTML provide a way to navigate to the other pages in the application and will display those pages in the main frame of the frameset you will create in the following steps.

  3. Add the following script procedure to the Contents page.

    VBScript
    <script language="vbscript">
' Updates the text in the banner frame.
Sub ShowBanner(Message)
   ' Write text to banner frame.
   parent.frames("banner").document.write("<h2>" & Message & "</h2>")
   ' Close document so next write replaces the current text.
   parent.frames("banner").document.close
End Sub
</script>
    JScript
    <script language="javascript">
// Updates the text in the banner frame.
function ShowBanner(Message)
{
   // Write text to banner frame.
   parent.frames["banner"].document.write("<h2>" + Message + "</h2>");
   // Close document so next write replaces the current text.
   parent.frames["banner"].document.close();
}
</script>

    The preceding ShowBanner script displays a title in the banner frame of the frameset from each of the hyperlinks created in the previous step.

  4. Create a frameset to display the project pages in. From the Project menu, choose Add New Item, and then select Frameset from the Templates list and name the file Frameset.htm. When you click Open, Visual Studio displays the Select A Frameset Template dialog box.

  5. Select the Banner And Content frameset template and click OK. Visual Studio displays a new, empty frameset in the Design window.

  6. Right-click in the leftmost frame and select Set Page For Frame from the pop-up menu. Visual Studio displays the Select Page dialog box.

  7. Select Contents.htm in the Select Page dialog box and click OK to display the Contents page in the frame.

  8. Make the frameset the start page for the application. In the Solution Explorer window, right-click the Frameset.htm file and select Set As Start Page from the pop-up menu.

Exercise 5: Extra Practice Using Scripts and Cookies

Exercises 1 through 4 walked you through the tasks you learned in this chapter. Now strike out on your own by attempting to complete the following tasks that extend the Advanced Features application:

  • Add a Window_OnLoad script to the Contents page to display a welcome message in the banner frame the first time the page is displayed.

  • Add the Guess A Number Web form created at the end of Lesson 4 to the Advanced Features application.

  • Create a cookie within the Mail Web form to remember the From address entered by the user.

  • Use the background color set on the Background Color Web form to change the background color displayed on each page in the frameset.

When you are done, compare your results to the Advanced Features application included on this book' s companion CD-ROM. Good luck!


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. Write the HTML for a hyperlink that will send mail when the user clicks the link.

  2. Show the code that writes a cookie containing the user name “Rob Young” and the current date to the user' s computer. Set the cookie to remain on the user' s computer for 30 days.

  3. Why can' t you open a new browser window using server-side code? How would you display a page in a new window with a client-side script?

  4. How do you display a page in one frame from a hyperlink in another frame?


Maintaining Security

Chapter 8
Maintaining Security
Lesson 1: Authenticating and Authorizing Users
Lesson 2: Using Windows Authentication
Lesson 3: Using Forms Authentication
Lesson 4: Using Passport Authentication
Lesson 5: Providing Secure Communication
About This Chapter

In this chapter, you will learn how to control access to your Web application using the three techniques that ASP.NET provides for identifying users and allowing them to have access to your application. You will also learn how to secure data transmitted across the Internet so that others cannot read it.

This chapter does not provide a comprehensive guide to server or network security. That is truly a broad subject involving a great deal of conceptual and system-specific information. What this chapter does provide is a practical guide to implementing Web application security through ASP.NET.

Before You Begin

To complete the lessons in this chapter, you must

  • Have access to two computers in a Microsoft Windows network. This can be a simple workgroup-based network such as you might set up at home, but a domain-based network is best.

  • Have Administrator privileges on a computer that functions as a server on the network.

If you don' t have access to a Windows network, you can perform most of the tasks in this chapter using a single computer and running the applications locally, as you do in Microsoft Visual Studio' s Debug mode. However, you will not be able to easily test authentication of multiple users.


Lesson 1: Authenticating and Authorizing Users

Authentication is the process of identifying users. Authorization is the process of granting access to those users based on identity. Together, authentication and authorization provide the means to keeping your Web application secure from intruders.

In this lesson, you will learn how ASP.NET handles anonymous users, and you' ll get an overview of the different ways you can identify and authorize users. This information will help you choose an authentication strategy based on the type of application you are creating.

After this lesson, you will be able to

  • Describe how ASP.NET grants access to users who are not authenticated and how those users appear within Windows security tools

  • Choose an authentication method based upon your application' s needs

  • Add HTML pages to the list of file types that will be included with your application' s authentication and authorization settings

Estimated lesson time: 10 minutes
Access by Anonymous Users

Anonymous access is the way most public Web sites work—sites containing public information allow anyone to see that information, so they don' t authenticate users. ASP.NET Web applications provide anonymous access to resources on the server by impersonation. Impersonation is the process of assigning a user account to an unknown user.

By default, the anonymous access account is named IUSER_machinename. You use that account to control anonymous users' access to resources on the server.

To see or change the access privileges to the anonymous access account, use the Windows Computer Management snap-in as described in the following steps:

  1. Log on to the server as the computer administrator.

  2. From the Start menu, choose Administrative Tools, then choose Computer Management to run the Computer Management console, as shown in Figure 8-1.

    Figure 8-1 Viewing users

  3. From the list on the left, choose Local Users And Groups and then select the Users folder to display the list of authorized users for this computer.

  4. From the users list on the right, double-click the anonymous user account named IUSR_computername. The Computer Management console displays the account' s properties, as shown in Figure 8-2.

    Figure 8-2 Properties of the anonymous access account

  5. Click the Member Of tab to view the user groups the account belongs to. By default, anonymous users belong to the Guests group, which has limited privileges.

Under the default settings, ASP.NET uses the ASPNET account to run the Web application. This means that if the application attempts to perform any tasks that are not included in the ASPNET account' s privileges, a security exception will occur and access will be denied. The account name will also show up in the security event log of the event viewer, as shown by Figure 8-3.

Figure 8-3 The security event log in the Computer Management console

You restrict the access of anonymous users by setting Windows file permissions. To be secure, your server must use the NT file system (NTFS). The earlier FAT or FAT32 file systems do not provide file-level security. For more information about setting Windows file permissions, see the Windows security Help topics.

Access by Authenticated Users

As stated earlier, anonymous access is fine for public information. But if your application contains private information or performs restricted tasks such as placing orders, you will want to authenticate and authorize individual users.

There are three major ways to authenticate and authorize users within an ASP.NET Web application:

  • Windows integrated authentication. Identifies and authorizes users based on the server' s users list. Access to resources on the server is then granted or denied based on the user account' s privileges. This works the same way as regular Windows network security.

  • Forms authentication. Directs users to a logon Web form that collects user name and password information, and then authenticates the user against a user list or database that the application maintains.

  • Passport authentication. Directs new users to a site hosted by Microsoft so that they can register a single user name and password that will authorize their access to multiple Web sites. Existing users are prompted for their Passport user name and password, which the application then authenticates from the Microsoft Passport user list.

Each of these approaches, along with anonymous access, has different advantages. These authentication methods are best suited for different types of Web applications, as summarized in Table 8-1.

Table 8-1 Web Application Types and Authentication Techniques
Application type | Use this type of authentication | Description
Public Internet Web application | Anonymous | This is the common access method for most Web sites. No logon is required and you secure restricted resources using NTFS file permissions.
Intranet Web application | Windows integrated | Windows authentication authenticates network users through the domain controller. Network users have access to Web application resources as determined by their user privileges on the server.
Private corporate Web application | Windows integrated | Corporate users can access the Web application using their corporate network user names and passwords. User accounts are administered using the Windows network security tools.
Commercial Web application | Forms | Applications that need to collect shipping and billing information should implement Forms authentication to gather and store customer information.
Multiple commercial Web applications | Passport | Passport authentication allows users to sign in once through a central authority. The user' s identity is then available to any application using the Passport SDK. Customer information is maintained in a Passport Profile, rather than in a local database.

The following sections describe how to authenticate and authorize users using each of the three techniques.

Using Authentication with HTM and HTML Files

The three ASP.NET authentication modes apply to files that are part of the Web application. That includes Web forms (.aspx), modules (.asax), and other resources that are processed through the Web application' s executable. It does not automatically include HTML pages (.htm or .html). Those pages are handled by IIS, rather than ASP.NET. If you want to authenticate users who access HTML pages from within your Web application using Windows, Forms, or Passport authentication modes, you must map those files to the ASP.NET executable.

To map .htm and .html files to the ASP.NET executable using the IIS snap-in, follow these steps:

  1. In IIS, select the folder containing your Web application and then choose Properties from the Action menu. IIS displays the Properties dialog box.

  2. Click the Directory tab and then click Configuration. IIS displays the Application Configuration dialog box, as shown in Figure 8-4.

    Figure 8-4 The Application Configuration dialog box

  3. Click Add. IIS displays the Add/Edit Application Extension Mapping dialog box, as shown in Figure 8-5.

    Figure 8-5 The Add/Edit Application Extension Mapping dialog box

  4. Click Browse and select the aspnet_isapi.dll file. That file is stored in the Windows Microsoft .Net Framework directory and the path will be something like C:\Windows\Microsoft.NET\Framework\versionnumber\aspnet_isapi.dll.

  5. Type .htm in the File Extension text box and click OK.

  6. Repeat steps 3 through 5 for the .html file extension. Click OK to close the IIS dialog boxes when you' re done.


Lesson 2: Using Windows Authentication

Windows authentication uses the security features integrated into the Windows NT and Windows XP operating systems to authenticate and authorize Web application users. The advantage of Windows authentication is that your Web application can use the exact same security scheme that applies to your corporate network—user names, passwords, and permissions are the same for network resources and Web applications.

After this lesson, you will be able to

  • Activate Windows authentication in your Web application

  • Allow or deny access to your application based on the user' s name or role in your organization

  • Identify users within your application once they have logged on

  • Use IIS settings to provide other forms of authentication

Estimated lesson time: 20 minutes
Enabling Windows Authentication

Windows authentication is the default authentication method when you create a new Web application project.

To see how Windows authentication works, follow these steps:

  1. Create a new Web application project. For a Visual Basic .NET project, change the authorization element to appear as follows. For a Visual C# project, add the following authorization element:

    <authorization>
    <deny users="?"  />  </authorization>

  2. Add the following HTML table definition to the project' s startup Web form:

    <TABLE id="tblUser" >
    <TD>Authenticated</TD>
    <TD><span runat="server" id="spnAuthenticated"></span></TD>
    <TD>User name</TD>
    <TD><span runat="server" id="spnUserName"></span></TD>
    <TD>Authentication type</TD>
    <TD><span runat="server" id="spnAuthenticationType"></span></TD>
</TABLE>

  3. Switch the Web form to Design view, and add the following code to the startup Web form' s code-behind module:

    Visual Basic .NET
    Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    spnAuthenticated.InnerText = User.Identity.IsAuthenticated
    spnUserName.InnerText = User.Identity.Name
    spnAuthenticationType.InnerText = User.Identity.AuthenticationType
End Sub
    Visual C#
    private void Page_Load(object sender, System.EventArgs e)
{
    spnAuthenticated.InnerText = User.Identity.IsAuthenticated.ToString();
    spnUserName.InnerText = User.Identity.Name;
    spnAuthenticationType.InnerText = User.Identity.AuthenticationType;
}

  4. Run the project.

When the project runs locally, ASP.NET will authenticate you using the name that you used to log on to Windows, as shown in Figure 8-6.

Figure 8-6 Windows integrated authentication running locally

When you run the project remotely (as across the Internet), ASP.NET displays a dialog box in the browser to collect your user name and password, as shown in Figure 8-7.

Figure 8-7 Windows integrated authentication running remotely

If the user name and password you enter match those authorized for the network domain, ASP.NET authenticates you and authorizes you to use the application. The application executes using the permissions found in your account.

When a user is authorized, ASP.NET issues an authorization certificate in the form of a cookie that persists for the duration of the user' s session. The user' s session ends when the browser closes or when the session times out.

Windows integrated authentication works best in a domain-based network. Networks that use workgroups rather than domains have more limited use of network security features. Domain-based networks use a domain controller to identify and authorize network users, as shown in Figure 8-8.

One of the key advantages of Windows integrated authentication is that users who are logged on to the network don' t have to log on again to access the Web application. They are automatically authenticated. Another advantage is that corporate users can use their same network user names and passwords when accessing the Web site remotely from home or while on business trips.

Windows integrated authentication also lets you establish another layer of security by permitting or prohibiting specific users or groups of users. Also, Windows integrated authorization overlaps similar features found in IIS. The following sections describe these topics in more detail.

Figure 8-8 Domain-based network authentication
Allowing or Denying Access to Specific Users

When the application uses Windows integrated authentication, ASP.NET checks the project' s Web.config authorization list to see which network users are allowed to access the application. The asterisk (*) and question mark (?) characters have special meaning in the authorization list: the * character indicates all users; the ? character indicates unauthorized users. For example, the following authorization list from Web.config requires all users to be authenticated:

<authorization>
    <deny users="?"  />   <!-- Deny unauthenticated users -->
</authorization>

To restrict access to specific users, list their names separated by commas in an <allow> element. When ASP.NET checks the authorization list in Web.config, it accepts the first match that it finds. Be to sure to end the authorization list with a <deny> element to deny access to any non-approved users, as shown here:

<authorization>
<allow users="Deanna Meyer, Michael Emanuel" />   <!-- Allow two users. -->
<deny users="*"  />                        <!-- Deny anyone else. -->
</authorization>

The preceding authorization list allows only two users access to the Web application. Everyone else is denied. In addition, Deanna Meyer and Michael Emanuel must have accounts on the network domain.

Using Role-Based Authorization

Role-based authorization lets you identify groups of users to allow or deny based on their role in your organization. In Windows NT and Windows XP, roles map to names used to identify user groups. Windows defines several built-in groups, including Administrators, Users, and Guests. You can view, modify, or add groups using the Computer Management console, as shown in Figure 8-9.

Figure 8-9 Viewing groups

To allow or deny access to certain groups of users, add the <roles> element to the authorization list in your Web application' s Web.config file. For example, the following authorization list allows access only to users logged on as system administrators:

<authorization>
    <allow roles="Administrators" />    <!-- Allow Administrators. -->
    <deny users="*"  />             <!-- Deny anyone else. -->
</authorization>
Getting the User Identity

Once a user is authenticated and authorized, your application can get information about the user by using the User object' s Identity property. The Identity property returns an object that includes the user name and role information, as shown by the following code:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load
     spnAuthenticated.InnerText = User.Identity.IsAuthenticated
     spnUserName.InnerText = User.Identity.Name
     spnAuthenticationType.InnerText = User.Identity.AuthenticationType
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
    spnAuthenticated.InnerText =
      User.Identity.IsAuthenticated.ToString();
    spnUserName.InnerText = User.Identity.Name;
    spnAuthenticationType.InnerText = User.Identity.AuthenticationType;
}

In addition, the User object provides an IsInRole method to determine the role of the current user. For example:

Visual Basic .NET
If User.IsInRole("Administrators") Then
    ' Do something
End If
Visual C#
if(User.IsInRole("Administrators"))
    // Do something.

These methods and properties can be used in conjunction with the Global module' s AuthorizeRequest event to customize the user authorization process. For example, you can use the AuthorizeRequest event to check user names against an external user file rather than use the list in Web.config.

Using IIS Settings with Windows Authentication

The authorization settings in Web.config overlap settings available in IIS. If authorization is set both in Web.config and IIS, the IIS setting is evaluated first and then the setting in Web.config is evaluated. In general, this means that the most restrictive setting will be used.

To view authorization settings in IIS, follow these steps:

  1. In the IIS snap-in, right-click the Web application' s folder and select Properties. IIS displays the folder' s Properties dialog box.

  2. Click the Directory Security tab and then click Edit in the Anonymous Access And Authentication Control group. IIS displays the Authentication Methods dialog box, as shown in Figure 8-10.

    Figure 8-10 IIS authentication settings

The first group of settings in the dialog box controls anonymous access by unauthenticated users. Clearing the check box is the equivalent of specifying <deny users=“?"> in Web.config. Allowing IIS to control the password for the anonymous account is highly recommended, but this setting might need to be overridden if your application is deployed over multiple servers.

The check boxes in the second group of dialog box controls allows your application to use Basic or Digest authentication in addition to Windows authentication. Those authentication methods provide less security than Windows integrated, Forms, or Passport authentication and are implemented through IIS rather than ASP.NET.

As you can see from Figure 8-10, you can enable multiple authentication methods through IIS. If multiple methods are enabled, you can detect which method was used to authenticate a user in code by using the Identity object' s AuthenticationType method, as shown here:

Visual Basic .NET
Response.Write(User.Identity.AuthenticationType)
Visual C#
Response.Write(User.Identity.AuthenticationType);

For more information about Basic and Digest authentication, see the IIS Help.


Lesson 3: Using Forms Authentication

Forms authentication automatically displays a designated Web form to collect user name and password information. Code associated with that Web form authenticates and authorizes users based on a users list stored in the application' s Web.config file or in a separate user database.

The advantage of Forms authentication is that users do not have to be member of a domain-based network to have access to your application. Another advantage is that many Web applications—particularly commercial sites where customers order products—want to have access to user information. Forms authentication makes these types of applications easier to create.

After this lesson, you will be able to

  • Activate Forms authentication for your Web application

  • Create a Web form to collect user name and password information

  • Authenticate users based on a user list stored in the application' s Web.config file

  • Add new users and authenticate existing ones from a database

Estimated lesson time: 20 minutes
Enabling Forms Authentication

Forms authentication allows you to create your own database of users and validate the identity of those users when they visit your Web site.

To use Forms authentication to identify and authorize users, follow these steps:

  1. Set the authentication mode in Web.config to Forms.

  2. Create a Web form to collect logon information.

  3. Create a file or database to store user names and passwords.

  4. Write code to add new users to the users file or database.

  5. Write code to authenticate users against the users file or database.

When someone accesses a Web application that uses Forms authentication, ASP.NET displays the logon Web form specified in Web.config. Once a user is authorized, ASP.NET issues an authorization certificate in the form of a cookie that persists for an amount of time specified by the authentication settings in Web.config. Figure 8-11 illustrates the authentication process.

Figure 8-11 Forms authentication

The difference between Windows integrated authentication and Forms authentication is that in Forms authentication your application performs all the authentication and authorization tasks. You must create Web forms and write code to collect user names and passwords and to check those items against a list of authorized users.

Forms authentication uses the classes found in the System.Web.Security namespace. To use these classes in your code, you should add an Imports statement (Visual Basic .NET) or a using (Visual C#) statement at the beginning of each module that performs authentication, as shown here:

Visual Basic .NET
Imports System.Web.Security
Visual C#
using System.Web.Security

The following sections describe how to implement Forms authentication in your application based on the general steps listed earlier in this section.

Setting the Forms Authentication Mode

You set an application' s authentication mode in the Web.config file' s <authorization> element. To set your application to use Forms authentication, make the following changes to the Web.config file:

<authentication mode="Forms" >               <!-- Set authentication mode -->
  <forms loginUrl="LogIn.aspx" >            <!-- Specify a log on form -->
       <credentials passwordFormat="Clear"> <!-- Create a user list -->
              <user name="June" password="JuneBug"/>
              <user name="Walter" password="Halifax"/>
       </credentials>
  </forms>
</authentication>

<authorization>
  <deny users="?" />  <!—Deny all unauthenticated users -->
</authorization>

The preceding Web.config fragment shows a simplified example of Forms authentication using most of the default settings and including a user list as part of the Web.config file. Table 8-2 lists all the possible attributes for the elements that make up the Forms authentication settings.

Table 8-2 Forms Authentication Settings in Web.config
Element | Attribute | Description
<authentication> | mode | Set to Forms to enable Forms authentication.
<forms> | name | Use to set the name of the cookie in which to store the user' s credential. The default is .authaspx. If more than one application on the server is using Forms authentication, you need to specify a unique cookie name for each application.
 | loginUrl | Use to set the name of the Web form to display if the user has not already been authenticated. If omitted, the default is Default.aspx.
 | protection | Use to set how ASP.NET protects the authentication cookie stored on the user' s machine. The default is All, which performs encryption and data validation. Other possible settings are Encryption, Validation, and None.
 | timeout | Use to set the number of minutes the authentication cookie persists on the user' s machine. The default is 30, indicating 30 minutes. ASP.NET renews the cookie automatically if it receives a request from the user and more than half of the allotted time has expired.
 | path | Use to set the path used to store the cookie on the user' s machine. The default is a backslash (\).
<credentials> | passwordFormat | Use to set the algorithm used to encrypt the user' s password. The default is SHA1. Other possible settings are MD5 and Clear (which prevents encryption).
<users> | name | Use to set the name of the user.
 | password | Use to set the password for the user.

The <credentials> element allows you to store your users list in the Web.config file. That is convenient for simple authentication, where an administrator adds new users and sets their passwords, but is not the best approach if you allow users to set up their own accounts or maintain their own passwords.

In those cases, you will want to create a users file or a users database to store user names and encrypted passwords. Using a database has the added benefit of allowing you to store all sorts of additional information about the user, such as shipping address and order history.

The following section shows how to authenticate users using credentials stored in Web.config. The subsequent sections show the more advanced (and more complicated) approach of using a user database.

Creating a LogIn Web Form

To authenticate users through Forms authentication, you need to create a Web form that allows users to log on. This Web form is identified by name in the <forms> element of Web.config.

The login Web form can be as simple as a pair of text boxes and a Button control, or it can appear on a page containing other, nonsecure content. For example, you can include logon fields on an application home page, as shown in Figure 8-12.

Figure 8-12 Login fields on an application Home page

When the user clicks Sign On, the application authenticates the user name and password, issues an authentication certificate, and allows access to the rest of the application, as shown by the following code:

Visual Basic .NET
' Include this line at the beginning of the module.
Imports System.Web.Security

Private Sub butSignOn_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butSignOn.Click
    ' Authenticate username/password from <credentials>.
    If FormsAuthentication.Authenticate(txtUserName.Text, txtPassword.Text) _
         Then
        ' If found, display the application's Start page.
        FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, True)
    Else
        ' Otherwise, clear the password.
        txtPassword.Text = ""
        ' If third try, display "Access Denied" page.
        If CInt(ViewState("Tries")) > 1 Then
            Response.Redirect("Denied.htm")
        Else
            ' Otherwise, increment number of tries.
            ViewState("Tries") = CInt(ViewState("Tries")) + 1
        End If
    End If
End Sub
Visual C#
// Include this line at the beginning of the module.
   using System.Web.Security;

private void butSignOn_Click(object sender, System.EventArgs e)
{
      // Authenticate username/password from <credentials>.
      if (FormsAuthentication.Authenticate(txtUserName.Text,
        txtPassword.Text))
            // If found, display the application's Start page.
            FormsAuthentication.RedirectFromLoginPage(txtUserName.Text,
              true);
      else
      {
            // Otherwise, clear the password.
            txtPassword.Text = "";
            // If third try, display "Access Denied" page.
            if (System.Convert.ToInt32(ViewState["Tries"]) > 1)
            Response.Redirect("Denied.htm");
            else
            // Otherwise, increment number of tries.
            ViewState["Tries"] = System.Convert.ToInt32(ViewState["Tries"])
              + 1;
      }
}

There are a few important things to note about the preceding code:

  • The FormsAuthentication class is part of the System.Web.Security namespace, so you must include that namespace using the Visual Basic .NET Imports statement or the Visual C# using statement, or the fully qualified references to the class.

  • The FormsAuthentication class' s Authenticate method checks the user name and password against the users list found in the <credentials> element of Web.config.

  • The FormsAuthentication class' s RedirectFromLoginPage method displays the application' s start page. If the logon fields appear on the application' s start page, you should disable them or otherwise indicate a successful log-on.

  • If the user name and password aren' t valid, the code lets the user have two more tries before displaying an Access Denied page. That page is an HTML page rather than a Web form, since access to any Web forms in the application is also denied. If you redirect users to another page in this way, make sure that the page is outside the scope of the application.

Use the FormsAuthentication class to sign out when the user is done with the application or when you want to remove the authentication cookie from his or her machine. For example, the following code ends the user' s access to an application and requires him or her to sign back in to regain access:

Visual Basic .NET
Imports System.Web.Security

 Private Sub butSignOut_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles butSignOut.Click
        ' Remove authentication cookie.
        FormsAuthentication.SignOut()
        ' Redirect back to this page (displays log in screen).
        Response.Redirect("UserInfo.aspx")
    End Sub
Visual C#
using System.Web.Security;

private void butSignOut_Click(object sender, System.EventArgs e)
{
   // Remove authentication cookie.
   FormsAuthentication.SignOut();
   // Redirect back to this page (displays log in screen).
   Response.Redirect("UserInfo.aspx");
}
Authenticating Users with a Database

The preceding sections showed how to authenticate users based on a list in Web.config. The FormsAuthentication class' s Authenticate method is set up to read from that file automatically. That is fine if user names and passwords are created and maintained by a system administrator, but if you allow users to create their own user names or change their passwords, you' ll need to store that information outside the Web.config file. This is because changing Web.config at run time causes the Web application to restart, which resets any Application- and Session-state variables used by the application.

You can store user names and passwords in any type of file; however, using a database has the following significant advantages:

  • User names can be used as primary keys to store other information about the user.

  • Databases can provide high performance for accessing user names and passwords.

  • Adding, modifying, and accessing records are standardized through SQL.

When storing user names and passwords in a file or database, you have the option of encrypting them using the FormsAuthentication class' s HashPasswordForStoringInConfigFile method. This uses the SHA1 or MD5 algorithms to encrypt data, as shown here:

Visual Basic .NET
' Encrypt the password.
Password = FormsAuthentication.HashPasswordForStoringInConfigFile(Password, _
  "SHA1")
Visual C#
Password = FormsAuthentication.HashPasswordForStoringInConfigFile(Password,
  "SHA1");

The following sections show how to add new user names and passwords to a simple database and how to use that database to authenticate users.

Adding Users to a Database

To add users to a database, collect the user name and password from two TextBoxes and provide an event procedure to add the user that displays a message indicating whether the user was added. The following event procedure calls the helper function AddUser to add the user name and password to the database:

Visual Basic .NET
Private Sub butNewUser_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butNewUser.Click
    If AddUser(txtUserName.Text, txtPassword.Text) Then
        spnNote.InnerText = "User added."
    Else
        spnNote.InnerText = "User exists. Choose a different user name."
    End If
End Sub
Visual C#
private void butNewUser_Click(object sender, System.EventArgs e)
{
   if (AddUser(txtUserName.Text, txtPassword.Text))
         spnNote.InnerText = "User added.";
   else
         spnNote.InnerText = "User exists. Choose a different user name.";
}

The AddUser helper function shown in the following code encrypts the password before storing the user name and password in a database using the SQL INSERT command. If the user name already exists in the database, the exception-handling block catches the error and returns False to indicate that the user was not added.

Visual Basic .NET
Private Function AddUser(ByVal UserName As String, ByVal Password As String) _As Boolean
    ' Declare variable to track success/failure.
    Dim bSuccess As Boolean
    ' Encrypt the password.
    Password = _
      FormsAuthentication.HashPasswordForStoringInConfigFile(Password,_
      "SHA1")
    ' Create command to insert user name and password.
    Dim oleCommand As New OleDbCommand("INSERT INTO Users" + _
      " VALUES('" + UserName + "', '" + Password + "')", oledbUsers)
    ' Catch errors in case record already exists.
    Try
        ' Open the database connection.
        oledbUsers.Open()
        ' If record added, set success to true.
        If oleCommand.ExecuteNonQuery() Then bSuccess = True
        ' Close connection.
        oledbUsers.Close()
    Catch
        ' Otherwise, success if false.
        bSuccess = False
        ' Close connection.
        oledbUsers.Close()
    End Try
    ' Return success/failure.
    Return bSuccess
End Function
Visual C#
private bool AddUser(string UserName, string Password)
{
   // Declare variable to track success/failure.
   bool bSuccess = false;
   // Encrypt the password.
   Password = FormsAuthentication.HashPasswordForStoringInConfigFile
                  (Password "SHA1");
   // Create command to insert user name and password.
   OleDbCommand oleCommand = new OleDbCommand("INSERT INTO Users" +
     " VALUES('" + UserName + "', '" + Password + "')", oledbUsers);
   // Catch errors in case record already exists.
   try
   {
         // Open the database connection.
         oledbUsers.Open();
         // If record added, set success to true.
         if (oleCommand.ExecuteNonQuery() != 0)
         {
               bSuccess = true;
               // Close connection.
               oledbUsers.Close();
         }
   }
   catch
   {
         // Otherwise, success if false.
         bSuccess = false;
         // Close connection.
         oledbUsers.Close();
   }
   // Return success/failure.
   return bSuccess;
}
Authenticating Users From a Database

When you authenticate users from Web.config, you use the Authenticate method. When you authenticate users from a database, you must write your own code to find and compare user names and passwords. The following event procedure uses the CheckPassword helper function to validate the user name and password text boxes before authenticating the user and allowing access to the application:

Visual Basic .NET
Private Sub butSignOn_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butSignOn.Click
    ' If user name and password are found,
    ' authorize the user and show start page.
    If CheckPassword(txtUserName.Text, txtPassword.Text) Then
        FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, True)
    Else
        ' Display message.
        spnNote.InnerText = "User name or password not found. Try again."
        ' Allow three tries to log in.
        ViewState("tries") = ViewState("tries") + 1
        If ViewState("tries") > 3 Then
            Response.Redirect("Denied.htm")
        End If
    End If
End Sub
Visual C#
private void butSignOn_Click(object sender, System.EventArgs e)
{
   // Authenticate username/password from <credentials>.
   if (CheckPassword(txtUserName.Text, txtPassword.Text))
         // If found, display the application's Start page.
         FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, true);
   else
   {
         // Otherwise, clear the password.
         txtPassword.Text = "";
         // Display message.
         spnNote.InnerText = "User name or password not found. Try again.";
         // If third try, display "Access Denied" page.
         if (System.Convert.ToInt32(ViewState["Tries"]) > 1)
               Response.Redirect("Denied.htm");
         else
         {
      // Otherwise, increment number of tries.
      ViewState["Tries"] =
        System.Convert.ToInt32(ViewState["Tries"]) + 1;
      if (System.Convert.ToInt32(ViewState["Tries"]) > 3)
            Response.Redirect("Denied.htm");
         }
    }
}

The CheckPassword helper function shown in the following code encrypts the password, finds the database record based on the user name, and compares the encrypted password against the password found in the database. Access to the database is performed within an exception-handling block to prevent locking conflicts from displaying errors to the user.

Visual Basic .NET
Private Function CheckPassword(ByVal UserName As String, _
  ByVal Password As String) As Boolean
    ' Declare variable to track success/failure.
    Dim bSuccess As Boolean
    ' Encrypt the password.
    Password = _
     FormsAuthentication.HashPasswordForStoringInConfigFile(Password, "SHA1")
    ' Create command to get row from users table based on UserName.
    Dim oleCommand As New OleDbCommand("SELECT * FROM Users" + _
      " WHERE UserName='" + txtUserName.Text + "'", oledbUsers)
    ' Check for errors using database
    Try
        ' Open the database connection.
        oledbUsers.Open()
        ' Get the author ID.
        Dim rdrUsers As OleDbDataReader = oleCommand.ExecuteReader()
        While rdrUsers.Read()
            If Password = rdrUsers.Item("Password") Then bSuccess = True
        End While
        ' Close connection.
        oledbUsers.Close()
    Catch
        ' Otherwise set failure.
        bSuccess = False
        ' Close connection.
        oledbUsers.Close()
    End Try
    Return bSuccess
End Function
Visual C#
private bool CheckPassword(string UserName, string Password)
{
   // Declare variable to track success/failure.
   bool bSuccess = false;
   // Encrypt the password.
Password = FormsAuthentication.HashPasswordForStoringInConfigFile(Password,
     "SHA1");
   // Create command to get row from users table based on UserName.
   OleDbCommand oleCommand = new OleDbCommand("SELECT * FROM Users" +
     " WHERE UserName='" + txtUserName.Text + "'", oledbUsers);
   // Check for errors using database
   try
   {
         // Open the database connection.
         oledbUsers.Open();
         // Get the author ID.
         OleDbDataReader rdrUsers = oleCommand.ExecuteReader();
         while (rdrUsers.Read())
         {
               if (Password == rdrUsers["Password"].ToString())
               bSuccess = true;
         }
         // Close connection.
         oledbUsers.Close();
   }
   catch
   {
         // Otherwise set failure.
         bSuccess = false;
         // Close connection.
         oledbUsers.Close();
   }
   return bSuccess;
}


Lesson 4: Using Passport Authentication

Passport authentication identifies users via Microsoft' s Passport single sign-in service. Microsoft Passport is meant to provide Internet users with a single identity that they can use to visit a wide variety of Web sites that require authentication. Information about the user is available to your application through a profile that is stored with Microsoft.

The advantage of Passport authentication is that the user doesn' t have to remember separate user names and passwords for various Web sites, and that the user can maintain his or her profile information in a single location. Passport authentication also provides access to other Microsoft services, such as Passport Express Purchase.

After this lesson, you will be able to

  • Install the Passport SDK

  • Activate Passport authentication for your application

  • Get information from the user' s Passport profile

  • Sign out the user from your Web application by deleting the Passport cookies

Estimated lesson time: 20 minutes
Enabling Passport Authentication

Passport authentication uses the Microsoft centralized authentication provider to identify users. Passport provides a way to for users to use a single identity across multiple Web applications. To use Passport authentication in your Web application, you must install the Passport SDK, which is available by searching the Downloads area of MSDN at http://msdn.microsoft.com/downloads.

The Passport SDK is free for preproduction development and testing. To deploy a site for public use, you must obtain an annual license from Microsoft. You can obtain more information about licensing from http://www.microsoft.com/myservices /passport/.

When a user accesses an application that implements Passport authentication, ASP.NET checks the user' s machine for a current passport authentication cookie. If none is found, ASP.NET directs the user to a Passport sign-on page. Once the user signs in, the Passport service authenticates the user, stores an authentication cookie on the user' s computer, and directs the user back to the originally requested Web page. Figure 8-13 illustrates the Passport authentication process.

Figure 8-13 The Passport authentication process

To use Passport authentication, follow these steps:

  1. Install the Passport SDK. Passport is not included with Visual Studio, although the .NET Framework does include classes for working with the Passport SDK once it is installed.

  2. Set the application' s authentication mode to Passport in Web.config. Set authorization to deny unauthenticated users.

  3. Use the PassportAuthentication_OnAuthenticate event to access the user' s Passport profile to identify and authorize the user.

  4. Implement a sign-out procedure to remove Passport cookies from the user' s machine.

For example, the following Web.config settings enable Passport authentication and require all users to be authenticated:

<authentication mode="Passport" />

<authorization>
    <deny users="?" /> <!-- Deny unauthenticated users -->
</authorization>

When you run an application locally with these settings, you are automatically redirected to the Passport sign-on page. If you' ve installed the preproduction (unlicensed) version of the Passport SDK, the sign-on page is not the same as the page displayed for a deployed site. Figure 8-14 shows the two Passport sign-on pages.

Figure 8-14 Passport sign-on pages

Once the user has signed in, Passport stores an authorization cookie on the user' s machine and redirects the user back to his or her originally requested page. Passport stores a profile of information about the user. You can access that profile from the PassportAuthentication_OnAuthenticate event in the Global.asax module, as shown here:

Visual Basic .NET
' Add to beginning of module
Imports System.Web.Security

Private Sub PassportAuthentication_OnAuthenticate(ByVal sender As Object, _
  ByVal e As PassportAuthenticationEventArgs)
    ' Get Session's passport identity if authenticated.
    If e.Identity.IsAuthenticated Then
        Response.Write("Name: " & e.Identity.Item("FirstName") & _
          " " & e.Identity.Item("LastName") & "<br>")
        Response.Write("Address: " & e.Identity.Item("City") & _
          "    " & e.Identity.Item("PostalCode") & "<br>")
        Response.Write("Email: " & e.Identity.Item("PreferredEmail") & _
          "<br>")
        Response.Write("Passport ID: " & e.Identity.Name & "<br>")
    End If
End Sub
Visual C#
// Add to the begging of module
   using System.Web.Security;

protected void PassportAuthentication_OnAuthenticate(Object sender,
PassportAuthenticationEventArgs e)
{
   // Get Session's passport identity if authenticated.
   if (e.Identity.IsAuthenticated)
   {
         Response.Write("Name: " + e.Identity["FirstName"] +
           " " + e.Identity["LastName"] + "<br>");
         Response.Write("Address: " + e.Identity["City"] +
           "    " + e.Identity["PostalCode"] + "<br>");
         Response.Write("Email: " + e.Identity["PreferredEmail"] +
           "<br>");
         Response.Write("Passport ID: " + e.Identity.Name + "<br>");
   }
}

The preceding code displays the user' s name, location, and identity information from his or her Passport profile. If you' ve installed the preproduction Passport SDK, that information reflects a test account rather than live data, as shown by Figure 8-15.

Figure 8-15 Passport identity information for the test account

The Passport authorization and profile information are stored in five separate cookies on the user' s machine. The Passport SDK requires that you remove these cookies when the user signs out of your Web application. The following event procedure demonstrates how to sign out by deleting the Passport cookies:

Visual Basic .NET
Private Sub butSignOut_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butSignOut.Click
    ' Sign out by deleting Passport cookies.
    Response.Cookies("MSPProf").Expires = Now
    Response.Cookies("MSPAuth").Expires = Now
    Response.Cookies("MSPSecAuth").Expires = Now
    Response.Cookies("MSPProfC").Expires = Now
    Response.Cookies("MSPConsent").Expires = Now
    ' Redisplay this page (goes back to sign-in).
    Response.Redirect("UserInfo.aspx")
End Sub
Visual C#
private void butSignOut_Click(object sender, System.EventArgs e)
{
   // Sign out by deleting Passport cookies.
   Response.Cookies["MSPProf"].Expires = DateTime.Now;
   Response.Cookies["MSPAuth"].Expires = DateTime.Now;
   Response.Cookies["MSPSecAuth"].Expires = DateTime.Now;
   Response.Cookies["MSPProfC"].Expires = DateTime.Now;
   Response.Cookies["MSPConsent"].Expires = DateTime.Now;
   // Redisplay this page (goes back to sign-in).
   Response.Redirect("UserInfo.aspx");
}

Passport authentication also provides additional commercial and child-protection features that are explained in the Passport SDK.


Lesson 5: Providing Secure Communication

Up until now we' ve discussed security as a matter of identifying users and preventing unauthorized users from accessing your Web applications, but it is just as important to ensure that sensitive data sent across the Internet cannot be read by others.

To provide secure communication across the Internet, IIS supports a standardized means of encrypting and decrypting Web requests and responses. This cryptography requires that you request an encryption key called a server certificate from an independent third party called a Certificate Authority.

After this lesson, you will be able to

  • Understand the process of using the Secure Sockets Layer (SSL) in IIS

  • Get a server certificate to enable secure, encrypted communication between your Web application and users

  • Begin secure communication between a user and your Web application

  • End secure communication

  • Require secure communication before users can access a Web form

Estimated lesson time: 20 minutes
Enabling Secure Communication

The Secure Sockets Layer (SSL) is the standard means of ensuring that data sent over the Internet cannot be read by others. When a user requests a secure Web page, the server generates an encryption key for the user' s session and then encrypts the page' s data before sending a response. On the client' s side, the browser uses that same encryption key to decrypt the requested Web page and to encrypt new requests sent from that page. Figure 8-16 illustrates the process.

Figure 8-16 Secure communication using SSL

Using SSL in your application requires special authorization from a recognized Certificate Authority. This authorization comes in the form of a server certificate, which you install in IIS to identify your server. The Certificate Authority licenses server certificates (for a fee) and acts as a clearinghouse to verify your server' s identity over the Internet.

When a user' s browser begins secure communications, it requests the server certificate and checks it against a list of trusted sites provided by the Certificate Authority. If the server certificate does not match one of the sites already authorized by the user, or if the server certificate does not match the Web address for which it was registered, or if there are any other problems with the server certificate, the browser displays a warning, as shown in Figure 8-17.

Figure 8-17 Problems with the server certificate

In this way, the Certificate Authority not only provides encryption for secure data transmission, but it also provides assurance to users that your Web site is authentic. The largest Certificate Authority is VeriSign, Inc. At the time of this writing, in addition to their fee-based services, they also offer free trial certificates for testing and evaluation at http://www.verisign.com.

To use SSL in your Web application, follow these steps:

  1. Generate a certificate request from IIS.

  2. Request a certificate from a Certificate Authority.

  3. Install the certificate on the server using IIS.

  4. Install the certificate on browsers if you are using a test certificate.

  5. Use the secure protocol (https:) when accessing secure pages in your application.

The following sections describe these steps in greater detail.

Generating a Certificate Request

Before you can request a server certificate from a Certificate Authority, you must generate a certificate request from IIS. The certificate request contains encrypted information about your server that the Certificate Authority uses to identify your server over the Internet.

To generate a certificate request from the IIS snap-in:

  1. Select Default Web Site from the folder list on the left of the IIS window, and then choose Properties from the Action menu. IIS displays the Default Web Site Properties dialog box.

  2. Click the Directory Security tab in the Properties dialog box, and then click Server Certificate. IIS starts the Web Server Certificate Wizard, as shown in Figure 8-18.

    Figure 8-18 The Web Server Certificate Wizard

  3. Step through the wizard by reading and clicking Next on each screen. The wizard instructions are straightforward.

  4. When you click Finish at the end, the wizard creates an encrypted text file with the .cer filename extension. That file is the certificate request that you send to the Certificate Authority.

IIS requires that a certificate be created at the server root before secure communications can be created or configured for subordinate sites on the server. That is why you have to select Default Web Site (or the root Web site if you have renamed it) in step 1. After you have installed a server certificate at the root, you can repeat the process for subordinate sites if you want separate certificates for those sites.

Requesting a Certificate

The process of requesting a server certificate from a Certificate Authority varies depending on the Certificate Authority you select. As mentioned earlier, VeriSign provides test certificates at no cost, which suits the purposes of this book very well!

To request a server certificate, follow these steps:

  1. Visit the Certificate Authority' s Web site and request a server certificate for SSL. The various levels of security and support are typically based on an annual fee.

  2. Select the type of server certificate you want to request and complete the registration information for the request.

  3. Copy or e-mail the certificate request you created in the preceding section to the Certificate Authority.

Once approved, the Certificate Authority will send you the server certificate, usually as part of an e-mail message. Save the certificate as a text file with the .cer file extension by cutting and pasting it into Notepad or some other editor, as shown in Figure 8-19.

Figure 8-19 Saving the server certificate
Installing the Certificate

After you have saved your server certificate, you can install it in IIS to enable SSL for your Web applications.

To install a server certificate in IIS:

  1. Select Default Web Site from the folder list on the left of the IIS window, and then choose Properties from the Action menu. IIS displays the Default Web Site Properties dialog box.

  2. Click the Directory Security tab in the Properties dialog box, and then click Server Certificate. IIS starts the Web Server Certificate Wizard.

  3. Click Next and select Process The Pending Request And Install The Certificate, as shown in Figure 8-20.

    Figure 8-20 Installing the certificate with the IIS Server Certificate Wizard

  4. Click Next and enter the name of the certificate file, as shown in Figure 8-21. The certificate file is the file you created in the preceding section.

    Figure 8-21 Entering the name of the server certificate file

  5. Click Next, and then click Finish to complete the installation.

Using Secure Communications

After you have installed the server certificate, secure communication is enabled for any Web pages requested through the Secure Hypertext Transfer Protocol (https). For example, the following hyperlink displays a secure Web page, as shown in Figure 8-22.

<a href="https://www.contoso.com/mscdkit/Secure.aspx">Go to secure page.</a>
Figure 8-22 Secure Web page

The protocol https: is what initializes the secure communication. When you' ve begun secure communication, it continues until you specify a nonsecure site. For example, clicking the Go Back link on the page shown in Figure 8-22 continues the secure communication because the link uses a relative address, as shown here:

<a href="Default.aspx">Go Back (still secure).</a>

To end secure communication, you need to include the http: protocol in the URL, as in the following link:

<a href="http://www.contoso.com/mscdkit/Default.aspx">
Go Back (not secure).</a>

IIS lets you require secure communication for specific folders or files in your Web application. This prevents users from accidentally (or intentionally) viewing a secure page using the nonsecure http: protocol.

To require secure communication for a Web page using IIS, follow these steps:

  1. Select the folder or file that requires secure communication, and then choose Properties from the Action menu. IIS displays the Properties dialog box.

  2. Click the Directory Security tab, and then click Edit in the Secure Communications group. IIS displays the Secure Communications dialog box, as shown in Figure 8-23.

    Figure 8-23 The Secure Communications dialog box

  3. Select the Require Secure Channel(SSL) check box and click OK.

When you require secure communication for a Web page, that page cannot be viewed using the http: protocol. The user must type in or click a link using the https protocol; otherwise, access is denied, as shown in Figure 8-24.

Figure 8-24 Access denied

Because the difference between http: and https: is a pretty obscure distinction for most users to remember, it is important to provide an http entry point for your application that links to the secure (https) pages. The http entry point can simply be a default page within the Web site that immediately redirects the user to the secure site. Requiring secure communication for an entire Web site is a sure way to cut traffic down to zero!


Summary

  • Authentication is the process of identifying users. Authorization is the process of granting access to those users.

  • The default method of access to a Web application is anonymous access. Anonymous users are granted access through the Windows IUSER_machinename user account.

  • ASP.NET provides three different authentication modes through the System.Web.Security namespace: Windows, Forms, and Passport.

  • HTML pages aren' t automatically included under ASP.NET authentication. To ensure that users are authenticated before they can view those types of files, use IIS to map the .htm and .html file extensions to the ASP.NET executable.

  • To require authentication, add a <deny users=“?”> element in the authorization section of Web.config. This applies to all three authentication modes.

  • Use the <authorization> element to allow or deny access to users under Windows authentication.

  • Use the <credentials> element or an external users database to allow users under Forms authentication.

  • Changing the Web.config file restarts the Web application, so it is not a good idea to add users to the Web.config <credentials> element at run time. Instead, use an external file or database to store user names and passwords.

  • Install the Passport SDK to enable the Passport classes in the System.Web.Security namespace.

  • To enable secure, encrypted communication over the Internet, install a server certificate and use the https protocol.


Lab: Creating a Secure Application

In this lab, you will create a simple application that uses Forms authentication to identify and authorize users. The application allows new users to add their user name and password, allows existing users to sign in, and displays the user' s identity once he or she is signed in. When complete, the Web application will appear as shown in Figure 8-25.

Figure 8-25 The LogIn and UserInfo Web forms
Estimated lesson time: 30 minutes
Exercise 1: Enabling Forms Authentication

To enable Forms authentication, create a new ASP.NET Web forms application and open the Web.config file generated for the project. Make the changes to the <authentication> element shown here:

<authentication mode="Forms">
    <forms loginUrl="LogIn.aspx">
        <credentials passwordFormat="SHA1">
    </credentials>
    </forms>
</authentication>

The preceding authentication settings enable Forms authentication, direct unauthenticated users to the LogIn.aspx Web form, and enable password encryption using the SHA1 algorithm. To require authentication using these settings, add the following <authorization> element to the Web.config file:

<authorization>
    <deny users="?" /> <!-- Deny unauthorized users -->
</authorization>

The preceding authorization setting denies access to any unauthenticated users, thus forcing authentication. When the changes to Web.config have been made, you can create the LogIn.aspx Web form that allows users to add new accounts and to log on to existing ones.

Exercise 2: Creating the LogIn Web Form

In this exercise, you will create a Web form named LogIn.aspx and a data file named Users.xml that are used to authenticate users. Because this is a simplified example, LogIn.aspx allows users to create a new account if their user name was not found.

In the real world, you might want to direct users to a separate Web form for creating new accounts or you might want an administrator to create their accounts and e-mail their passwords back to them. In either case, the basic concepts are the same.

To create the Users.xml data file, use Visual Studio or another text editor to create a text file containing the following line:

<users name="" password="" />

The sample will use this file to as a template to store user names and passwords. Because passwords will be encrypted, the first name and passwords are blank—you can delete this line later, after other user names and passwords have been added.

Next, create the LogIn.aspx Web form. This Web form contains text boxes, validation, and other controls, as shown in the following table.

Control type | Property | Value
TextBox | ID | txtUserName
RequiredFieldValidator | ID | vldUserName
 | ControlToValidate | txtUserName
 | ErrorMessage | Name is required.
TextBox | ID | txtPassword
 | TextMode | Password
RequiredFieldValidator | ID | vldPassword
 | ControlToValidate | txtPassword
 | ErrorMessage | Password is required.
Button | ID | butSignOn
 | Text | Sign On
Button | ID | butAddUser
 | Text | Add User
 | Visible | False
Label | ID | lblStatus

Begin writing the code for LogIn.aspx by adding the following line to beginning of the Login.aspx code module:

Visual Basic .NET
Imports System.Web.Security
Visual C#
using System.Web.Security;

Add the following code to the butSignOn control' s Click event to check the user name and password and to prompt the user if the user name is not found. The event procedure will use two helper functions: UserExists to check if the user name exists in Users.xml, and PasswordValid to check if the entered password is valid.

Visual Basic .NET
Private Sub butSignOn_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles butSignOn.Click
    ' Check the user name.
    If UserExists(txtUserName.Text) Then
        ' Check the password (encrypted).
        If PasswordValid(txtUserName.Text, txtPassword.Text) Then
            ' Sign the user on.
            FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, True)
        Else
            ' Display an invalid password message.
            lblStatus.Text = "Password does not match. Try again."
        End If
    Else
        ' If user name not found, offer to add it.
        lblStatus.Text = "User name not found. Click Add User to add it now."
        ' Make Add User button visible.
        butAddUser.Visible = True
    End If
    ' Keep track of tries.
    Session("Tries") = CInt(Session("Tries")) + 1
     If Session("Tries") > 2 Then
        ' After third try, deny access.
        Response.Redirect("Denied.htm")
    End If
End Sub
Visual C#
private void butSignOn_Click(object sender, System.EventArgs e)
{
   // Check the user name.
   if (UserExists(txtUserName.Text))
         // Check the password (encrypted).
         if (PasswordValid(txtUserName.Text, txtPassword.Text))
               // Sign the user on.
               FormsAuthentication.RedirectFromLoginPage(txtUserName.Text,
                 true);
         else
               // Display an invalid password message.
               lblStatus.Text = "Password does not match. Try again.";
   else
   {
         // If user name not found, offer to add it.
         lblStatus.Text = "User not found. Click Add User to add it now.";
         // Make Add User button visible.
         butAddUser.Visible = true;
   }
   // Keep track of tries.
   ViewState["Tries"] = System.Convert.ToInt32(ViewState["Tries"]) + 1;
   if (System.Convert.ToInt32(ViewState["Tries"]) > 3)
         Response.Redirect("Denied.htm");
}

The UserExists and PasswordValid functions load the Users.xml file into a data set and then search through the data set for a matching user name and password. If a match is found, the functions return True; otherwise, they return False.

Visual Basic .NET
Private Function UserExists(ByVal UserName As String) As Boolean
    ' Create a data set to read the XML file.
    Dim dsUsers As New DataSet()
    ' Use error handling in case is file missing.
    Try
        ' Build the Users.xml file path.
        Dim strXMLFile As String = Server.MapPath(Request.ApplicationPath) &_
          "\Users.xml"
        ' Read the file
        dsUsers.ReadXml(strXMLFile, XmlReadMode.InferSchema)
        ' For each row in the Users table.
        Dim rowUser As DataRow
        For Each rowUser In dsUsers.Tables("Users").Rows
            ' Check for name match.
            If rowUser("name") = UserName Then
                Return True
                Exit For
            End If
        Next
    Catch
        ' In case of error return False.
        Return False
    End Try
End Function

Private Function PasswordValid(ByVal UserName As String, _
  ByVal Password As String)
    ' Create a data set to read the XML file.
    Dim dsUsers As New DataSet()
    ' Use error handling in case is file missing.
    Try
        ' Build the Users.xml file path.
        Dim strXMLFile As String = Server.MapPath(Request.ApplicationPath) &_
          "\Users.xml"
        ' Read the file
        dsUsers.ReadXml(strXMLFile, XmlReadMode.InferSchema)
        ' For each row in the Users table.
        Dim rowUser As DataRow
        For Each rowUser In dsUsers.Tables("Users").Rows
            ' Check for name match.
            If rowUser("name") = UserName Then
                If rowUser("password") = _
                  FormsAuthentication.HashPasswordForStoringInConfigFile _
                  (Password, "SHA1") Then
                    Return True
                    Exit For
                End If
            End If
        Next
    Catch
        ' In case of error return false.
        Return False
    End Try
End Function
Visual C#
private bool UserExists(string UserName)
{
    // Create a data set to read the XML file.
   DataSet dsUsers = new DataSet();
   // Use error handling in case is file missing.
   try
   {
         // Build the Users.xml file path.
         string strXMLFile = Server.MapPath(Request.ApplicationPath) +
           "\\Users.xml";
         // Read the file
         dsUsers.ReadXml(strXMLFile, XmlReadMode.InferSchema);
         // For each row in the Users table.
         foreach (DataRow rowUser in dsUsers.Tables["Users"].Rows)
               // Check for name match.
               if (rowUser["name"].ToString() == UserName)
                     return true;
   }
   catch
   {
         // In case of error return False.
         return false;
   }
   // Otherwise, return false
   return false;
}

private bool PasswordValid(string UserName, string Password)
{
   // Create a data set to read the XML file.
   DataSet dsUsers = new DataSet();
   // Use error handling in case is file missing.
   try
   {
         // Build the Users.xml file path.
         string strXMLFile = Server.MapPath(Request.ApplicationPath) +
           "\\Users.xml";
         // Read the file
         dsUsers.ReadXml(strXMLFile, XmlReadMode.InferSchema);
         // For each row in the Users table.
         foreach (DataRow rowUser in dsUsers.Tables["Users"].Rows)
               // Check for name match.
               if (rowUser["name"].ToString() == UserName)
                     if (rowUser["password"].ToString() ==
                       FormsAuthentication.HashPasswordForStoringInConfigFile
                       (Password, "SHA1"))
                           return true;
   }
   catch
   {
         // In case of error return False.
         return false;
   }
   // Otherwise, return false.
   return false;
}

The PasswordValid function in the preceding code uses the HashPasswordForStoringInConfigFile method because the passwords are stored using encryption. The same function is used by the AddUser function when storing the password in Users.xml, as shown in the following code:

Visual Basic .NET
Private Sub butAddUser_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butAddUser.Click
    If AddUser(txtUserName.Text, txtPassword.Text) Then
        ' Display success.
        lblStatus.Text = "User added. Click Sign On to continue."
        ' Hide button.
        butAddUser.Visible = False
    Else
        ' Display failure.
        lblStatus.Text = "User could not be added. Choose a different name."
    End If
End Sub

Private Function AddUser(ByVal UserName As String, ByVal Password As String)
    ' If the user alread exists, return False and exit.
    If UserExists(UserName) Then
        Return False
        Exit Function
    End If
    ' Otherwise, add user to XML file.
    Dim dsUsers As New DataSet()
    ' Use error handling in case is file missing.
    Try
        ' Build the Users.xml file path.
        Dim strXMLFile As String = Server.MapPath(Request.ApplicationPath) &_
          "\Users.xml"
        ' Read the file
        dsUsers.ReadXml(strXMLFile, XmlReadMode.InferSchema)
        ' Add a new row.
        Dim rowUser As DataRow = dsUsers.Tables("users").NewRow()
        ' Set Username.
        rowUser("name") = UserName
        ' Set password (encrypted).
        rowUser("password") = _
          FormsAuthentication.HashPasswordForStoringInConfigFile _
         (Password, "SHA1")
        ' Add row
        dsUsers.Tables("users").Rows.Add(rowUser)
        ' Write data set.
        dsUsers.WriteXml(strXMLFile)
        Return True
    Catch
        ' In case of error return False.
        Return False
    End Try
End Function
Visual C#
private void butAddUser_Click(object sender, System.EventArgs e)
{
   if (AddUser(txtUserName.Text, txtPassword.Text))
   {
         // Display success.
         lblStatus.Text = "User added. Click Sign On to continue.";
         // Hide button.
         butAddUser.Visible = false;
   }
   else
         // Display failure.
         lblStatus.Text =             "User could not be added. Choose a different name.";
}

private bool AddUser(string UserName, string Password)
{
   // If the user alread exists, return False and exit.
   if (UserExists(UserName))
   {
         return false;
   }
   // Otherwise, add user to XML file.
   DataSet dsUsers = new DataSet();
   // Use error handling in case is file missing.
   try
   {
         // Build the Users.xml file path.
         string strXMLFile  = Server.MapPath(Request.ApplicationPath) +
           "\\Users.xml";
         // Read the file
         dsUsers.ReadXml(strXMLFile, XmlReadMode.InferSchema);
         // Add a new row.
         DataRow rowUser  = dsUsers.Tables["users"].NewRow();
         // Set Username.
         rowUser["name"] = UserName;
         // Set password (encrypted).
         rowUser["password"] =
               FormsAuthentication.HashPasswordForStoringInConfigFile
                (Password, "SHA1");
         // Add row
         dsUsers.Tables["users"].Rows.Add(rowUser);
         // Write data set.
         dsUsers.WriteXml(strXMLFile);
         return true;
   }
   catch
   {
         // In case of error return false.
         return false;
   }
}
Exercise 3: Displaying User Information

In this exercise, you will create the UserInfo.aspx Web form to display the user' s name and authentication information using the User class' s Identity object. You will also create an HTML page to display if access is denied. Because HTML pages aren' t automatically included in a Web application' s authentication scheme, users can access that page even if they aren' t authenticated.

Create the UserInfo.aspx Web form and make it the start page for the application. Unauthenticated users will actually be directed to the LogIn.aspx Web form first, because that is the login page specified in Web.config. After users are authenticated, they are directed to the application' s start page by the RedirectFromLoginPage method.

The UserInfo.aspx page contains a table in which to display the user name and authentication information and a command button to allow the user to sign out from the application. The table includes named <span> elements as placeholders for the user data, as shown by the following HTML:

<H2>Forms Authorization</H2>
<TABLE id="tblUser" borderColor="black" cellSpacing="1" cellPadding="1" width="500" bgColor="aliceblue" border="1">
 <TR>
     <TD>Authenticated</TD>
     <TD><SPAN id="spnAuthenticated" runat="server"></SPAN></TD>
 </TR>
 <TR>
     <TD>User name</TD>
     <TD><SPAN id="spnUserName" runat="server"></SPAN></TD>
 </TR>
 <TR>
     <TD>Authentication type</TD>
     <TD><SPAN id="spnAuthenticationType" runat="server"></SPAN></TD>
 </TR>
</TABLE>
<P>
<asp:Button id="butSignOut" runat="server" Text="Sign Out"></asp:Button>
</P>

The Web form' s Page_Load event procedure gets the user information to display, as shown here:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
        ' Display user authentication information.
        spnAuthenticated.InnerText = User.Identity.IsAuthenticated()
        spnUserName.InnerText = User.Identity.Name()
        spnAuthenticationType.InnerText = User.Identity.AuthenticationType()
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
   // Display user authentication information.
   spnAuthenticated.InnerText = User.Identity.IsAuthenticated.ToString();
   spnUserName.InnerText = User.Identity.Name;
   spnAuthenticationType.InnerText = User.Identity.AuthenticationType;
}

The butSignOut_Click event procedure signs the user out of the application using the SignOut method. This removes the authentication cookie that was stored on the user' s computer and requires that the user sign in again to access the Web application.

Visual Basic .NET
Private Sub butSignOut_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butSignOut.Click
        ' Remove authentication cookie.
        FormsAuthentication.SignOut()
        ' Redirect back to this page (displays log in screen).
        Response.Redirect("UserInfo.aspx")
End Sub
Visual C#
private void butSignOut_Click(object sender, System.EventArgs e)
{
    // Remove authentication cookie.
   FormsAuthentication.SignOut();
   // Redirect back to this page (displays log in screen).
   Response.Redirect("UserInfo.aspx");
}

After creating the UserInfo.aspx Web form, add an HTML page to the application named Denied.htm. This is the page that the LogIn Web form displays if the user doesn' t enter a correct user name and password after three tries. Denied.htm includes a message to the user, as shown by the following HTML:

<h2>Access Denied</h2>
<p>You must enter a valid user name and password to use this application.</p>
Exercise 4: Advanced Topics

The preceding exercises show how to implement Forms authentication in a step-by-step fashion. To gain a thorough understanding of other types of authentication, you can experiment by changing the authentication mode in the preceding application' s Web.config file.

  • To switch the application to Windows authentication, comment out the current <authentication> element in Web.config and add the element shown here:

    <authentication mode="Windows" />

  • To switch the application to Passport authentication, install the Passport SDK and modify the <authentication> element in Web.config, as shown here:

    <authentication mode="Passport" />

Finally, for extra credit, get a server certificate from a Certificate Authority, such as VeriSign, Inc., and implement secure communications for the application. Use the https protocol to activate secure communications, as shown in Lesson 5.


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. Which ASP.NET authentication mode is best suited to identifying and authorizing users who belong to a corporate network?

  2. What is the difference between Windows and Forms authentication users lists in Web.config?

  3. How do you require authentication using the Web.config file? (The answer is the same for all ASP.NET authentication modes.)

  4. How does the Secure Sockets Layer (SSL) provide security in a Web application?

  5. How do you begin and end secure communication via SSL?


Building and Deploying Web Applications

Chapter 9
Building and Deploying Web Applications
Lesson 1: Building a Web Application
Lesson 2: Deploying a Web Application
Lesson 3: Maintaining a Deployed Application
Lesson 4: Deploying Across Multiple Servers
About This Chapter

In this chapter, you will learn how to publish your completed Web applications on a Web server for general use. You will also learn how to monitor and maintain the application when it is in use so that it continues to perform well as the demands of your users change.

Before You Begin

To complete this chapter, you must:

  • Have access to a Web server either through a Web hosting service or through your local area network.

  • Have successfully completed one or more of the Web applications in this book.

  • Be familiar with Microsoft Internet Information Services (IIS) and the Microsoft Management Console (MMC).

  • Have installed Microsoft Visual Studio .NET Professional, Microsoft Visual Studio .NET Enterprise Developer, or Microsoft Visual Studio .NET Enterprise Architect. This requirement applies only to the last lesson in this chapter.


Lesson 1: Building a Web Application

At its simplest level, building an application in Visual Studio .NET requires almost no effort. In fact, the application is built automatically any time you click Run while developing or debugging the application.

However, when you are ready to deploy your application, you need to take these additional steps before building it:

  1. Set the build options.

  2. Identify the application.

  3. Configure the application.

In this lesson, you will learn how to perform these tasks to prepare your Web application before deployment.

After this lesson, you will be able to

  • Build a release version of a Web application

  • Add name, company, copyright, description, and version information to the application' s assembly

  • Set the application' s configuration options in its Web.config file

Estimated lesson time: 10 minutes
Setting Build Options

Web applications have two build options: debug and release.

To change the build options, follow these steps:

  1. From the Build menu, choose Configuration Manager. Visual Studio .NET displays the Configuration Manager dialog box, as shown in Figure 9-1.

    Figure 9-1 The Configuration Manager dialog box

  2. Select the build option and click OK.

  3. Open the project' s Web.config file and set the compilation element to match the build setting you selected in step 2. For example, use the following setting for a release build:

    <compilation defaultLanguage="vb" debug="false" />

Selecting the debug build option generates a program database file (.pdb) containing information about symbols used within the application when the project is compiled. Visual Studio .NET uses the program database to monitor the values of variables, set breakpoints, and evaluate Debug class members.

Selecting the release build option does not generate this file, thereby disabling Debug class members and causing breakpoints to be ignored. Because release builds don' t have to monitor this extra information, they execute faster than debug builds.

The application' s build option and Web.config setting should agree. There is no advantage in having one set to debug and the other set to release; however, Visual Studio .NET does not automatically change one when you change the other.

Identifying the Application

Before you deploy an application, you should include information about the application such as the application' s title, author, and version. This information is displayed when the user views the version properties of the application' s assembly (.dll) from within Microsoft Windows, as shown in Figure 9-2.

Figure 9-2 Assembly properties

To identify your application, open the AssemblyInfo file and enter the application' s information in the assembly attributes. For example, the following attributes set the product name and version information shown in Figure 9-2:

Visual Basic .NET
Imports System.Reflection
Imports System.Runtime.InteropServices

' General Information about an assembly is controlled through the following
' set of attributes. Change these attribute values to modify the information
' associated with an assembly.

' Review the values of the assembly attributes

<Assembly: AssemblyTitle("Web Flyer Storefront")>
<Assembly: AssemblyDescription("Low-price reservation finder.")>
<Assembly: AssemblyCompany("Contoso Ltd. ")>
<Assembly: AssemblyProduct("Web Flyer")>
<Assembly: AssemblyCopyright("2002")>
<Assembly: AssemblyTrademark("Web Flyer is a trademark of Contoso Ltd.")>
<Assembly: CLSCompliant(True)>

' The following GUID is for the ID of the typelib if
' this project is exposed to COM
<Assembly: Guid("CE84F243-D7D9-4B7E-B43B-520A0D6B9B30")>

' Version information for an assembly consists of the following four values:
'
'      Major Version
'      Minor Version
'      Build Number
'      Revision
'
' You can specify all the values or you can default
' the Build and Revision Numbers  by using
' the '*' as shown below:

<Assembly: AssemblyVersion("2.1.*")>
Visual C#
using System.Reflection;
using System.Runtime.CompilerServices;

//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
//
[assembly: AssemblyTitle("Web Flyer Storefront")]
[assembly: AssemblyDescription("Low-price reservation finder.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Contoso Ltd.")]
[assembly: AssemblyProduct("Web Flyer")]
[assembly: AssemblyCopyright("2002")]
[assembly: AssemblyTrademark("Web Flyer is a trademark of Contoso Ltd.")]
[assembly: AssemblyCulture("")]

//
// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Revision and Build
// Numbers by using the '*' as shown below:

[assembly: AssemblyVersion("2.1.*")]

Two pieces of information are automatically generated for you in the AssemblyInfo file:

  • The Guid attribute is generated by Visual Studio .NET to identify the assembly to COM components. You need to change this number only if you break compatibility with previous versions of the assembly by removing or significantly changing public objects and members that are used by COM components.

  • The AssemblyVersion attribute automatically updates the build version number, indicated by the asterisk (*). You can add levels of versioning or remove the build number if you want.

Configuring the Application

Web applications use text configuration files (.config) to determine how they run. A Web application' s configuration file (Web.config) resides in the Web application' s root directory, but that file' s function is really to override the settings inherited from the following locations:

  • The Machine.config file located in the Windows\Microsoft.NET\Framework\version\config directory. This sets the base configuration for all .NET assemblies running on the server.

  • The Web.config file located in the IIS root directory. This sets the base configuration for all Web applications and overrides settings in Machine.config.

  • Any Web.config files in the current application' s parent directories. These settings are inherited from Web.config files along the application' s path.

The Web.config file uses XML elements to control different aspects of the Web application, as described in Table 9-1. Because these elements use XML syntax, they are case sensitive and must be typed exactly as shown.

Table 9-1 Web.config Attributes
Element | Use to | For more information, see
compilation | Set the build type to debug or release | “Setting Build Options” in this lesson
customErrors | Display custom error pages in response to HTTP response codes | Chapter 6, “Catching and Correcting Errors”
authentication | Set the type of authentication to use to identify users | Chapter 8, “Maintaining Security”
authorization | List user names or user roles that are authorized to run the application | Chapter 8, “Maintaining Security”
trace | Enable tracing to help diagnose errors or tune performance | Chapter 6, “Catching and Correcting Errors”
sessionState | Determine how ASP.NET stores Session state variables | Lesson 3 in this chapter, and Chapter 3, “Working with Web Objects”
globalization | Set cultural information for localized applications | Chapter 15, “Globalizing Web Applications”

When you make changes to an application' s Web.config file, IIS automatically restarts the application and applies the changes. This has the side effect of resetting current Application or Session state variables, which can adversely affect users.

For security reasons, you can' t access the Web.config file from a browser. If a user requests the Web.config file from your Web site, he or she will receive an access denied error message.


Lesson 2: Deploying a Web Application

Deploying a Web application means installing the application on the server where it will run. Because Web applications run on servers, Web applications usually don' t need an installation program. In this way, Web applications are very different from Windows applications, which are distributed to users on CD-ROMs or other media and which use an installation program to install and register components on the user' s machine.

In this chapter, you will learn how to deploy a completed Web application to a hosting service or to your own server. You will also learn about IIS and security settings required by Web applications.

After this lesson, you will be able to

  • Upload a completed Web application to a hosting service

  • Create a virtual folder in IIS to host a Web application on your own server

  • Copy a Web application to a virtual folder on your own server

  • Install COM and .NET components used by your Web application

  • Assign security privileges to the ASPNET user account

Estimated lesson time: 15 minutes
Using the Upload Tools

If you signed up with an ASP.NET host service, as described in Chapter 1, “Introduction to Web Programming,” you can install your Web application on the host' s servers directly from Visual Studio .NET.

To install your application on a hosting service from Visual Studio .NET, follow these steps:

  1. Open the project you want to install using Visual Studio .NET.

  2. On the Visual Studio .NET start page, click Web Hosting.

  3. On the Web Hosting page, click the Premier Providers or Additional Providers tab.

  4. In the section of the providers page that lists your host, click Upload Directly To Your Account. Visual Studio .NET displays your host' s upload page, as shown in Figure 9-3.

    Figure 9-3 Uploading a Web application

  5. Follow the upload instructions on the host' s page. These instructions vary slightly among the different host services.

Hosting on Your Own Server

If your application is intended to run as part of a local network or if you have access to your own public Web server, install your application on the server by following these steps:

  1. Use IIS to set up a virtual folder for the application.

  2. Copy the Web application to the virtual directory.

  3. Add any shared .NET components to the server' s global assembly cache.

  4. Set the security permissions on the server to allow the application to access required resource.

The following sections describe these steps in more detail.

Setting Up a Virtual Folder

ASP.NET Web applications run under IIS, so both IIS and the Microsoft .NET Framework must be installed on the server before that server can host Web applications. ASP.NET requires the following component versions:

  • IIS version 5.0 or later.

  • The .NET Framework using the same version number as you used to compile the Web application' s assembly.

The Visual Studio .NET distribution disks include tools for setting up the required server components. When you have confirmed that the correct components are installed on the server, create a virtual folder for the application on the server by following these steps:

  1. Using Windows Explorer, create a physical folder on the server to store the application.

  2. Using IIS, start the Virtual Directory Creation Wizard to create the new virtual folder for the physical location created in step 1. Detailed instructions on using this wizard are covered in Chapter 2, “Creating Web Forms Applications.”

Copying the Application

Web application boundaries are defined by the application' s directory structure. That means that an application starts at its root folder and extends to all of its subordinate folders. This makes installing a Web application as easy as dragging the application' s files and subfolders from its development directory to its physical location on the server.

In general, a Web application' s content files can be organized into subfolders any way you like. However, you should follow a couple of conventions:

  • The application' s assembly (.dll) is stored in the /bin subfolder.

  • The start page for the application is generally named Default.aspx or Default.htm.

Whether or not your application uses a default start page is determined by settings in IIS. A default page enables IIS to display a page if the user does not specify one in his or her request. For example, a user might make a request using only your domain name, such as http://www.contoso.com. If a default page is enabled, IIS will respond with http://www.contoso.com/default.aspx.

note | Don' t confuse IIS default pages with the Web Forms application start page in Visual Studio .NET. Visual Studio .NET requires that you set a start page for each project so that the development environment knows which page to display first during debugging. This setting has no effect on the IIS default page settings.
Installing Shared Components

If your application uses components that are shared with other applications on the server, such as custom server controls, those components must be installed on the server. There are three ways to do this, depending on the type of component:

  • For COM components. Copy the component to the server and register the component using the COM registration tools.

  • For weakly-named .NET components. Copy the component to the application' s /bin directory.

  • For strong-named .NET components. Install the component in the server' s global assembly cache.

COM components generally provide a setup program to install or remove them from the system. If the component doesn' t provide a setup program, you can copy it to the server and register it using the MFC RegSvr32.exe utility. For example:

RegSvr32 MyComp.dll

.NET component names come in two flavors: weak and strong. This distinction refers to how the names are stored within the assembly. Weak names are not guaranteed to be unique and thus cannot be shared without potentially causing conflicts. Strong names are digitally signed and provide a public key that ensures there are no conflicts. Furthermore, .NET components with strong names can' t call unmanaged code (such as COM components) and thus avoid potential conflicts with dependencies.

Weak-named .NET components must be individually copied to the /bin directories of the Web applications where they are used. Strong-named .NET components can be copied into the server' s global assembly cache, as shown in Figure 9-4.

Figure 9-4 Viewing .NET components in the global assembly cache

The global assembly cache is a special subfolder within the Windows folder that stores the shared .NET components. When you open the folder, Windows Explorer starts a Windows shell extension called the Assembly Cache Viewer (ShFusion .dll), as shown in Figure 9-4.

You can install strong-named .NET components by dragging them into the Assembly Cache Viewer, or by using the Global Assembly Cache tool (GacUtil.exe), as shown here:

GacUtil –i MyServeControls.dll
Assigning Security Privileges

By default, the ASP.NET worker process runs using the ASPNET account, which is created when you install the .NET Framework. This account has limited privileges, which can cause permission-denied errors if your application writes files or tries to read files outside the current Web application' s boundaries.

If your application requires additional permissions, you have three options:

  • Grant the ASPNET user access to the required files. To use this option, the server must be using the NTFS file system.

  • Change the group the ASPNET user belongs to.

  • Use impersonation to run the process as another user.

To grant the ASPNET user access to a file or folder, follow these steps:

  1. In Windows Explorer, choose Folder Options from the View menu. Windows displays the View Options dialog box.

  2. On the View Menu, click the View tab and deselect the Use Simple File Sharing check box at the bottom of the scrolling list, as shown in Figure 9-5. Click OK to make the change and close the dialog box.

    Figure 9-5 Enabling full file permissions

  3. In Windows Explorer, right-click the file or folder you want to grant access to and select Properties. Windows displays the Properties dialog box.

  4. Click the Security tab, and then click Add to add the ASPNET user to the Group Or User Names list, as shown in Figure 9-6.

    Figure 9-6 Adding the ASPNET user account

  5. Select the check boxes in the permissions list to grant the required access. Click OK to make the changes and close the dialog box.

To change the group the ASPNET user account belongs to, follow these steps:

  1. From the Start menu, choose Administrative Tools and then Computer Management. Windows displays the Computer Management console.

  2. Select Local Users And Groups, and then select Users from the list on the left, as shown in Figure 9-7.

    Figure 9-7 Modifying the ASPNET user account

  3. Right-click the ASPNET user account and select Properties. The Computer Management console displays the properties for the account, as shown in Figure 9-8.

    Figure 9-8 Adding a group to ASPNET

  4. Click the Member Of tab, and then click Add to add the ASPNET user to a new group. Click OK to make the changes and close the dialog boxes.

Adding the ASPNET user to the Administrators group gives your Web application full privileges on the server; however, it also poses a potential security risk because outside users might be able to manipulate your application to hack your server.

To use impersonation to run the ASP.NET process as user other than ASPNET, set the identity element' s impersonation attribute in the application' s Web.config file. For example, the following entry sets the application to run as the WebFlyr user account:

<identity impersonate="true" name="localhost\WebFlyr" password="hpB14dQi" />

If your application uses Windows authentication, you can also use impersonation to pass the identity of authenticated users through to the server. For example, the following Web.config fragment tells the application to use the permissions of the user who signed on to the application:

<identity impersonate="true" />       <!-- Use impersonation. -->

<authorization>
<deny users="?"  />                <!-- Require authentication. -->
</authorization>

For more information about authenticating users, see Chapter 8, “Maintaining Security.”


Lesson 3: Maintaining a Deployed Application

After you have deployed a Web application, you need to monitor how it performs on the server. Many issues can crop up at this point because of the number of users accessing the application, the unpredictable nature of user interaction, and the possibility of malicious attack.

Maintaining a deployed application is an ongoing task that involves three major steps:

  • Monitoring the application for error, performance, and security issues

  • Repairing the application as issues are discovered

  • Tuning the application to respond to user traffic

In this lesson, you will learn how to perform these tasks, as well as learn about some optimization techniques.

After this lesson, you will be able to

  • Use the Windows tools to monitor application errors, security, and performance on the server

  • Create alerts to notify you when a problem occurs

  • Chart performance and monitor activity using event logs

  • Repair a deployed application by replacing the assembly or content files

  • Use the server' s configuration settings to control how ASP.NET applications maintain their processes

  • Tune an application for better performance

  • Evaluate how programming techniques and ASP.NET features affect performance

Estimated lesson time: 30 minutes
Monitoring the Server

After your application is deployed, you need to monitor the server where it is running to help detect and correct problems related to security, performance, and unanticipated errors. Windows provides the following MMC snap-ins for monitoring security, performance, and error events:

  • The Event Viewer snap-in. Lists application, system, and security events as they occur on the system. Use this tool to see what is currently happening on the server and to get specific information about a particular event.

  • The Performance Log and Alert snap-in. Lets you create new events to display in the Event Viewer and allows you to track event counters over time for display graphically or in report form.

The following sections describe how to use these tools in more detail.

Viewing Events

Use the MMC Event Viewer snap-in to view application, system, and security events as they happen.

To run the Event Viewer, choose Event Viewer from the Administrative Tools submenu on the Windows Start menu. (You can show the Administrative Tools menu in Windows XP Professional by opening the Start button' s Properties dialog box and clicking the Customize button on the Start Menu tab. The Administrative Tools option is located on the Advanced tab of the Customize Start Menu dialog box.) After clicking the Event Viewer shortcut, Windows displays the Event Viewer snap-in in the MMC, as shown in Figure 9-9.

Figure 9-9 Viewing events

The Event Viewer snap-in displays general application, system, and security events at three levels: informational events, warning events, and error events.

To view information about a specific event, double-click the event. The snap-in displays the event properties, as shown in Figure 9-10.

Figure 9-10 Viewing event details

You can use the Event Viewer to get a snapshot of what is happening on the server or to get details about a particular event. To create new events or to track events over time, you need to use the MMC Performance Logs and Alerts snap-in, as described in the following sections.

Defining New Events

In many cases the standard system, application, and security events don' t tell you what you really want to know. For example, you might want to know when the number of users connected to a Web application exceeds 1000 or when an unhandled exception occurs within the Web application. To add events such as these to your counter and trace logs, use the MMC Performance Logs and Alerts snap-in.

To run the Performance Logs and Alerts snap-in, choose Performance from the Administrative Tools submenu on the Windows Start menu. Windows displays the Performance Logs and Alerts snap-in in the MMC, as shown in Figure 9-11.

Figure 9-11 Adding events to monitor

The Performance Logs and Alerts snap-in allows you to create log files that record activity on the server and to create alerts that raise events in the Event Viewer. Using this tool, you can monitor a wide variety of information about the server, the applications it is hosting, and the users connected to it.

Perhaps the best way to understand how this works is to walk through adding an event to monitor on your own system, as described in the following steps:

  1. From the Performance window, select Alerts from the Performance Logs And Alerts list in the left pane.

  2. From the Action menu, choose New Alert Settings. The snap-in displays the New Alert Settings dialog box, as shown in Figure 9-12.

    Figure 9-12 Naming a new alert

  3. Type a name for identifying the alert and click OK. The snap-in displays the New Alert dialog box, as shown in Figure 9-13.

    Figure 9-13 Setting alert properties

  4. Add an event to monitor by clicking Add. The snap-in displays the Add Counters dialog box, as shown in Figure 9-14.

    Figure 9-14 Adding counters to monitor ASP.NET users

  5. Select the hardware item or application to monitor from the Performance Objects drop-down list. The snap-in updates the counters list with items that apply to the selected Performance object. Click Add to add the selected counter to the alert. Click Close when done.

  6. Set the criterion that raises the alert in the New Alert dialog box by typing a number in the Limit text box.

    For example, Figure 9-15 shows settings to raise the alert when the ASP.NET user count exceeds 1000.

    Figure 9-15 Setting a criterion to raise the alert

  7. Click the Action tab to specify what the server should do when the alert is raised. By default, the server records the alert as an event that can be viewed from the Event Viewer. You can also send the alert as an e-mail or write the alert to a log file, as shown by Figure 9-16.

    Figure 9-16 Specifying the action to take

  8. When you are done, click OK. The snap-in adds the alert to the Alerts list, as shown in Figure 9-17.

    Figure 9-17 The new alert

By default, the snap-in starts monitoring the new alert immediately. You can stop or restart monitoring the alert by right-clicking the alert and choosing Stop or Start from the pop-up menu.

Windows raises the alert whenever the alert criterion is met. For some counters, such as error counts, this means that after the alert is raised the first time, it is raised after every interval. Because the error count is not automatically reset, it always exceeds the count limit when the limit is met. For these types of alerts, you should be careful what action you specify—sending an e-mail in response to an error count alert can result in a lot of redundant mail messages.

Creating Event Logs

Defining a new event by creating an alert is a good way to be notified the moment a problem occurs or when a performance has degraded, but to track errors and performance over time you must create an event log. Event logs let you gather information about a wide variety of events over time and then display that information graphically or in text form.

To create an event log, follow these steps:

  1. From the Performance window, select Counter Logs from the Performance Logs And Alerts list in the left pane.

  2. From the Action menu, choose New Log Settings. The snap-in displays the New Log Settings dialog box, as shown in Figure 9-18.

    Figure 9-18 Naming a new log file

  3. Type the name of the log and click OK. The snap-in displays the log file' s properties dialog box, as shown in Figure 9-19.

    Figure 9-19 Setting log properties

  4. Click Add Objects or Add Counters to add items to track within the log file. The snap-in displays the Add dialog box, as shown in Figure 9-20. These counters work the same way they do for alerts.

    Figure 9-20 Adding counters to record

  5. Click the Log Files tab to specify the format of the log file to create. The snap-in displays the Log Files properties, as shown in Figure 9-21.

    Figure 9-21 Specifying log file format

  6. Log files can be saved in binary, text, or database formats. Click OK when done. The snap-in adds the log to the Counter Logs list, as shown in Figure 9-22.

    Figure 9-22 The new log

As with alerts, the snap-in starts monitoring the new log immediately. You can stop or restart monitoring the log by right-clicking the log and choosing Stop or Start from the pop-up menu.

Viewing Event Logs

By default, the Performance Logs and Alerts snap-in saves log files to the PerfLogs root folder. To view the log file from within the snap-in, follow these steps:

  1. From the Performance window, select System Monitor from the list in the left pane. The snap-in displays the system monitor with the current performance statistics, as shown in Figure 9-23.

    Figure 9-23 Current CPU, memory, and disk usage statistics

  2. Click the View Log Data toolbar button. The snap-in displays the System Monitor Properties dialog box, as shown in Figure 9-24.

    Figure 9-24 System Monitor properties

  3. Select Log Files and click Add to specify a file to open. The snap-in displays the Select Log File dialog box, as shown in Figure 9-25.

    Figure 9-25 Opening a log file

  4. Select a log file to open from the PerfLogs folder and click Open to add it to the monitor.

  5. Click the Data tab in the System Monitor Properties dialog box to add items from the log to the chart, as shown in Figure 9-26.

    Figure 9-26 Adding items to chart

  6. Click OK to close the System Monitor Properties dialog box. The snap-in displays the log file, as shown in Figure 9-27.

    Figure 9-27 Viewing a log file in chart form

  7. Click the View Report toolbar button to view the data as text rather than graphically. The System Monitor displays the text view, as shown in Figure 9-28.

    Figure 9-28 Viewing a log file in report form

    The MMC Event Viewer and Performance Logs and Alerts snap-ins provide a way to monitor the health of a server, detect application errors, identify resource conflicts, and predict future hardware needs. For more information about performing these tasks, see the online Help provided with each of these snap-ins.
Repairing Deployed Applications

After a Web application is deployed, you can revise and repair it in place, without restarting the server or IIS.

To repair a deployed Web application, copy the new assembly (.dll) and/or content files (.aspx, .ascx, and so on) to the application folder on the server. ASP.NET automatically restarts the application when you replace the assembly; you do not need to install or register the assembly on the server.

ASP.NET Web applications also have a limited ability to repair themselves through process recycling. Process recycling is the technique of shutting down and restarting an ASP.NET worker process (aspnet_wp.exe) that has become inactive or is consuming excessive resources. You can control how ASP.NET processes are recycled through attributes in the processModel element in the Machine.config file, as shown in bold here:

<processModel
   enable="true"
   timeout="Infinite"
   idleTimeout="Infinite"
   shutdownTimeout="0:00:05"
   requestLimit="Infinite"
   requestQueueLimit="5000"
   restartQueueLimit="10"
   memoryLimit="60"
   webGarden="false"
   cpuMask="0xffffffff"
   userName="machine"
   password="AutoGenerate"
   logLevel="Errors"
   clientConnectedCheck="0:00:05"
   comAuthenticationLevel="Connect"
   comImpersonationLevel="Impersonate"
   responseRestartDeadlockInterval="00:09:00"
   responseDeadlockInterval="00:03:00"
   maxWorkerThreads="25"
   maxIoThreads="25"/>

Table 9-2 describes the processModel attributes that relate to process recycling.

Table 9-2 Process Recycling Attributes
Attribute | Determines
timeout | The amount of time (hh:mm:ss) before the process is shut down and restarted. Use this setting to automatically recycle a process after a certain number of requests as a preventative measure.
shutDownTimeOut | How much time each process has to shut itself down. After this amount of time, the process is terminated by the system if it still running.
requestLimit | The number of queued requests to serve before the process is shut down and restarted. Use this setting the same way you use the timeout attribute.
restartQueueLimit | The number of queued requests to retain while the process is shut down and restarted.
memoryLimit | The percentage of physical memory the ASP.NET process is allowed to consume before that process is shut down and a new process is started. This setting helps prevent memory leaks from degrading the server.
responseRestartDeadlocInterval | The amount of time to wait before restarting a process that was shut down because it was deadlocked. This setting is usually several minutes to prevent applications with serious errors from degrading the server.
responseDeadlockInterval | The amount of time to wait before restarting a process that is deadlocked. The process is restarted if there are queued requests and the process has not responded within this time limit.
Tuning Deployed Applications

ASP.NET Web applications have a number of settings you can control to tune performance after they are deployed:

  • Use the Microsoft FrontPage Server Extensions to control how pages are cached in the server' s memory.

  • Use the IIS Application Settings to control the application protection level.

  • Use the processModel element' s attributes in the server' s Machine.config file to control the number of threads and time-out behavior that the server provides for Web applications.

  • Use the sessionState element' s attributes in the application' s Web.config file to control how Session state information is saved.

The following sections discuss each of these techniques in turn.

Setting Cache Options

ASP.NET Web applications can store frequently requested documents in the server' s memory to speed up access to those items. Storing items in this way is called caching.

To configure caching for a Web application, follow these steps:

  1. Configure the FrontPage Server Extensions for the Web application in IIS. To do this:

    • Select the Web application, choose All Tasks from the Action menu, and then choose Configure FrontPage Server Extensions. IIS displays the New Subweb Wizard.

    • Walk through the wizard by clicking Next and following the instructions, which are straightforward.

  2. In IIS, right-click the Web application and select Properties from the pop-up menu. IIS displays the Web application Properties dialog box.

  3. Click the Server Extensions tab, and then select the number of pages your Web application contains from the Performance drop-down list, as shown in Figure 9-29.

    Figure 9-29 Tuning performance with FrontPage Server Extensions

  4. The FrontPage Server Extensions provide predefined caching options for Web applications based on how many documents (.aspx or .htm files) they contain. Alternatively, you can set your own cache settings by selecting Use Custom Settings from the Performance drop-down list. IIS displays the Performance dialog box, as shown in Figure 9-30. Make your changes and click OK to close the dialog boxes.

    Figure 9-30 Setting custom caching options

Changing cache settings is a balancing act. Saving too much information in cache reduces the total amount of memory available to run the application. Saving too little requires more disk accesses to retrieve the required documents.

Changing Application Protection Level

As mentioned in Chapter 2, “Creating Web Forms Applications,” IIS can run your Web application as a process within IIS itself, as a separate process pooled with other Web applications, or as an isolated process. The greater the level of isolation, the more reliably the Web application runs, because the application is more protected from other processes that can contain errors or exhibit other faults.

However, greater isolation also slows the execution of a Web application, because procedure calls must cross process boundaries. As with caching, this is a balancing act: reliability vs. speed.

To change a Web application' s protection level, follow these steps:

  1. From IIS, right-click the Web application and choose Properties from the pop-up menu. IIS displays the Properties dialog box.

  2. In the Application Protection drop-down list, select Low, Medium, or High to specify the application protection level. Click OK.

Adjusting processModel Attributes

The processModel element attributes in the server' s Machine.config file provides attributes that control certain performance aspects, such as the maximum number of requests to be queued, how long to wait before checking if a client is connected, and how many threads to allow per processor, as shown here in bold:

<processModel
   enable="true"
   timeout="Infinite"
   idleTimeout="Infinite"
   shutdownTimeout="0:00:05"
   requestLimit="Infinite"
   requestQueueLimit="5000"
   restartQueueLimit="10"
   memoryLimit="60"
   webGarden="false"
   cpuMask="0xffffffff"
   userName="machine"
   password="AutoGenerate"
   logLevel="Errors"
   clientConnectedCheck="0:00:05"
   comAuthenticationLevel="Connect"
   comImpersonationLevel="Impersonate"
   responseRestartDeadlockInterval="00:09:00"
   responseDeadlockInterval="00:03:00"
   maxWorkerThreads="25"
   maxIoThreads="25"
   />

Table 9-3 describes the processModel attributes that relate to performance tuning.

Table 9-3 Performance Attributes
Attribute | Determines
requestQueueLimit | The number of queued requests allowed before ASP.NET returns response code 503 (Server too busy) to new requests
clientConnectedCheck | The amount of time (hh:mm:ss) to wait before checking if a client is still connected
maxWorkerThreads | The maximum number of threads per processor
maxIOThreads | The maximum number of I/O threads per processor

In general, lowering these settings allows your server to handle fewer clients more quickly. Increasing these settings permits more clients and more queued requests, but slows response times.

Adjusting sessionState Attributes

Storing state information for each client session takes time. If your Web application does not use Session state variables, you should turn off Session state tracking.

To turn off Session state, in the application' s Web.config file, set the sessionState element' s mode attribute to “Off”, as shown here:

    <sessionState
            mode="Off"
            stateConnectionString="tcpip=127.0.0.1:42424"
            sqlConnectionString="data source=127.0.0.1;user id=sa;password="
            cookieless="false"
            timeout="20"
    />

Turning off Session state means that you can' t use Session state variables in code anywhere in the application.

Optimization Tips

Think of tuning as making adjustments to a deployed application that don' t affect code. Optimization usually refers to writing code in a way that executes more quickly or consumes fewer resources. In general, optimizations simply reflect the good programming practices that you have learned so far in this book. Some of the most important points are summarized here:

  • Turn off debugging for deployed applications. Code that has been compiled with Release options runs faster than code compiled with Debug options.

  • Avoid round trips between the client and server. ASP.NET uses post-back to process server events on a page. Try to design Web forms so that the data on the Web form is complete before the user posts the data to the server. You can use the Validation controls to ensure that data is complete on the client side before the page is submitted.

  • Turn off Session state if it is not needed. In some cases you can design your code to use other techniques, such as cookies, to store client data.

  • Turn off ViewState for server controls that do not need to retain their values.

  • Use stored procedures with databases. Stored procedures execute more quickly than ad hoc queries.

  • Use SqlDataReader rather than data sets for read-forward data retrieval. Using SqlDataReader is faster and consumes less memory than creating a data set.

When applying these optimization tips, please be guided by good sense. For instance, if you need to retain data about a client session, go ahead and use Session state rather than write your own implementation to do the same thing.


Lesson 4: Deploying Across Multiple Servers

Web applications that serve a large number of users or that present large amounts of data need to able to add capacity as users' demands increase. The ability to add capacity to an application is called scalability. ASP.NET Web applications support this concept through their ability to run in multiple processes and to have those processes distributed across multiple CPUs and/or multiple servers.

A Web application running on a single server that has multiple CPUs is called a Web garden in the ASP.NET documentation. A Web application running on multiple servers is called a Web farm. In this lesson, you will learn how to enable your Web application to use each of these server configurations.

After this lesson, you will be able to

  • Configure a server with multiple processors to run a Web application on several or all of its CPUs

  • Run a Web application on multiple servers

  • Use a state server to share Session state information between multiple instances of the same Web application

  • Share Session state information using a SQL database

Estimated lesson time: 10 minutes
Scaling Up with Multiple Processors

If your server has multiple processors, you can specify that ASP.NET runs on all or some of the CPUs by setting the webGarden attribute of the processModel element in the server' s Machine.config file, as shown here in bold:

<processModel
   enable="true"
   timeout="Infinite"
   idleTimeout="Infinite"
   shutdownTimeout="0:00:05"
   requestLimit="Infinite"
   requestQueueLimit="5000"
   restartQueueLimit="10"
   memoryLimit="60"
   webGarden="true"
   cpuMask="0xffffffff"
   userName="machine"
   password="AutoGenerate"
   logLevel="Errors"
   clientConnectedCheck="0:00:05"
   comAuthenticationLevel="Connect"
   comImpersonationLevel="Impersonate"
   responseRestartDeadlockInterval="00:09:00"
   responseDeadlockInterval="00:03:00"
   maxWorkerThreads="25"
   maxIoThreads="25"
/>

Table 9-4 describes the processModel attributes that relate to Web gardens.

Table 9-4 Web Garden Attributes
Attribute | Usage
webGarden | Set to “true” to run ASP.NET Web applications on more than one processor on this server.
cpuMask | Specifies which CPUs should run ASP.NET Web applications. The setting “0xffffffff” runs the applications on all CPUs.

The cpuMask attribute is a bit mask used to turn ASP.NET on (1) or off (0) for each CPU on the server. For example, binary 1101 turns ASP.NET on for CPU 0, 2, and 3 on a four-CPU server, leaving CPU 1 free for other uses. Binary 1101 maps to the hexadecimal setting “0x0000000d”.

Running ASP.NET on multiple processors requires that you take special steps to handle Application and Session state information. See “Sharing State Information” later in this lesson for more information.

Scaling Up with Multiple Servers

In order for multiple servers to handle requests for a single HTTP address, you need to install load balancing to your network. Load-balancing services can be provided by hardware or software solutions.

Microsoft Windows 2000 Advanced Server and Microsoft Windows 2000 Datacenter both include Network Load Balancing (NLB) software to distribute requests to multiple servers. See the Visual Studio .NET online Help topic, “Network Load Balancing Provider” for more information on installing and using this tool.

When load balancing is enabled for your network, you can install your Web application on multiple servers and have client requests distributed automatically to the least busy server at any given time.

Running a Web application on multiple servers requires that you take special steps to handle Application and Session state information. See the following section for more information.

Sharing State Information

In both a Web garden and a Web farm, client requests are directed to the ASP.NET process that is currently least busy. That means that a single client can interact with different CPUs or servers over the course of his or her session. This has the following implications for Application and Session state variables:

  • Application state variables are unique to each separate instance of the Web application. Clients can share information through Application state if the Web application is running on a Web garden or a Web farm.

  • Session state variables as stored in-process by default. To enable Session state in a Web garden or Web farm, you need to specify a Session state provider.

The following sections explore these issues in more detail.

Sharing Application State

To share data across multiple sessions in a Web garden or Web farm, you must save and restore the information using a resource that is available to all the processes. This can be done through an XML file, a database, or some other resource using the standard file or database access methods.

Sharing Session State

ASP.NET provides two built-in ways to share Session state information across a Web garden or Web farm. You can share Session state using:

  • A state server, as specified by a network location. This technique is simple to implement and doesn' t require you to install Microsoft SQL Server.

  • A SQL database, as specified by a SQL connection. This technique provides the best performance for storing and retrieving state information.

To share Session state information using a state server, follow these steps:

  1. In the Web application' s Web.config file, set the sessionState element' s mode and stateConnectionString attributes. For example, the following settings use the state server located at the TCP/IP address “192.168.1.102” on port 42:

    <sessionState
   mode="StateServer"
   stateConnectionString="tcpip=192.168.1.102:42"
   sqlConnectionString="data source=192.168.1.102;user id=sa;password="
   cookieless="false"
   timeout="20"
/>

  2. Run the aspnet_state.exe utility on the Session state server. The aspnet_state.exe utility is installed in the \WINDOWS\Microsoft.NET\ Framework\version folder when you install Visual Studio .NET Professional or Visual Studio .NET Enterprise Architect editions.

To share Session state information using a SQL database, follow these steps:

  1. In the Web application' s Web.config file, set the sessionState element' s mode and sqlConnectionString attributes. For example, the following settings use the SQL Server located at the TCP/IP address “192.168.1.102”:

    <sessionState
   mode="SQLServer"
   stateConnectionString="tcpip=192.168.1.102:42"
   sqlConnectionString="data source=192.168.1.102;user id=sa;password="
   cookieless="false"
   timeout="20"
/>

  2. Run the InstallSqlState.sql utility to the Session state server. This utility installs the SQL database that shares Session state information across processes. The InstallSqlState.sql utility is installed in the \WINDOWS\Microsoft.NET\ Framework\version folder when you install Visual Studio .NET Professional, Visual Studio .NET Enterprise Developer, or Visual Studio .NET Enterprise Architect editions.


Summary

  • Set the build option to Release and debug attribute to “false” in Web.config before building your application for deployment.

  • Use the AssemblyInfo.vb or AssemblyInfo.cs file to identify the application' s assembly and provide versioning information.

  • To deploy a Web application, use the Visual Studio .NET upload tools or simply copy the application' s assembly and content files to the server.

  • Set the security privileges on the ASPNET user account to enable your Web application to write files or access other security-restricted resources on the server.

  • After deployment, monitor the Web application using the MMC Event Viewer and Performance Logs and Alerts snap-ins.

  • You can repair Web applications by uploading new versions of the application' s assembly or content files to the server.

  • Web applications with memory leaks or other problems can repair themselves through ASP.NET process recycling.

  • Use the FrontPage Server Extensions to improve Web application performance by caching frequently used pages.

  • To enable a Web garden, use the processModel element attributes in the server' s Machine.config file.

  • To enable a Web farm, use a load balancer to distribute requests to multiple servers.

  • For both Web gardens and Web farms, share Session state information by setting the sessionState element in the application' s Web.config file.


Lab: Build and Deploy a Web Application

In this lab, you will prepare a Web application for deployment, deploy the application to a server, and monitor and tune the application' s performance. Before you begin, choose one of the applications you have completed as part of this book—or choose a Web application that you have completed on your own—to use for the exercises that follow.

This lab covers material that applies to all types of Web applications, so it will be more interesting if you choose an application you want to share with others or are interested in using yourself over the Internet.

Estimated lesson time: 30 minutes
Exercise 1: Prepare the Application

In this exercise, you will build a release version of the Web application. Use the skills you learned in Lesson 1 to complete the following major tasks:

  1. Identify the application and set its version number in the AssemblyInfo.vb or AssemblyInfo.cs file.

  2. Set the compilation element' s debug attribute to “false” in the application' s Web.config file.

  3. Set the application' s build option to Release.

  4. Build the application.

Exercise 2: Deploy the Application

How you deploy your application depends on the type of server you have available. If you are hosting your applications through a Web hosting service, as discussed in Chapter 1, “Introduction to Web Programming,” use the Visual Studio .NET upload tools to deploy your application to the hosting service.

If you are hosting applications on your own server, use the skills you learned in Lesson 2 in this chapter to perform the following tasks:

  1. Create a virtual folder for the application using IIS on the server.

  2. Copy the Web application to the virtual folder.

  3. View the security settings on the server to verify that the ASPNET user account has the permissions to perform the tasks required by your application.

  4. If needed, grant access to resources used by your application.

  5. Run the application from the server to verify that your security settings allow the application to run correctly.

Exercise 3: Chart Application Performance

In this exercise, you will use the MMC Performance and Alerts snap-in to keep a log of the number of requests processed by ASP.NET and chart those requests against processor activity on the server. When complete, the chart will appear similar to that shown in Figure 9-31.

Figure 9-31 Charting Web application performance

Use the skills you learned in Lesson 3 to perform the following tasks:

  1. Run the MMC Performance and Alerts snap-in and connect to the server that is running the deployed Web application. If you deployed your application to a Web hosting service in Exercise 2, you will not be able to connect to that server. Instead, run the Web application locally on your development machine and connect to your local machine instead.

  2. Create a new counter log named “Exercise 3.”

  3. Add the counters to the log, as shown in the following table:

    4. Performance object | Counter
Processor | %Processor time
ASP.NET applications | Anonymous requests
ASP.NET | Applications running
ASP.NET | Worker processes running

  4. Start the counter log and stop it after a few minutes to collect data about the server.

  5. Select the System Monitor in the snap-in and change the monitor' s properties to view the log files:

    • Right-click the System Monitor and select Properties from the pop-up menu.

    • Click the Properties Source tab and select Log Files, then click Add to add the log files to the monitor.

    • Click the Properties Data tab and then click Add to add the ASP.NET counters to the monitor.

    • On the Properties Data tab, set the scale of each of the ASP.NET counters to 10.

    • Click OK to view the log as a chart.

  6. In the System Monitor, select Report to view the log data in text form.

  7. Experiment with other counters, such as ASP.NET application errors, cache hits, and so on.

Exercise 4: Tune the Application

In this exercise, you will install the FrontPage Server Extensions and set the cache options for the Web application. By default, Visual Studio .NET installs the FrontPage Server Extensions on your computer when you install the .NET components. If you deployed your application to a Web hosting service in Exercise 2, you will not be able to perform these tasks on the deployed application because you will not have physical access to the server. Instead, use the local version of your Web application, as you did in Exercise 3.

Use the skills you learned in Lesson 3 to perform the following tasks:

  1. Run IIS and install the FrontPage Server Extensions on the server' s virtual folder containing the Web application.

  2. In IIS, view the virtual folder' s properties and click the Server Extensions tab.

  3. In the Performance drop-down list box, select Tune For <100 Pages and click OK.

  4. Rerun the counter log created in Exercise 3 to see if you can detect any changes on the server.


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. What permissions do Web applications run under by default?

  2. Why is the Machine.config file important to deployed Web applications?

  3. How do you set cache options for a Web application?

  4. How does deploying to a Web farm or a Web garden affect Session state in a Web application?


Testing Web Applications

Chapter 10
Testing Web Applications
Lesson 1: Creating Tests
Lesson 2: Running Tests
Lesson 3: Debugging
About This Chapter

The goal of testing software is to ensure that the software meets product requirements. Two preconditions lurk in that simple statement:

  • Without product requirements, you can' t start testing.

  • Without measurable goals, you don' t know when to stop testing.

Product requirements and goals are generally determined outside the development process—usually by customers or management. In this chapter, you will learn how to apply product requirements to plan, create, run, and correct problems found by tests.

Before You Begin

To complete this chapter, you must:

  • Have access to a Web server—either through a Web hosting service or through your local area network—that you can use to test and debug Web applications.

  • Have successfully completed and deployed one or more of the sample Web applications in this book.

  • Be familiar with a Windows scripting language, such as VBScript or JScript.

  • Have installed Microsoft Application Center Test (ACT) included with Microsoft Visual Studio .NET Enterprise Architect Edition. This requirement applies only to the material in this chapter that discusses load testing Web applications.


Lesson 1: Creating Tests

All software intended for public consumption should receive some level of testing. The more complex or widely distributed a piece of software is, the more essential testing is to its success. Without testing, you have no assurance that software will behave as expected. The results in a public environment can be truly embarrassing.

For software, testing almost always means automated testing. Automated tests use a programming language to replay recorded user actions or to simulate the internal use of a component. Automated tests are reproducible (the same test can be run again and again) and measurable (the test either succeeds or fails). These two advantages are key to ensuring that software meets product requirements.

In this chapter, you will learn how to write a test plan, how to ensure the product is thoroughly tested using different types of tests, and how to create different types of tests using Visual Studio .NET.

After this lesson, you will be able to

  • Understand a software test plan

  • Identify different types of software testing

  • Write unit tests for software components

  • Create integration tests for completed components

  • Explain how a test library can identify regressions

  • Use ACT to create Web application load tests

  • Test Web applications for different client configurations

Estimated lesson time: 30 minutes
Developing a Test Plan

The first step in testing is developing a test plan based on the product requirements. The test plan is usually a formal document that ensures the product meets the following standards:

  • Is thoroughly tested. Untested code adds an unknown element to the product and increases the risk of product failure.

  • Meets product requirements. To meet customer needs, the product must provide the features and behavior described in the product specification. For this reason, product specifications should be clearly written and well understood.

  • Does not contain defects. Features must work within established quality standards, and those standards should be clearly stated within the test plan.

Having a test plan helps you avoid ad hoc testing—the kind of testing that relies on the uncoordinated efforts of developers or testers to ensure that code works. The results of ad hoc testing are usually uneven and always unpredictable. A good test plan answers the following questions:

  • How are tests written? Describe the languages and tools used for testing.

  • Who is responsible for the testing? List the teams or individuals who write and perform the tests.

  • When are the tests performed? The testing schedule closely follows the development schedule.

  • Where are the tests and how are test results shared? Tests should be organized so that they can be rerun on a regular basis.

  • What is being tested? Measurable goals with concrete targets let you know when you have achieved success.

Some of these questions might have more than one answer, depending on the type of test. For instance, individual developers are often responsible for writing the first level of tests for their own code, while a separate testing team might be responsible for ensuring that all code works together. The following sections describe the different types of tests and the techniques used with Visual Studio .NET to perform these tests.

Types of Tests

The test plan specifies the different types of tests that will be performed to ensure the product meets customer requirements and does not contain defects. Table 10-1 describes the most common test types.

Table 10-1 Types of Tests
Test type | Ensures that
Unit test | Each independent piece of code works correctly
Integration test | All units work together without errors
Regression test | Newly added features do not introduce errors to other features that are already working
Load test (also called stress test) | The product continues to work under extreme usage
Platform test | The product works on all of the target hardware and software platforms

These test types build on each other and are usually performed in the order shown in Table 10-1. The testing process follows the flow diagram shown in Figure 10-1.

Figure 10-1 The testing cycle

The process shown in Figure 10-1 is based on a modular product design in which the product is developed as a set of components that can be programmed, tested, integrated, and released individually. This is the modern approach, and you should be wary of any project that proposes a monolithic design in which integration and testing are performed only at the end of the development cycle.

Unit Testing

A product unit is the smallest piece of code that can be independently tested. From an object-oriented programming perspective, classes, properties, methods, and events are all individual units. A unit should pass its unit test before it is checked into the project for integration.

Unit tests are commonly written by the developer who programmed the unit, and are either written in the same programming language as the product unit being tested or in a similar scripting language, such as VBScript. The unit test itself can be as simple as getting and setting a property value or it can be more complicated. For instance, a unit test might take sample data and calculate a result, and then compare that result against the expected result to check for accuracy.

Group the unit tests together logically. Tests that check each property and method in a class can be included in a single procedure, which is then called from the Sub Main of a separate testing application. For example, the following test procedure creates an instance of the FlashCardClass created earlier in this book, and then tests each of its properties and methods:

Visual Basic .NET
Function TestFlashCard() As Boolean
    'Create class
    Dim FlashCard As New vbFlashCards.FlashCardClass()
    'Test if class exists.
    If IsNothing(FlashCard) Then
        Console.WriteLine("FlashCardClass failed")
        ' Return False and end here.
        Return False
    End If
    'Test shuffle method using maximum and minimum values.
    FlashCard.Shuffle(Integer.MinValue, Integer.MaxValue)
    'Test properties.
    Dim bResult As Boolean = True
    Dim intFirst As Integer = FlashCard.FirstNumber
    Dim intSecond As Integer = FlashCard.SecondNumber
    Dim dblAnswer As Double = intFirst + intSecond
    If dblAnswer <> FlashCard.Answer Then
        Console.WriteLine("Error: Numbers don't add up!")
        bResult = False
    End If
    ' Shuffle again.
    FlashCard.Shuffle(Integer.MinValue, Integer.MaxValue)
    ' Make sure the new values are unique.
    If intFirst = FlashCard.FirstNumber Then
        Console.WriteLine("Warning: FirstNumber not unique after shuffle")
    End If
    If intSecond = FlashCard.SecondNumber Then
        Console.WriteLine("Warning: SecondNumber not unique after shuffle")
    End If
    ' Check different operators.
    FlashCard.Operation = "-"
    If FlashCard.FirstNumber - FlashCard.SecondNumber <> FlashCard.Answer Then
        Console.WriteLine("Error: - operator doesn't substract.")
        bResult = False
    End If
    FlashCard.Operation = "x"
   If FlashCard.FirstNumber * FlashCard.SecondNumber <> FlashCard.Answer Then
        Console.WriteLine("Error: x operator doesn't multiply.")
        bResult = False
    End If
    FlashCard.Operation = "+"
   If FlashCard.FirstNumber + FlashCard.SecondNumber <> FlashCard.Answer Then
        Console.WriteLine("Error: + operator doesn't add.")
        bResult = False
    End If
    ' Return success/failure.
    Return bResult
End Function
Visual C#
static bool TestFlashCardClass()
{
   //Create class
   csFlashCards.FlashCardClass FlashCard = new csFlashCards.FlashCardClass();
   //Test is class exists.
   if (FlashCard == null)
   {
         Console.WriteLine("FlashCardClass failed");
         // Return false and end here.
         return false;
   }
   //Test shuffle method using maximum and minimum values.
   FlashCard.Shuffle(int.MinValue, int.MaxValue);
   //Test properties.
   bool bResult  = true;
   int intFirst = FlashCard.FirstNumber;
   int intSecond = FlashCard.SecondNumber;
   double dblAnswer = intFirst + intSecond;
   if (dblAnswer != (double)FlashCard.Answer())
   {
         Console.WriteLine("Error: Numbers don't add up!");
         bResult = false;
   }
   // Shuffle again.
   FlashCard.Shuffle(int.MinValue, int.MaxValue);
   // Make sure new values unique.
   if (intFirst == FlashCard.FirstNumber)
         Console.WriteLine("Warning: FirstNumber not unique after shuffle");
   if (intSecond == FlashCard.SecondNumber)
         Console.WriteLine("Warning: SecondNumber not unique after shuffle");
   // Check different operators.
   FlashCard.Operation = "-";
   if (FlashCard.FirstNumber - FlashCard.SecondNumber != FlashCard.Answer())
   {
         Console.WriteLine("Error: - operator doesn't substract.");
         bResult = false;
   }
   FlashCard.Operation = "x";
   if (FlashCard.FirstNumber * FlashCard.SecondNumber != FlashCard.Answer())
   {
         Console.WriteLine("Error: x operator doesn't multiply.");
         bResult = false;
   }
   FlashCard.Operation = "+";
   if (FlashCard.FirstNumber + FlashCard.SecondNumber != FlashCard.Answer())
   {
         Console.WriteLine("Error: + operator doesn't add.");
         bResult = false;
   }
    // Return success/failure.
   return bResult;
}

The most important factor in a unit test is thoroughness. Unit tests should exercise each piece of code in the application and use a range of possible values. The preceding sample demonstrates using a wide range by passing the integer MinValue and MaxValue to the Shuffle method.

The tests should report errors at the unit level so that locating and fixing problems is straightforward. The preceding sample does this by displaying a message in the Output window using Console.WriteLine. Alternatively, you can write errors to a testing log file using Debug.WriteLine or raise alerts using Debug.Assert.

Integration Testing

The first integration test always answers the question, “Does the application compile?” At this point, a compilation error in any of the components can keep the integration testing from moving forward. Some projects use nightly builds to ensure that the product will always compile. If the build fails, the problem can be quickly resolved the next morning.

The most common build problem occurs when one component tries to use another component that has not yet been written. This occurs with modular design because the components are often created out of sequence. You solve this problem by creating stubs. Stubs are non-functional components that provide the class, property, or method definition used by the other component. Stubs are a kind of outline of the code you will create later.

When all of the build problems are resolved, integration testing really becomes just an extension of unit testing, though the focus is now whether the units work together. At this point, it is possible to wind up with two components that need to work together through a third component that has not been written yet. To test these two components, you create a driver. Drivers are simply test components that make sure two or more components work together. Later in the project, testing performed by the driver can be performed by the actual component.

In addition to adding stubs and drivers to your application, you might need to add a testing interface to help automate the integration testing of some components. A testing interface is a set of public properties and methods that you can use to control a component from an external testing program. For example, the following testing interface allows automated testing of the FlashCard application' s Web form:

Visual Basic .NET
#If DEBUG Then
    ' Public integration testing interface.
    Public FirstNumber As String
    Public SecondNumber As String

    Public Function TestUI(ByVal Answer As String) As Boolean
        FirstNumber = lblFirst.Text
        SecondNumber = lblSecond.Text
  ' Put answer in txtAnswer.
        txtAnswer.Text = Answer
        ' Run Changed event.
        txtAnswer_TextChanged(Me, EventArgs.Empty)
        ' Check if correct.
        If lblFeedback.Text = "Correct!" Then
            Return True
        Else
            Return False
        End If
    End Function
#End If
Visual C#
#if DEBUG
// Public integration testing interface.
public string FirstNumber;
public string SecondNumber;

public bool TestUI(string Answer)
{
   FirstNumber = lblFirst.Text;
   SecondNumber  = lblSecond.Text;
   txtAnswer.Text = Answer;
   // Run Changed event.
   txtAnswer_TextChanged(this, EventArgs.Empty);
   // Check if correct.
   if (lblFeedback.Text == "Correct!")
         return true;
   else
         return false;
}
#endif

The preceding code uses the #If…#End If/#if…#endif preprocessor directives to prevent the testing interface from being compiled into the release version of the product. This is important because a testing interface exposes the inner workings of the application, which you generally want to keep private in the final version.

Regression Testing

Unit and integration tests form the basis of regression testing. As each test is written and passed, it gets checked into the test library for a regularly scheduled testing run. If a new component or a change to an existing component breaks one of the existing unit or integration tests, the error is called a regression.

If you design your test properly, the unit and integration tests report their errors in a way that makes it easy to locate the cause of the regression. If the test doesn' t provide an easy way to locate the cause, you might need to improve those tests or you might need to write new tests to target the regression.

The key to success with both integration and regression testing is to run the full set of tests frequently—if possible, as part of the nightly build. Detecting problems early and resolving them as they occur prevents one error from hiding others.

Load Testing

When you deploy a Web application, it might be used by one or two clients at a time or it might get deluged by thousands of requests. To find out how well a Web application will work in these varying conditions, you need to perform load tests. Use the Microsoft Application Center Test (ACT) to create and run load tests on a Web application.

To use ACT, follow these steps:

  1. Create a test by recording a user session with the Web application using ACT.

  2. Set the load level and duration properties for the test.

  3. Run the test.

In general, load tests are not created as early in the development process or run as frequently as unit, integration, or regression tests. The following sections describe how to create and run load tests in more detail.

Recording a Load Test

To record a load test in ACT, follow these steps:

  1. From the ACT Action menu, choose New Test. ACT displays the New Test Wizard.

  2. Click Next. The wizard displays the Test Source page, as shown in Figure 10-2.

    Figure 10-2 Recording a new load test

  3. Select Record A New Test and click Next. The wizard displays the Test Type page, as shown in Figure 10-3.

    Figure 10-3 Selecting a language

  4. Click Next. The wizard displays the Browser Record page, as shown in Figure 10-4.

    Figure 10-4 Starting and stopping recording

  5. Click Start Recording. The wizard displays a browser window, as shown in Figure 10-5.

    Figure 10-5 Performing actions to record

  6. Perform the actions you want to record. For instance, you might walk through all the pages in your Web application, performing tasks on each page.

  7. On the Browser Record page, click Stop Recording when you are done recording actions, and then click Next. The wizard displays the Test Properties page, as shown in Figure 10-6.

    Figure 10-6 Naming the test

  8. Enter a name for the test and click Next. The wizard displays the Completing The New Test page. Click Finish to close the wizard.

Setting Test Properties

After you record a test, it is displayed in the Tests list in ACT, as shown in Figure 10-7.

Figure 10-7 The recorded test

ACT tests can be written in the VBScript or JScript languages, but ACT records tests using VBScript only. After you record a test, set the load and duration of the test by following these steps:

  1. From the Action menu, choose Properties. ACT displays the test' s load and duration properties, as shown in Figure 10-8.

    Figure 10-8 Test load and duration

  2. Set the Test load level to increase the number of users accessing the Web application at the same time. Set the Test duration to run the test for a certain amount of time or for a certain number of requests. Click OK when done.

Running Load Tests

To run a load test in ACT, choose Start Test from the Action menu. ACT displays the Test Status dialog box, as shown in Figure 10-9.

Figure 10-9 The Test Status dialog box

Running tests with ACT and analyzing their results is covered in more detail in Lesson 2.

Platform Testing

For Web applications, platform testing usually means verifying four main points:

  • Web forms display correctly on all supported browsers and supported versions of those browsers.

  • The Web application appropriately handles unsupported browser versions, such as by displaying instructions for downloading the required version.

  • The client is prompted to install any required components, such as ActiveX objects or plug-ins, if they are not already installed on his or her computer.

  • The Web application has acceptable performance over slower forms of network connections, such as modems.

To run platform tests, you need to set up a platform lab that contains computers representing the different hardware and software configurations that your application supports, as shown in Figure 10-10.

Figure 10-10 Platform testing lab

Verifying that each Web form displays correctly requires some human intervention. ASP.NET renders server controls differently on different browsers, so you should visually inspect your Web application as part of the final testing cycle.


Lesson 2: Running Tests

In the preceding lesson, you learned how to plan testing and how to create different types of tests using Visual Studio .NET. In this lesson, you will learn how to run those tests and capture their results.

After this lesson, you will be able to

  • Use ACT to run load tests on Web applications

  • Record results and performance data from a load test

  • Mimic different browser types with ACT and check browser responses for contained strings within test scripts

  • Use the Windows Scripting Host to test .NET assemblies

  • Create command files to build and test .NET assemblies

  • Run command files at specified times using the Windows Task Manager to perform unattended nightly build and test runs

Estimated lesson time: 30 minutes
Running Tests with ACT

The preceding lesson briefly showed you how to create a load test and run it in ACT. This section describes how to configure and run ACT tests in more detail by describing how to perform the following tasks:

  • Log test results

  • Configure the test to use multiple simultaneous connections

  • Run tests with different user name and password combinations

  • Add performance counters to a test

  • Mimic different browsers and different network connection types

These tasks are explained in the following sections.

Logging Test Results

By default, ACT creates a log file for each test project in the /Perflogs folder on the drive where ACT is installed.

To change the location where ACT stores the log file, follow these steps:

  1. Right-click the project item in the left pane of the ACT window and choose Properties from the pop-up menu. ACT displays the test project' s properties, as shown in Figure 10-11.

    Figure 10-11 Setting log file location

  2. Click the Debugging tab and enter a location for storing test project logs in the Folder To Save Log In text box. Click OK when done.

ACT log files record different amounts of information, depending on the test' s TraceLevel settings. To change the amount of information logged, set the Test object' s TraceLevel property within a test script. For example, the following line of script tells ACT to log all messages:

VBScript
Test.TraceLevel = -1
JScript
Test.TraceLevel = -1;

The TraceLevel property has the settings shown in Table 10-2.

Table 10-2 TraceLevel Settings
TraceLevel setting | Logs
-1 | All information, including requests sent to the application, responses from the application, and messages written by Trace methods in the test script.
0 | No information—turns off logging.
1 | Requests sent to the application and responses from the application.
2 | Messages written by Trace methods in the test script. This is the default setting.

Use the Test object' s Trace method to write messages to the log from within the test script. For example, the following script writes a message to the log if ACT is not able to connect to the server:

VBScript
Dim g_oConnection

ConnnectToServer

Sub ConnectToServer()
Set g_oConnection = Test.CreateConnection("http://www.contoso.com, 80, false)
    If (g_oConnection is Nothing) Then
        Test.Trace "Error: Unable to create connection to contoso"
    End If
End Sub
JScript
var g_oConnection

ConnectToServer();

function ConnectToServer()
{
    g_oConnection = Test.CreateConnection
                        ("http://www.contoso.com", 80, false);
    if (g_oConnection == null)
        Test.Trace("Error: Unable to create connection to contoso");
}
Using Multiple Connections

ACT can simulate different server load levels by adding simultaneous connections to use during the test. By default, ACT uses one connection to send requests to the Web application. Additional connections are the equivalent of additional simultaneous users.

To increase the number of connections used for a test, follow these steps:

  1. Right-click the test item in the right pane of the ACT window and choose Properties from the pop-up menu. ACT displays the test properties, as shown in Figure 10-12.

    Figure 10-12 Adding connections

  2. Change the number in the Test Load Level section to set the number of simultaneous connections. Click OK when done.

Adding Users and Passwords

ACT can supply user names and passwords to a Web application through the user' s groups stored with the test project. This allows you to test how a Web application authenticates users and also permits you to test cookies.

To add user names and passwords to a user group, follow these steps:

  1. Select Users in the left pane of the ACT window to expand the list of user groups.

  2. Select a user group, and then enter user names and passwords in the table displayed in the left pane of the ACT window. Figure 10-13 shows changes to the Default Users Group.

    Figure 10-13 Adding users

  3. Right-click the test item in the right pane of the ACT window and select Properties. ACT displays the test properties, as shown in Figure 10-14.

    Figure 10-14 Setting test properties to activate users

  4. Click the Users tab and select Specify Users. Click OK when done.

ACT automatically changes users for each new iteration of a test and for each new connection it creates. You can also manually change users within a test using the Test object' s GetNextUser method, as shown here:

VBScript
Sub ChangeUser()
   Test.GetNextUser
   Test.Trace("Next user name: " & Test.GetCurrentUser.Name)
End Sub
JScript
function ChangeUser()
{
   Test.GetNextUser();
   Test.Trace("Next user name: " + Test.GetCurrentUser.Name);
}
Adding Performance Counters

You can track a Web application' s performance during a load test by adding performance counters to the test' s properties. These performance counters are the same ones that you used to monitor server performance in Chapter 9, “Building and Deploying Web Applications,” using the Microsoft Management Console (MMC) Performance Logs and Alerts snap-in.

To add performance counters to a load test, follow these steps:

  1. Right-click the test item in the left pane of the ACT window and select Properties. ACT displays the test Properties dialog box.

  2. Click the Counters tab and then click Add. ACT displays the Browse Performance Counters dialog box, as shown in Figure 10-15.

    Figure 10-15 Adding counters

  3. Select the counters you want to monitor and click Add. Click Close when you are done adding counters.

  4. Click OK to close the test properties.

When a test runs, ACT saves performance statistics in an XML file that is listed in the test results pane of the ACT window, as shown in Figure 10-16.

Figure 10-16 Viewing performance results
Mimicking Browser Types

ACT can mimic different types of browsers by modifying the user agent information included in the header it sends with each request. The Browser Type test in the ACTSamples project that is installed with ACT demonstrates how to do this.

You can copy the Browser Type test to your own test projects to test browser compatibility and to test whether your application responds correctly to unsupported browser types. Because ACT does not actually display the responses, however, you cannot rely on this test to visually check that your Web application displays correctly on different browsers.

Getting Content of Responses

ACT returns the server' s response to a request as the Response object' s Body property. You can use the VBScript Instr function to test if a response contains an expected result, as shown here:

VBScript
Set oResponse = oConnection.Send(oRequest)
If Instr(oResponse.Body, "Correct!") Then
    Test.Trace("Correct response.")
Else
    Test.Trace("Incorrect response.")
End If
JScript
oResponse = oConnection.Send(oRequest);
if (oResponse.Body.search"Correct!"))
    Test.Trace("Correct response.");
else
    Test.Trace("Incorrect response.");

The Body search test in the ACTSamples project further demonstrates using Instr to locate items in a response.

The ACT online Help provides a complete reference to the testing object model and includes conceptual information about using ACT to load test Web applications.
Running Tests with .NET Assemblies

ACT was written primarily to interact with Web applications through requests and responses. This is great for testing the user interface aspects of a Web application, but it is limiting when you' re trying to perform tests on the underlying classes used by the application.

For example, say that you want to use ACT to test the FlashCardClass, as shown in the Unit Testing section of Lesson 1. How would you do it? Probably by attempting something like this:

VBScript
Test.TraceLevel = -1

Dim FlashCard
Set FlashCard = CreateObject("vbFlashCards.FlashCardClass")

If (FlashCard Is Nothing) Then
   Test.Trace("Error: FlashCard not created.")
   Test.StopTest
End If
JScript
Test.TraceLevel = -1;

var FlashCard;
FlashCard = CreateObject("csFlashCards.FlashCardClass");

if (FlashCard == null)
{
   Test.Trace("Error: FlashCard not created.");
   Test.StopTest();
}

The only problem is that this doesn' t work! Because Web applications are based on the Microsoft .NET Framework, you can' t use their namespaces as an argument to the CreateObject method. CreateObject expects a COM object' s programmatic ID (progID), not a namespace.

To use scripting to test a .NET assembly, follow these steps:

  1. Register the .NET assembly for use with COM.

  2. Make sure the COM application can find the .NET assembly.

  3. Create the .NET object using the progID registered for the .NET assembly.

The following sections describe these steps in more detail.

Registering .NET Assemblies

Use the RegAsm.exe utility to register a .NET assembly with the system registration database. The RegAsm.exe utility is installed with the .NET Framework.

To use the RegAsm.exe utility, follow these steps:

  1. Start a Visual Studio .NET command prompt. From the Start menu, choose All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and then Visual Studio .NET Command Prompt.

  2. Within the command prompt, navigate to the folder containing the assembly to register and run the following command:

RegAsm.exe assembly.dll

The variable assembly.dll is the name of the assembly to register.

RegAsm.exe registers the public classes from the .NET assembly using their namespace and class names as their progIDs. For example, registering the FlashCards assembly created in Chapter 2, “Creating Web Forms Applications,” results in the registry entries shown in Figure 10-17.

Figure 10-17 FlashCards system registration database entries
Locating the .NET Assembly

After you register a .NET assembly, COM components need a way to find the assembly. There are a number of ways to do this, but the three simplest ways are:

  • Install the assembly in the global assembly cache (GAC).

  • Copy the assembly to the same folder as the COM component that will use it.

  • Copy the COM component to the folder where the assembly resides.

Assemblies installed in the GAC are available to all applications on the computer. Assemblies must be configured to use strong names before they can be installed in the GAC. For complete instructions on configuring and installing assemblies in the GAC, see the Visual Studio .NET online Help.

For assemblies that are not intended for sharing with other applications, copying the assembly or the COM component to the same folder might be the easiest solution. This is particularly true for testing, when you might not want an assembly to be generally available.

Creating the .NET Object

After you have registered a .NET assembly and made sure the COM components can find it, you can create an instance of the .NET object using the COM object creation methods, such as the VBScript CreateObject function.

To see how this works with a testing tool such as the Windows Scripting Host, follow these steps:

  1. Register a Web application' s assembly as described in the preceding section.

  2. Copy CScript.exe from the \Windows\system32 or \WINNT\system32 folder to the Web application' s \bin folder. This is the copy of CScript that you will use to run your testing script; it must be in the same folder as the .NET assembly so that the COM and .NET objects can interoperate.

  3. Create a VBScript or JScript file and run it using the copy of CScript.exe stored in the \bin folder.

The following code demonstrates how to test FlashCardClass using the Windows Scripting Host:

VBScript
' TestFlash.vbs
' FlashCardClass unit test
Option Explicit

Dim FlashClass

Set FlashClass = CreateObject("vbFlashCards.FlashCardClass")
' Check if class created.
If (FlashClass is Nothing) Then
    WScript.Echo "Error: FlashCardClass not created."
    WScript.Quit
End If
' Test Shuffle
FlashClass.Shuffle 1, 1000
Dim X, Y
x = FlashClass.FirstNumber
y = FlashClass.SecondNumber
' Shuffle again
FlashClass.Shuffle 1, 1000
If (x = FlashClass.FirstNumber) or (y = FlashClass.SecondNumber) Then
    WScript.Echo "Warning: Number not unique."
End If
' Test Answer
If (FlashClass.FirstNumber + FlashClass.SecondNumber = FlashClass.Answer) Then
    WScript.Echo "Success: Answer adds up!"
Else
    WScript.Echo "Error: Answer incorrect."
End If
JScript
// TestFlash.js
// FlashCardClass unit test
var FlashClass;

FlashClass = new ActiveXObject("vbFlashCards.FlashCardClass");
// Check if class created.
if (FlashClass == null)
{
    WScript.Echo("Error: FlashCardClass not created.");
    WScript.Quit();
}
// Test Shuffle
FlashClass.Shuffle(1, 1000);
var X, Y;
x = FlashClass.FirstNumber;
y = FlashClass.SecondNumber;
// Shuffle again
FlashClass.Shuffle(1, 1000);
if ((x == FlashClass.FirstNumber) ││ (y == FlashClass.SecondNumber))
    WScript.Echo("Warning: Number not unique.");
// Test Answer
if (FlashClass.FirstNumber + FlashClass.SecondNumber == FlashClass.Answer())
    WScript.Echo("Success: Answer adds up!");
else
    WScript.Echo("Error: Answer incorrect.");

To run the preceding script, use a command line similar to the following:

@rem VBScript
bin\cscript TestFlash.vbs > logs\FlashErr.log

@rem JScript
bin\cscript TestFlash.js > logs\FlashErr.log
Running Unattended Builds and Tests

Running unit, integration, and load tests on a regular basis provides constant quality assurance and checks for regressions. Most development teams run builds and a set of tests nightly. Running the tests at night prevents conflicts with check-ins and doesn' t disrupt workflow while a machine is tied up doing a build. Because they run late at night, these builds are usually unattended, meaning that no one has to be there to start them or monitor their progress.

To run unattended builds and tests, follow these steps:

  1. Create a command file to set the environment variables, build, and test commands you want to run.

  2. Use the Windows Task Manager to schedule the command file to be run nightly.

  3. Monitor the log files written by the commands to ensure the builds and tests are succeeding.

The following sections describe these steps in more detail.

Creating a Command File

You can create a command file (.bat) to execute any set of commands you want to run. In general, the command file has three main sections:

  • Setting environment variables, such as PATH and LIB, that are required to find the components used by the build

  • Running the Visual Studio .NET development environment to build the assembly

  • Running the test tools used to test the assembly

The environment variables used by Visual Studio .NET can be found in the VsVars32.bat command file that is installed in Visual Studio .NET' s Command\Tools folder. You can cut and paste the settings from this file to your command file.

To run Visual Studio .NET from the command line, use the DevEnv.exe command with the project' s solution file (.sln). For example, the following command line builds the FlashCards application using the Debug configuration and records any errors in the Logs folder:

DevEnv vbFlashCards.sln /build Debug > Logs\builders.log

You can put all of the steps together in a single command file. For example, the following command file sets the required environment variables, builds a Web application, and runs test scripts on the assembly:

@rem Build.bat
@rem ========================================================================
@rem Environment settings from vsvars32.bat
@rem ========================================================================
@SET VSINSTALLDIR=E:\Program Files\Microsoft Visual Studio .NET\Common7\IDE
@SET VCINSTALLDIR=E:\Program Files\Microsoft Visual Studio .NET
@SET FrameworkDir=C:\WINDOWS\Microsoft.NET\Framework
@SET FrameworkVersion=v1.0.3705
@SET FrameworkSDKDir=C:\Program Files\Microsoft Visual Studio
.NET\FrameworkSDK
@rem Root of Visual Studio common files.

@rem
@rem Root of Visual Studio ide installed files.
@rem
@set DevEnvDir=%VSINSTALLDIR%

@rem
@rem Root of Visual C++ installed files.
@rem
@set MSVCDir=%VCINSTALLDIR%\VC7

@set PATH=%DevEnvDir%;%MSVCDir%\BIN;%VCINSTALLDIR%\Common7\Tools;
%VCINSTALLDIR%\Common7\Tools\bin\prerelease;%VCINSTALLDIR%\Common7\Tools\bin;%FrameworkSDKDir%\bin;%FrameworkDir%\%FrameworkVersion%;%PATH%;
@set INCLUDE=%MSVCDir%\ATLMFC\INCLUDE;%MSVCDir%\INCLUDE;
%MSVCDir%\PlatformSDK\include\prerelease;%MSVCDir%\PlatformSDK
\include;%FrameworkSDKDir%\include;%INCLUDE%
@set LIB=%MSVCDir%\ATLMFC\LIB;%MSVCDir%\LIB;
%MSVCDir%\PlatformSDK\lib\prerelease;
%MSVCDir%\PlatformSDK\lib;%FrameworkSDKDir%\lib;%LIB%

@rem ========================================================================
@rem End environment settings
@rem ========================================================================

@rem ========================================================================
@rem Development environment build commands
@rem ========================================================================
devenv vbBuildSnippet.sln /build debug > logs\builderrs.log
@rem ========================================================================
@rem End build commands
@rem ========================================================================

@rem ========================================================================
@rem Testing commands
@rem ========================================================================
bin\cscript RunTests.vbs > logs\testerrs.log
@rem ========================================================================
@rem End testing commands
@rem ========================================================================
Scheduling Tasks

To schedule a command file to run nightly, use the Windows Task Manager.

To run the Task Manager, follow these steps:

  1. From the Start Menu, choose All Programs, Accessories, System Tools, and then Scheduled Tasks. Windows displays the Scheduled Tasks folder.

  2. Double-click Add Scheduled Task to run the Scheduled Task Wizard.

  3. Click Next, and then click Browse to select the command file to run. When you' ve selected the command file, the wizard displays the schedule page, as shown in Figure 10-18.

    Figure 10-18 Choosing a schedule

  4. Select an option indicating when to run the task and click Next. The wizard displays the time page, as shown in Figure 10-19.

    Figure 10-19 Choosing a time to run

  5. Enter a time to run the task and click Next. The wizard displays the password page, as shown in Figure 10-20.

    Figure 10-20 Entering user name and password

  6. Enter the user name and password to run the command under, click Next, and then click Finish to complete the wizard.

Viewing Log Files

Because builds and tests scheduled with the Task Manager run unattended, it is important to monitor log files created by the commands in the build process. The build and test commands shown earlier in this section used the redirect operator (>) to write output from commands to log files stored in a Log folder.

Viewing log files in Notepad is easy, as shown in Figure 10-21.

Figure 10-21 Viewing a build log


Lesson 3: Debugging

Testing is probably one of the few endeavors in which failure is a kind of success. In this lesson, you will learn how to use those successes to fix problems in your code. The topics covered in this lesson build on the discussion of the Visual Studio debugging tools, which was covered in Chapter 1, “Introduction to Web Programming.” This includes fixing specific problems uncovered by testing, adding diagnostic code to an application, and debugging a Web application as it runs on a remote server.

After this lesson, you will be able to

  • Apply the results of unit tests to fix common problems

  • Use the Debug and Trace diagnostic classes to display alerts and generate logs during development

  • Configure a Web server to allow remote workstations to debug Web applications

  • Debug Web applications as they run on a remote server

Estimated lesson time: 30 minutes
Putting Test Results to Work

Something you probably noticed if you ran the unit tests for the FlashCards application shown in Lesson 1 is that FlashCards didn' t pass the tests. That' s because FlashCards is a simplified demonstration program, not a robust application intended for commercial use. Try that excuse at work sometime!

Seriously, though, the results are instructive. The first set of errors you encounter with the FlashCardClass unit tests are overflows because you used integer MinValue and MaxValue to initialize the random numbers. Adding two values near the integer' s maximum value results in a number larger than an integer. The related code is shown here:

Visual Basic .NET
' Test code
Function TestFlashCard() As Boolean
    'Create class
    Dim FlashCard As New vbFlashCards.FlashCardClass()
    'Test is class exists.
    If IsNothing(FlashCard) Then
        Console.WriteLine("FlashCardClass failed")
        ' Return false and end here.
        Return False
    End If
    'Test shuffle method.
    FlashCard.Shuffle(Integer.MinValue, Integer.MaxValue)
    'Test properties.
    If (FlashCard.Answer <> FlashCard.FirstNumber + FlashCard.SecondNumber) _
        Then
        Console.Write("Error: Answers don't match")
    End If
End Function

' FlashCardClass code with error.
' Calculates answer based on current operation.
Public Function Answer() As Integer
    Select Case mstrOp
        Case "+"
            Return mintFirstNumber + mintSecondNumber ' Overflow!!!!
        Case "x", "*"
            Return mintFirstNumber * mintSecondNumber
        Case "-"
            Return mintFirstNumber – mintSecondNumber
        Case Else
            Return 0
    End Select
End Function
Visual C#
// Test code.
static bool TestFlashCardClass()
{
   //Create class
   csFlashCards.FlashCardClass FlashCard = new csFlashCards.FlashCardClass();
   //Test is class exists.
   if (FlashCard == null)
   {
         Console.WriteLine("FlashCardClass failed");
         // Return false and end here.
         return false;
   }
   //Test shuffle method using maximum and minimum values.
   FlashCard.Shuffle(int.MinValue, int.MaxValue);
   //Test properties.
   int intFirst = FlashCard.FirstNumber;
   int intSecond = FlashCard.SecondNumber;
   double dblAnswer = intFirst + intSecond;
   if (dblAnswer != (double)FlashCard.Answer())
   {
         Console.WriteLine("Error: Numbers don't add up!");
   }
}

// FlashCardClass code with error.
// Calculates answer based on current operation.
public int Answer()
{
   switch(mstrOp)
   {
         case "+":
               return mintFirstNumber + mintSecondNumber;
         case "x":
               return mintFirstNumber * mintSecondNumber;
         case "*":
               return mintFirstNumber * mintSecondNumber;
         case "-":
               return mintFirstNumber - mintSecondNumber;
         default :
               return 0;
   }
}

You have two ways to tackle this problem: Restrict the range of possible FirstNumber and SecondNumber property values, or change the return type for the Answer method. The second approach is more obvious from a user' s standpoint because integers are a common input type and it would be hard to explain a new, smaller type for those properties.

The following code shows the changes to the Answer method that allow it to pass its unit test:

Visual Basic .NET
Public Function Answer() As Double
    Select Case mstrOp
        Case "+"
        Return CDbl(mintFirstNumber) + CDbl(mintSecondNumber)
        Case "x", "*"
        Return CDbl(mintFirstNumber) * CDbl(mintSecondNumber)
        Case "-"
        Return CDbl(mintFirstNumber) - CDbl(mintSecondNumber)
        Case "/"
        Return CDbl(mintFirstNumber) / CDbl(mintSecondNumber)
        Case Else
        Return 0
    End Select
End Function
Visual C#
public double Answer()
{
   switch(mstrOp)
   {
        case "+":
        return mintFirstNumber + mintSecondNumber;
        case "x":
        return mintFirstNumber * mintSecondNumber;
        case "*":
        return mintFirstNumber * mintSecondNumber;
        case "-":
        return mintFirstNumber - mintSecondNumber;
        default :
        return 0;
   }
}

Another problem arises with FlashCardClass if you specify an Operation property other than +, -, /, * or x. The Operation property simply returns 0, which is a little sloppy. The following changes ensure that only valid operations are used:

Visual Basic .NET
Public Property Operation() As String
    Get
        Operation = mstrOp
    End Get
    Set(ByVal Value As String)
        If InStr("+-/x*", Value) Then
            mstrOp = Value
        Else
            Throw New Exception("Unrecognized operator")
        End If
    End Set
End Property
Visual C#
public string Operation
{
   get
   {
         return mstrOp;
   }
   set
   {
         if (" +-/x*".IndexOf(value, 1) != 0)
               mstrOp = value;
         else
               throw(new Exception("Unrecognized operator"));
   }
}

These bug fixes illustrate how important it is to perform unit tests before checking new classes into a project. When a class is part of a larger project, it becomes much harder to locate the cause of an error.

Using the Debug and Trace Classes

The .NET Framework includes the Debug and Trace classes in the System.Diagnostics namespace as tools to help you ferret out errors and unexpected behaviors in your application. These classes let you display alerts or write messages based on results within your application.

The difference between the two classes is how they are handled in release builds. By default, Debug methods and properties are automatically stripped out of code compiled for release. Trace methods and properties are retained in release code by default. This gives you two levels of diagnostics: one for debugging errors prior to release, and one for diagnosing performance and problems in the field.

Because Debug and Trace are part of a .NET namespace, you need to add an Imports or using statement to code modules before using them:

Visual Basic .NET
Imports System.Diagnostics
Visual C#
using System.Diagnostics;
Displaying Alerts and Messages

The Debug and Trace classes' Assert method tests a value and displays an alert if the value is False. Use Assert to halt an application for an unexpected result. For example, the following code displays an alert if the numbers shown on the FlashCards form don' t add up as expected:

Visual Basic .NET
Private Sub txtAnswer_TextChanged(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles txtAnswer.TextChanged
    If txtAnswer.Text = FlashCard.Answer Then
        lblFeedback.Text = "Correct!"
        Debug.Assert(CInt(txtAnswer.Text) = CInt(lblFirst.Text) + _
        CInt(lblSecond.Text), "Form values don't add up!")
        ' Get another set of numbers.
        FlashCard.Shuffle()
        ' Refresh display to show new numbers.
        RefreshDisplay()
        ' Clear answer
        txtAnswer.Text = ""
    Else
        lblFeedback.Text = "Oops! Try Again."
    End If
End Sub
Visual C#
private void txtAnswer_TextChanged(object sender, System.EventArgs e)
{
   if(txtAnswer.Text == FlashCard.Answer().ToString())
   {
        lblFeedback.Text = "Correct!";
        Debug.Assert(Convert.ToInt32(txtAnswer.Text) ==
           Convert.ToInt32(lblFirst.Text) +
           Convert.ToInt32(lblSecond.Text),
           "Form values don't add up!");
        // Get another set of numbers.
        FlashCard.Shuffle();
        // Refresh display to show new numbers.
        RefreshDisplay();
        // Clear answer
        txtAnswer.Text = "";
   }
   else
   {
        lblFeedback.Text = "Oops! Try Again.";
   }
}

In a Web application, the alert is displayed on the server. If you are running the Web application' s server on your local machine, this alert appears as expected, as shown in Figure 10-22. If you are running the Web application on a remote server, however, you will not see the alert if you are only attached to the application' s process running on the server for remote debugging, as described later in this lesson.

Figure 10-22 Displaying an alert (local server)

Use the Write or WriteLine methods to write a message without halting the application. By default, these messages are displayed in the Visual Studio .NET Output window during debugging.

Logging Debug and Trace Messages

To record Debug and Trace messages on a deployed application, create a TextWriterTraceListener class and add it to the Debug or Trace classes' Listeners collection. For example, the following code directs Debug messages to the server' s console:

Visual Basic .NET
Debug.Listeners.Add(New TextWriterTraceListener(Console.Out))
Debug.WriteLine("Starting tests.")
Visual C#
Debug.Listeners.Add(new TextWriterTraceListener(Console.Out));
Debug.WriteLine("Starting tests.");

To write messages to a file rather than to the console, specify a filename and use the Flush method to write the output. For example, the following code writes a message to the Results.log file:

Visual Basic .NET
Debug.Listeners.Add(New TextWriterTraceListener("Results.log"))
Debug.WriteLine("Starting tests.")
Debug.Flush()
Visual C#
Debug.Listeners.Add(new TextWriterTraceListener("Results.log"));
Debug.WriteLine("Starting tests.");
Debug.Flush();
Remote Debugging

You usually debug a Web application by running the code locally on your development machine, but that is not the environment in which the application ultimately runs. To resolve problems with Web applications in real-world situations, you need to be able to debug them as they run on a remote server.

To debug an application on a remote server, follow these steps:

  1. Install the Visual Studio .NET remote debugging server components on the Web application server.

  2. Configure the server permissions to allow you to debug applications remotely.

  3. Attach to the remote Web application process from your debug machine.

The following sections describe these steps in greater detail.

Installing Remote Components

The Visual Studio .NET Setup Wizard includes an option for installing remote components. Run Visual Studio .NET Setup on the server and choose the Full Remote Debugging option during installation, as shown in Figure 10-23.

Figure 10-23 Installing remote debugging components

Setup installs the required components and configures the server to allow remote debugging.

Setting Server Permissions

To be able to debug processes running on a server, you must have a user account on the server and that account must belong to the Debugger Users group. The Visual Studio .NET Setup Wizard creates this group when you install the remote debugging components. You should make sure that all users who need debugging privileges on the server belong to this group.

Use the MMC to add users to this group, as shown in Figure 10-24.

Figure 10-24 Adding remote debugger users
Attaching to a Remote Process

Visual Studio .NET can attach to processes running on a remote server. While attached to the process, Visual Studio .NET can receive Output window messages, display alerts from the Assert method, set breakpoints, view variables, and step through code running on the remote server.

To attach to a remote process, follow these steps:

  1. From the Tools menu, choose Debug Processes. Visual Studio .NET displays the Processes dialog box, as shown in Figure 10-25.

    Figure 10-25 Attaching to a process

  2. By default, Web applications run in the process named aspnet_wp.exe. Select the remote process you want to debug, click Attach, and then click Close.

When attached to a remote process, Visual Studio .NET can step into the code by setting breakpoints. The easiest way to do this with a Web application is to set a breakpoint for the Page_Load function to halt the application at the beginning of a Web form, and then set breakpoints or watches within the remote code as desired.

To set a breakpoint in a remote process, follow these steps:

  1. Click the Breakpoints tab in Visual Studio .NET, as shown in Figure 10-26.

    Figure 10-26 Breakpoints window

  2. Click the New button on the Breakpoints window toolbar. Visual Studio .NET displays the New Breakpoint dialog box, as shown in Figure 10-27.

    Figure 10-27 Adding a breakpoint

  3. Enter a procedure name in the Function text box and click OK. For Web applications, it is easiest to stop the application at the Page_Load procedure, and then set additional breakpoints as required.

When a remote process is stopped at a breakpoint, you can use Visual Studio .NET' s debugging tools to view variables, step through code, or execute commands as you would with a local process.


Summary

  • Testing measures conformance to product requirements.

  • Unit tests check that each piece of code works correctly.

  • Integration tests verify that all components work together.

  • Regression tests make sure that new code does not break existing code.

  • Load tests check that the product works under extreme usage.

  • Platform tests make sure that a Web application displays correctly and has reasonable performance using different browser and network connection configurations.

  • Use ACT to record user interaction with a Web application in VBScript, then replay that script to simulate multiple users and log application errors and server performance.

  • To test a .NET assembly with the Windows Scripting Host, register the assembly for use with COM, and then copy the scripting host (CScript.exe) to the assembly' s folder.

  • To run attended builds and tests, create a command file containing the build commands to execute and test scripts to run, and then schedule the command file to run using the Windows Task Scheduler.

  • Use the Debug and Trace diagnostic classes to display alerts and log error conditions during the development process.

  • By default, code written with the Debug class is stripped out of release builds, while code using the Trace class is left in.

  • Visual Studio .NET can attach to the process of a Web application running on a remote server to debug that application in a real-world setting.


Lab: Testing and Debugging

In this lab, you will apply the lessons from this chapter to review a real-world testing plan, write unit tests for a sample application, load test a deployed application, and step through a Web application running on a remote server using the Visual Studio .NET debugging tools.

Estimated lesson time: 30 minutes
Exercise 1: Review a Test Plan

Testing strategies and terminology vary from company to company. In this exercise, you will use a test plan from your own company to identify the concepts discussed in Lesson 1. If you do not work for a company that uses test plan documents, search the Internet using the phrase “software test plan” and choose a test plan from the search results.

Using an actual test plan, highlight any mention of the following items:

  • Unit testing

  • Integration testing

  • Ad hoc testing

See if you can find answers to the questions posed in Lesson 1:

  • How are tests written?

  • Who is responsible for the testing?

  • When are the tests performed?

  • Where are the tests and test results stored?

  • What is being tested?

Based on this information, does the plan give you a clear idea of how testing will verify that the product requirements are met?

Exercise 2: Write Unit Tests

In this exercise, you will use VBScript or JScript to create unit tests for the Circle and Sphere classes that were derived from the Shapes abstract class in Chapter 3, “Working with Web Objects.” The unit tests should verify that the following conditions are met:

  • Circle and Sphere classes can be created.

  • Classes can be passed to procedures accepting the IFigure interface.

  • All properties work with minimum and maximum values.

  • All methods return expected results for two or more test cases.

To create unit tests in VBScript or JScript:

  1. Register the assembly for use with COM by running the RegAsm.exe utility from the Visual Studio .NET command prompt.

  2. Copy the Windows Scripting Host (CScript.exe) to the folder containing the assembly to test.

  3. Using Visual Studio .NET, Notepad, or some other editor, create the tests in VBScript or JScript.

  4. Run the scripts from the Visual Studio .NET command prompt using the copy of CScript.exe in the assembly folder.

When you are done, compare your results to those found in this book' s companion CD-ROM sample files.

Exercise 3: Create a Load Test

If you have Visual Studio .NET Enterprise Architect, use ACT to create a load test for a Web application that has been deployed to a test server. Choose any deployed Web application that you want, but follow these test parameters:

  1. Record at least five different requests to the server.

  2. Add performance counters for the server' s percentage of processor time, memory pages per second, and average disk read queue length.

  3. Log the results to a Log folder within the Web application' s folder structure.

  4. Set the test properties to run for 30 seconds using 4 simultaneous connections.

  5. Run the test.

  6. View the performance counters for the server in chart form within ACT.

When you are done, compare your results to the ACT sample recorded for the FlashCards sample application on the CD-ROM included with this book.

Exercise 4: Step Through a Remote Process

In this exercise, you will use Visual Studio .NET to pause and step through a Web application running on a remote server. To complete this exercise, you must belong to the Debugger Users group on the server and the server must have the Visual Studio .NET remote components installed.

To step through a Web application running on a remote server:

  1. From your workstation, access the Web application using Microsoft Internet Explorer. This ensures that the Web application is running on the server when you try to attach to its process.

  2. From the Visual Studio .NET Tools menu, choose Debug Processes. Select the server where the application is running, select the application' s process (named aspnet_wp.exe), and then click Attach.

  3. Set a breakpoint on the Page_Load function.

  4. Switch to Internet Explorer and click Refresh.

  5. Switch back to Visual Studio .NET. The remote application should be paused at Page_Load.

  6. Set breakpoints at other locations in code.

  7. In Visual Studio .NET, click Continue to run to the next breakpoint.


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. How do unit, integration, and regression testing relate to each other?

  2. Why is load testing likely to be more important for a Web application than for a stand-alone Windows application?

  3. What is the difference between the Debug and Trace classes?

  4. What are the two special steps you need to take to ensure that a COM component can use a component from a .NET assembly?


Creating Custom Web Controls

Chapter 11
Creating Custom Web Controls
Lesson 1: Creating Web User Controls
Lesson 2: Creating Composite Custom Controls
Lesson 3: Creating Rendered Custom Controls
About This Chapter

Custom controls extend the tools available to Web developers. Using custom controls, you can encapsulate key aspects of the visual interface and program logic that you want to reuse throughout your application, or throughout your organization. Microsoft Visual Studio .NET provides three types of custom control for use on Web forms. In this chapter, you will learn how to develop and use each of these controls:

  • Web user controls. These combine existing server and HTML controls by using the Visual Studio .NET Designer to create functional units that encapsulate some aspect of the user interface. User controls reside in content files, which must be included in the project where they are used.

  • Composite custom controls. These create new controls from existing server and HTML controls. Though similar to user controls, composite controls are created in code rather than visually, and therefore can be compiled into an assembly (.dll), which can be shared between multiple applications and used from the Toolbox in Visual Studio .NET.

  • Rendered custom controls. These create entirely new controls by rendering HTML directly rather than using composition. These controls are compiled and can be used from the Toolbox, just like composite controls, but you must write extra code to handle tasks that are performed automatically in composite controls.

Before You Begin

To complete this chapter, you must

  • Be familiar with the life cycle of Web applications.

  • Understand object-oriented programming techniques, namespaces, and state information issues, as discussed in Chapter 3, “Working with Web Objects.”

  • Have experience developing Web applications with the standard server and HTML controls.


Lesson 1: Creating Web User Controls

Web user controls combine one or more server or HTML controls on a Web User Control page, which can, in turn, be used on a Web form as a single control. User controls make it possible to create a single visual component that uses several controls to perform a specific task.

Once created, user controls can be used on Web forms throughout a project. However, because they are not compiled into assemblies, they have the following limitations not found with other types of controls:

  • A copy of the control must exist in each Web application project where it is used.

  • User controls cannot be loaded in the Visual Studio .NET Toolbox; instead, you must create them by dragging the control from the Solution Explorer to the Web form.

  • User control code is initialized after the Web form loads, which means user-control property values are not updated until after the Web form' s Page_Load event.

In this lesson, you will learn how to create user controls and how to use them on a Web form.

After this lesson, you will be able to

  • Create a Web application that contains and uses user controls

  • Add properties, methods, and events to a user control

  • Include the user control on a Web form by editing the Web form' s HTML

  • Enable design-time settings, such as grid layout, for a user control

Estimated lesson time: 30 minutes
Creating and Using User Controls

There are five steps to creating and using a user control in a Web application:

  1. Add a Web User Control page (.ascx) to your project.

  2. Draw the visual interface of the control in the designer.

  3. Write code to create the control' s properties, methods, and events.

  4. Use the control on a Web form by dragging it from the Solution Explorer to the Web form where you want to include it.

  5. Use the control from a Web form' s code by declaring the control at the module level and then using the control' s methods, properties, and events as needed within the Web form.

The following sections describe these steps in greater detail. The last section in this lesson describes special considerations you need to take into account when working with user controls.

Creating a User Control and Drawing Its Interface

You create user controls out of other server and HTML controls in the Visual Studio .NET Toolbox. You do this by drawing the controls on a user control page, which is simply another file type in a Web application project, just like a Web form or an HTML page. User controls are identified by their .ascx filename extensions.

To create a user control and add it to a Web application, follow these steps:

  1. From the Project menu, choose Add Web User Control. Visual Studio .NET displays the Add New Item dialog box.

  2. Type the name of the user control in the Name text box and click OK. Visual Studio .NET creates a new, blank user control page and adds it to the project, as shown in Figure 11-1.

    Figure 11-1 A new, empty user control page

After you' ve added a user control page to your project, create the visual interface of the control by adding server or HTML controls to it. User controls support flow layout only, so if you want to position controls on the page using grid layout, add an HTML Grid Layout Panel control to the user control as a container for the controls you want to position. Figure 11-2 shows a simple user control created by placing two Button server controls on an HTML Grid Layout Panel control.

Figure 11-2 A simple user control

The control shown in Figure 11-2 is the Web form' s equivalent of a Spin control. The control allows you to increment or decrement a value by clicking on the up or down arrow. This example is used in the sections that follow; the HTML for the user control is shown here for reference:

<DIV id="pnlGrid" runat="server" style="WIDTH: 20px; POSITION: relative;
HEIGHT: 48px" ms_positioning="GridLayout">
   <asp:Button id="butUp" Text="^" runat="server" />
   <asp:Button id="butDown" Text="v" runat="server" />
</DIV>
Writing the Control' s Properties, Methods, and Events

You manipulate the controls you add to the user control from the user control' s code module. That allows you to hide the inner workings of the control and expose the tasks the control performs as properties, methods, and events.

To edit the user control' s code module, simply double-click the user control. Visual Studio .NET automatically generates the code template shown in Figure 11-3 when you create a user control.

Figure 11-3 The user control code template (Visual Basic .NET)

The code in Figure 11-3 is similar to the generated code for a Web form, with these notable differences:

  • The user control' s class is declared as MustInherit in Visual Basic .NET or abstract in C#. The user control' s class can be used as a base class only for a derived class. In this case, the derived class is the user control created on the Web form.

  • The user control' s class is based on the System.Web.UI.UserControl base class. This base class provides the base set of properties and methods you use to create the control and get the control' s design-time settings from its HTML attributes on the Web form.

  • The user control' s Page_Load event occurs when the control is loaded by the Web form that contains it. The control' s Page_Load runs after the Web form' s Page_Load, which is important to remember when you work with user controls on a Web form.

To create properties and methods for the user control that you can use from a Web form, follow these steps:

  1. Create the public property or method that you want to make available on the containing Web form.

  2. Write code to respond to events that occur for the controls contained within the user control. These event procedures do the bulk of the work for the user control.

  3. If the property or method needs to retain a setting between page displays, write code to save and restore settings from the control' s ViewState.

For example, the following code shows a Value property that returns the value of the Spin control created in the preceding section:

Visual Basic .NET
Public Property Value() As Integer
    Get
        ' Return the Value.
        Return ViewState("Value")
    End Get
    Set(ByVal Value As Integer)
        ' Set the Value.
        ViewState("Value") = Value
    End Set
End Property
Visual C#
public int Value
{
    get
{
        // Return the Value.
        return Convert.ToInt32(ViewState["Value"]);
    }
    set
    {
        // Set the Value.
        ViewState["Value"] = value;
    }
}

The user changes the value of the Spin control by clicking the up or down buttons, so the following code increments or decrements the value, depending on which button the user clicks:

Visual Basic .NET
Private Sub butDown_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butDown.Click
    ' Decrement the Value.
    Me.Value -= 1
End Sub

Private Sub butUp_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butUp.Click
    ' Increment the Value.
    Me.Value += 1
End Sub
Visual C#
private void butDown_Click(object sender, System.EventArgs e)
{
      // Decrement the Value.
      this.Value -= 1;
}

private void butUp_Click(object sender, System.EventArgs e)
{
      // Increment the Value.
      this.Value += 1;
}
Adding the Control to a Web Form

User controls can be dragged directly from the Solution Explorer onto a Web form. When you add a user control to a Web form in this way, Visual Studio .NET generates a Register directive and HTML tags to create the control on the Web form.

For example, the following sample shows the HTML generated when you drag the Spin user control onto a Web form:

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm1.aspx.vb" Inherits="vbUserSnippet.WebForm1"%>
<%@ Register TagPrefix="uc1" TagName="Spin" Src="Spin.ascx" %>
<HTML>
   <body>
         <form id="Form1" method="post" runat="server">
               <uc1:Spin id="Spin1" runat="server"></uc1:Spin>
         </form>
   </body>
</HTML>

User controls can exist alongside and interact with other controls on a Web form. For example, the following HTML shows the Spin user control next to a TextBox control:

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm1.aspx.vb" Inherits="vbUserSnippet.WebForm1"%>
<%@ Register TagPrefix="uc1" TagName="Spin" Src="Spin.ascx" %>
<HTML>
   <body>
         <form id="Form1" method="post" runat="server">
               <asp:TextBox id="TextBox1" runat="server"></asp:TextBox>
               <uc1:Spin id="Spin1" runat="server" Value="5"></uc1:Spin>
         </form>
   </body>
</HTML>

Notice that the user control includes an attribute for the Value property. When the Spin control loads at run time, ASP.NET will set the control' s properties based on the attributes in the HTML. The control' s attributes provide a way to set the control' s properties from HTML.

When you view the preceding HTML in Visual Studio .NET' s Design mode, it appears as shown in Figure 11-4.

Figure 11-4 The Spin user control on a Web form

As you' ll notice from Figure 11-4, the Web form designer does not display the user control as it will appear at run time. Instead, it shows a sort of generic control. This is a limitation of Visual Studio .NET —it can' t display user controls in Design mode.

Another thing you' ll notice is that the Spin control supports only flow layout on the Web form. That is a limitation of the control itself. To support grid layout and absolute positioning on the Web form, you have to add code to support the style attribute. You' ll see that technique later in this lesson.

Using the Control in Code

When you' ve created a user control and added it to a Web form, you can use it from the Web form' s code module by following these steps:

  1. Declare the user control at the module level. For example, the following line declares the Spin user control that was added to the Web form in the preceding section:

    Visual Basic .NET
    Protected WithEvents Spin1 As Spin
    Visual C#
    protected Spin Spin1;

  2. Use the control' s properties, methods, and events as you would any other control. For example, the following event procedure displays the Spin control' s value in a text box:

    Visual Basic .NET
    Private Sub Page_PreRender(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.PreRender
    ' Display the Spin control's value.
    TextBox1.Text = Spin1.Value
End Sub
    Visual C#
    private void Page_PreRender(object sender, System.EventArgs e)
{
      // Display the Spin control's value.
      TextBox1.Text = Spin1.Value.ToString();
}

One very important thing to notice here is that the preceding code uses the Web form' s Page_PreRender event procedure, not its Page_Load event procedure. If you use Page_Load, you' ll see only the Value set in the user control' s HTML Value attribute the first time you click the Spin control. (Try it!) That' s because the user control' s code doesn' t run until after the Web form' s Page_Load event has finished. Any changes that were saved in the control' s ViewState aren' t loaded until the control' s Page_Load has run.

Adding Events to the User Control

In addition to properties and methods, user controls can provide events that can respond to user actions on the Web form.

To add an event to a user control, follow these steps:

  1. Declare the event within the user control' s code module. For example, the following code declares a Click event for the Spin user control:

    Visual Basic .NET
    ' Declare event for Spin control.
Public Event Click(ByVal sender As System.Object, ByVal e As
System.EventArgs)
    Visual C#
    // Declare the event.
public event EventHandler Click;

  2. Create a method to raise the event. This step makes it easier for other classes to derive from this class, since they can override this method.

    Visual Basic .NET
          ' Method to raise event.
Protected Overridable Sub OnClick(ByVal e As EventArgs)
    RaiseEvent Click(Me, e)
End Sub
    Visual C#
    // Method to raise event.
protected virtual void OnClick(EventArgs e)
{
      if (Click != null)
      {
            Click(this, e);
      }
}

  3. Raise the event from within the user control' s code. For example, the following code raises the Click event whenever a user clicks the up or down buttons in the Spin user control:

    Visual Basic .NET
    Private Sub butDown_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butDown.Click
    ' Decrement the Value.
    Me.Value -= 1
    ' Call the OnClick method.
    OnClick(e)
End Sub

Private Sub butUp_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butUp.Click
    ' Increment the Value.
    Me.Value += 1
    ' Call the OnClick method.
    OnClick(e)
End Sub
    Visual C#
    private void butDown_Click(object sender, System.EventArgs e)
{
      // Decrement the Value.
      this.Value -= 1;
      // Call the OnClick method.
      OnClick(e);
}

private void butUp_Click(object sender, System.EventArgs e)
{
      // Increment the Value.
      this.Value += 1;
      // Call the OnClick method.
}

To use the user control event from a Web form, include the user control on a Web form, as shown in the preceding two sections, and then write an event procedure that responds to the event. For example, the following code updates a text box when the user clicks the Spin control:

Visual Basic .NET
Private Sub Spin1_Click(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles Spin1.Click
    ' Update info from the user control's event handler.
    TextBox1.Text = Spin1.Value
End Sub
Visual C#
private void Spin1_Click(object sender, System.EventArgs e)
{
      // Display the Spin control's value.
      TextBox1.Text = Spin1.Value.ToString();
}
Enabling Grid Layout

As mentioned previously, user controls don' t have built-in support for the absolute positioning used by Web form' s grid layout. In ASP.NET, controls support absolute positioning through the style attribute. You can simply add a style attribute to your user control' s HTML on the Web form to get design-time support for absolute positioning. For example, the following HTML creates a Spin user control that you can drag to different locations on a Web form in Design mode:

<UserControl:Spin id="Spin1" runat="server" Value="5"
  style="Z-INDEX: 101; LEFT: 248px; POSITION: absolute; TOP: 88px">
  </UserControl:Spin>

You' ll see the problem with this solution as soon as you run the Web application, however. At run time, ASP.NET ignores the style attribute and displays the Spin user control using flow layout.

To make the style attribute work, you have to add a Style property to the user control to set the control' s position and size based on the attribute settings. If your control uses a container, such as the Grid Layout Panel used by the Spin user control, you can simply pass the user control' s style attribute to the Panel control, as shown by the following code:

Visual Basic .NET
Public Property Style() As String
    Get
        ' Return the containing Panel control's style attribute.
        Return pnlGrid.Attributes("style")
    End Get
    Set(ByVal Value As String)
        ' Set the containing Panel control's style attribute.
        pnlGrid.Attributes("style") = Value
    End Set
End Property
Visual C#
public string Style
{
      get
      {
            // Return the containing Panel control's style attribute.
            return pnlGrid.Attributes["style"];
      }
      set
      {
            // Set the containing Panel control's style attribute.
            pnlGrid.Attributes["style"] = value;
      }
}

You can use this same programming technique—sometimes called delegation—to implement other standard control attributes such as Visible, Enabled, or BackColor. Simply add a property to the user control that passes the attribute value to a containing control using the Attributes property, as shown in the preceding code.


Lesson 2: Creating Composite Custom Controls

Composite custom controls combine one or more server or HTML controls within a single control class, which can be compiled along with other control classes to create an assembly (.dll) that contains a custom control library. Once created, the custom control library can be loaded into Visual Studio .NET and used in the same way as the standard server and HTML controls.

Composite custom controls are functionally similar to user controls, but they reside in their own assemblies, so you can share the same control among multiple projects without having to copy it to each project, as you must do with user controls. However, composite controls are somewhat more difficult to create because you can' t draw them visually using the Visual Studio .NET Designer. You have many more options for defining their behavior, though, so composite controls are more complex than user controls.

After this lesson, you will be able to

  • Create a two-project solution that you can use to develop and debug composite custom controls

  • Add a composite custom control to a Web form in a test project by manually using the Register directive and HTML tags

  • Create a composite control' s appearance by adding controls to it using the CreateChildControls method

  • Create properties, methods, and events for a composite control

  • Handle design-time changes to the control, such as resizing or repositioning on a Web form using grid layout

  • Create new controls by deriving directly from a single server control

Estimated lesson time: 35 minutes
Creating and Using Composite Custom Controls

There are six steps to creating and using a custom control in a Web application:

  1. Create a solution containing a custom control project.

  2. Add a Web application project to the solution and set it as the startup project. You will use the Web application project to test the custom control during development.

  3. Add a project reference from the Web application to the custom control project and add an HTML Register directive and control element to use the custom control on a Web form.

  4. Create the custom control' s visual interface by adding existing controls to it through the custom control' s CreateChildControls method.

  5. Add the properties, methods, and events that the custom control provides.

  6. Build and test the custom control. This last step is actually part of each of the preceding steps.

The following sections describe these steps in greater detail.

Creating the Custom Control Project

Custom controls are simply classes that are built into an assembly. These custom control classes inherit much of their behavior from the WebControl class and they implement various interfaces, depending on the type of control you are creating.

The easiest way to get started is to use the Web Control Library project template provided by Visual Studio .NET.

To create a custom control using the project template, follow these steps:

  1. From the File menu, choose New and then select Project from the submenu. Visual Studio .NET displays the New Project dialog box, as shown in Figure 11-5.

    Figure 11-5 Creating a new custom control project

  2. Select the Web Control Library template icon from the Templates list, type the name of the project in the Name text box, and click OK. Visual Studio .NET creates a project containing code for a single custom control named WebControl1, as shown in Figure 11-6.

    Figure 11-6 The custom control code template (Visual Basic .NET)

The code shown in Figure 11-6 serves as a template for creating a new custom control. The template includes a class named WebControl1 that contains one property, which is named Text.

Visual Basic .NET
Imports System.ComponentModel
Imports System.Web.UI

<DefaultProperty("Text"), ToolboxData("<{0}:WebCustomControl1 _
runat=server></{0}:WebCustomControl1>")>
Public Class WebCustomControl1
    Inherits System.Web.UI.WebControls.WebControl

    Dim _text As String

    <Bindable(True), Category("Appearance"), DefaultValue("")> _
      Property [Text]() As String
        Get
            Return _text
        End Get

        Set(ByVal Value As String)
            _text = Value
        End Set
    End Property

     Protected Overrides Sub Render(ByVal output As _
       System.Web.UI.HtmlTextWriter)
        output.Write([Text])
    End Sub

End Class
Visual C#
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

namespace csCompositeSnippet
{
   /// <summary>
   /// Summary description for WebCustomControl1.
   /// </summary>
[DefaultProperty("Text"),
  ToolboxData("<{0}:WebCustomControl1
   runat=server></{0}:WebCustomControl1>")]
   public class WebCustomControl1 : System.Web.UI.WebControls.WebControl
   {
         private string text;

          [Bindable(true),
               Category("Appearance"),
               DefaultValue("")]
         public string Text
         {
               get
               {
                     return text;
               }

               set
               {
                     text = value;
               }
         }

         /// <summary>
         /// Render this control to the output parameter specified.
         /// </summary>
         /// <param name="output"> The HTML writer to write out to </param>
         protected override void Render(HtmlTextWriter output)
         {
               output.Write(Text);
         }
   }
}

The preceding code contains some elements you might not have seen before. Table 11-1 describes each of these items in turn.

Table 11-1 The Custom Control Template

Part

Name

Description

<DefaultProperty("Text"),
 ToolboxData("<{0}:
 WebCustomControl1
runat=server></{0}:
WebCustomControl1>")>

[DefaultProperty("Text"),
  ToolboxData("<{0}:
WebCustomControl1
runat=server></{0}:
WebCustomControl1>")]

Class attributes

Class attributes determine the design-time settings for the control. These values help determine what appears in the Visual Studio .NET Properties window when the custom control is selected on a Web form.

Inherits System.Web.UI.
WebControls.WebControl

: System.Web.UI.WebControls.WebControl

Base class

Custom controls are derived from one of the base control classes.

<Bindable(True),
Category("Appearance"),
 DefaultValue("")>

[Bindable(true),
Category("Appearance"),
 DefaultValue("")]

Property attributes

Like class attributes, property attributes determine the design-time settings of a property.

Property [Text]() As String

public string Text

Property definition

The property definition determines what the property does at run time. The square brackets ([]) indicate that the property name can be a keyword (though in this case, it is not).

Protected Overrides Sub
Render(ByVal output As
System.Web.UI.HtmlTextWriter)

protected
override void
Render(HtmlTextWriter
output)

Render method

The Render method displays the custom control. This overrides the base class' s Render method.

The following sections describe how to add to and modify these parts to create a composite custom control, but for more complete information about attributes, see the Visual Studio .NET Help topic “Design-Time Attributes for Components.”

Creating the Test Project

The custom control project created in the preceding section has the output type of Class Library. This means that the project can be used only from another application—it can' t run as a stand-alone application. To run and debug a custom control in Visual Studio .NET, you must add a second project to the current solution.

To create a custom control test project and add it to the current solution, follow these steps:

  1. With the custom control project open, choose Add Project from the File menu, and then select New Project from the submenu. Visual Studio .NET displays the New Project dialog box.

  2. Select ASP.NET Web Application from the Templates list, type the name of the project in the Location text box, and click OK. Visual Studio .NET creates a new Web application project and adds it to the current solution.

  3. In the Solution Explorer, right-click the Web application project and select Set As StartUp Project from the pop-up menu. Visual Studio .NET indicates that the Web application is the startup project by displaying the project name in bold.

  4. In the Solution Explorer, right-click the Web application' s References item and select Add Reference from the pop-up menu. Visual Studio .NET displays the Add Reference dialog box, as shown in Figure 11-7.

    Figure 11-7 Add a project reference

  5. Click the Projects tab, click Select to add a reference from the custom control project to the Web application project, and then click OK. Visual Studio .NET adds the reference, as shown by the Solution Explorer in Figure 11-8.

    Figure 11-8 The solution

Establishing a project reference as described in the preceding steps copies the custom control assembly (.dll) to the /bin directory of the Web application. This makes the custom control available to the Web application. Any changes to the custom control assembly are automatically copied to the Web application' s /bin directory.

As you develop your custom control, remember that changes aren' t updated in the test Web application until you rebuild the custom control. That happens automatically when you run the application, but not when you switch between the custom control and the test Web application in Design mode. To see changes to the control in Design mode, you must rebuild the custom control before switching to the test application.

Adding the Custom Control to the Test Project

To test and debug the custom control during development, you must add an instance of the control to a Web form in the test Web application created in the preceding section.

To add a custom control to a Web form, follow these steps:

  1. In the test Web application, display a Web form and switch to HTML mode by clicking the HTML tab at the bottom left corner of the Design window.

  2. Add a Register directive to the top of the Web form. For example, the following directive registers the custom control assembly named CompositeControls:

    <%@ Register TagPrefix="Custom" Namespace="vbCompositeSnippet"
Assembly="vbCompositeSnippet" %>

  3. Create an instance of the custom control on the Web form using HTML syntax. For example, the following HTML creates an instance of WebCustomControl1 created in the preceding section:

    <Custom:WebCustomControl1 id="custTest" runat="server" />

For custom controls, the Register directive' s three attributes have the meanings described in Table 11-2.

Table 11-2 The Register Directive' s Attributes
Attribute | Meaning
TagPrefix | This name identifies the group that the user control belongs to. For example, the tag prefix for ASP.NET server controls is “asp”. You use this prefix to create a naming convention to organize your custom controls.
Namespace | This is the project name and namespace within the custom control assembly that contains the controls to register. Visual Basic .NET uses the project name as an implicit namespace, so for controls written in Visual Basic .NET, use the project name.
Assembly | This is the name of the assembly (.dll) containing the custom controls. The control assembly must be referenced by the Web application. As mentioned previously, referencing the assembly maintains a copy of it in the Web application' s /bin directory.

The Register directive' s TagPrefix makes up the first part of the name you use in the custom control' s HTML element. The second part is the project name and class name of the control within the assembly. TagPrefix=“Custom” and the class name WebCustomControl1 means that you create the custom control using an HTML <Customl:WebCustomControl1 …/> element.

Custom controls can exist alongside and interact with other controls on a Web form. For example, the following HTML shows the WebControl1 custom control next to a Button control:

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="Default.aspx.vb"
Inherits="vbCompositeSnippet.WebForm1"%>
<%@ Register TagPrefix="Custom" NameSpace="vbCompositeSnippet "
Assembly="CompositeSnippet" %>
<HTML>
<body>
<form id="Form1" method="post" runat="server">
   <asp:button id="butCount" runat="server" />
   <Custom:WebCustomControl1 id="custTest" runat="server"
      Text="This is a custom control" />
</form>
</body>
</HTML>

Notice that the custom control includes an attribute for the Text property. The control' s HTML attributes provide a way to set the control' s properties from HTML. Unlike user controls, custom controls display correctly in the Visual Studio .NET Designer, as shown in Figure 11-9.

Figure 11-9 The WebControl1 custom control on a Web form (design time)

Another thing you should notice about custom controls is that when they are selected in the Visual Studio .NET Designer, their properties appear in the Properties window. Again, unlike user controls, custom controls are fully supported by Visual Studio .NET.

To use the custom control in code, simply refer to it as you would any other control. For example, the following Button Click event procedure increments the value shown in the WebCustomControl1 custom control:

Visual Basic .NET
Private Sub butAdd_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butAdd.Click
    ' Increment the Text property of the custom control.
    custTest.Text = Val(custTest.Text) + 1
End Sub
Visual C#
private void butAdd_Click(object sender, System.EventArgs e)
{
      // Convert the string to a number.
      int intText = 0;
      try
      {
            intText = int.Parse(custTest.Text);
      }
      catch
      {
            intText = 0;
      }
      // Increment the number.
      intText ++;
      // Display result in control
     custTest.Text = intText.ToString();
}

When you run the preceding code as part of a test Web application, ASP.NET displays the Web form with the custom control and the Button server control, as shown in Figure 11-10.

Figure 11-10 The WebControl1 custom control on a Web form (run time)

If you click the Button control in the preceding example more than once, the displayed value never gets past 1. Whoa! What gives? The custom control template that Visual Studio .NET creates doesn' t take steps to save properties between page displays. To do that, you' ll need to add code, as described in the following sections.

Creating the Composite Control' s Appearance

You create the visual interface of composite custom controls by adding existing server or HTML controls to the Controls collection in the CreateChildControls method. The custom control' s CreateChildControls method overrides the method in the base class.

The following code shows how to create a composite custom control' s appearance. The code creates a composite custom control named MathBox. The MathBox control contains TextBox, Button, and Label controls, which are declared at the class level so that they are available to all procedures in the class. The CreateChildControls procedure adds these controls and some Literal controls to the custom control' s Controls collection to create the custom control' s appearance.

Visual Basic .NET
Public Class MathBox
        Inherits System.Web.UI.WebControls.WebControl

        Dim txtMath As New TextBox()
        Dim butSum As New Button()
        Dim lblResult As New Label()

        ' Create control's appearance.
        Protected Overrides Sub CreateChildControls()
            ' Add the sub controls to this composite control.
            ' Set the TextMode property and add textbox.
            txtMath.TextMode = TextBoxMode.MultiLine
            Controls.Add(txtMath)
            ' Start a new line
            Controls.Add(New LiteralControl("<br>"))
            ' Set the Text property and add the Button control.
            butSum.Text = "Sum"
            Controls.Add(butSum)
            ' Add Label and Literals to display result.
            Controls.Add(New LiteralControl("&nbsp&nbspResult:&nbsp<b>"))
            Controls.Add(lblResult)
            Controls.Add(New LiteralControl("</b>"))
        End Sub
End Class
Visual C#
public class MathBox : System.Web.UI.WebControls.WebControl
   {
         TextBox txtMath = new TextBox();
         Button butSum = new Button();
         Label lblResult = new Label();

         private string text;

         protected override void CreateChildControls()
         {
               // Add the sub controls to this composite control.
            // Set the TextMode property and add textbox.
            txtMath.TextMode = TextBoxMode.MultiLine;
            Controls.Add(txtMath);
            // Start a new line
            Controls.Add(new LiteralControl("<br>"));
            // Set the Text property and add the Button control.
            butSum.Text = "Sum";
            Controls.Add(butSum);
            // Add Label and Literals to display result.
            Controls.Add(new LiteralControl("&nbsp&nbspResult:&nbsp<b>"));
            Controls.Add(lblResult);
            Controls.Add(new LiteralControl("</b>"));

         }
   }

The MathBox class shown in the preceding code is used as the basis for the rest of this lesson. If you add it to the composite control project created in the preceding sections, you can add it to the test Web application using the same Register directive you added for the WebCustomControl1, and you' ll create an instance of the control with the following HTML:

<Custom:MathBox id="mathTest" runat="server" />

If you add the MathBox custom control to a Web form at this point, you will notice it appears as only a small green box at design time. At run time, however, the control will appear as shown in Figure 11-11.

Figure 11-11 The MathBox custom control (run time)

The MathBox custom control doesn' t have a design-time appearance at this point because it does not yet expose any design-time properties. That problem is remedied in the next section.

You don' t need to override the Render method when creating a composite custom control. The custom control code template that Visual Studio .NET generates overrides this method. Therefore, if you are using the template, you must delete the generated Render method or change it to delegate back to the base class (making it a null operation), as shown here:

Visual Basic .NET
' Delegate back since not needed in composite control.
    Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
        MyBase.Render(writer)
    End Sub
Visual C#
// Delegate back since not needed in composite control.
protected override void Render(HtmlTextWriter output)
{
        base.Render(output);
     }
Creating Properties and Methods

The MathBox custom control will take a list of numbers entered in its text box, add them, and return the result. From a design standpoint, that requires two properties: Values, which contains an array of numbers; and Result, which contains the sum of those numbers. It also implies a Sum method to perform the calculation.

The following code shows the Values and Result properties and the Sum method used by MathBox. It also includes a Text property for consistency with the ASP.NET server controls and to simplify setting default text from HTML attributes.

Visual Basic .NET
' MathBox Properties and Methods.
        <DefaultValue("")> Property Text() As String
            Get
                ' Make sure child controls exist.
                EnsureChildControls()
                ' Return the text in the TextBox control.
                Return txtMath.Text
            End Get
            Set(ByVal Value As String)
                ' Make sure child controls exist.
                EnsureChildControls()
                ' Set the text in the TextBox control.
                txtMath.Text = Value
            End Set
        End Property

        Property Values() As String()
            Get
                EnsureChildControls()
                ' Return an array of strings from the TextBox.
                Return txtMath.Text.Split(Chr(13))
            End Get
            Set(ByVal Value() As String)
                EnsureChildControls()
                ' Set the text in the TextBox from an array.
                txtMath.Text = String.Join(Chr(13), Value)
            End Set
        End Property

        ReadOnly Property Result() As String
            Get
                EnsureChildcontrols()
                ' Return the result from the Label.
                Return lblResult.Text
            End Get
        End Property

        Sub Sum()
            EnsureChildcontrols()
            ' If there is text in the TextBox.
            If txtMath.Text.Length Then
                ' Break the text into an array, line by line.
                Dim arrNums As String()
                arrNums = txtMath.Text.Split(Chr(13))
                Dim strCount As String, dblSum As Double
                ' Add each element in the array together.
                For Each strCount In arrNums
                    ' Use error handling to ignore non-number entries.
                    Try
                        dblSum += Convert.ToDouble(strCount)
                    Catch
                    End Try
                Next
                ' Display the result in the label.
                lblResult.Text = dblSum.ToString
            Else
                lblResult.Text = "0"
            End If
        End Sub
Visual C#
// MathBox properties and methods.
         [DefaultValue("0")]
         public string Text
         {
               get
               {
                // Make sure child controls exist.
                EnsureChildControls();
                // Return the text in the TextBox control.
                return txtMath.Text;
               }

               set
               {
                     // Make sure child controls exist.
                     EnsureChildControls();
                     // Set the text in the TextBox control.
                     txtMath.Text = value;
               }
         }

         char[] strSep = {'\r'};

         public string[] Values
         {
               get
               {
                EnsureChildControls();
                // Return an array of strings from the TextBox.
                     return txtMath.Text.Split(strSep);
               }
               set
               {
                EnsureChildControls();
                // Set the text in the TextBox from an array.
                txtMath.Text = String.Join(" ", value);
               }
         }

         public string Result
         {
               get
               {
                EnsureChildControls();
                // Return the result from the Label.
                return lblResult.Text;
               }
         }

         public void Sum()
         {
            EnsureChildControls();
            // If there is text in the TextBox.
            if (txtMath.Text.Length != 0)
               {
                // Break the text into an array, line by line.
                string[] arrNums;
                arrNums = txtMath.Text.Split(strSep);
                     double dblSum = 0;
                // Add each element in the array together.
                     foreach (string strCount in arrNums)
                     {
                           // Use error handling to
                           // ignore non-number entries.
                           try
                           {
                              dblSum += Convert.ToDouble(strCount);
                           }
                           catch
                           {
                           }
                     }
                // Display the result in the label.
                     lblResult.Text = dblSum.ToString();
               }
               else
                lblResult.Text = "0";
         }
   }

The EnsureChildControls statement in the preceding code makes sure the child controls have been instantiated. You use this in a composite control before referring to any contained controls.

After you add properties and methods to the composite control, rebuild the control to update the control' s assembly. At that point, the composite control should appear in Design mode as well as at run time, as shown in Figure 11-12.

Figure 11-12 The MathBox custom control (design time)

The control does not appear at design time. You can usually cause it to appear by setting one of the custom control' s properties, as shown by the following HTML:

<Custom:MathBox id="mthTest" runat="server" Text="0" />

Because composite controls use their base class' s Render method, they automatically support absolute positioning with grid layout. In other words, you can drag composite controls to various positions on a Web form and they will be displayed there at run time.

Handling Events

So far, the MathBox can accept values, perform calculations, and display a result, but if you click the Sum button, it doesn' t do any of that.

To enable the Sum button, follow these steps:

  1. Add an event handler for the button' s Click event to the CreateChildControls procedure using the AddHandler method.

  2. Create a procedure to handle the event. This procedure has to have the same arguments and return type as the button' s Click event.

The following code shows the additions to the MathBox control in bold. Notice that the event procedure simply calls the Sum method created earlier to perform the calculation and display the result.

Visual Basic .NET
Protected Overrides Sub CreateChildControls()
    ' Add the sub controls to this composite control.
    ' Set the TextMode property and add textbox.
    txtMath.TextMode = TextBoxMode.MultiLine
    Controls.Add(txtMath)
    ' Start a new line
    Controls.Add(New LiteralControl("<br>"))
    ' Set the Text property and add the Button control.
    butSum.Text = "Sum"
    Controls.Add(butSum)
    ' Add Label and Literals to display result.
    Controls.Add(New LiteralControl("&nbsp&nbspResult:&nbsp<b>"))
    Controls.Add(lblResult)
    Controls.Add(New LiteralControl("</b>"))
    ' Add a handler for the Button's Click event.
    AddHandler butSum.Click, AddressOf Me.butSumClickedEnd Sub

' Event procedure for Button Click
Sub butSumClicked(ByVal source As Object, ByVal e As EventArgs)
    ' Call the Sum method.
    Sum()
End Sub
Visual C#
protected override void CreateChildControls()
{
      // Add the sub controls to this composite control.
   // Set the TextMode property and add textbox.
   txtMath.TextMode = TextBoxMode.MultiLine;
   Controls.Add(txtMath);
   // Start a new line
   Controls.Add(new LiteralControl("<br>"));
   // Set the Text property and add the Button control.
   butSum.Text = "Sum";
   Controls.Add(butSum);
   // Add Label and Literals to display result.
   Controls.Add(new LiteralControl("&nbsp&nbspResult:&nbsp<b>"));
   Controls.Add(lblResult);
   Controls.Add(new LiteralControl("</b>"));
   // Add event handler.
   butSum.Click += new EventHandler(butSumClicked);}

void butSumClicked(object sender, EventArgs e)
{
      // Call the Sum method.
      Sum();
}
Raising Events

In addition to handling the Sum button' s Click event, you might want to raise an event that can be handled from the Web form containing the custom control.

To raise an event from a custom control, follow these steps:

  1. Add a public event declaration to the custom control' s class.

  2. Raise the event from code within the custom control using the control' s event method.

The following code shows how to declare and raise an event from within the MathBox control class:

Visual Basic .NET
' Declare an event
        Event Click(ByVal sender As Object, ByVal e As EventArgs)

        ' Event procedure for Button Click
        Sub butSumClicked(ByVal source As Object, ByVal e As EventArgs)
            ' Call the Sum method.
            Sum()
            ' Call method to raise event.
            OnClick(EventArgs.Empty)
        End Sub

        Protected Overridable Sub OnClick(ByVal e As EventArgs)
            ' Raise the event.
            RaiseEvent Click(Me, e)
        End Sub
Visual C#
// Declare the event.
         public event EventHandler Click;

         void butSumClicked(object sender, EventArgs e)
         {
               // Call the Sum method.
               Sum();
               // Call the event method.
               OnClick(EventArgs.Empty);
         }

         protected virtual void OnClick(EventArgs e)
         {
               // Raise the event.
               Click(this, e);
         }

To make the Click event the default event for the control, add a DefaultEvent attribute to the class declaration. A default event is the event that Visual Studio .NET automatically creates when you double-click the control within the Web form. The following code shows the addition in bold:

Visual Basic .NET
<DefaultEvent("Click")> Public Class MathBox
Visual C#
[DefaultEvent("Click")]
   public class MathBox : System.Web.UI.WebControls.WebControl

To use the MathBox' s event from a Web form, double-click the control in the test Web form and use the following event procedure:

Visual Basic .NET
Private Sub mathTest_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mathTest.Click
    Response.Write(mathTest.Result)
End Sub
Visual C#
private void mthTest_Click(object sender, System.EventArgs e)
{
      Response.Write(mthTest.Result.ToString());
}
Handling Control Resizing

Composite controls handle a lot of design-time and run-time display features automatically through their base class' s Render method. However, they cannot automatically resize their child controls because there is no reasonable way to tell what type of resizing behavior is appropriate.

To handle resizing composite controls, follow these steps:

  1. Override the base class' s Render method.

  2. Add code to resize the child controls as appropriate.

  3. Call the base class' s Render method to display the control.

The following code resizes TextBox child control based on the width and height of the MathBox control:

Visual Basic .NET
Protected Overrides Sub Render(ByVal writer As _
  System.Web.UI.HtmlTextWriter)
    EnsureChildcontrols()
    ' Resize text box to match control width.
        txtMath.Width = Me.Width
        ' Resize text box to match control height.
        txtMath.Height = Unit.Parse(Me.Height.Value  butSum.Height.Value)
        ' Render the control.
        MyBase.Render(writer)
    End Sub
Visual C#
protected override void Render(HtmlTextWriter output)
{
   EnsureChildControls();
   // Resize text box to match control width.
   txtMath.Width = this.Width;
   // Resize text box to match control height.
      double dHeight = this.Height.Value - butSum.Height.Value;
   txtMath.Height = Unit.Parse(dHeight.ToString());
   // Render the control.
      base.Render(output);
}
Superclassing Server Controls

The preceding sections showed you how to create a composite control from multiple existing controls. You can use the same techniques to create new controls from an existing single control to add properties, methods, or events for that control, or to change the behavior of that control.

Because that type of custom control is based on only one control, you can derive directly from that control' s class rather than using the more general class of WebControl. Custom controls of this type aren' t really composite controls—they are sometimes referred to as superclassed controls.

For example, the following SuperText class creates a custom control based on the TextBox server control and adds a method to sort the contents of the text box:

Visual Basic .NET
Public Class SuperText
    Inherits System.Web.UI.WebControls.TextBox

    ' New method for the text box.
    Public Sub Sort()
        ' Create an array.
        Dim arrText As String()
        ' Put the words in the text box into the array.
        arrText = Me.Text.Split(" ")
        ' Sort the array.
        Array.Sort(arrText)
        ' Join the string and put it back in the text box.
        Me.Text = String.Join(" ", arrText)
    End Sub

End Class
Visual C#
public class SuperText : System.Web.UI.WebControls.TextBox
{

     public void Sort()
     {
         // Create an array.
        string[] arrText;
        // Put the words in the text box into the array.
        char[] strSep = {' '};
        arrText = this.Text.Split(strSep);
        // Sort the array.
        Array.Sort(arrText);
        // Join the string and put it back in the text box.
        this.Text = String.Join(" ", arrText);
     }
}

To use the control from a Web form, register the control' s assembly as shown in the preceding examples, and include the following HTML on a Web form:

<Custom:SuperText id="superTest" runat="server"  />
<asp:Button ID="butSort" Runat="server" Text="Sort"  />

To test the Sort method, add the following code to the Web form' s code module:

Visual Basic .NET
Private Sub butSort_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butSort.Click
    superTest.Sort()
End Sub
Visual C#
private void butSort_Click(object sender, System.EventArgs e)
{
       superTest.Sort();
}

At either run time or design time, the SuperText control behaves just like a normal TextBox, as shown in Figure 11-13.

Figure 11-13 SuperText in action


Lesson 3: Creating Rendered Custom Controls

Rendered custom controls are created almost entirely from scratch, which means you have complete control over how they appear and act. However, since they are not composed of child controls, the way composite controls are, you have to write code to perform the tasks that you could otherwise delegate to child controls.

Composite and rendered controls are closely related, and this lesson extends the skills you learned in Lesson 2. Some of the topics here apply equally to composite and rendered controls—you should consider this lesson to be advanced topics for Web custom control development. That said, there is still a lot more that could be taught on the subject. For a comprehensive guide to developing Web custom controls, see Developing ASP.NET Server Controls and Components, by Nikhil Kothari and Vandana Datye (Microsoft Press, 2002).

After this lesson, you will be able to

  • Create a custom control' s appearance using the Render method and the HtmlTextWriter utility methods

  • Save and restore property values through the control' s ViewState

  • Get text and controls contained between a custom control' s begin and end HTML tags

  • Respond to user actions on a control by raising cached or post-back events

  • Get data that was entered on the control by the user

  • Add custom controls to the Visual Studio .NET Toolbox

  • Change the Toolbox icon and HTML TagPrefix used for the control by Visual Studio .NET

Estimated lesson time: 40 minutes
Creating and Using Rendered Controls

If you walked through the procedures described in Lesson 2, you have already created a rendered custom control. The code template that Visual Studio .NET generates when you create a Web Control Library project is, in fact, a rendered control. It is, however, a simple control that doesn' t do much. You can create more interesting controls more quickly using composition, which is why creating composite controls is shown before creating rendered controls in this chapter.

Because rendered custom controls are very similar to composite custom controls, the techniques you learned in Lesson 2 apply throughout this lesson as well, with one exception: Instead of creating the control' s visual interface from other controls, rendered controls create their visual interfaces exclusively from HTML elements. This means that you don' t have built-in properties, methods, or events you can use to get or display data; instead, you must create them yourself.

When creating rendered custom controls, the first three steps are the same as the steps described in Lesson 2 for creating custom control. To create rendered custom controls, complete these steps:

  1. Create a solution containing a custom control project.

  2. Add a Web application project to the solution and set it as the startup project. You will use the Web application project to test the custom control during development.

  3. Add a project reference from the Web application to the custom control project, and then add an HTML Register directive and control element to use the custom control on a Web form.

  4. Create the custom control' s visual interface by overriding the base class' s Render method.

  5. Write code to store property values, respond to user actions, and get data from the user as needed.

The following sections describe the last two steps in greater detail. The last two sections in this lesson show how to add completed custom controls to the Toolbox and how to set the icons and generated tags used by Visual Studio .NET for the controls. These last two sections apply equally to composite controls.

Creating the Rendered Control' s Appearance

You create a rendered custom control' s appearance by overriding the base class' s Render method and writing to the method' s output argument using the HtmlTextWriter utility methods. The HtmlTextWriter methods summarized in Table 11-3 let you add HTML to the output argument.

Table 11-3 HtmlTextWriter Utility Methods
Method | Description
AddAttribute | Adds an HTML attribute to the next HTML element to render.
RenderBeginTag | Renders the begin tag of an HTML element for later writing by the WriteLine method.
RenderEndTag | Renders the end tag of an HTML element and writes the element and any rendered attributes that are pending. All rendered attributes are cleared after RenderEndTag.
Write | Immediately writes a string.
WriteAttribute | Immediately writes an HTML attribute.
WriteBeginTag | Immediately writes the begin tag of an HTML element.
WriteEndTag | Immediately writes the end tag of an HTML element.
WriteFullBeginTag | Immediately writes the begin tag along with the closing bracket (>) for the HTML element.
WriteLine | Immediately writes a line of content. This is equivalent to the Write method, but WriteLine adds a newline character as well.

As you can probably tell from Table 11-3, there are two approaches to writing HTML elements to the output argument. The most direct approach is to use the Write methods to add the HTML directly to the HtmlTextWriter. For example, the following code creates a button that displays a message box when the user clicks it:

Visual Basic .NET
Imports System.ComponentModel
Imports System.Web.UI

Public Class AlertButton
    Inherits System.Web.UI.WebControls.WebControl

    Protected Overrides Sub Render(ByVal output As _
      System.Web.UI.HtmlTextWriter)
        ' Write a title
        output.Write("<h3>Rendered Control</h3>")
        ' Opens an Input HTML tag (inserts "<INPUT").
        output.WriteBeginTag("INPUT")
        ' Write some attributes.
        output.WriteAttribute("value", "Custom Button")
        output.WriteAttribute("type", "button")
        output.WriteAttribute("onclick", "javascript:alert('Howdy!')")
        ' Close the Input HTML tag (inserts ">").
        output.WriteEndTag("INPUT")
    End Sub

End Class
Visual C#
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

namespace csRenderedSnippet
{
   public class AlertButton : System.Web.UI.WebControls.WebControl
   {
         protected override void Render(HtmlTextWriter output)
         {
              // Write a title
              output.Write("<h3>Rendered Control</h3>");
              // Opens an Input HTML tag (inserts "<INPUT").
              output.WriteBeginTag("INPUT");
              // Write some attributes.
              output.WriteAttribute("value", "Custom Button");
              output.WriteAttribute("type", "button");
              output.WriteAttribute("onclick", "javascript:alert('Howdy!')");
              // Close the Input HTML tag (inserts ">").
              output.WriteEndTag("INPUT");
         }
   }
}

If you add the preceding code to a custom control project, build the control, and then add the resulting custom control to a Web form, at run time the control will appear as shown in Figure 11-14.

Figure 11-14 The AlertButton rendered custom control in action

As an alternative to writing directly to the HtmlTextWriter, you can render tags and attributes and then write them as a unit using the RenderEndTag method. The following code shows the AlertButton custom control Render method written using this alternative approach:

Visual Basic .NET
Protected Overrides Sub Render(ByVal output As _
      System.Web.UI.HtmlTextWriter)
        ' Write a title
        output.Write("<h3>Rendered Control</h3>")
        ' Add some attributes.
        output.AddAttribute("value", "Custom Button")
        output.AddAttribute("type", "button")
        output.AddAttribute("onclick", "javascript:alert('Howdy!')")
        ' Opens an Input HTML tag (inserts "<INPUT").
        output.RenderBeginTag("INPUT")
        ' Close the Input HTML tag (inserts ">").
        output.RenderEndTag()
    End Sub
Visual C#
protected override void Render(HtmlTextWriter output)
{
   // Write a title
   output.Write("<h3>Rendered Control</h3>");
   // Add some attributes.
   output.AddAttribute("value", "Custom Button");
   output.AddAttribute("type", "button");
   output.AddAttribute("onclick", "javascript:alert('Howdy!')");
   // Opens an Input HTML tag (inserts "<INPUT").
   output.RenderBeginTag("INPUT");
   // Close the Input HTML tag (inserts ">").
   output.RenderEndTag();
}

The advantage of rendering the HTML elements in this way is that you can create the HTML attributes in any sequence.

Storing Property Settings

The composite custom control samples shown in Lesson 2 stored property settings using the properties from their contained controls. Since rendered controls don' t have contained controls, you must use the rendered control' s ViewState to store any property settings you want to retain between page displays.

The following code shows a Text property that retains its value between page displays:

Visual Basic .NET
Property Text() As String
    Get
        ' If the property has been set
        If Not IsNothing(ViewState("Text")) Then
            ' Return the settng.
            Return ViewState("Text")
        Else
            ' Otherwise return "".
            Return ""
        End If
    End Get
    Set(ByVal Value As String)
        ' Store the property setting.
        ViewState("Text") = Value
    End Set
End Property
Visual C#
public string Text
{
      get
      {
      // If the property has been set
       if (ViewState["Text"] != null)
           // Return the settng.
           return ViewState["Text"].ToString();
       else
           // Otherwise return "".
           return null;
      }

      set
      {
            // Store the property setting.
            ViewState["Text"] = value;
      }
}

Setting ViewState serializes data and stores it in a hidden field on the Web form. When the page is posted back to the server, returning a value from ViewState retrieves and de-serializes the data from the hidden field.

ViewState is optimized for String, ArrayList, and HashTable data types, but can save any data that can be serialized or that provides a TypeConverter. For other types of data, you can create your own code to save and restore ViewState by overriding the SaveViewState and LoadViewState methods.

Getting Contained Text

HTML elements often enclose literal text that they modify in some way, as shown by the following HTML tags to create bold text:

<b>Bold me</b>

To get contained text from within a custom control, follow these steps:

  1. Add a ParseChildren attribute to the control' s class definition.

  2. Implement the INamingContainer interface in the custom control class.

  3. Retrieve the contained text using the Controls collection.

The following custom control class shows how to retrieve the text between a custom control' s begin and end tags to display the contained text in red:

Visual Basic .NET
' ParseChildren attribute enables contained controls.
<ParseChildren(False)> Public Class Red
    Inherits WebControls.WebControl
    ' This interface makes it possible to get the Controls array.
    Implements INamingContainer

    Protected Overrides Sub Render(ByVal output As _
      System.Web.UI.HtmlTextWriter)
        ' Contained text is returned as a Literal control.
        Dim litText As LiteralControl
        ' Get the contained text from the Controls collection.
        litText = Controls(0)
        ' Make it red using the <font> element.
        output.Write("<font color='red'>" & litText.Text & "</font>")
    End Sub

End Class
Visual C#
[ParseChildren(false)]
   public class Red : System.Web.UI.WebControls.WebControl, INamingContainer
   {
   // INamingContainer interface makes it possible to get the Controls array.

         protected override void Render(HtmlTextWriter output)
         {
            // Contained text is returned as a Literal control.
           LiteralControl litText ;
            // Get the contained text from the Controls collection.
           litText = (LiteralControl)Controls[0];
            // Make it red using the <font> element.
           output.Write("<font color='red'>" + litText.Text + "</font>");
         }
   }

On a Web form, you would use the preceding custom control through the following HTML:

<custom:red id="Red1" runat="server">Some red text</custom:red>

You can use the Controls collection to get any items that exist between the custom control' s begin and end tags, including other controls. For example, the following custom control class groups contained controls in a panel, centers the controls on the page, and displays them on a red background:

Visual Basic .NET
' ParseChildren attribute enables contained controls.
    <ParseChildren(False)> Public Class Center
        Inherits WebControls.WebControl
        ' This interface makes it possible to get the Controls array.
        Implements INamingContainer

        Protected Overrides Sub Render(ByVal output As _
          System.Web.UI.HtmlTextWriter)
            ' Add some attributes for the panel.
            output.AddAttribute("align", "center")
            output.AddStyleAttribute("BACKGROUND-COLOR", "red")
            ' Start a panel
            output.RenderBeginTag("div")
            ' Declare a variable to get contained controls.
            Dim ctrItem As Control
            ' For each contained control.
            For Each ctrItem In Controls
                ' Render the control
                ctrItem.RenderControl(output)
            Next
            output.RenderEndTag()
        End Sub

    End Class
Visual C#
[ParseChildren(false)]
   public class Center : System.Web.UI.WebControls.WebControl,
     INamingContainer
   {
   // INamingContainer interface makes it possible to get the Controls array.

         protected override void Render(HtmlTextWriter output)
         {
               // Add some attributes for the panel.
               output.AddAttribute("align", "center");
               output.AddStyleAttribute("BACKGROUND-COLOR", "red");
               // Start a panel
               output.RenderBeginTag("div");
               // For each contained control.
               foreach (Control ctrItem in Controls)
               {
                     // Render the control
                     ctrItem.RenderControl(output);
               }
               output.RenderEndTag();
         }
   }
Responding to User Actions

Composite custom controls can use their contained controls' events to respond to user actions, as shown in Lesson 2. Rendered controls, however, need to take special steps to respond to user actions. How you deal with user actions depends on the type of event:

  • Cached events can be raised from the custom control' s code.

  • Post-back events must be raised from the client side using a script rendered by the custom control.

The following sections show how to raise each of these types of events.

Raising Cached Events

Cached events are handled by the Web form' s server-side code when the Web form is posted back to the server and after the Page_Load and post-back events have been handled. Raising a cached event involves two steps:

  1. Declare an event to raise.

  2. Raise the event from somewhere within the custom control' s code.

For example, the following custom control class defines an OnChange event that is raised whenever the Text property changes:

Visual Basic .NET
<DefaultEvent("OnChange")>Public Class RenderText
        Inherits WebControls.WebControl

        Event Change(ByVal sender As Object, ByVal e As EventArgs)

        Property Text() As String
            Get
                ' If the property has been set
                If Not IsNothing(ViewState("Text")) Then
                    ' Return the settng.
                    Return ViewState("Text")
                Else
                    ' Otherwise return "".
                    Return ""
                End If
            End Get
            Set(ByVal Value As String)
                ' Store the property setting.
                ViewState("Text") = Value
                OnChange(EventArgs.Empty)
            End Set
        End Property

        ' Event method.
        Protected Overridable Sub OnChange(ByVal e As EventArgs)
            ' Raise event.
            RaiseEvent Change(Me, EventArgs.Empty)
        End Sub

        Protected Overrides Sub Render(ByVal output As _
          System.Web.UI.HtmlTextWriter)
            ' Set the Input control's attributes.
            output.AddAttribute("value", Me.Text)
            ' Create an Input control element using above.
            output.RenderBeginTag("Input")
            ' Close the Input control element (inserts />).
            output.RenderEndTag()
        End Sub

    End Class
Visual C#
[DefaultEvent("Change")]
   public class RenderText : System.Web.UI.WebControls.WebControl
   {
         // Declare event.
         public event EventHandler Change;

         public string Text
         {
               get
               {
                // If the property has been set
                if (ViewState["Text"] != null)
                     // Return the settng.
                     return ViewState["Text"].ToString();
                else
                    // Otherwise return "".
                    return null;
               }
               set
               {
                     // Store the property setting.
                     ViewState["Text"] = value;
                     // Call event method.
                     OnChange(EventArgs.Empty);
               }
         }

         protected virtual void OnChange(EventArgs e)
         {
               if (Change != null)
                      // Raise event.
                      Change(this, e);
         }

         protected override void Render(HtmlTextWriter output)
         {
            // Set the Input control's attributes.
            output.AddAttribute("value", this.Text);
            // Create an Input control element using above.
            output.RenderBeginTag("Input");
            // Close the Input control element (inserts />).
            output.RenderEndTag();
         }
   }

Notice that the class definition above sets Change as the default event for the control. That means that Visual Studio .NET will generate the event procedure if you double-click the custom control in the Web form Designer.

Use the following code to handle the Change event from the Web form:

Visual Basic .NET
Private Sub txtTest_OnChange(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles txtTest.OnChange
          Response.Write("Text changed!")
    End Sub
Visual C#
private void txtTest_Change(object sender, System.EventArgs e)
         {
               Response.Write("Text changed!");
         }
Raising Post-back Events

Post-back events cause the Web form to be posted back to the server for immediate processing. They are handled immediately after the Web form' s Page_Load event. To raise a post-back event from a custom control, follow these steps:

  1. Implement the IPostBackEventHandler interface in the custom control.

  2. Declare an event to raise.

  3. Render an HTML element that can respond to client-side events, such as onclick.

  4. Add an attribute to the HTML element containing a script to post the page back to the server.

  5. Raise the event from the RaisePostBackEvent method, which is part of the IPostBackEventHandler interface.

The following code shows how to use these steps to raise the Click post-back event from a custom control:

Visual Basic .NET
' Raises PostBack event.
    <DefaultEvent("Click")> Public Class AlertButton
        Inherits System.Web.UI.WebControls.WebControl
        ' (1) Interface that allows PostBack event processing.
        Implements IPostBackEventHandler

        ' (2) Declare postback event
        Event Click(ByVal sender As Object, ByVal e As EventArgs)

        ' (3) Render an HTML element that can detect user events.
        Protected Overrides Sub Render(ByVal output As _
          System.Web.UI.HtmlTextWriter)
            ' Add some attributes.
            output.AddAttribute("value", "Postback Button")
            output.AddAttribute("type", "button")
            ' (4) Add attribute to raise Postback event on client.
            output.AddAttribute("onclick", "javascript:alert('Howdy!');" & _
              Page.GetPostBackEventReference(Me))
            ' Opens an Input HTML tag (inserts "<INPUT").
            output.RenderBeginTag("INPUT")
            ' Close the Input HTML tag (inserts ">" and writes attributes).
            output.RenderEndTag()
        End Sub

        ' Part of the IPostBackEventHandler interface.
        ' you must create this method.
        Public Sub RaisePostBackEvent(ByVal eventArgument As String) _
          Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent
            ' (5) Raise the event.
            OnClick(EventArgs.Empty)
        End Sub

        Protected Overridable Sub OnClick(ByVal e As EventArgs)
            ' Raise the event.
            RaiseEvent Click(Me, EventArgs.Empty)
        End Sub

    End Class
Visual C#
// (1) IPostBackEventHandler interface  allows PostBack event processing.
   [DefaultEvent("Click")] public class AlertButtonThree :
     System.Web.UI.WebControls.WebControl, IPostBackEventHandler
   {
         // (2) Declare postback event
         public event EventHandler Click;

         // (3) Render an HTML element that can detect user events.
         protected override void Render(HtmlTextWriter output)
         {
               // Write a title
               output.Write("<h3>Rendered Control</h3>");
               // Add some attributes.
               output.AddAttribute("value", "Custom Button");
               output.AddAttribute("type", "button");
               // (4) Add attribute to raise Postback event on client.
               output.AddAttribute("onclick", "javascript:alert('Howdy!');" +
                     Page.GetPostBackEventReference(this));
               // Opens an Input HTML tag (inserts "<INPUT").
               output.RenderBeginTag("INPUT");
               // Close the Input HTML tag (inserts ">").
               output.RenderEndTag();
         }

         // Part of the IPostBackEventHandler interface.
         // you must create this method.
         public void RaisePostBackEvent(string eventArgument)
         {
               // (5) Call the event method to raise event.
               OnClick(EventArgs.Empty);
         }

         protected virtual void OnClick(EventArgs e)
         {
               // Raise the event.
               if (Click != null)
                     Click(this, e);
         }

   }

When the user clicks the preceding custom control at run time, the button' s onclick event runs the following script:

alert('Howdy!');            //Displays a message box.
__doPostBack('winTest',''); //Generated by Page.GetPostBackEventReference(Me);

The __doPostBack() method posts the page back to the server, where it is intercepted by the IPostBackEventHandler interface' s RaisePostBackEvent method, which in turn raises the Click event from within the control.

Use the following code to handle the Click event from the Web form:

Visual Basic .NET
Private Sub altTest_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles altTest.Click
         Response.Write("Button clicked!")
    End Sub
Visual C#
private void altTest(object sender, System.EventArgs e)
         {
               Response.Write("Button clicked!");
         }
Getting Data from the User

Rendered controls don' t automatically retain the data entered by the user. You can see this if you create a TextBox custom control by rendering an HTML Input element: You can type in the Input element, but as soon as the page is posted back to the server, whatever you typed is gone.

To get data from the user, follow these steps:

  1. Implement the IPostBackDataHandler interface.

  2. Add a name attribute to uniquely identify the HTML element to get data from.

  3. Override the LoadPostBackData method from the IPostBackDataHandler interface. Use this method to get the data from the user.

  4. Override the RaisePostDataChangedEvent method from the IPostBackDataHandler interface. You don' t have to write code for this method, but you must override it because it is part of the interface.

  5. Raise an event to indicate that the data has changed. This step if optional—if you don' t want to provide a change event for the control, you don' t have to.

The following custom control shows how to use these steps to retain data entered by a user in a custom TextBox control rendered from an HTML Input element:

Visual Basic .NET
<DefaultEvent("Change")> Public Class RenderText
        Inherits WebControls.WebControl
        ' (1) Interface that allows you to get PostBack data.
        Implements IPostBackDataHandler

        Event Change(ByVal sender As Object, ByVal e As EventArgs)

        Property Text() As String
            Get
                ' If the property has been set
                If Not IsNothing(ViewState("Text")) Then
                    ' Return the settng.
                    Return ViewState("Text")
                Else
                    ' Otherwise return "".
                    Return ""
                End If
            End Get
            Set(ByVal Value As String)
                ' Store the property setting.
                ViewState("Text") = Value
               ' Call event method.
               OnChange(EventArgs.Empty)
            End Set
        End Property

        Protected Overrides Sub Render(ByVal output As _
          System.Web.UI.HtmlTextWriter)
            ' Set the Input control's attributes.
            output.AddAttribute("value", Me.Text)
            ' (2) You must define this attribute to enable PostBack data.
            output.AddAttribute("name", Me.UniqueID)
            ' Create an Input control element using above.
            output.RenderBeginTag("Input")
            ' Close the Input control element.
            output.RenderEndTag()
            ' Write the element to the HtmlTextWriter.
            output.WriteLine()
        End Sub

        ' (3) Get data from the user.
        ' You must create this method.
        Public Function LoadPostData(ByVal postDataKey As String, _
        ByVal postCollection As _
        System.Collections.Specialized.NameValueCollection) As Boolean _
        Implements System.Web.UI.IPostBackDataHandler.LoadPostData
            ' If the user changes Input value, update the text property.
            If Me.Text <> postCollection(postDataKey) Then
                Me.Text = postCollection(postDataKey)
                ' Returning True invokes RaisePostDataChangedEvent below.
                Return True
            Else
                ' Returning False does not invoke RaisePostDataChangedEvent.
                Return False
            End If
        End Function

        ' (4) Override RaisePostDataChangedEvent method.
        ' You must create this method.
        Public Sub RaisePostDataChangedEvent() Implements _
          System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent
            ' (5) Call the event method. This is optional.
            OnChange(EventArgs.Empty)
        End Sub

        ' Event method.
        Protected Overridable Sub OnChange(ByVal e As EventArgs)
            ' Raise event
            RaiseEvent Change(Me, EventArgs.Empty)
        End Sub
    End Class
Visual C#
// (1) IPostBackDataHandler interface allows you to get PostBack data.
  [DefaultProperty("Text"), DefaultEvent("Change")] public class RenderText :
     System.Web.UI.WebControls.WebControl, IPostBackDataHandler
   {
         // Declare event.
         public event EventHandler Change;

          [DefaultValue("")]
         public string Text
         {
               get
               {
                // If the property has been set
                if (ViewState["Text"] != null)
                    // Return the settng.
                    return ViewState["Text"].ToString();
                else
                    // Otherwise return "".
                    return null;
               }

               set
               {
                     // Store the property setting.
                     ViewState["Text"] = value;
                     // Call event method.
                     OnChange(EventArgs.Empty);
               }
         }

         protected override void Render(HtmlTextWriter output)
         {
            // Set the Input control's attributes.
            output.AddAttribute("value", this.Text);
            // (2) You must define this attribute to enable PostBack data.
            output.AddAttribute("name", this.UniqueID);
            // Create an Input control element using above.
            output.RenderBeginTag("Input");
            // Close the Input control element (inserts />).
            output.RenderEndTag();
         }

         // (3) Get data from the user.
         // You must create this method.
         public bool LoadPostData(string postDataKey,
           System.Collections.Specialized.NameValueCollection postCollection)
         {
            // If the user changes Input value, update the text property.
               if (this.Text != postCollection[postDataKey])
               {
                     this.Text = postCollection[postDataKey];
                     // Returning true invokes RaisePostDataChangedEvent.
                     return true;
               }
            else
                // Returning False does not invoke RaisePostDataChangedEvent.
                return false;
         }

         // (4) Override RaisePostDataChangedEvent method.
         // You must create this method.
         public void RaisePostDataChangedEvent()
         {
            // (5) Call the event method. This is optional.
            OnChange(EventArgs.Empty);
         }

         protected virtual void OnChange(EventArgs e)
         {
               if (Change != null)
                     // Raise event.
                     Change(this, e);
         }
Adding Custom Controls to the ToolBox

After you' ve created and debugged a set of custom controls, you can add them to the Visual Studio .NET Toolbox by following these steps:

  1. Choose Customize Toolbox from the Tools menu. Visual Studio .NET displays the Customize Toolbox dialog box, as shown in Figure 11-15.

    Figure 11-15 Adding items to the Toolbox

  2. Click the .NET Framework Components tab and then click Browse. Visual Studio .NET displays the Open dialog box.

  3. Use the Open dialog box to locate and select the assembly (.dll) containing the custom controls to add to the Toolbox, and then click Open. Visual Studio .NET opens the assembly and adds the contained controls to the list of controls in the Customize Toolbox dialog box.

  4. Click OK to close the Customize Toolbox dialog box.

When you' ve added custom controls to the Toolbox, they appear under the General category when Visual Studio .NET is in Design mode, as shown in Figure 11-16.

Figure 11-16 Custom controls in the Toolbox

To add a custom control to a Web form, simply double-click the control or drag the control from the Toolbox to the Web form. When you add a custom control from the Toolbox, Visual Studio .NET automatically adds the Register directive for the control to the Web form and inserts the HTML for the custom control, as shown in bold by the following HTML:

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm1.aspx.vb" Inherits="vbRenderedTest.WebForm1"%>
<%@ Register TagPrefix="cc1" Namespace="vbRenderedSnippet"
Assembly="vbRenderedSnippet" %><HTML>
         <form id="Form1" method="post" runat="server">
               <cc1:Chart id="Chart1" runat="server"></cc1:Chart>
         </form>
   </body>
</HTML>

The icons that appear for the custom control in the Toolbox and the TagPrefix used in the HTML for the control can be changed using attributes in the custom control' s assembly, as described in the following section.

Setting the Toolbox Icon and TagPrefix for Custom Controls

You can customize your controls to display unique icons in the Toolbox and use their own TagPrefix attributes in HTML. If you have already added the custom controls to your Toolbox, you must unload them and then add them back to the Toolbox before any of these changes take effect.

To add a toolbox icon to a custom control, follow these steps:

  1. Create a 16-by-16 bitmap file containing the icon to display in the Toolbox. Save the file in the /bin directory of the custom control project.

  2. Add the following line of code to the beginning of the control' s class module:

    Visual Basic .NET
    Imports System.Drawing
    Visual C#
    using System.Drawing;

  3. Add the ToolboxBitmap attribute to the control' s class definition. For example, the following line specifies an icon file named Red for the Red custom control:

    Visual Basic .NET
    <ParseChildren(False), ToolboxBitmap("Red")> Public Class Red
    Visual C#
    [ParseChildren(false), ToolboxBitmap("Red")]

  4. Rebuild the control.

To change the TagPrefix used by a custom control in HTML, follow these steps:

  1. Add the following lines to the custom control project' s AssemblyInfo file:

    Visual Basic .NET
    Imports System.Web.UI

<Assembly: TagPrefix("project.namespace", "prefix")>
    Visual C#
    using System.Web.UI

[assembly: TagPrefix("namespace", "prefix")]

  2. Rebuild the project.

In the preceding procedure, project.namespace is the project name and namespace for the custom control and prefix is the prefix to use for the custom controls from that namespace.


Summary

  • Create user controls by drawing server and HTML controls on a user control page (.ascx).

  • Add a user control to a Web form by dragging the control from the Solution Explorer, rather than from the Toolbox.

  • User controls must be part of the project where they are used and are initialized after their containing Web form' s Page_Load event.

  • Create composite custom controls by defining a class derived from the WebControl base class.

  • Composite controls are made up of server and HTML controls added through the CreateChildControls method.

  • To add features to an existing server control, superclass that control by deriving a new custom control from the existing control' s class.

  • Rendered custom controls provide more control over the appearance and behavior of the control, but require more programming to handle the tasks that composite custom controls usually handle through their child controls.

  • Use the Render method and the HtmlTextWriter utility methods to create the appearance of a rendered custom control.

  • Use the IPostBackEventHandler interface to enable a rendered control to trigger post-back events.

  • Use the IPostBackDataHandler interface to enable a rendered control to get data that the user enters in a control at run time.


Lab: Creating Custom Controls

In this lab, you will create a test Web application project to contain the three different controls you create in each of the exercises. When complete, the controls will appear on the Web form, as shown in Figure 11-17. You' ll find this example, named either vbUserControlSample or csUserControlSample, on this book' s companion CD-ROM.

Figure 11-17 The completed test application with user and custom controls
Estimated lesson time: 60 minutes
Exercise 1: Create an AddressBox User Control

In this exercise, you will create a Web application containing a user control that gathers name and address information. Use the skills you learned in Lesson 1 to create and test a user control by completing the following major tasks:

  1. Create a Web application project.

  2. Add a user control (.ascx) that contains text and controls for collecting name, address, city, state, and ZIP code information.

  3. Use validation controls within the user control to ensure required fields are completed.

  4. Use a Grid Layout Panel HTML control to format and position the text and controls within the user control.

  5. Provide a Click event that is fired from the user control once the data entries are validated.

  6. Provide public read-only properties to return address information from the user control.

  7. Provide a public read/write Style property to support absolute positioning in grid layout.

  8. Test the user control by adding it to a Web form and accessing its event and properties from code.

When you are done (or if you get stuck), compare your results to the AddressBox user control included on this book' s companion CD-ROM.

Exercise 2: Create the DigitPad Composite Custom Control

In this exercise, you will create a Web Control Library containing a custom control that allows the user to enter numbers by clicking buttons. Use the skills you learned in Lesson 2 to create and test the custom control by completing the following major tasks:

  1. Add a Web Control Library project to the solution you created in the previous exercise.

  2. Add a reference from the user control project to the Web Control Library project so that you can test the custom control from Web forms within that project.

  3. Create a composite custom control that uses InputButton HTML controls to accept numeric input.

  4. Provide a post-back event that is fired from the custom control once the numeric data is complete.

  5. Provide public read-only property to return the number entered in the control.

  6. Test the user control by adding it to a Web form and accessing its event and properties from code.

When done, compare your results to the DigitPad custom control included on this book' s companion CD-ROM.

Exercise 3: Create the BarChart Rendered Custom Control

In this exercise, you will add a rendered custom control to the Web Control Library you created in Exercise 2. The custom control will create a simple bar chart from numbers entered between the HTML begin and end tags of the custom control. Use the skills you learned in Lesson 3 to create and test the custom control by completing the following major tasks:

  1. Add a new Web Custom Control Class to the Web Control Library project you created in the previous exercise.

  2. Create a Values property that gets an array of data from text entered between the begin and end tags of the custom control.

  3. Create a ChartTitle property that sets or returns a heading displayed at the top of the chart.

  4. Override the control' s Render method to create a bar chart from the data in the Values property. To simplify the charting, use Horizontal Rule HTML controls (<HR>) to create the chart.

  5. Test the user control by adding it to a Web form and accessing its event and properties from code.

Compare your results to the BarChart custom control included on the this book' s companion CD-ROM.


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. Briefly describe the best uses for each of the three types of Web controls by completing the following sentences:

    Create a user control when you want to…

    Create a composite custom control when you want to…

    Create a rendered control when you want to…

  2. How is deriving important to creating custom Web controls?

  3. What is the most important method to override when creating a composite custom control?

  4. What is the most important method to override when creating a rendered control?

  5. How is raising a post-back event in a composite control different from raising a post-back event in a rendered control?


Working with Multimedia

Chapter 12
Working with Multimedia
Lesson 1: Playing Audio
Lesson 2: Displaying Video
Lesson 3: Animating Web Pages
About This Chapter

One thing that makes the Internet interesting is the array of media types that a Web site can present. Text and images can coexist with music, video, and animation. In this chapter, you will learn how to add those media components to ASP.NET Web applications and how to control their display and playback in code.

Before You Begin

To complete this chapter, you must

  • Understand the life cycle of an ASP.NET Web form

  • Know how to edit Web forms in HTML

  • Be familiar with creating client-side scripts on a Web page using JScript or VBScript


Lesson 1: Playing Audio

When redisplaying a Web page that contains a background sound, the browser stops and then restarts the audio playback. For this reason, the ASP.NET technique of using page post-back to intercept client events on the server causes audio on the page to stop and then start over when the server returns a response. Therefore, it is generally best to control audio and other multimedia using client-side scripts rather than server-side code.

In Chapter 7, “Advanced Web Form Programming,” you learned how to add scripts to a Web form to respond to client-side event and control the browser. In this lesson, you will learn how to use HTML elements to include audio on a Web form or an HTML page and how to control audio playback using client-side scripts.

After this lesson, you will be able to

  • Explain the advantages and disadvantages of the different ways to play audio on a Web page

  • Play audio as a background sound and insert background sounds from server-side code

  • Play audio on a Web page using the user' s default media player

  • Use ActiveX objects and plug-ins on a Web page

  • Play audio using Microsoft Windows Media Player and RealNetworks RealPlayer ActiveX objects

  • Include ActiveX objects as embedded plug-ins to support Netscape Navigator

Estimated lesson time: 30 minutes
Ways to Play Audio

There are three basic ways to play audio on an ASP.NET Web form:

  • As a background sound using the bgsound HTML element

  • As an embedded sound using the embed HTML element to insert the user' s default media player on the page

  • As an ActiveX object using the object HTML element to insert a specific media player on a page

The first technique is supported by the widest array of browsers and platforms, but it doesn' t allow you to control the playback as you can with either of the other approaches. The following sections explore these techniques in more detail.

Playing Background Sounds

The bgsound HTML element plays a sound when a Web page is displayed. This element is not provided as an HTML control, so it does not appear in the Microsoft Visual Studio .NET Toolbox.

To add a background sound to a Web form, follow these steps:

  1. In Visual Studio, display the Web form and switch to HTML mode.

  2. Insert a bgsound HTML element. For example:

    <bgsound src="test.wav" id="bgTest" loop="infinite">

One of the first things you notice about background sounds is that they restart any time the page is redisplayed. This isn' t usually much of an issue with regular HTML pages, but ASP.NET Web forms are redisplayed any time a post-back event, such as a Button server control click, occurs.

If you want a sound to play once the first time a Web form is displayed, use the IsPostBack property of the page to determine whether to insert the background sound. For example, the following code plays the background sound only once—the first time the page is displayed:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
    ' Plays theme first time page is displayed
    If Not (IsPostBack) Then
        Response.Write("<bgsound src='test.wav' id='bgsTest' loop='1'>")
    End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
      // Plays theme first time page is displayed.
      if (!IsPostBack)
        Response.Write("<bgsound src='test.wav' id='bgsTest' loop='1'>");
}

You can use a similar technique to play sounds in response to other page events, such as button clicks. Background sounds are additive—if you play more than one at a time, they are all heard simultaneously.

Embedding Sounds

One of the limitations of background sounds is that you can' t control them once they start playing. Background sounds begin when the page is displayed and stop when they reach their end or when a new page is displayed. To allow the user to start, stop, or change the volume of a sound, you need to use the embed HTML element. For example, the following HTML embeds a sound and displays the user' s default media player plug-in on the Web form:

<embed src="test.wav" id="wavTest" hidden="false" height="20" width="150"
autostart="false" type="audio/wav" loop="true"></embed>

The plug-in displayed by the preceding HTML can vary, depending on which audio player plug-in the user has installed. Figure 12-1 shows the QuickTime media plug-in, which allows the user to start, stop, skip, and change volume of the sound.

Figure 12-1 QuickTime plug-in for audio

Embedding a sound lets the user control the playback, but you have very little control from client-side code. You can hide or display the plug-in, set it to repeat, change its size and position, but that' s about it. For example, the following HTML buttons hide and display the plug-in:

<input type="button" value="Hide" id="butHide" onclick="wavTest.hidden='true'">
<input type="button" value="Show" id="butShow" onclick="wavTest.hidden='false'">

To start, stop, pause, or mute the sound from client-side scripts, you need to include the sound as an object on the Web form. The following section describes how to do this.

Sounds as ActiveX Objects

You can include sounds as ActiveX objects on a Web form by inserting an HTML object element with the appropriate class ID (clsid) information for the specific player you want to use. Inserting sounds as objects allows you to control the playback from client-side scripts using the element as an object.

To include a sound as an object, follow these steps:

  1. Add a media player ActiveX object to the Web form.

  2. Set the properties of the ActiveX object to play the sound.

  3. Add code to determine if the user' s system has the ActiveX object installed and take appropriate action if it does not.

The following sections describe these steps in more detail.

Adding an ActiveX Object to a Web Form

A number of ActiveX objects can play media such as sounds from within the browser. Two of the most widely distributed are Windows Media Player and RealPlayer. You can add either of these players to a Web form by following these steps:

  1. With a Web form displayed in Design mode, click the HTML tab on the Visual Studio Toolbox, right-click the Toolbox, and then select Customize Toolbox from the pop-up menu.

  2. Click the COM Components tab and scroll through the list of installed components. Find the components to add to the Toolbox and select them. For example, the RealPlayer object is named RealPlayer G2 Control; the Microsoft player object is named Windows Media Player.

  3. Click OK to close the dialog box. Visual Studio loads the controls in the Toolbox, as shown in Figure 12-2.

    Figure 12-2 ActiveX objects in the Toolbox

  4. Drag the media player object from the Toolbox to the Web form. Visual Studio inserts an object HTML element on the Web form with the player' s class id and default parameter settings, as shown by the following HTML:

    <p>Here's the sound as a Windows Media Player object:</p>
<OBJECT id="objWMPlay" classid="clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95" VIEWASTEXT>
   <PARAM NAME="AudioStream" VALUE="-1">
   <PARAM NAME="AutoSize" VALUE="-1">
   <PARAM NAME="AutoStart" VALUE="0">
   <PARAM NAME="AnimationAtStart" VALUE="-1">
   <PARAM NAME="AllowScan" VALUE="-1">
   <PARAM NAME="AllowChangeDisplaySize" VALUE="-1">
   <PARAM NAME="AutoRewind" VALUE="0">
   <PARAM NAME="Balance" VALUE="0">
   <PARAM NAME="BaseURL" VALUE="">
   <PARAM NAME="BufferingTime" VALUE="5">
   <PARAM NAME="CaptioningID" VALUE="">
   <PARAM NAME="ClickToPlay" VALUE="-1">
   <PARAM NAME="CursorType" VALUE="0">
   <PARAM NAME="CurrentPosition" VALUE="-1">
   <PARAM NAME="CurrentMarker" VALUE="0">
   <PARAM NAME="DefaultFrame" VALUE="">
   <PARAM NAME="DisplayBackColor" VALUE="0">
   <PARAM NAME="DisplayForeColor" VALUE="16777215">
   <PARAM NAME="DisplayMode" VALUE="0">
   <PARAM NAME="DisplaySize" VALUE="0">
   <PARAM NAME="Enabled" VALUE="-1">
   <PARAM NAME="EnableContextMenu" VALUE="-1">
   <PARAM NAME="EnablePositionControls" VALUE="-1">
   <PARAM NAME="EnableFullScreenControls" VALUE="0">
   <PARAM NAME="EnableTracker" VALUE="-1">
   <PARAM NAME="Filename" VALUE="test.wav">
   <PARAM NAME="InvokeURLs" VALUE="-1">
   <PARAM NAME="Language" VALUE="-1">
   <PARAM NAME="Mute" VALUE="0">
   <PARAM NAME="PlayCount" VALUE="1">
   <PARAM NAME="PreviewMode" VALUE="0">
   <PARAM NAME="Rate" VALUE="1">
   <PARAM NAME="SAMILang" VALUE="">
   <PARAM NAME="SAMIStyle" VALUE="">
   <PARAM NAME="SAMIFileName" VALUE="">
   <PARAM NAME="SelectionStart" VALUE="-1">
   <PARAM NAME="SelectionEnd" VALUE="-1">
   <PARAM NAME="SendOpenStateChangeEvents" VALUE="-1">
   <PARAM NAME="SendWarningEvents" VALUE="-1">
   <PARAM NAME="SendErrorEvents" VALUE="-1">
   <PARAM NAME="SendKeyboardEvents" VALUE="0">
   <PARAM NAME="SendMouseClickEvents" VALUE="0">
   <PARAM NAME="SendMouseMoveEvents" VALUE="0">
   <PARAM NAME="SendPlayStateChangeEvents" VALUE="-1">
   <PARAM NAME="ShowCaptioning" VALUE="0">
   <PARAM NAME="ShowControls" VALUE="-1">
   <PARAM NAME="ShowAudioControls" VALUE="-1">
   <PARAM NAME="ShowDisplay" VALUE="0">
   <PARAM NAME="ShowGotoBar" VALUE="0">
   <PARAM NAME="ShowPositionControls" VALUE="-1">
   <PARAM NAME="ShowStatusBar" VALUE="0">
   <PARAM NAME="ShowTracker" VALUE="-1">
   <PARAM NAME="TransparentAtStart" VALUE="0">
   <PARAM NAME="VideoBorderWidth" VALUE="0">
   <PARAM NAME="VideoBorderColor" VALUE="0">
   <PARAM NAME="VideoBorder3D" VALUE="0">
   <PARAM NAME="Volume" VALUE="0">
   <PARAM NAME="WindowlessVideo" VALUE="0">
</OBJECT>

<p>Here's the sound as a RealPlayer object:</p>
<OBJECT id="objRPlay" classid="clsid:CFCDAA03-8BE4-11CF-B84B-0020AFBBCCFA" VIEWASTEXT>
   <PARAM NAME="_ExtentX" VALUE="7938">
   <PARAM NAME="_ExtentY" VALUE="2646">
   <PARAM NAME="AUTOSTART" VALUE="0">
   <PARAM NAME="SHUFFLE" VALUE="0">
   <PARAM NAME="PREFETCH" VALUE="0">
   <PARAM NAME="NOLABELS" VALUE="0">
   <PARAM NAME="LOOP" VALUE="0">
   <PARAM NAME="NUMLOOP" VALUE="0">
   <PARAM NAME="CENTER" VALUE="0">
   <PARAM NAME="MAINTAINASPECT" VALUE="0">
   <PARAM NAME="BACKGROUNDCOLOR" VALUE="#000000">
</OBJECT>

As you can see from the preceding HTML, the different players have different sets of parameters that control them. From a programming perspective, it is much simpler to choose a particular player and require the user to install that player than it is to support multiple types of players. Many users have more than one media player installed, so choosing one player over another might not pose much of a compatibility problem.

Setting the Player Object' s Properties and Methods

The param elements included in the ActiveX object' s HTML represent the object' s properties. You use these properties along with the object' s methods and events to control the player from within client-side scripts.

It can be very hard to get good reference information about an ActiveX object. One of the easiest ways to learn more about an object' s properties, methods, and events is to add a reference from the current project to that object and then use the Object Browser to view that object' s members, as shown in Figure 12-3.

Figure 12-3 Viewing the Media Player properties, methods, and events

To use an ActiveX object' s properties or methods from within a script, use the HTML element' s methods. For example, the following buttons' OnClick scripts start and stop the playback for Windows Media Player:

<input type="button" value="Start" id="butStart" onclick="objWMPlay.play()">
<input type="button" value="Stop" id="butStop" onclick="objWMPlay.stop()">

The RealPlayer object uses slightly different method names for starting and stopping playback, as shown here:

<input type="button" value="Start" id="butStartPlay" onclick="objRPlay.object.source='test.wav';objRPlay.doplay()">
<input type="button" value="Stop" id="butStopPlay" onclick="objRPlay.dostop()">

To respond to object events on the client, create a script event handler. The following event-handling script displays a message box when the sound finishes playing in Windows Media Player:

JScript
<SCRIPT FOR="objWMPlay" EVENT="EndOfStream(lResult)" LANGUAGE="Jscript">
      alert("End of clip");
</SCRIPT>
VBScript
<script language="vbscript">
      Sub objWMPlay_EndOfStream(lResult)
            alert("End of clip.")
      End Sub
</script>

Again, the RealPlayer object has slightly different names for the same event, as shown by the following code:

JScript
<SCRIPT FOR="objRPlay" EVENT="OnClipClosed()" LANGUAGE="Jscript">
      alert("End of clip.");
</SCRIPT>
VBScript
<script language="vbscript">
      Sub objRPlay_OnClipClosed()
            alert("End of clip.")
      End Sub
</script>

You can use an ActiveX object' s properties, methods, and events from server-side code as well. However, doing so reloads the Web page and therefore restarts the sound, which is not usually what you want to do.

Installing ActiveX Objects on the Client

Use the codebase attribute in the object element to specify a location from which the user can install the ActiveX object if it does not currently exist on his or her system. The codebase attribute takes two arguments: the first is the location to download the control from, and the second is the version number of the control. For example, the following ActiveX object element downloads Windows Media Player if the user does not have version 6.4 installed:

<OBJECT ID="objWMPlayer" classid="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95"
CODEBASE="http://activex.microsoft.com/activex/controls/mplayer/en/
 nsmp2inf.cab#Version=6,4,5,715" VIEWASTEXT>
   <PARAM NAME="AutoStart" VALUE="True">
   <PARAM NAME="FileName" VALUE="test.avi">
</OBJECT>

When the user displays a page with the preceding object element, the browser checks if the indicated version of the object exists on the user' s machine; if it does not, the browser displays the dialog box shown in Figure 12-4 before downloading and installing the object.

Figure 12-4 Installing the object

When the object is installed, the page is displayed and the sound plays.

Handling ActiveX Objects Within Netscape Navigator

Netscape Navigator does not support ActiveX objects or the object HTML element. To include sounds as objects within Navigator, you need to include them as embedded plug-ins.

Navigator ignores the object element, so you can include the embed element within the object element to support both Microsoft Internet Explorer and Netscape Navigator on the same page. The following HTML displays Windows Media Player on either browser:

<OBJECT ID="objWMPlayer" classid="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95"
CODEBASE="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf
.cab#Version=6,4,5,715" type="application/x-oleobject" VIEWASTEXT>
   <PARAM NAME="AutoStart" VALUE="False">
   <PARAM NAME="FileName" VALUE="test.wav">
   <EMBED type="application/x-mplayer2"
        pluginspage="http://www.microsoft.com/Windows/MediaPlayer/"
        SRC="test.wav"
        name="objWMPlayer"
        autostart="0"
        showcontrols="1"
   </EMBED>
</OBJECT>

If the Media Player plug-in is not installed in Netscape Navigator, the preceding code will direct users to the download page from which they can install the plug-in. To ensure that users don' t have to restart their browsers after the plug-in is installed, include the following script after the object on the page:

<!-- Must be written in JavaScript, since Navigator doesn't support VBScript -->
<script language="JavaScript">
   // If the browser is Netscape Navigator.
   if ( navigator.appName == "Netscape" )
   {
      // Refresh the plugins that were just installed.
      navigator.plugins.refresh();
   }
</script>

Netscape plug-ins support some control through scripting, but it is not consistent with ActiveX scripting. Specifically:

  • Properties are accessed through Set and Get methods rather than through assignment.

  • Plug-in events are intercepted through a Java applet proxy.

These programming issues, along with the issues of the browser' s stability and the difficulty of debugging different code for different platforms, limit the practicality of scripting ActiveX objects as plug-ins within Netscape Navigator.

If your application uses ActiveX objects and needs to support Netscape Navigator, it is more practical to embed the object as a plug-in, as shown in this section, and disable attempts to control the object in scripts. For example, the following HTML checks if the user' s browser is Navigator; if it is, the code disables the Input buttons that control Windows Media Player and instead displays the full Media Player interface:

<html>
   <head>
      <title>ActiveX Objects Cross Browser</title>
   </head>
   <body>
      <h2>Using ActiveX Objects Across Different Browsers</h2>
      <OBJECT ID="objWMPlayer"
         classid="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95"
        CODEBASE="http://activex.microsoft.com/activex/controls/mplayer/en/ns
         mp2inf.cab#Version=6,4,5,715" type="application/x-oleobject"
         VIEWASTEXT>
            <PARAM NAME="AutoStart" VALUE="False">
            <PARAM NAME="FileName" VALUE="test.wav">
            <!-- Hide Media Player controls if IE -->
            <param name="ShowControls" value="False">
            <EMBED type="application/x-mplayer2"
               pluginspage="http://www.microsoft.com/Windows/MediaPlayer/"
               SRC="test.wav"
               name="objWMPlayer"
               autostart="0"
               showcontrols="1"> <!-- Show Media Player controls if
                                    Navigator -->
            </EMBED>
      </OBJECT>
      <!-- Input buttons control the Media Player to create custom
         interface in IE -->
      <div id="pnlControls">
      <!-- Must be written in Java, since Navigator doesn't support
         VBScript -->
      <script language="JavaScript">
            // If the browser is Netscape Navigator.
            if ( navigator.appName == "Netscape" )
            {
               // Refresh the plugins that were just installed.
               navigator.plugins.refresh();
               // End the page here (doesn't Input display buttons).
               alert("You are using Netscape Navigator. Some ActiveX
                    object features have been disabled.");
               document.all["pnlControls"].setAttribute("disabled","1");
            }
      </script>
      <br>
      <p>Use these buttons to control the Media Player object.</p>
      <input type="button" value="Start" id="butStart"
            onclick="objWMPlayer.Play();" >
      <input type="button" value="Stop" id="butStop"
            onclick="objWMPlayer.stop()" >
      <input type="button" value="Repeat" id="butRepeat"
            onclick="objWMPlayer.PlayCount='2'" >
      <input type="button" value="Mute" id="butMute"
            onclick="objWMPlayer.Mute=!(objWMPlayer.Mute)" >
      </div>
   </body>
</html>


Lesson 2: Displaying Video

Many of the techniques used to control video are the same as or similar to those used for controlling audio that you saw in the preceding lesson. As with audio, redisplaying a Web page containing video restarts the video. Therefore, it is generally best to control video and respond to video events using client-side scripts rather than server-side code.

In this lesson, you will learn how to include video on a Web form or HTML page using HTML elements and ActiveX objects. You will also learn how to control video playback using client-side scripting in JScript and VBScript.

After this lesson, you will be able to

  • Explain the advantages and disadvantages of the different ways to play video on a Web page

  • Display video on a Web page as an HTML img element

  • Use the client' s default media player to display video

  • Display video using the Windows Media Player ActiveX object

  • Control video playback using Windows Media Player and client-side scripts

  • Use different versions of Windows Media Player

Estimated lesson time: 20 minutes
Ways to Display Video

There are three basic ways to display video on a Web form:

  • As an img HTML element using the dynsrc attribute

  • As an embedded video using the embed HTML element to insert the user' s default media player on the page

  • As an ActiveX object using the object HTML element to insert a specific media player on a page

As with audio, each of these approaches has advantages and disadvantages. The following sections explore these techniques in more detail.

Using the img Element

The easiest way to display a video on a Web form is to include the video as an HTML img element. The dynsrc attribute of the img element allows you to specify a video file to display in the element, as shown here:

<img dynsrc="clock.avi">

The Image and ImageButton server controls and the Image and Image Button HTML controls all support the dynsrc attribute. For example, the following HTML displays a video in an ImageButton control:

<asp:ImageButton id="ImageButton1" dynsrc="clock.avi"
runat="server"></asp:ImageButton>

However, the dynsrc attribute has the following limitations:

  • It is not supported by Netscape Navigator.

  • It accepts only AVI (Audio Video Interleaved) format files.

  • It cannot be controlled from client-side scripts the way that videos included as ActiveX objects can.

Embedding Videos

Use the embed HTML element to display a video on a Web form with the user' s default media player. For example, the following HTML embeds a video on a page:

<embed src="clock.avi" id="aviTest" hidden="false" height="200" width="200"
autostart="false" type="video/avi" loop="true"></embed>

Embedded videos are displayed using the plug-in that is registered for the media type. For example, Figure 12-5 shows the preceding HTML displayed using Windows Media Player as a plug-in.

Figure 12-5 Video as an embedded plug-in

Using the embed HTML element to display video has the following advantages:

  • It is supported by most browsers, including Internet Explorer and Netscape Navigator.

  • The user can control the playback using the controls of the embedded media player.

  • Multiple video formats can be displayed.

The disadvantage of using the embed HTML element is that it doesn' t provide the level of scripting support that you can get from an ActiveX object.

Videos as ActiveX Objects

As with sounds, you can include videos as ActiveX objects on a Web form by inserting an HTML object element with the appropriate class ID (clsid) information for the specific player you want to use. Inserting videos as objects allows you to control the playback from client-side scripts using the element as an object.

You use the same procedures to display a video as an ActiveX object that you used to play a sound as an ActiveX object. The only difference is that you set the object' s FileName parameter to a video file rather than an audio file. For example, the following HTML displays a video as an ActiveX Windows Media Player object:

<OBJECT id="objWMPlay"
classid="clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95"
CODEBASE="http://activex.microsoft.com/activex/controls/mplayer/en/ns
mp2inf.cab#Version=6,4,5,715"
type="application/x-oleobject" VIEWASTEXT>
   <PARAM NAME="AutoStart" VALUE="0">
   <PARAM NAME="EnableTracker" VALUE="-1">
   <PARAM NAME="Filename" VALUE="clock.avi">
   <PARAM NAME="Mute" VALUE="0">
   <PARAM NAME="ShowControls" VALUE="-1">
   <PARAM NAME="ShowAudioControls" VALUE="-1">
   <PARAM NAME="ShowDisplay" VALUE="0">
   <PARAM NAME="ShowGotoBar" VALUE="0">
   <PARAM NAME="ShowPositionControls" VALUE="-1">
   <PARAM NAME="ShowStatusBar" VALUE="0">
   <PARAM NAME="ShowTracker" VALUE="-1">
   <PARAM NAME="VideoBorderWidth" VALUE="0">
   <PARAM NAME="VideoBorderColor" VALUE="0">
   <PARAM NAME="VideoBorder3D" VALUE="0">
   <PARAM NAME="Volume" VALUE="0">
   <PARAM NAME="WindowlessVideo" VALUE="0">
</OBJECT>

You can control the Windows Media Player ActiveX object' s video playback using the same properties and methods that you used to control its audio playback in the preceding lesson. For example, the following buttons allow you to start, stop, pause, and mute the video:

<input onclick="objWMPlay.object.play()" type="button" value="Start">
<input onclick="objWMPlay.stop()" type="button" value="Stop">
<input onclick="objWMPlay.PlayCount='2'" type="button" value="Repeat">
<input onclick="objWMPlay.mute=!objWMPlay.mute" type="button" value="Mute">
<input onclick="objWMPlay.showcontrols='false'" type="button"
  value="Hide Controls">

At run time, the Windows Media Player ActiveX object appears as shown in Figure 12-6.

Figure 12-6 Windows Media Player ActiveX object displaying video

To respond to Media Player events, create client-side event-handler scripts as you did for audio playback. For example, the following client-side script displays a message box when the user pauses the video:

JScript
<SCRIPT language="Jscript" event="PlayStateChange(iState)" for="objWMPlay">
   if (iState==2)
         alert("Video paused.");
</SCRIPT>
VBScript
<script language="vbscript">
Sub objWMPlay_PlayStateChange(iState)
   if iState=2 then alert("Video paused.")
End Sub
</script>
ActiveX Objects Within Netscape Navigator

As mentioned in the previous lesson, Netscape Navigator does not support ActiveX objects or the object HTML element. To include videos within Navigator, you need to include them as embedded plug-ins. To do this, use the same procedure shown in the preceding lesson for handling ActiveX sounds within Netscape Navigator.

Versions of Windows Media Player

So far, the audio and video examples for Windows Media Player have used version 6.4 of the player, even though the current Windows Media Player is version 8.0. There are a couple of reasons for this:

  • Version 6.4 has an equivalent plug-in to support Netscape Navigator.

  • Version 7.0 and later cannot be installed on earlier Microsoft operating systems, such as Microsoft Windows 95 or Microsoft Windows NT 4.

If compatibility with the earlier operating systems is not an issue, you will probably want to use the current version. To include the current Windows Media Player on a Web form, add the following object HTML element:

<OBJECT id="Player" height="240" width="320"
classid="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6"
VIEWASTEXT>
   <PARAM NAME="URL" VALUE="clouds.avi">
   <PARAM NAME="rate" VALUE="1">
   <PARAM NAME="balance" VALUE="0">
   <PARAM NAME="currentPosition" VALUE="0">
   <PARAM NAME="defaultFrame" VALUE="">
   <PARAM NAME="playCount" VALUE="1">
   <PARAM NAME="autoStart" VALUE="-1">
   <PARAM NAME="currentMarker" VALUE="0">
   <PARAM NAME="invokeURLs" VALUE="-1">
   <PARAM NAME="baseURL" VALUE="">
   <PARAM NAME="volume" VALUE="50">
   <PARAM NAME="mute" VALUE="0">
   <PARAM NAME="uiMode" VALUE="full">
   <PARAM NAME="stretchToFit" VALUE="0">
   <PARAM NAME="windowlessVideo" VALUE="0">
   <PARAM NAME="enabled" VALUE="-1">
   <PARAM NAME="enableContextMenu" VALUE="-1">
   <PARAM NAME="fullScreen" VALUE="0">
   <PARAM NAME="SAMIStyle" VALUE="">
   <PARAM NAME="SAMILang" VALUE="">
   <PARAM NAME="SAMIFilename" VALUE="">
   <PARAM NAME="captioningID" VALUE="">
</OBJECT>

Media Player version 7 and later have a different class ID and object model than version 6.4. For example, instead of using the Filename property to specify a video, you use the URL property.

The later version also does not have a .cab file from which you can install using the codebase attribute. Instead, you need to check whether the media player is installed and direct users to the location from which they can install the player if it is not installed. For example, the following code checks for Media Player version 7 or later and directs the user to the Microsoft download site if it is not installed:

JScript
<script language="JavaScript">
// Use exception handling to test if Player could be created.
try
{
     if(document.objWMPlayer.object.enabled)
          ; // If it works, don't do anything.
}
catch(ex)
{
      // If error, then prompt user to get Media Player.
      document.writeln("<p>You don't have the current version of Media
      Player.");
      document.writeln(
      "<a href='http://www.microsoft.com/windows/windowsmedia/download/'>
      Click here</a> to install it.</p>");
}
</script>
VBScript
<script language="vbscript">
' Use exception handling to test if Player could be created.
On Error Resume Next
' If it works, don't do anything.
If Document.objWMPlayer.enabled then x=0
' If error, then prompt user to get Media Player.
If Err Then
   document.writeln("<p>You don't have the current version of Media Player.")
   document.writeln( _
   "<a href='http://www.microsoft.com/windows/windowsmedia/download/'>" & _
   "Click here</a> to install it.</p>")
End If
On Error Goto 0
</script>

Windows Media Player version 7 has extensive reference information available in the Visual Studio online Help, starting at the Help topic titled “Windows Media Player 7.1 SDK.”


Lesson 3: Animating Web Pages

Web pages often use moving text and images to attract the user' s eye to advertisements, to make a point more clearly, or just to have fun. You can use a broad array of techniques to create animated content for the Web. This lesson introduces you to the core techniques, compares their advantages and disadvantages, and shows you how to use them.

After this lesson, you will be able to

  • Explain the advantages and disadvantages of different animation techniques

  • Use the HTML marquee element to animate text

  • Create and use animated GIF files

  • Animate elements on a Web page using client-side scripts

  • Synchronize timed events on a Web page

  • Change an HTML element' s attributes, color, or location as a timed event

  • Play audio, video, or other media as a timed event

Estimated lesson time: 35 minutes
Ways to Display Animations

There are many different ways to animate elements on a Web form. Table 12-1 compares the advantages and disadvantages of the most common techniques.

Table 12-1 Animation Techniques

Technique

Advantages

Disadvantages

  • Use the marquee HTML element to display moving text and graphics

  • Uses simple HTML tags and attributes

  • Requires no scripting code

  • Available in Internet Explorer only

  • Limited number of effects

  • Create animated GIF files

  • Can be used in any HTML element that accepts a graphic

  • Requires no code

  • Files become large as size and complexity increase

  • Entire file must download before display

  • Can' t control playback

Embed ActiveX objects or plug-ins on a page

  • Can create complex animated presentations

  • Animations can start before download is complete (support for streaming)

  • Widely available third-party tools for creation and display

  • Can interact with the user

  • ActiveX objects and some plug-ins can be controlled by client-side scripts

  • Requires users to have the ActiveX object or plug-in installed

  • Content creator might have to purchase a license and learn how to use the tool

Write client-side scripts to control HTML elements on the page

  • You can control any element on the page

  • If you write in JScript, your code is supported by most browsers

  • There are a limited number of effects you can generate in code alone

  • Code can become very complex and difficult to maintain

  • Synchronizing effects is complex and difficult

Use HTML+TIME to control elements on a page

  • Uses a standard XML-based language

  • You can control any element on a page using time-based animation

  • Can be combined with client-side scripts to add interactivity

  • Not supported in Netscape Navigator

  • Can' t view effects using Visual Studio Design view—you must edit in HTML

The following sections explore each of these techniques in more detail.

Animating Text

The marquee HTML element animates any contained items by scrolling them up, down, right, or left. The marquee element is commonly used to display attention-getting banners. For example, the following HTML displays the text “Notice Me” in contrasting colors bouncing right to left on the screen:

<marquee bgcolor="red" scrollamount="10" behavior="alternate"
direction="right" loop="true">
   <h1 style="COLOR:Yellow">Notice Me</h1>
</marquee>

You can also use the marquee element to reveal text by scrolling the text past a view window. The following HTML scrolls a list of credits; the credits start at the bottom of the marquee, scroll up, and disappear when they reach the top:

<marquee scrollamount="1" behavior="scroll" direction="up" loop="false">
<h2>Credits</h2>
<p>Thanks to the following participants:</p>
<p>Denise Smith - trusted friend and advisor.</p>
<p>Alan Steiner - original party animal.</p>
<p>Will Shakespeare - lead grammarian.</p>
<p>Gary Yukish - thanks for the spell check. </p>
<p>Karin Zimprich - who invented the four-point seat belt.</p>
</marquee>

Developers can nest marquee elements to create various visual effects. For example, the following HTML displays a ball that bounces around a frame:

<marquee scrollamount="10" behavior="alternate" direction="up" loop="true">
   <marquee scrollamount="5" behavior="alternate" direction="right"
   loop="true">
      <img src="circles.gif">
   </marquee>
</marquee>

The marquee element includes an onbounce event that you can use to create further effects. For example, the following marquee elements display a bouncing ball that slowly stops bouncing and rolls to a stop:

<marquee id="mrqVert" scrollamount="10" behavior="alternate" direction="down"
loop="true" height="400" onbounce="OnBounce()">
   <marquee id="mrqHoriz" scrollamount="5" behavior="alternate"
   direction="right" loop="true">
      <img src="circles.gif">
   </marquee>
</marquee>
VBScript
<script language=vbscript>
bOdd=0
Sub OnBounce()
   ' Get height.
   x = document.all("mrqVert").height
   ' Switch bounce flag.
   bOdd = Not bOdd
   if x > 100 then
         ' If odd (bottom) bounce.
         if bOdd then
               ' Reduce height each bounce.
               document.all("mrqVert").height = x - 20
         end if
   else
         ' Stop movement.
         document.all("mrqVert").scrollamount=0
         document.all("mrqHoriz").scrollamount=0
   end if
End Sub
</script>
JScript
<script language=JavaScript>
var bOdd=0;
function OnBounce()
{
   // Get height.
   x = document.all["mrqVert"].height;
   // Switch bounce flag.
   bOdd = !bOdd;
   if (x > 100)
         // If odd (bottom) bounce.
         if (bOdd)
               // Reduce height each bounce.
               document.all["mrqVert"].height = x - 20;
   else
   {
         // Stop movement.
         document.all["mrqVert"].scrollamount=0;
         document.all["mrqHoriz"].scrollamount=0;
   }
}
</script>
Animating Simple Graphics

The simplest form of animation for the Web is an animated GIF (Graphic Interchange Format) file. GIF files can contain multiple images that you can combine using a tool such as Microsoft PhotoDraw to create a single, moving image, as shown in Figure 12-7.

Figure 12-7 Animated GIF in Microsoft PhotoDraw

Using Microsoft PhotoDraw or another tool, you can add images as frames within the animation, set how long each frame is displayed, and set the other attributes of the animation. After you' ve created the GIF file, you can use it within any HTML element that accepts a graphic, including the Image button server control.

Animated GIF files can also be combined with other animation techniques, such as the marquee element. For example, the following HTML displays an animated paper airplane that flies around the browser screen:

<marquee behavior="scroll" direction="down" height="800" width="800">
   <marquee behavior="alternate" direction="right">
      <img src="airplane.gif">
   </marquee>
</marquee>
Displaying Animations as Objects or Plug-Ins

There are a number of ActiveX objects and browser plug-ins that you can use to create animated presentations over the Web. Two of the most widely used are Macromedia ShockWave and Windows Media Player.

These ActiveX objects and plug-ins use different techniques to present animations, and each has documentation that discusses how to use them to create presentations. See Lesson 1 for information about how to add ActiveX objects and plug-ins to a Web page.

Animating with Client-Side Scripts

Because client-side scripts can control any element on a Web form, you can use them to animate those elements by hiding and showing them or by moving them around. For example, the following HTML and client-side script creates an animated image that chases the cursor around the browser window:

<html>
<body MS_POSITIONING="GridLayout" onmousemove="OnMove()">
   <img id="imgPlane" src="circles.gif" height="20" width="20"
   style="Z-INDEX: 101; LEFT: 1px; POSITION: absolute; TOP: 1px">
VBScript
<script language="vbscript">
' Global variables.
iCount = 0
x = 0
y = 0
xImg = 0
yImg = 0
Sub OnMove()
         if iCount > 0 then exit sub
         ' Get X and Y mouse coordinates.
         xMouse = window.event.clientx
         yMouse = window.event.clienty
         ' Get X and Y image coordinates.
         xImg = document.all("imgPlane").style.getAttribute("LEFT",0)
         yImg = document.all("imgPlane").style.getAttribute("TOP",0)
         ' Convert the coords from a string to a number.
         xImg = cint(left(xImg,len(xImg) -2))
         yImg = cint(left(yImg,len(yImg) -2))
         ' Close the distance between the two sets of coords.
         x = (xMouse - xImg) / 4
         y = (yMouse - yImg) / 4
         ' Launch every .1 seconds.
         window.setInterval "MoveImg", 100, "VBScript"
         window.setInterval "MoveImg", 200, "VBScript"
         window.setInterval "MoveImg", 300, "VBScript"
         window.setInterval "MoveImg", 400, "VBScript"
         window.setInterval "MoveImg", 500, "VBScript"
End Sub

Sub MoveImg()
         if iCount < 5 then iCount = iCount + 1 else iCount = 0
         ' Move the IMG elment to follow mouse.
         document.all("imgPlane").style.setAttribute "LEFT", xImg + x, 0
         document.all("imgPlane").style.setAttribute "TOP", yImg + y, 0

End Sub
</script>
JScript
<script language="JScript">
// Global variables.
var iCount = 0;
var x = 0;
var y = 0;
var xImg = 0;
var yImg = 0;

function OnMove()
{
         if (iCount > 0 )
           return;
         // Get X and Y mouse coordinates.
         xMouse = parseInt(window.event.clientX);
         yMouse = parseInt(window.event.clientY);
         // Get X and Y image coordinates.
         xImg = document.all["imgPlane"].style.getAttribute("LEFT",0);
         yImg = document.all["imgPlane"].style.getAttribute("TOP",0);
         // Convert the coords from a string to a number.
         xImg = parseInt(xImg);
         yImg = parseInt(yImg);
         // Close the distance between the two sets of coords.
         x = (xMouse - xImg) / 4;
         y = (yMouse - yImg) / 4;
         // Launch every .1 seconds.
         window.setInterval ("MoveImg()", 100, "JavaScript") ;
         window.setInterval ("MoveImg()", 200, "JavaScript") ;
         window.setInterval ("MoveImg()", 300, "JavaScript") ;
         window.setInterval ("MoveImg()", 400, "JavaScript") ;
         window.setInterval ("MoveImg()", 500, "JavaScript") ;
}

function MoveImg()
{
         if (iCount < 4)
           iCount ++;
         else
           iCount = 0;
         // Move the IMG elment to follow mouse.
         document.all["imgPlane"].style.setAttribute ("LEFT", xImg + x, 0);
         document.all["imgPlane"].style.setAttribute ("TOP", yImg + y, 0);
}
</script>

The preceding code shows both the power and the difficulty of using client-side scripts to animate HTML elements: You can control any element any way you want to, but it is very difficult to synchronize those animations.

The sample above uses the setInterval method to coordinate the animation, causing the image to move a quarter of the distance every tenth of a second. In practice, the effect is interesting, but not very smooth. To truly solve this problem, you need to be able to coordinate events using HTML itself. The following section explains how to do this using an extension to the HTML standard.

Animating with HTML+TIME

So far, our discussion of audio, video, and animation has lacked one important element: time. The original HTML standards didn' t foresee the need to synchronize events on the page, but synchronization is the key to creating and presenting time-based media such as audio, video, and animation.

To solve this problem, the World Wide Web Consortium (W3C) has produced the Synchronized Multimedia Integration Language (SMIL, pronounced “smile”) standard. Internet Explorer implements this standard under the name HTML+TIME.

HTML+TIME lets you coordinate timed events on a page to create multimedia presentations. Events can occur with sequential, parallel, or exclusive timelines. HTML+TIME requires Internet Explorer 5.5 or later to run. The following sections describe how to use HTML+TIME on a Web form or HTML page.

Using HTML+TIME

To use HTML+TIME on a Web form or HTML page, follow these steps:

  1. Add an XML namespace (XMLNS) attribute to the page' s HTML element. The following HTML declares the HTML+TIME namespace for a page:

    <HTML XMLNS:t ="urn:schemas-microsoft-com:time">

  2. Add a style to define the time behavior for HTML elements on the page. The following style definition set the default behavior (show/hide) for the time class:

    <style>
   .time    {behavior: url(#default#time2);}
</style>

  3. Add an IMPORT directive to import the previously declared XML namespace into the page. The following HTML imports the HTML+TIME namespace:

    <?IMPORT namespace="t" implementation="#default#time2">

  4. Create a timeline for the elements you want to control using the HTML+TIME elements. For example, the following HTML establishes a sequential timeline for the elements that follow:

    <t:SEQ>

  5. Set the time attributes for the HTML elements you want to control. For example, the following HTML displays a line of text for two seconds:

    <DIV CLASS="time" DUR="2">These words are presented</DIV>

The following HTML puts all the preceding steps together to display a line of text that changes every two seconds until the last line is displayed. The HTML+TIME elements are shown in bold to highlight the changes you must make to an ordinary Web form or HTML page.

<html XMLNS:t ="urn:schemas-microsoft-com:time">
   <head>
     <title>HTMLTIME</title>
     <meta name="GENERATOR" content="Microsoft Visual Studio.NET 7.0">
     <meta name="CODE_LANGUAGE" content="Visual Basic 7.0">
     <meta name="vs_defaultClientScript" content="JavaScript">
     <meta name="vs_targetSchema"
     content="http://schemas.microsoft.com/intellisense/ie5">
     <style>
         .time    {behavior: url(#default#time2);}
     </style>
   </head>
   <body MS_POSITIONING="GridLayout">
     <form id="Form1" method="post" runat="server">
       <?IMPORT namespace="t" implementation="#default#time2">
       <t:SEQ>
         <DIV CLASS="time" DUR="2">These words are presented</DIV>
         <DIV CLASS="time" DUR="2">sequentially, one line at a time</DIV>
         <DIV CLASS="time" DUR="2">to make their point more clearly.</DIV>
         <DIV CLASS="time">Can you remember what I just said?</DIV>
       </t:SEQ>
     </form>
   </body>
</html>

The Visual Studio Design window does not support the IMPORT directive. You can only view HTML+TIME elements from within Visual Studio by running the application. If you want to create or adjust elements on the page using the Visual Studio Design window, temporarily comment out the IMPORT directive before switching to Design mode, as shown here:

<!-- <?IMPORT namespace="t" implementation="#default#time2"> -->

With the IMPORT directive commented out, the HTML+TIME elements will not appear and regular HTML elements will not display their time attributes; however, you will be able to add and modify other elements on the page.

Creating Timelines

In the preceding section, the HTML+TIME t:SEQ element creates a sequential timeline. As the name suggests, events in a sequential timeline happen one after the other as determined by the duration (DUR) attribute. For example, the following HTML fragment displays each line in turn for two seconds:

<t:SEQ>
   <DIV CLASS="time" DUR="2">These words are presented</DIV>
   <DIV CLASS="time" DUR="2">sequentially, one line at a time</DIV>
   <DIV CLASS="time" DUR="2">to make their point more clearly.</DIV>
   <DIV CLASS="time" DUR="2">Can you remember what I just said?</DIV>
</t:SEQ>

In addition to sequential timelines, you can create exclusive (t:EXCL) and parallel (t:PAR) timelines in HTML+TIME. Exclusive timelines display one element at a time as determined by the element' s BEGIN attribute. If a second element begins before the first element' s duration is over, the second element overrides the first. For example, the following HTML fragment displays each line in turn for two seconds. The DUR attribute is essentially ignored until the last line, which is displayed for three seconds:

<t:EXCL>
   <DIV CLASS="time" BEGIN="0" DUR="3">These words are presented</DIV>
   <DIV CLASS="time" BEGIN="2" DUR="3">using an exclusive timeline.</DIV>
   <DIV CLASS="time" BEGIN="4" DUR="3">Each line has a start time.</DIV>
   <DIV CLASS="time" BEGIN="6" DUR="3">Can you remember what I said?</DIV>
</t:EXCL>

Parallel timelines can display more than one element at a time. Each element uses both the BEGIN and DUR attributes to determine when they start and how long they are displayed. For example, the following HTML fragment displays each line in turn for three seconds. At some points, two lines are displayed at the same time:

<t:PAR>
   <DIV CLASS="time" BEGIN="0" DUR="3">These words are presented</DIV>
   <DIV CLASS="time" BEGIN="2" DUR="3">using a parallel timeline.</DIV>
   <DIV CLASS="time" BEGIN="4" DUR="3">More than one is displayed</DIV>
   <DIV CLASS="time" BEGIN="6" DUR="3">at a time.</DIV>
</t:PAR>

Another difference between the sequential timelines and the other two types is that elements in a sequential timeline replace each other. In the first of the two preceding examples, each element is displayed on the same line of the page. In the exclusive and parallel timeline examples, each element appears on its own line.

Timelines can be nested so that a master timeline controls multiple subordinate timelines. For example, the following HTML demonstrates of each of the three different timeline sequentially:

<h1>Timeline Demos</h1>
<t:SEQ>
   <h2 CLASS="time" DUR="2">Sequential Timeline</h2>
   <t:SEQ>
      <DIV CLASS="time" DUR="2">These words are presented</DIV>
      <DIV CLASS="time" DUR="2">sequentially, one line at a time</DIV>
      <DIV CLASS="time" DUR="2">to make their point more clearly.</DIV>
      <DIV CLASS="time" DUR="2">Can you remember what I just said?</DIV>
   </t:SEQ>
   <h2 CLASS="time" DUR="2">Exclusive Timeline</h2>
   <t:EXCL>
      <DIV CLASS="time" BEGIN="0" DUR="3">These words are presented</DIV>
      <DIV CLASS="time" BEGIN="2" DUR="3">using an exclusive timeline.</DIV>
      <DIV CLASS="time" BEGIN="4" DUR="3">Each line has a start time.</DIV>
      <DIV CLASS="time" BEGIN="6" DUR="3">Can you remember what I just
         said?</DIV>
   </t:EXCL>
   <h2 CLASS="time" DUR="2">Parallel Timeline</h2>
   <t:PAR>
      <DIV CLASS="time" BEGIN="0" DUR="3">These words are presented</DIV>
      <DIV CLASS="time" BEGIN="2" DUR="3">using a parallel timeline.</DIV>
      <DIV CLASS="time" BEGIN="4" DUR="3">More than one is displayed</DIV>
      <DIV CLASS="time" BEGIN="6" DUR="3">at a time.</DIV>
   </t:PAR>
   <h2 CLASS="time" DUR="4">That's it!</h2>
</t:SEQ>
Creating Time Containers

The t:SEQ, t:EXCL, and t:PAR elements shown previously create a timeline for the elements between their begin and end tags. You can also create timelines within individual HTML elements using the TIMECONTAINER attribute. For example, the following HTML is equivalent to the sequential timeline example in the preceding section:

<DIV class="time" timeContainer="seq">
   <DIV CLASS="time" DUR="2">These words are presented</DIV>
   <DIV CLASS="time" DUR="2">sequentially, one line at a time</DIV>
   <DIV CLASS="time" DUR="2">to make their point more clearly.</DIV>
   <DIV CLASS="time" DUR="2">Can you remember what I just said?</DIV>
</DIV>

The TIMECONTAINER attribute makes it a little easier to create timelines because they can simply be added to existing HTML containers, such as div, form, or body elements. They also make it easier to create separate timelines for individual elements on a page. For example, the following HTML uses a separate timeline for the last line displayed in the marquee:

<marquee CLASS="time" TIMECONTAINER="seq" REPEATCOUNT="indefinite" behavior=alternate>
   <h2 CLASS="time" DUR="2">These words are presented</h2>
   <h2 CLASS="time" DUR="2">sequentially, one line at a time</h2>
   <h2 CLASS="time" DUR="2">to make their point more clearly.</h2>
   <h2 TIMECONTAINER="seq" CLASS="time" DUR="2">
          <div class="time" dur="1">Can you remember what I just said?</div>
          <div class="time" dur="1">Cause I've already forgotten.</div>
   </h2>
</marquee>
Animating Attributes

The default behavior of a timeline is to show or hide elements. You can create other effects by using the t:ANIMATE element to change the attributes of an element at a specified time. For example, the following HTML switches the text in the span element from “end” to “beginning” every quarter second:

<t:ANIMATE targetelement="sEnd" attributename="innerHTML" from="end"
to="beginning"
dur=".25" repeatcount="indefinite" />
<h2>This is the <span id="sEnd">end</span>.</h2>

You can create color transitions using the t:ANIMATECOLOR element. For example, the following HTML fades the heading text from yellow to red over a period of five seconds:

<t:ANIMATECOLOR targetElement="hEnd" attributeName="color" from="yellow" to="red" start="2" dur="5" fill="hold">
<h2 id="hEnd" style="color:black">This is the <span id="sEnd">end</span>.
</h2>

You use the t:ANIMATEMOTION element to move elements on a page. For example, the following HTML moves a heading from the grid coordinates (400, 400) to (1, 1):

<t:ANIMATEMOTION targetElement="hEnd" from="400,400" to="1,1" start="2" dur="5" fill="hold">
<h2 id="hEnd" style="position:absolute;color:black">This is the
<span id="sEnd">end</span>.</h2>

You can move elements relative to their starting positions using the t:ANIMATEMOTION element' s BY attribute in place of the TO attribute. Alternatively, you can create a course for the element to follow using the VALUES or PATH attributes. The VALUES attribute accepts a string of coordinate pairs. For example, the following HTML moves a heading around a 200-by-200 square:

<t:ANIMATEMOTION targetElement="hEnd" values="1,200;200,200;200,1;1,1;1,200" start="2" dur="5" fill="hold">
<h2 id="hEnd" style="position:absolute;color:black">This is the <span
id="sEnd">end</span>.</h2>

The PATH attribute takes a string argument containing Scalable Vector Graphics (SVG) commands that allows you to move the element using absolute or relative coordinates. For example, the following HTML moves a heading in a triangle pattern:

<t:ANIMATEMOTION targetElement="hEnd" path="M 0 200 h 200 v -200 z" start="2" dur="10" repeatcount="indefinite">
<h2 id="hEnd"style="position:absolute;color:black">This is the <span
id="sEnd">end</span>.</h2>
Controlling Audio, Video, and Other Media

HTML+TIME provides elements for audio, video, still images, and generic timed media. However, each of those elements is currently equivalent to the t:ANIMATION element. You can use the media-specific tags if you feel it better documents your code or if you hope for some benefit from future releases of HTML+TIME.

You use the t:AUDIO, t:VIDEO, t:MEDIA, t:IMG, and t:REF elements exactly the same way you using the t:ANIMATION element. For example, the following HTML plays a beep every two seconds:

<t:AUDIO src="beep.wav" hasaudio="True" dur="2" repeatcount="indefinite" />

Similarly, the following HTML switches between two images every five seconds:

<t:par REPEATCOUNT="indefinite" >
      <t:IMG src="gondola.jpg" style="position:absolute;" begin="0" end="5" >
      <t:IMG SRC="bridge.jpg" style="postion:absolute;" begin="5" end="10" >
</t:par>

Using t:IMG or the equivalent t:ANIMATE in the preceding example isn' t really necessary because a <img class="time"> tag will do the same thing. However, the HTML+TIME elements let you add transitions that aren' t available with the img element. The following HTML adds fade and wipe transitions to the image changes:

<t:seq REPEATCOUNT="indefinite" >
   <t:IMG src="gondola.jpg" style="position:absolute;" DUR="5"
   FILL="transition" timecontainer="par" >
         <t:TRANSITIONFILTER TYPE="fade" mode="in" begin="0" end="1"/>
         <t:TRANSITIONFILTER TYPE="fade" mode="out" begin="4" end="5" />
   </t:IMG>
   <t:IMG SRC="bridge.jpg" style="postion:absolute;" DUR="5" FILL="transition"
   timecontainer="par" >
         <t:TRANSITIONFILTER TYPE="clockwipe" mode="in" begin="0" end="1" />
         <t:TRANSITIONFILTER TYPE="clockwipe" mode="out" begin="4" end="5" />
   </t:IMG>
</t:SEQ>

HTML+TIME allows you to control the rate the animation is played at, the transitions used between images, and many other aspects of animation not covered in this lesson. For more information, see the HTML+TIME topic in the Visual Studio .NET online Help.


Summary

  • Use client-side events and scripts to control multimedia, since server-side code requires the page to be redisplayed, thereby causing the media to restart.

  • The bgsound HTML element plays audio as a background sound.

  • Use the img HTML element' s dynsrc attribute to display video without being able to control playback.

  • The embed HTML element plays audio and displays video using the user' s default media player.

  • Include audio or video as an ActiveX object using the object HTML element. You can control the playback of an ActiveX object using client-side scripts.

  • Add ActiveX objects to the Visual Studio .NET Toolbox, and then drag them from the Toolbox to the Web form to get the correct class id (clsid) and default attributes for the object.

  • Use the HTML object element' s codebase attribute to automatically check if the client has the required version of an ActiveX object installed and to automatically install it if he or she does not.

  • Animated .gif files and the HTML marquee element offer simple ways to create animation.

  • Client-side scripts can animate any element on a Web form and can respond to user events, but present problems when you' re trying to synchronize events.

  • Synchronize multimedia events in Internet Explorer using HTML+TIME, which is the Microsoft implementation of the SMIL standard.

  • HTML+TIME allows you to:

    • Create different types of timelines on a Web page

    • Specify the order, timing, and duration of events within a timeline

    • Control the attributes, color, and position of elements in response to those timed events


Lab: Creating a Multimedia Application

In this lab, you will create a Web application containing two Web forms or HTML pages. The first page demonstrates using sound and motion, and the second page demonstrates using HTML+TIME to create a text slide show.

These samples are intentionally simple to show how much you can do by properly applying the appropriate multimedia technique. After you have completed these basic exercises, feel free to add to them and make them as fancy as you like.

Estimated lesson time: 15 minutes
Exercise 1: Using Sound and Motion

In this exercise, you will create a Web form or HTML page that displays an animated GIF that bounces around the browser screen and plays a sound when it bounces. When you are done, the page will appear similar to the page shown in Figure 12-8.

Figure 12-8 Sound and motion demo

Combine the skills you learned in Lessons 1, 2, and 3 to create a Web form or HTML page that performs the following major tasks:

  1. Displays an animated .gif file as an HTML img element.

  2. Moves the animated .gif around the screen using nested HTML marquee elements.

  3. Provides buttons that allow the user to control the speed of the movement.

  4. Uses a Windows Media Player ActiveX object to play a sound in response to a bounce event.

  5. Checks if the user' s browser is Internet Explorer, and if it is not, notifies the user that the page' s features are not available.

When you are done, compare your results to the multimedia sample included on this book' s companion CD-ROM.

Exercise 2: Displaying a Slide Show

In this exercise, you will create a Web form or HTML page that uses HTML+TIME to display an animated slide show. When you are done, the page will appear similar to the page shown in Figure 12-9.

Figure 12-9 Slide show demo

Use the skills you learned in Lesson 3 to create a Web form or HTML page that performs the following major tasks:

  1. Enable the page for HTML+TIME by adding the required namespace, style, and IMPORT directives.

  2. Create a timeline to display three text slides sequentially.

  3. Create parallel timelines within the first two text slides to display three list items.

  4. Create a sequential timeline with the last text slide to count down from 1 to 5.

  5. Navigate to another page when the last timeline has ended.

When you are done, compare your results to the multimedia sample included on this book' s companion CD-ROM.


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. Why do you usually control multimedia from client-side scripts, rather than from server-side code?

  2. Which HTML sound and video elements are supported by most browsers?

  3. How do you support Netscape Navigator users within an ActiveX object element?

  4. What central feature does HTML+TIME provide that is not available with other animation techniques?

  5. List two ways to create a new timeline using HTML+TIME.


Formatting Web Application Output

Chapter 13
Formatting Web Application Output
Lesson 1: Using Cascading Style Sheets
Lesson 2: Using XSL Transformations
About This Chapter

ASP.NET provides two complementary ways for you to format output in a Web application:

  • Use cascading style sheets (CSS) to control the appearance of elements on a Web form. These styles can set the color, size, font, and behavior of the HTML elements on a Web page.

  • Use Extensible Stylesheet Language Transformations (XSLT) to convert information from an XML file into HTML output and position that information on a Web form. XSLT puts data from the XML file into HTML elements and applies styles to those elements.

In this chapter, you will learn when and how to use each of these formatting techniques within a Web application.

Before You Begin

To complete this chapter, you must

  • Know how to create Web forms using HTML mode within Microsoft Visual Studio .NET

  • Be familiar with the HTML elements used to create Web forms and HTML pages


Lesson 1: Using Cascading Style Sheets

Cascading style sheets (CSS) collect and organize all of the formatting information applied to HTML elements on a Web form. Because they keep this information in a single location, style sheets make it easy to adjust the appearance of Web applications.

Visual Studio .NET includes style-editing tools, along with a default style sheet that is created as part of new Web application projects. In this lesson, you will learn how to use the Visual Studio .NET tools to modify the default style sheet, attach the sheet to Web forms, and switch between style sheets at run time.

After this lesson, you will be able to

  • Explain how ASP.NET applies styles defined at different levels within a Web form

  • Attach a style sheet to a Web form

  • Modify styles within a style sheet

  • Create new styles and add them to a style sheet

  • Create style classes that can be applied to different types of HTML elements

  • Change style sheets at run time to provide an enlarged-type version of a Web form or to format a Web form for printing

  • Use behaviors to control dynamic elements on a Web form

Estimated lesson time: 35 minutes
How Styles Work

As mentioned in the opening of this lesson, style sheets store formatting in a single location: the CSS file. However, formatting can be applied at three different levels within a Web application. Before you start working with style sheets, you should understand how styles defined at different levels affect formatting. Table 13-1 describes these levels and tells how they apply to the elements on a Web form.

Table 13-1 Levels of Style
Level | Defined in | Applies to
Global | The style sheet file | All pages referencing the style sheet
Page | The page' s head element | All elements on the current page
Inline | The HTML element itself | Only the current element

These three levels follow precedence rules that are similar to the levels of scoping you are already familiar with as a programmer. Inline formatting takes precedence over local formatting, which, in turn, takes precedence over global formatting. These precedence rules are the reason style sheets are referred to as cascading. Figure 13-1 illustrates how styles defined at different levels are applied to a paragraph element.

Figure 13-1 Style precedence

The following HTML shows the style definitions used to create the output shown in Figure 13-1:

<HTML>
   <HEAD>
         <title>WebForm1</title>
         <!-- (1) Style sheet reference. -->
         <LINK REL="stylesheet" TYPE="text/css" HREF="Styles.css">
         <!-- (2) Page-level style definition. -->
         <style>
         p {
         font-family: 'Comic Sans MS', Lucida Sans, sans-serif;
         font-size: medium;
         }
         </style>
   </HEAD>
   <body>
         <p>The alignment is from the style sheet.</p>
         <p>The font is from the style in the page's head element.</p>
         <!-- (3) Inline style definition -->
         <p style="FONT-SIZE: large; FONT-STYLE: italic">The italic is from
         the inline style.</p>
   </body>
</HTML>

<!-- (1) From Styles.css style sheet referenced in HEAD element. -->
p
{
   font-size: small;
   text-align: center;
}

The preceding example shows how style attributes can be applied to a single element at different levels. The font-size attribute is applied at all three levels, but you see only the effects of the page level and inline settings because the page setting takes precedence over the style sheet setting. The text-align, font-family, and font-style attributes are each defined only once, so their effects are additive.

There are a couple of advantages to storing style definitions in a style sheet file (.css) rather than locally in each Web form or inline with each HTML element:

  • Formatting can be maintained in one location, so that you make changes only once for an entire application.

  • Several sets of parallel formatting rules can be maintained in separate style sheets for formatting output on different devices or for different user needs. For example, an application might provide standard, enlarged-type, and printer-friendly style sheets that the user can select at run time.

    tip

    In general, you should use page and inline styles only when you have a really good reason to override the global styles. Relying heavily on page and inline styles can make it difficult to maintain the formatting in a Web application.
Using Style Sheets with Web Forms

When you create a new Visual Basic .NET Web application, Visual Studio .NET automatically creates a style sheet named Styles.css for the application. However, Visual Studio .NET does not automatically use that style sheet in any of the Web forms or HTML pages you create.

To use that style sheet, you must add a link element to the page' s head element, as shown here in bold:

<HEAD>
<title>WebForm1</title>
<meta name="GENERATOR" content="Microsoft Visual Studio.NET 7.0">
<meta name="CODE_LANGUAGE" content="Visual Basic 7.0">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
<LINK REL="stylesheet" TYPE="text/css" HREF="Styles.css"></HEAD>
important | Only Visual Basic .NET projects are created with a new default style sheet. For Visual C# projects, you must create a new style sheet from scratch or cut and paste the content of a default style sheet from a Visual Basic .NET project to get started.
Modifying Styles

Use the Style Builder to change the appearance of any of the styles in a style sheet. Changes to the style sheet change the appearance of all Web forms that reference that style sheet.

To modify a style, follow these steps:

  1. Open the style sheet in Visual Studio. Visual Studio .NET displays the style definitions in the Document window and an outline of the style sheet in the Tool window, as shown in Figure 13-2.

    Figure 13-2 Style sheet Document and Tool windows

  2. Select the style to modify from the Tool window. Visual Studio .NET displays the definition for that style in the Document window.

  3. Right-click in the style definition or right-click the style in the Tool window, and select Build Style from the pop-up menu. Visual Studio .NET displays the Style Builder Wizard, as shown in Figure 13-3.

    Figure 13-3 Style Builder

  4. Use the Style Builder to compose the formatting that you want to add or modify in the selected style. When you are done, click OK.

    When you' re through, you' ll see that the Style Builder adds the new or modified style attributes to the style definition.

Adding Styles

So far, we have created applied styles using HTML element names. CSS uses the element name to locate the default formatting to apply to each element. However, you can also apply styles using class names or element IDs.

Using class names allows you to apply a single style to a number of different elements or to style the same element differently, depending on how the element is used. Using element IDs allows you to apply a style to a unique element on one or more Web forms.

To add a style, follow these steps:

  1. Open the style sheet. Right-click the style sheet' s Document or Tool window and select Add Style Rule from the pop-up menu. Visual Studio .NET displays the Add Style Rule Wizard, as shown in Figure 13-4.

    Figure 13-4 Add a style

  2. Select the type of style you want to create. You can create styles that apply to HTML elements, classes, or specific element IDs. Type the name of the item to create the style for and click the > (Add) button to add the item to the style rule.

  3. Repeat step 2 for each item you want the style to apply to.

Using Style Classes

Style classes allow you to apply the same formatting to different HTML elements on a Web form. When you create a style rule for a class, Visual Studio .NET adds a style definition to the style sheet using a .classname identifier, as shown here:

.emphasis
{
    font-style: italic;
}

The preceding .emphasis style class formats all elements that use the class in italic, as shown in Figure 13-5.

Figure 13-5 Applying a style class

You apply the style class to HTML elements by using the class attribute. You apply the style to server controls by using the CssClass attribute. The following HTML shows this difference in bold:

<p>This is a paragraph containing <span class="emphasis">emphasis</span>.</p>
<asp:TextBox ID="Text1" Runat="server" CssClass="emphasis">Some text
</asp:TextBox>
Creating Styles for Specific Elements

You can also create styles for specific elements on a Web form based on their element IDs. Element IDs must be unique for each element on a page, so formatting set by element ID can apply to only one element on each page. When you create a style rule for an element ID, Visual Studio .NET adds a style definition to the style sheet using a #elementID identifier, as shown here:

#inserted
{
      text-decoration: underline;
}

#deleted
{
      text-decoration: line-through;
}

The preceding style definitions format elements with the ID inserted as underlined text and elements with the ID “deleted” with strikethrough, as shown in Figure 13-6.

Figure 13-6 Applying a style by element ID

Styles applied in Visual Studio .NET don' t always show up right away in the Web form designer. If you' re not seeing the formatting applied through a particular style, try the following:

  • Make sure the style sheet has been saved.

  • Switch the Web form between HTML view and the Design view.

  • Right-click the Web form and select View In Browser from the pop-up menu.

Creating Nested Styles

If you look at the style definitions in the default style sheet, Styles.css, you' ll notice that some style definitions list multiple element names. These definitions establish which formatting to use for nested elements. For example, the following styles specify different bullet types for nested, unnumbered lists:

UL LI    {
   list-style-type:  square ;
   }

UL LI LI {
   list-style-type:  disc;
   }

UL LI LI LI    {
   list-style-type:  circle;
   }

The preceding nested files format the nested list items, as shown in Figure 13-7.

Figure 13-7 Nested list

To create your own nested styles, add multiple items to the Style rule hierarchy in the Add Style Rule dialog, as shown in Figure 13-8.

Figure 13-8 Creating nested styles
Changing Style Sheets at Run Time

As mentioned previously, you can create multiple style sheets and then allow the user to select the styles to use at run time to format the Web form for printing or to provide features such as enlarged-type versions of Web forms.

To switch style sheets automatically when printing a Web form, use the media attribute of the style sheet link element to specify the style sheet to use for printing and screen display. For example, the following link elements apply formatting from the default style sheet when the Web form is displayed on screen, but use the Print.css style sheet when the user prints the Web form:

<LINK REL="stylesheet" TYPE="text/css" HREF="Styles.css" media="screen">
<LINK REL="stylesheet" TYPE="text/css" HREF="Print.css" media="print">

To switch between style sheets at run time, create a client-side script to change the href element of the style sheet link element. For example, the following HTML creates a Web form that switches between the style sheets Styles.css and BigType.css when the user clicks a hyperlink in text:

VBScript
<HTML>
<HEAD id="myhead">
<title>ChangeSheets</title>
<LINK ID="ScreenStyle" REL="stylesheet" TYPE="text/css" HREF="Styles.css">
<script language="vbscript">
' VBScript
' Switch between default and large-type style sheets.
Sub SwitchSheets()
   If document.all("ScreenStyle").GetAttribute("HREF") = "Styles.css" then
         document.all("ScreenStyle").SetAttribute "HREF", "BigType.css", 0
   else
         document.all("ScreenStyle").SetAttribute "HREF", "Styles.css", 0
   end if
End Sub
</script>
</HEAD>
   <body>
         <form id="Form1" method="post" runat="server">
               <h2>Changing Style Sheets at Run-Time</h2>
               <p>This page first appears using the default style sheet.</p>
               <p><a onclick="SwitchSheets" href="#">Click here
</a> to switch between the standard and the enlarged-type version.</p>
         </form>
   </body>
</HTML>
JScript
<HTML>
   <HEAD>
         <title>ChangeSheets</title>
         <LINK ID="ScreenStyle" REL="stylesheet" TYPE="text/css"
         HREF="Styles.css" media="screen">
         <LINK ID="PrintStyle" REL="stylesheet" TYPE="text/css"
         HREF="Print.css" media="print">
         <script id=clientEventHandlersJS language=jscript>
         function SwitchSheets()
         {
               // Switch between default and large-type style sheets.
         if (document.all["ScreenStyle"].getAttribute("HREF") == "Styles.css")
               document.all["ScreenStyle"].setAttribute ("HREF",
               "BigType.css", 0);
         else
               document.all["ScreenStyle"].setAttribute ("HREF", "Styles.css",
               0);
         }
         </script>
         </HEAD>
   <body MS_POSITIONING="FlowLayout">
         <FORM id="Form1" method="post" runat="server">
               <H2>Changing Style Sheets at Run-Time</H2>
               <P>This page first appears using the default style sheet.</P>
               <P><A onclick="SwitchSheets();" href="#">Click here</A>
               to switch between the standard and
               the enlarged-type version.</P>
               <A href="Behave.aspx">Next&gt;&gt;</A>
         </FORM>
   </body>
</HTML>

Figure 13-9 shows the how the Web form changes at run time when the user changes the style sheets by clicking the button.

Figure 13-9 Changing style sheets at run time
Using Behaviors

In addition to formatting, style can also define behaviors that HTML elements exhibit at run time. Behaviors are a sort of dynamic formatting: because Web content isn' t always static, style sheets use behaviors to describe dynamic changes.

If you read Chapter 12, “Working with Multimedia,” you are already somewhat familiar with behaviors. HTML+TIME animations use the time class to apply animations to HTML elements on a page, as shown here:

<style>
   .time    {behavior: url(#default#time2);}
</style>

The behavior property imports behavior files that contain JScript or VBScript code that controls what the behavior does. The Behaviors Library topic in the Visual Studio .NET online Help provides a number of sample behaviors that you can download and use in your own code.

For example, the following HTML imports the Movable.htc sample behavior file and uses it to create a paragraph that the user can drag to a new location on the Web form at run time:

<HEAD>
   <title>Behave</title>
   <style>
         .movable {behavior: url(behaviors/movable.htc); CURSOR: move;
         mv--boundary: 0 800 600 0}
   </style>
</HEAD>
<body>
   <form id="Form1" method="post" runat="server">
         <p class="movable">You can drag this text around the screen.</p>
   </form>
</body>
</HTML>

In the preceding HTML, the style definition for the movable class includes the URL of the behavior file, the cursor style to use, and the boundaries the elements are confined to. The style is applied by the p element' s class attribute. At run time, the user can drag the paragraph around the screen, as shown in Figure 13-10.

Figure 13-10 The movable behavior

The movable behavior can be applied to any element on a Web form, including server controls. Some behaviors, however, can be applied only to certain types of controls. For instance, the ColorPick behavior shown in the following code can be applied only to HTML Text Field controls (input elements of type text):

<HTML xmlns:control>
<HEAD>
   <title>Behave</title>
   <style>
         .color { BEHAVIOR: url(behaviors/ColorPick.htc) }
   </style>
</HEAD>
<body>
   <form id="Form1" method="post" runat="server">
         <p>Use the color picker below to change the background color:</p>
         <input ID="ColorPic" type="text" class="color" value="#fFFf00"
         onchange="document.bgColor=ColorPic.value;" style="cp--grid-size:2">
   </form>
</body>
</HTML>

The preceding HTML displays a color palette that the user can click to change the background color at run time, as shown in Figure 13-11.

Figure 13-11 The ColorPick behavior

Finally, some behaviors create their own types of elements that you can use on a Web form. For example, the Slider sample behavior creates a slider element that you can use to get input from a user. To use the Slider control, you must first add an xmlns attribute to the Web form' s html element to declare the name for the control, as shown here:

<HTML xmlns:control>
   <HEAD>
         <title>Behave</title>
         <style>
               control\:slider { BEHAVIOR: url(behaviors/slider.htc) }
         </style>
   </HEAD>
   <body>
         <form id="Form1" method="post" runat="server">
               <control:slider ID="Slider" onchange="alert(Slider.value)"
               STYLE="sl--orientation:horizontal"></control:slider>
         </form>
   </body>
</HTML>

The preceding HTML creates a slider control that displays the control' s value when the user slides the control on a Web form, as shown in Figure 13-12.

Figure 13-12 The Slider behavior
important | Although you can use behaviors that create controls on Web forms, it is usually a better idea to create and use custom controls for tasks that aren' t handled by the standard server and HTML controls. Controls created by behaviors don' t display correctly in the Visual Studio .NET Designer window and don' t provide properties and methods that are readily available from server-side code. Custom controls provide both of these advantages.


Lesson 2: Using XSL Transformations

In this lesson, you' ll learn about using XSL to transform XML files, using expressions written in XML Path Language (XPath).

After this lesson, you will be able to

  • Use the XML control to transform an XML data file into formatted HTML on a Web form

  • Explain the differences between XML and HTML

  • Create an XML file

  • Create XSL templates to control how the XML file is transformed into HTML

  • Use the XSL elements to repeat tasks, perform conditional operations, insert HTML elements, and sort output

  • Evaluate expressions using XPath

Estimated lesson time: 40 minutes
How XSL Transformations Work

XSL transformations use the XML server control to generate formatted output from an XML input file using a set of rules stored in an XSL description file. The XSL file is similar to the CSS file you learned about in the preceding lesson—it provides the formatting rules that are applied to the output displayed on the Web form. Unlike CSS files, however, XSL can position elements anywhere on the Web form and can perform logical operations, such as repeating and conditional operations.

You can think of XSL files as an intelligent formatting layer that complements—rather than replaces—CSS. Use XSL to place structured data on a Web form, and then use CSS to set the appearance of the elements within that layout.

To perform an XSL transformation on a Web form, follow these steps:

  1. Add an XML server control to a Web form.

  2. Set the control' s DocumentSource property to the XML file to format.

  3. Set the TransformSource property to the XSL file to use to format the output.

At run time, the XML server control processes the XML input using the XSL description to provide formatted output, as shown in Figure 13-13.

Figure 13-13 Performing XSL transformation
Creating an XML File

XML files are a way to describe structured data in text format. XML identifies data items using <element>…</element> tags much the same as HTML. Unlike HTML, however, the rules of XML are strict:

  • Each item must have a begin tag and an end tag. In HTML, <li>, <br>, and other tags can exist without equivalent </li> or </br> tags. That is not allowed in XML.

  • Item tag names are case sensitive. In HTML, <P> and <p> are equivalent. In XML, they constitute different tags.

  • Attribute values must always be enclosed in double quotation marks. In HTML, you can omit quotation marks for numeric attributes such as <table cols=3>. In XML, you must always include the quotation marks: <table cols="3">.

  • The nested items must be terminated before the containing item is terminated. For example, the following is OK in HTML: <b>Bold text<i>italic bold</b> regular italic</i>. However, in XML it must be written like this: <b>Bold text<i>italic bold</i></b> <i>regular italic</i>

These strict rules allow XML to have an important capability that HTML does not have: In XML, you can define your own tags and you can assign those tags any meaning you choose.

To create an XML file in Visual Studio .NET, choose Add New Item from the Project menu, choose XML file from the Templates list, and then click Open.

When you create a new XML file, it is a good idea to first write out the tags and structure that you will use throughout the document. This saves you the trouble of trying to retag elements if you discover that you missed something. For example, the following XML describes cookbook data that we' ll be using as a sample in this lesson:

<cookbook>
   <title></title>
   <subtitle></subtitle>
   <description></description>
   <recipe>
         <title></title>
         <subtitle></subtitle>
         <description></description>
         <servings></servings>
         <ingredients>
               <ingredient>
                     <name></name>
                     <quantity></quantity>
               </ingredient>
               <ingredient>
                     <name></name>
                     <quantity></quantity>
               </ingredient>
         </ingredients>
         <instructions>
               <introduction></introduction>
               <step></step>
               <step></step>
               <step></step>
               <summary></summary>
         </instructions>
   </recipe>
</cookbook>

The data for each of these items will go between each of the begin and end tags. What' s important to notice at this point is the structure of the file and that the element names describe the content of the element, rather than its formatting. By labeling the content, you can apply content-based formatting using XSL.

The XML structure is strictly hierarchical. XML refers to the items in this hierarchy as nodes. Nodes have parent-child relationships that are identified using the XML Path language (XPath), as shown in Figure 13-14.

Figure 13-14 XML nodes

XML is well suited for structured data such as stock quotes, employee information, product inventory, order status, and other data that we are used to dealing with in an organized fashion. XML is not as well suited for information that uses creative or ad hoc organization.

Creating an XSL File

The XSL file provides the layout and logic that transforms data stored in the XML file into the output you see on the Web form. To create an XSL file in Visual Studio, choose Add New Item from the Project menu, choose XSL file from the Templates list, and then click Open.

The XSL file contains template statements that select the items to import from the XML file. Each template includes standard HTML elements to apply formatting to the elements imported from the XML file. For example, the following XSL template converts cookbook title, subtitle, and introduction elements from an XML file into h1, h2, and p elements in HTML:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:template match="/cookbook">
      <xsl:apply-templates select="cookbook" />
      <h1><xsl:value-of select="title" /></h1>
      <h3><xsl:value-of select="subtitle" /></h3>
      <hr />
      <p><xsl:value-of select="introduction" /></p>
      <hr />
      <h2><xsl:text>Recipes</xsl:text></h2>
      <xsl:apply-templates select="recipe" />
   </xsl:template>
 </xsl:stylesheet>

The preceding example uses the XSL elements and attributes summarized in Table 13-2.

Table 13-2 Basic XSL Elements
Element | Attributes | Use to
xsl:stylesheet | version | Identify the version of XSL being used. Version 1.0 is the current version at the time of this writing.
 | xmlns:xsl | Specify the prefixes for elements in the XSL file.
xsl:template | match | Define a template for an XML node.
xsl:apply-templates | select | Apply a template to the selected node.
xsl:value-of | select | Retrieve the value of an XML node or evaluate an XPath expression.
xsl:text |  | Include literal text or white space characters in the output.

Figure 13-15 shows how these elements work to transform cookbook XML data through the XML server control.

Figure 13-15 Basic XSL elements in action

The XSL elements demonstrated in Figure 13-15 perform the minimum tasks for displaying data with XSL: they define a template, import data into that template, display values from the XML, and insert literal text. Those are all tasks that are more easily performed simply by using regular HTML and style sheets. However, using XSL offers two advantages:

  • You can change the position of the element (page layout) in the XSL file without having to change your data.

  • You can perform logical operations on data, such as looping and conditional processing.

These advantages are discussed in the following sections.

Changing Layout

You change page layout in XSL by moving elements in the XSL file. For example, the following XSL displays the cookbook XML in a two-column format and displays the subtitle before the title:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:template match="/cookbook">
      <xsl:apply-templates select="cookbook" />
         <table>
               <tr>
                     <td width="300">
                           <h3><xsl:value-of select="subtitle" />
                           <xsl:text>...</xsl:text></h3>
                           <h1><xsl:value-of select="title" /></h1>
                     </td>
                     <td width="500">
                        <xsl:text>recipe text goes here..</xsl:text>
                     </td>
               </tr>
               <tr>
                     <td width="300">
                           <hr />
                           <xsl:value-of select="introduction" />
                           <hr />
                     </td>
                     <td width="500">
                     </td>
                </tr>
      </table>
   </xsl:template>
</xsl:stylesheet>

Figure 13-16 shows the changes to the output on the Web form.

Figure 13-16 Changing layout with XSL
Including Other Templates

An XSL file can contain multiple templates. You define an XSL template for each XML node that is uniquely formatted. For example, the cookbook XML shown earlier in this lesson could be organized into four templates:

  • The main template formats the title and description of the book and provides a starting point. XSL execution starts with this template because it selects the root node (match="/cookbook”).

  • The recipe template formats the recipes included in the book.

  • The ingredients template formats the list of ingredients.

  • The instructions template formats the steps that follow the list of ingredients.

Use the xsl:apply-templates element to apply a template to a node. For example, the following XSL applies the ingredient and instruction templates from the recipe template, which is in turn applied from the main template. The links between the templates are shown in bold:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:template match="/cookbook">
      <xsl:apply-templates select="cookbook" />
      <h1><xsl:value-of select="title" /></h1>
      <h3><xsl:text>...</xsl:text><xsl:value-of select="subtitle" /></h3>
      <hr />
      <p><xsl:value-of select="introduction" /></p>
      <hr />
      <xsl:apply-templates select="recipe" />
   </xsl:template>

   <xsl:template match="recipe">
         <h3><xsl:value-of select="title" /></h3>
         <p><xsl:value-of select="description" /></p>
         <p><b><xsl:text>Serves: </xsl:text></b>
         <i><xsl:value-of select="servings" /></i></p>
         <xsl:apply-templates select="ingredients" />
         <xsl:apply-templates select="instructions" />
   </xsl:template>

   <xsl:template match="ingredients">
   <ul>
         <li><xsl:value-of select="ingredient/name" />
         <xsl:text>     </xsl:text>
         <b><xsl:value-of select="ingredient/quantity" /></b></li>
   </ul>
   </xsl:template>

   <xsl:template match="instructions">
   <p><xsl:value-of select="introduction" /></p>
   <ol>
         <li><xsl:value-of select="step" /></li>
   </ol>
   <p><xsl:value-of select="summary" /></p>
   </xsl:template>

</xsl:stylesheet>

At run time the preceding XSL template creates HTML output, as shown in Figure 13-17.

Figure 13-17 Using multiple templates together

But wait! There' s only one ingredient (cooter turtle) and one instruction (cut it up). As the templates are written, they process only one node each. To repeat tasks you need to use the XSL looping elements, as described in the following section.

Repeating Tasks

To format repeating elements in an XML file, such as list or table items, use the xsl:for-each XSL element. The xls:for-each element selects the repeated node, and then the contained XSL is applied to each occurrence of that node, as shown here:

<xsl:for-each select="ingredient">
   <xsl:value-of select="name" /><br />
</xsl:for-each>

The preceding XSL outputs the name of each repeated ingredient node. To refer to the repeated node itself, use the <;$QD>.<;$QD> XPath identifier. For example, the following XSL outputs the text of each repeated step node in the XML:

<xsl:for-each select="step">
   <xsl:value-of select="." /><br />
</xsl:for-each>

The following XSL uses the xsl:for-each element (shown in bold) to display all of the recipes, ingredients, and steps in the cookbook:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:template match="/cookbook">
      <xsl:apply-templates select="cookbook" />
      <h1><xsl:value-of select="title" /></h1>
      <h3><xsl:text>...</xsl:text><xsl:value-of select="subtitle" /></h3>
      <hr />
      <p><xsl:value-of select="introduction" /></p>
      <hr />
      <xsl:for-each select="recipe">
<xsl:apply-templates select="." />
</xsl:for-each>
   </xsl:template>

   <xsl:template match="recipe">
<h3><xsl:value-of select="title" /></h3>
<p><xsl:value-of select="description" /></p>
<p><b><xsl:text>Serves: </xsl:text></b>
<i><xsl:value-of select="servings" /></i></p>
<xsl:apply-templates select="ingredients" />
<xsl:apply-templates select="instructions" />
   </xsl:template>

   <xsl:template match="ingredients">
   <ul>
<xsl:for-each select="ingredient">
<li><xsl:value-of select="name" />
<xsl:text>  </xsl:text>
<b><xsl:value-of select="quantity" /></b></li>
</xsl:for-each>
   </ul>
   </xsl:template>

   <xsl:template match="instructions">
   <p><xsl:value-of select="introduction" /></p>
   <ol>
<xsl:for-each select="step">
<li><xsl:value-of select="." /></li>
</xsl:for-each>
   </ol>
   <p><xsl:value-of select="summary" /></p>
   </xsl:template>

</xsl:stylesheet>

At run time, the preceding XSL formats the cookbook XML, as shown in Figure 13-18.

Figure 13-18 Formatting repeated elements
Inserting Hyperlinks and Other HTML Elements

Use xsl:element to compose HTML elements that contain attributes, such as hyperlinks, server controls, or HTML controls. For example, the following XSL creates a hyperlink that returns the user to the top of the page:

<xsl:element name="a">
   <xsl:attribute name="name">
      <xsl:text>#top</xsl:text>
   </xsl:attribute>
   <xsl:text>Return to top.</xsl:text>
</xsl:element>

At run time, the preceding XSL generates the following output:

<a href="#top">Return to top.</a>

You can couple this XSL with the XPath generate-id function to automatically create a table of contents for your document that links to locations within a document, as shown in bold by the following XSL:

<xsl:template match="/cookbook">
   <h1><xsl:value-of select="title" /></h1>
   <h3><xsl:text>...</xsl:text><xsl:value-of select="subtitle" /></h3>
   <hr />
   <p><xsl:value-of select="introduction" /></p>
   <hr />
   <!-- Display table of contents -->
<h4>Contents</h4>
<xsl:apply-templates mode="contents" select="recipe" />
   <xsl:for-each select="recipe">
      <xsl:apply-templates select="." />
   </xsl:for-each>
</xsl:template>

<xsl:template match="recipe">
      <!-- Create location links -->
      <xsl:element name="a">
            <xsl:attribute name="name">
                  <xsl:value-of select="generate-id(title)" />
            </xsl:attribute>
            <h3><xsl:value-of select="title" /></h3>
      </xsl:element>
      <p><xsl:value-of select="description" /></p>
      <p><b><xsl:text>Serves: </xsl:text></b>
      <i><xsl:value-of select="servings" /></i></p>
      <xsl:apply-templates select="ingredients" />
      <xsl:apply-templates select="instructions" />
</xsl:template>

<xsl:template mode="contents" match="recipe">
      <!-- Create table of contents links -->
      <xsl:element name="a">
            <xsl:attribute name="href">
                  <xsl:text>#</xsl:text>
                  <xsl:value-of select="generate-id(title)" />
            </xsl:attribute>
            <xsl:value-of select="title" />
      </xsl:element>
      <br />
</xsl:template>

At run time the preceding XSL generates the output shown in Figure 13-19.

Figure 13-19 Generating a table of contents

The generate-id XPath function generates a unique ID for the XML node passed to it as an argument. The ID takes the form XSLTnodename# where # is a 12-digit integer. For example: XSLTtitle120123120120.

Sorting Items

Use the xsl:sort element to sort items within another XSL element. For example, the following XSL sorts the table of contents created in the preceding section:

<h4>Contents</h4>
<xsl:apply-templates mode="contents" select="recipe">
   <xsl:sort select="title" order="ascending" />
</xsl:apply-templates>

The xsl:sort element can be included in xsl:apply-templates elements, as shown in the preceding code, or in xsl:for-each elements.

Performing Conditional Tasks

XSL includes two sets of elements for performing conditional tasks: use xsl:if to evaluate a single condition; use xsl:choose with contained xsl:when and xsl:otherwise elements to evaluate multiple conditions. Table 13-3 summarizes these conditional elements.

Table 13-3 Conditional XSL Elements
Element | Attributes | Use to
xsl:if | test | Test a Boolean expression and process the contained elements if the result is true.
xsl:choose |  | Test multiple conditions using contained xsl:when and xsl:otherwise elements. This is the equivalent of a Select Case (Visual Basic .NET) or switch statement (Visual C#).
xsl:when | test | Test one Boolean expression within a xsl:choose element, and then process the contained item and exit the xsl:choose element if the result is true. This is the equivalent of the Case (Visual Basic .NET) or case (Visual C#) statements.
xsl:otherwise |  | Process the contained item if none of the xsl:when elements within an xsl:choose element are true. This is the equivalent of the Case Else (Visual Basic .NET) or default statement (Visual C#).

The conditional elements' test attribute evaluates an XPath expression to determine whether the XSL contained in the conditional element should be processed. For example, the following XSL creates a table of contents if there are more than four recipes in the cookbook:

<xsl:if test="count(recipe) > 4">
   <h4>Contents</h4>
   <xsl:apply-templates mode="contents" select="recipe" />
</xsl:if>

The preceding XSL uses the XPath count function to return the number of recipe nodes in the cookbook. If the count is greater than four, the Contents heading is output and the contents template is processed; otherwise, the contents are omitted.

Similarly, you can use XPath functions within xsl:choose elements to change output based on several conditions. For example, the following XSL inserts a line break after every four contents items; otherwise, it separates items with a tab space:

<xsl:template mode="contents" match="recipe">
      <xsl:element name="a">
            <xsl:attribute name="href">
                  <xsl:text>#</xsl:text>
                  <xsl:value-of select="generate-id(title)" />
            </xsl:attribute>
            <xsl:value-of select="title" />
      </xsl:element>
      <xsl:choose>
            <xsl:when test="position() mod 4 = 0">
                  <br />
            </xsl:when>
            <xsl:otherwise>
                  <xsl:text>  </xsl:text>
            </xsl:otherwise>
      </xsl:choose>
</xsl:template>

The preceding XSL uses the XPath position function to return the position of the current node within the node' s collection. It also uses the XPath mod operator to tell whether the current position is evenly divisible by four.

If you feel like I' ve only scratched the surface of what you can do with XSL, you are right. XML, XSL, and XPath are all languages in their own right. The preceding lesson gives you enough information to get started, but for the complete education see the Microsoft XML SDK documentation included in the Visual Studio .NET online Help. Microsoft Press books that cover this subject include XML Step by Step, Second Edition, by Michael J. Young, and XML Programming (Core Reference), by R. Allen Wyke, Sultan Rehman, and Brad Leupen.


Summary

  • Styles follow this order of precedence based on where they are applied: local (applied on an element), page (applied within a page' s head element), and global (applied through a style sheet).

  • Use the link element within a Web form' s head element to attach a style sheet.

  • Use the Visual Studio .NET Style Builder Wizard to modify existing styles.

  • Use the Visual Studio .NET Add Style Rule Wizard to create a new style.

  • Create a style class to apply the same formatting to different types of elements.

  • Use the CssStyle attribute to apply a style class to a server control. Use the class attribute to apply a style class to an HTML element.

  • XSL transformations convert XML input files into HTML through the XML server control.

  • XML organizes data into a hierarchical set of nodes.

  • XSL templates apply formatting to nodes within an XML file.

  • Use the xsl:value-of element to retrieve data from an XML node.

  • Use the xsl:text element to include literal text and white-space characters in XSL output.

  • Use the xsl:for-each element to repeat tasks for lists, tables, and other repeated nodes within an XML file.

  • Use the xsl:if, xsl:choose, xsl:when, and xsl:otherwise elements to perform conditional processing within XSL.


Lab: Creating the Cookbook

In this lab, you will build a single Web form that uses a customized style sheet, provides an enlarged-type style sheet for visually impaired users, and uses XML with XSL transformations to format information. You will also be encouraged to apply your knowledge of XML to real-world data you are already familiar with.

Estimated lesson time: 30 minutes
Exercise 1: Create and Attach a Style Sheet

Use the skills you learned in Lesson 1 to create your own style sheet based on the default style sheet Visual Studio .NET creates. Because style (and taste) are personal matters, you can make the changes suggested in the following steps, or you can develop your own unique look. When you are done, your result might look similar to Figure 13-20.

Figure 13-20 Test Web form with styles
To create a custom style sheet and attach it to a Web form

  1. Make the following suggested changes to the default style sheet (Styles.css), or choose your own changes:

    • Change the default font for all headings to Comic Sans MS.

    • Change the default font for body text to Lucida Sans-Serif.

    • Add a background image to the page from within the style sheet.

    • Change the color of the headings and rule lines to stand out against your chosen background image.

  2. When you are done modifying the style sheet, attach it to a Web form by adding a link element in the Web form' s head element, as shown here in bold:

    <HEAD>
   <meta name="GENERATOR" content="Microsoft Visual Studio.NET 7.0">
   <meta name="CODE_LANGUAGE" content="Visual Basic 7.0">
   <meta name="vs_defaultClientScript" content="JavaScript">
   <meta name="vs_targetSchema"
         content="http://schemas.microsoft.com/intellisense/ie5">
   <LINK REL="stylesheet" TYPE="text/css" HREF="Styles.css"></HEAD>

  3. Add some sample text and headings to the Web form to test the appearance of your style sheet.

  4. View the Web form in the browser and adjust the style sheet until you are happy with the Web form' s appearance.

Exercise 2: Provide Alternative Style Sheets

In this exercise, you will create an alternative style sheet that displays headings and text in a larger font size, making your Web form readable for users with impaired vision. You will also add a client-side control that enables the user to switch to the large-type style sheet at run time. When you are done, your result should look similar to Figure 13-21.

Figure 13-21 Enlarged type sample
To create the enlarged type style sample

  1. Add a new, empty style sheet to your project. Name the file BigType.css.

  2. Cut and paste the contents of the default style sheet (Styles.css) into BigType.css. If you are using Visual C#, you will have to get the default style sheet from a Visual Basic .NET project and add it to your Visual C# project.

  3. In BigType.css, change the body element' s font size to large.

  4. On the Web form created in exercise 1, add an ID and a media attribute to the link element that attaches the default style sheet to the Web form, as shown here in bold:

    <LINK ID="ScreenStyle" REL="stylesheet" TYPE="text/css" HREF="Styles.css" media="screen">

  5. Add another link element to use the default style sheet for printing, as shown here:

    <LINK ID="PrintStyle" REL="stylesheet" TYPE="text/css" HREF="Styles.css" media="print">

  6. On the Web form you created in Exercise 1, add a hyperlink with an onclick event to switch between the default style sheet and BigType.css, as shown here:

    <a onclick="SwitchSheets()" href="#">Click here
</a> to switch between the standard and the enlarged-type version.

  7. Add the following client-side script to switch between style sheets:

    VBScript
    <script language="vbscript"> ' Switch
 between default and large-type style sheets.
Sub SwitchSheets()
   If document.all("ScreenStyle").GetAttribute("HREF", 0) = "Styles.css" then
         document.all("ScreenStyle").SetAttribute "HREF", "BigType.css", 0
   else
         document.all("ScreenStyle").SetAttribute "HREF", "Styles.css", 0
   end if
End Sub
</script>
    JScript
    <script id="clientEventHandlersJS" language="jscript">
function SwitchSheets()
{
   // Switch between default and large-type style sheets.
if (document.all["ScreenStyle"].getAttribute("HREF") == "Styles.css")
   document.all["ScreenStyle"].setAttribute ("HREF", "BigType.css", 0);
else
   document.all["ScreenStyle"].setAttribute ("HREF", "Styles.css", 0);
}
</script>

  8. Display the Web form in the browser and test switching back and forth between the BigType.css and the default style sheets. Make adjustments to BigType.css to create the best appearance.

Exercise 3: Perform XSL Transformation

In this exercise, you will add an XML server control to the Web form you created in Exercise 1 and modified in Exercise 2. You will set the XML control' s DocumentSource and TransformSource properties to the sample XML and XSL files used in Lesson 2, which are included with the samples that come with this book. When you are done, your results should appear as shown in Figure 13-22.

Figure 13-22 The cookbook sample
To perform the sample XSL transformation

  1. Using the same Web form from Exercises 1 and 2, delete the sample text you used to test style sheets. Retain the hyperlink and client-side script you created in Exercise 2.

  2. Add an XML server control after the hyperlink, as shown by the following HTML:

    <a onclick="SwitchSheets" href="#">Click here</a> to switch between the
standard and the enlarged-type version.
<asp:Xml id="XmlTransform" runat="server" DocumentSource="" TransformSource="" />

  3. Copy the cook.xml and cook.xsl sample files to your project directory.

  4. Set the XML control' s DocumentSource property to cook.xml and TransformSource property to cook6.xsl.

  5. Display the Web form in the browser to confirm that the XML transformation works correctly.

Exercise 4: On Your Own

Think about ways to apply XML with XSL transformations to data that you currently present on the Web with HTML. Using XML to label content is a much more conceptual task than using HTML directly to format content, and it takes some getting used to. Perhaps the best way to learn is to try it on your own, using real-world data that you are familiar with, as described in the following steps.

  1. Choose a simple project, such as an order form or an address list, and then create a sample XML file based on that data.

  2. Create an XSL file that contains templates to transform the XML file into HTML.

  3. Create a Web form that uses an XML control to perform the transformation.

  4. As you work, think about the following questions:

    1. How do you determine the node hierarchy?

    2. What are the advantages of creating collections within the hierarchy? For example:

      <items>
      <item>Item 1</item>
      <item>Item 2</item>
</items>

    3. How does writing XML leverage your database and object-oriented programming skills?


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. What is the advantage of using CSS rather than in-line styles for formatting a Web application?

  2. Why would you create a style for a class rather than for an HTML element?

  3. How do CSS and XSL relate to each other when it comes to formatting a Web application?

  4. What are the differences between HTML and XML?


Providing Help

Chapter 14
Providing Help
Lesson 1: Adding Help to Web Applications
Lesson 2: Using the HTML Help Workshop
Lesson 3: Linking HTML Help to Web Forms
About This Chapter

In this chapter, you will learn about the different ways that you can display user assistance (Help) from within a Web application. You will learn how to use the HTML Help Workshop to create compiled Help files, and how to display those files from a Web application interactively.

Before You Begin

To complete this chapter, you must

  • Be familiar with HTML

  • Be comfortable creating Web forms in HTML mode


Lesson 1: Adding Help to Web Applications

Web applications can display help in several ways: as ToolTips for controls, as Web forms or HTML pages, or as compiled HTML Help files. In this lesson, you will learn to use these different techniques from within Web forms.

After this lesson, you will be able to

  • Add ToolTips to server controls and HTML controls on a Web form

  • Display Help as a Web form or HTML page in a browser window

  • Control the size and focus of the browser window displaying Help

  • Display Help using the HTML Help Viewer

Estimated lesson time: 15 minutes
Displaying ToolTips

A ToolTip is a short, descriptive message that is displayed when a user places the mouse pointer over a control and leaves it there for a couple of seconds. These messages are used throughout Microsoft Windows applications to provide helpful information about toolbar buttons and other graphic controls whose meaning might not otherwise be obvious. In a sense, ToolTips are subtitles for the icon-impaired. Figure 14-1 shows a ToolTip in action.

Figure 14-1 A ToolTip

Most server controls include a ToolTip property that you can set to the message you want to display. At run time, ASP.NET outputs the ToolTip property as the control' s title attribute, which Microsoft Internet Explorer displays as a ToolTip. Only Internet Explorer displays the title attributes as ToolTips. Other browsers do not display ToolTips.

HTML controls do not include a ToolTip property. Instead, you can use the title attribute directly. The following HTML shows the difference between setting a ToolTip for a server control and setting one for an HTML control:

<asp:Button id="butOK" runat="server" Text="OK"
  ToolTip="Submits purchase."></asp:Button>
<INPUT type="reset" value="Cancel" title="Cancels purchase.">

Not all controls include a ToolTip or title property. In particular, the DropDownList and ListBox server controls do not provide a ToolTip property or a title attribute.

Displaying Help as Web Forms or HTML

Creating your Help content as Web forms or HTML pages is probably the simplest approach to providing user assistance. The Help files can reside in either the same project folder as your Web application or in a subordinate folder.

You use hyperlinks or other controls to display the Help in the browser, just as you would any other page, with one important difference: you usually want to display Help in a new window or in a separate frame from the rest of the Web application, as shown in Figure 14-2.

Figure 14-2 Displaying a Help browser window

Displaying Help in a separate browser window preserves the user' s place in the Web application and allows him or her to compare the Help to the current task being performed.

To display Help in a separate window, use the client-side window object' s open method, as shown by the following hyperlink:

<a href="#" onclick="window.open('topic1.aspx', 'helpwin').focus()">Click here
for help.</a>

The preceding HTML uses the onclick event to display the Help topic in a new browser window on top of the Web application' s window. Setting the href attribute to <;$QD>#<;$QD> simply links to the current location in the Web application, rather than directing the user to a new location. The first argument to the open method specifies the Help topic to display; the second argument names the new window so that subsequent Help topics are directed to the same window. Finally, the focus method ensures that the Help window is displayed on top of the Web application.

Other Help hyperlinks within the Web application can use the same onclick script to display different topics in the same window. If your application includes more than one Help hyperlink per Web form, it is a good idea to place the script in a procedure and call it from the onclick procedure, as shown here:

VBScript
<HTML>
   <HEAD>
   <title>WebForm1</title>
<script language="vbscript">
' Shows a Web form or HTML page in a small browser window
' and switches the focus to that window.
Sub ShowHelp(topicName)
   Dim HelpWindow
   ' Display topic in named window.
   Set HelpWindow = window.open(topicName,
   "helpwin","left=600,height=300,width=200")
   ' Displays the window on top.
   HelpWindow.focus
End Sub
</script>
   </HEAD>
   <body>
         <h2>Show Help in a New window</h2>
         <A onclick="ShowHelp('topic1.aspx')" href="#">Click here
         for help.</A>
         <BR>
         <A onclick="ShowHelp('topic2.aspx')" href="#">Click here
         for more help.</A>
   </body>
</HTML>
JScript
<HTML>
   <HEAD>
<script language=jscript>
// Shows a Web form or HTML page in a small browser window
// and switches the focus to that window.
function ShowHelp(topicName)
{
   var HelpWindow;
   // Display topic in named window.
   HelpWindow = window.open(topicName,
     "helpwin","left=600,height=300,width=200");
   // Displays the window on top.
   HelpWindow.focus();
}
</script>
   </HEAD>
   <body>
         <h2>Show Help in a New window</h2>
         <a href="#" onclick="ShowHelp('topic1.aspx')">Click here for
         help.</a>
         <br>
         <a href="#" onclick="ShowHelp('topic2.aspx')">Click here for
         more help.</a>
   </body>
</HTML>

Within the Help topic, you can use the window object' s close method to provide a link to close the Help window within the Help topic, as shown here:

<a href="#" onclick="window.close()">Click here to close help.</a>

There are other ways to display a topic in a named window, such as using the target attribute of a hyperlink. However, those techniques make it more difficult to ensure that the Help window is displayed on top of the Web application.

Displaying HTML Help

You can use the window object' s showHelp method to display HTML Help files, regular HTML files, or Web forms using the HTML Help Viewer (hh.exe). An HTML Help file consists of topic files written in HTML that have been compiled into a single, compressed file that provides additional Help features.

The showHelp method displays topics in the HTML Help Viewer, which by default is displayed on top of the Web application' s browser window, as shown in Figure 14-3.

Figure 14-3 The HTML Help Viewer

To display a Web form or HTML page using showHelp, specify the Web form or HTML page filename as the first argument of the method, as shown here:

<a href="#" onclick="window.showHelp('Topic1.aspx')">Show Help</a>

The preceding HTML displays the Web form Topic1.aspx when the user clicks the Show Help hyperlink. The Topic1.aspx file in not a compiled Help file—it is simply a Help topic written using ASP.NET. Using showHelp in this way gives you the advantage of the window always being on top of the Web application—even when the Web application has focus.

The showHelp message can also display topics from compiled Help files. These files are made up of HTML pages that are compiled into a single file (.chm) using the HTML Help Workshop. When you work with compiled Help files, showHelp identifies the topic to display using a context ID that is mapped to a topic filename during compilation.

To display a topic from a compiled HTML Help file, specify the compiled filename and topic filename as the first argument of the showHelp method, as shown here:

<a href="#" onclick="window.showHelp('c:\\Help\\HelpSample.chm')">
Show Help</a><br>

The preceding HTML displays the compiled file named HelpSample.chm. Note that the compiled Help file must be downloaded and stored on the user' s machine. Using compiled Help files has the following advantages over storing and displaying Help files as separate Web forms or HTML pages:

  • Reduced size. Compiling Help compresses the topics so their total size is much less than the total of the individual source topics.

  • Contents, Index, and Search tools. The HTML Help Workshop includes tools for creating these features and adding them to your Help system.

  • Embedded display. The HTML Help ActiveX control and Java applet allow you to display your Help within the browser window rather than as a separate window.

  • Ease of localization. Because HTML Help maps topic filenames to context IDs, it is possible to have multiple compiled Help files written in different natural languages that all use the same set of context IDs.

Compiling HTML Help has a couple of limitations that you should be aware of as well:

  • Source files must be in HTML format. Web forms cannot be compiled into .chm files.

  • The entire compiled file must be downloaded to the user' s machine. If you have a large Help file and your users are connected via modem, it can take a long time to load Help the first time. Once the Help file is downloaded, however, it can be accessed quickly.

The following lessons discuss creating compiled HTML Help files and using those files from Web applications in more detail.


Lesson 2: Using the HTML Help Workshop

Microsoft Visual Studio .NET includes the HTML Help Workshop, which is installed in a separate location on the developer' s machine. In this lesson, you will learn how to use the HTML Help Workshop to create a compiled Help file that includes a table of contents, an index, and full-text search capabilities.

After this lesson, you will be able to

  • Create a new Help project

  • Add topics to a Help project and compile those topics into a single Help file

  • Build a table of contents automatically during compilation

  • Modify an automatically generated table of contents

  • Add index keywords to Help topics

  • Create different types of cross-references between topics within a Help file

  • Add special features such as associative links and Related Topics links to Help topics

  • Enable full-text searches within a Help file

  • Compile and preview Help from the HTML Help Workshop or from the command line

Estimated lesson time: 30 minutes
Creating HTML Help

An HTML Help file is comprised of individual HTML pages, each representing a single Help topic. Each Help topic can include keywords that are included in the index and cross-references to other topics. HTML Help projects include the types of files described in Table 14-1.

Table 14-1 HTML Help Project Files
File type | Use to
Help topic (.htm) | Create content to display in Help
Project (.hhp) | Define how the project is compiled and which Help topic, contents, and index files to include
Contents (.hhc) | Create a table of contents for the Help file
Index (.hhk) | Create an index for the Help file
Search stop list (.stp) | Exclude specific words from searches
Compiled (.chm) | Deploy the completed Help

The HTML Help Workshop provides tools to organize, edit, and compile these files. By default, Visual Studio .NET installs the HTML Help Workshop in the Program Files\HTML Help Workshop folder during setup.

To create a new HTML Help project, follow these steps:

  1. From the HTML Help Workshop File menu, choose New. The Workshop starts the New Project Wizard.

  2. The wizard lets you convert an existing WinHelp project or create a new HTML Help project. Click Next to create a new project. The wizard displays the Destination page.

  3. Type the location and name of the Help project file you want to create, and then click Next. The wizard displays the Existing Files page.

  4. Select the check boxes for any contents, index, or topic files you have already created and click Next. If you select any of these existing files, the wizard prompts you for the location of each of the selected file types. Otherwise, the wizard creates a new, empty Help project, as shown in Figure 14-4.

    Figure 14-4 A new, empty Help project

Using HTML Help Workshop is fairly simple. You use the Project, Contents, and Index tabs on the left pane to edit the contents of the project, contents, and index files. The buttons at the top of the pane create new files, open existing files, compile the project, display the compiled project, and display HTML Workshop Help.

To add topic files to the Help project, follow these steps:

  1. Click New at the top of the HTML Help Workshop window, select HTML File, and then specify a title for the topic. The Workshop creates a new Help topic file, as shown in Figure 14-5.

    Figure 14-5 Creating topic files

  2. Save the HTML file, then click Add/Remove Topic Files on the Project tab. The Workshop displays the Topic Files dialog box.

  3. Click Add, and then select the files to add to the project. Click OK.

  4. Click OK again to close the Topic Files dialog box. The Workshop adds the file to the project.

The HTML Help Workshop provides only basic text-editing capabilities. In general, you will author your Help topic files in a full-featured HTML editor, such as Microsoft FrontPage, and then use the Workshop to create the project file and compile the Help.

Setting Project Options

Help project options are displayed on the Project tab of the HTML Help Workshop. These options are stored in the Help project file (.hhp) in plain text and can be modified using the HTML Help Workshop or a text editor such as Notepad.

To set project options in HTML Help Workshop, on the Project tab, click Project Options. The Workshop displays the Project Options dialog box, as shown in Figure 14-6.

Figure 14-6 Setting options

The project options let you specify a title for the Help project, locale information about the project, how the contents and index files are created, whether to support searching, and other features. These items are placed in the OPTIONS section of the project file. For example, the following project options create index and contents files automatically when compiling a Help file named HelpSample.chm, which displays the title “HTML Help Sample” in the Help window:

[OPTIONS]
Auto Index=Yes
Auto TOC=9
Compatibility=1.1 or later
Compiled file=HelpSample.chm
Contents file= Contents.hhc
Default topic=topic1.htm
Display compile progress=No
Full text search stop list file=Search.stp
Index file=Index.hhk
Language=0x409 English (United States)
Title=HTML Help Sample

The project file also includes a FILES section that lists the Help topic files to compile. The order of the files in this section determines the order of items in the contents file when Auto TOC is selected in the OPTIONS section. For example, the following topic files appear in sequential order in Contents:

[FILES]
topic1.htm
topic1a.htm
topic1b.htm
topic2.htm
topic3.htm
Building Tables of Contents

When building the table of contents automatically, the HTML Help Workshop uses the heading level of the topic and its order in the FILES section of the project file to determine how the table of contents is organized.

The Workshop puts <h1> headings at the top level, <h2> headings under those, and so on until a new <h1> heading is encountered. If you have multiple heading levels within a single topic, the Workshop creates subordinate Contents items for each of those headings—even though they all represent the same Help topic. For this reason, it is best to use only one HTML heading element per Help topic when generating Contents automatically.

For example, the following FILES section includes topic files with three heading levels, as indicated by their filenames:

[FILES]
topic1.htm
topic1a.htm
topic1b.htm
topic2.htm
topic2a.htm
topic2b.htm
topic2bi.htm
topic2bii.htm
topic3.htm

At run time, the generated contents files appear as shown in Figure 14-7.

Figure 14-7 Automatic table of contents

The Workshop stores the generated table of contents in an HTML file. You can generate an automatic table of contents, turn off Auto TOC in the project file, and then edit the generated contents file to customize the table of contents.

To edit a contents file, click the Contents tab in the HTML Help Workshop. The Workshop displays the Contents, as shown in Figure 14-8.

Figure 14-8 Editing the contents file

One thing you might have noticed in the automatic Contents shown in Figure 14-8 is that the icons for top-level topics aren' t consistent. For instance, Topic 3 shows a page icon rather than a book icon.

To change the icon displayed for a topic, follow these steps:

  1. Select the contents topic, click Edit Selection, and then click the Advanced tab. The Workshop displays the Table Of Contents Entry dialog box, as shown in Figure 14-9.

    Figure 14-9 Changing the Contents entry icon

  2. Select the image to use for the entry in the Image Index list box, and then click OK.

The contents file is stored as an HTML file that uses object elements to specify the contents items, as shown by the following HTML:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<meta name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1">
<!-- Sitemap 1.0 -->
</HEAD><BODY>
<UL>
   <LI> <OBJECT type="text/sitemap">
          <param name="Name" value="Topic1">
          <param name="Local" value="topic1.htm">
          </OBJECT>
   <UL>
          <LI> <OBJECT type="text/sitemap">
               <param name="Name" value="Topic 1.a">
               <param name="Local" value="topic1a.htm">
               </OBJECT>
          <UL>
               <LI> <OBJECT type="text/sitemap">
                     <param name="Name" value="Topic 1.a.i">
                     <param name="Local" value="topic1ai.htm">
                     </OBJECT>
               <LI> <OBJECT type="text/sitemap">
                     <param name="Name" value="Topic 1.a.ii">
                     <param name="Local" value="topic1aii.htm">
                     </OBJECT>
         </UL>
         <LI> <OBJECT type="text/sitemap">
               <param name="Name" value="Topic 1.b">
               <param name="Local" value="topic1b.htm">
               <param name="ImageNumber" value="1">
               </OBJECT>
   </UL>
   <LI> <OBJECT type="text/sitemap">
         <param name="Name" value="Topic 2">
         <param name="Local" value="topic2.htm">
         </OBJECT>
   <UL>
         <LI> <OBJECT type="text/sitemap">
               <param name="Name" value="">
               <param name="Local" value="topic2.htm">
               </OBJECT>
         <LI> <OBJECT type="text/sitemap">
               <param name="Name" value="Topic 2.a">
               <param name="Local" value="topic2a.htm">
               </OBJECT>
          <LI> <OBJECT type="text/sitemap">
               <param name="Name" value="Topic 2.b">
               <param name="Local" value="topic2b.htm">
               </OBJECT>
   </UL>
   <LI> <OBJECT type="text/sitemap">
         <param name="Name" value="Topic 3">
         <param name="Local" value="topic3.htm">
         <param name="ImageNumber" value="1">
         </OBJECT>
</UL>
</BODY></HTML>

The HTML list items determine the hierarchy of the items in the table of contents. The object elements determine the title, icon, location, and other information about the link. For example, the following contents item links to a company' s New Products page and opens the page in a new window when the user clicks it:

<OBJECT type="text/sitemap">
   <param name="Name" value="New Products">
   <param name="Local" value="http://www.mycompany.com/newproducts.htm">
   <param name="WindowName" value="NewWindow">
   <param name="Comment" value="Sample of text/sitemap params">
   <param name="New" value="1">
   <param name="ImageNumber" value="7">
</OBJECT>
Adding Index Entries

The HTML Help Workshop can automatically build an index file from object elements entered in the topic files. Within a topic file, an index entry is created as an ActiveX object with a specific classID, as shown in the following HTML:

<Object type="application/x-oleobject" classid="clsid:1e2a7bd0-dab9-11d0-
b93a-00c04fc99f9e">
   <param name="Keyword" value="Index Item 1">
   <param name="Keyword" value="Index Item 1, Subitem a">
   <param name="Keyword" value="Index Item 1, Subitem b">
</OBJECT>

The preceding HTML creates three index entries for that link to the topic file, as shown in Figure 14-10.

Figure 14-10 Automatic index entries at run time

The HTML Help Workshop does not automatically store index entries from topic files in the index file; however, you can add Index entries to the index file to help organize or to supplement the automatic entries. The index file (.hhk) and the topic index entries are added together to create the Index displayed in the compiled Help file. For example, the following index file (.hhk) is combined with the automatic entries to create the results shown in Figure 14-11:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<meta name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1">
<!-- Sitemap 1.0 -->
</HEAD><BODY>
<UL>
   <LI> <OBJECT type="text/sitemap">
         <param name="Name" value="Index Item 1">
         <param name="Name" value="Topic 1.b">
         <param name="Local" value="topic1b.htm">
         </OBJECT>
   <LI> <OBJECT type="text/sitemap">
         <param name="Name" value="Index Item 2">
         <param name="Name" value="Topic 2.a">
         <param name="Local" value="topic2a.htm">
         </OBJECT>
</UL>
</BODY></HTML>
Figure 14-11 Combined index entries
Adding Cross-References

There are three ways to cross-reference Help topics within HTML Help:

  • Use standard HTML hyperlinks to create a link from one Help topic to another. Hyperlinks allow one-to-one relationships.

  • Use the Related Topics command to create a link from one topic to a list of other specific topics files.

  • Use associative links (ALinks) to create links from one topic to a list of other topics based on keywords entered in those topic files.

Since you already know how to create hyperlinks (by now you' d better), the following sections discuss how to add Related Topics and create associative links.

Adding Related Topics

You can add related topics as a cross-reference or a hyperlink.

  • To add a Related Topics cross-reference to a Help topic, insert an HTML Help ActiveX control in the topic file. For example, the following object element displays a button that lists two Related Topics as pop-up menu choices:

    <OBJECT id=hhctrl type="application/x-oleobject"
        classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11"
        codebase="hhctrl.ocx#Version=4,74,9273,0"
        width=100
        height=100>
    <PARAM name="Command" value="Related Topics, MENU">
    <PARAM name="Button" value="Text:Related Topics">
    <PARAM name="Item1" value="Topic 2;Topic2.htm">
    <PARAM name="Item2" value="Topic 3;Topic3.htm">
</OBJECT>

    At run time, the preceding HTML appears as shown in Figure 14-12.

    Figure 14-12 Related topics

  • To display related topics as a hyperlink instead of as a button control, specify a Text parameter rather than a Button parameter in the object element. For example, the following HTML displays the preceding Related Topics as a hyperlink:

    <OBJECT id= hhctrl type="application/x-oleobject"
        classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11"
        codebase="hhctrl.ocx#Version=4,74,9273,0"
        width=100
        height=100>
   <PARAM name="Command" value="Related Topics, MENU">
   <PARAM name="Text" value="Text:Related Topics">
   <PARAM name="Item1" value="Topic 2;Topic2.htm">
   <PARAM name="Item2" value="Topic 3;Topic3.htm">
</OBJECT>
Creating Associative Links

Associative links reference keywords rather than specific filenames. For this reason, they are useful for cross-referencing between HTML Help files—you don' t have to know how the topics within a compiled file are named, you just need to know which keywords were used. Associative links are useful within a single Help file as well—filenames can change during development, and it is easier to maintain a keyword list than it is to update filenames within cross-references.

There are two parts to an associative cross-reference:

  • The keywords entered in the target topic files provide the destinations that cross-references link to.

  • The link entered in the referencing topic file provides the source from which the cross-reference links.

To create associative link keywords, insert an object element in the target topic. For example, the following object element creates the associative link keyword “Link1”:

<Object type="application/x-oleobject" classid="clsid:1e2a7bd0-dab9-11d0-
b93a-00c04fc99f9e">
   <param name="ALink Name" value="Link1">
</OBJECT>

Associative links are very similar to keywords used in indexing HTML Help. However, they do not appear in the Index. The reason for this is to allow cross-referencing and indexing to be maintained separately.

To create a link to topics with a specific keyword, insert an HTML Help control ActiveX object in the topic file making the cross-reference. For example, the following object element displays a list of cross-references in a pop-up menu when the user clicks the control:

<OBJECT id=hhctrl type="application/x-oleobject"
        classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11"
        codebase="hhctrl.ocx#Version=4,74,9273,0"
        width=100
        height=100>
    <PARAM name="Command" value="ALink, menu">
    <PARAM name="Button" value="Text:See Also">
    <PARAM name="Flags" value=",,1">
    <PARAM name="Item1" value="">
    <PARAM name="Item2" value="Link1">
</OBJECT>

At run time the preceding HTML displays a list of topic titles that contain the associative link keyword “Link1”, as shown in Figure 14-13.

Figure 14-13 Associative links

To display associative links as a hyperlink rather than as a button control, omit the Button parameter from the object element and call the object' s Click event from the hyperlink. For example, the following HTML displays the links when the user clicks the See Also hyperlink:

<OBJECT id=hhctrl type="application/x-oleobject"
        classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11"
        codebase="hhctrl.ocx#Version=4,74,9273,0"
        width=100
        height=100>
    <PARAM name="Command" value="ALink, menu">
    <PARAM name="Flags" value=",,1">
    <PARAM name="Item1" value="">
    <PARAM name="Item2" value="Link1">
</OBJECT>

<a href="#" onclick="hhctrl.Click()">See also</a>
Enabling Searches

To enable full-text searches within a Help file, select Compile Full-Text Search Information from the Compiler tab of the Project Options dialog box.

When full-text searching is enabled, the HTML Help Workshop builds a concordance table of all the words in your topic files and includes that table in the compiled Help file. Because this table references all words in the topic files, it can become quite large. To help control the size of this table, you can omit words that are not useful for searching. For example, you might want to omit articles, conjunctions, personal pronouns, and numbers from full-text searching.

To omit words from full-text searching, follow these steps:

  1. Create a text file listing the words to omit.

  2. Click Change Project Options on the HTML Help Workshop Project tab.

  3. On the Compiler tab of the Project Options dialog box, type the filename of the list of words to omit in the Full Text Search Stop List text box.

When a user tries to search on a word that you include in the stop list, HTML Help displays a message box stating that the phrase cannot be searched on.

Compiling and Previewing Help

To compile an HTML Help project, choose one of the following two steps:

  • On the Project tab of the HTML Help Workshop, click Save All Files And Compile.

  • Use the HTML Help Compiler (hhc.exe) from the command line. For example, the following command line compiles the HelpSample.hhp Help project.

    hhc.exe HelpSample.hhp

To preview a compiled HTML Help project, choose one of the following two steps:

  • At the top of the HTML Help Workshop window, click View Compiled File button, and then select the filename of the compiled file.

  • Double-click the compiled Help file (.chm) in Windows.

    important

    You cannot recompile a Help file while the compiled Help file is open for viewing. You must close the compiled Help file before recompiling.


Lesson 3: Linking HTML Help to Web Forms

In this lesson, you will learn how to display HTML Help from within a Web form. This lesson expands on the techniques shown briefly in Lesson 1 by showing the different ways you can display HTML Help, describing the differences between compiled and non-compiled Help, and explaining how to make Help respond to user actions in a Web application.

After this lesson, you will be able to

  • Display compiled and non-compiled HTML Help using the HTML Help Viewer

  • Display HTML Help in an ordinary browser window

  • Cause different Help topics to display, depending on which controls in a Web form have focus

Estimated lesson time: 10 minutes
Displaying the HTML Help Viewer

As shown in Lesson 1, HTML Help is displayed in the HTML Help Viewer using the showHelp method. How you use showHelp depends on whether your HTML Help is compiled:

  • Non-compiled Help can be displayed using the address of the Help topic via the Internet, as shown here:

    <a href="#" onclick="window.showHelp('Topic1.htm')">Help!</a>

  • Compiled Help must be copied locally before you can display topics using showHelp, as shown here:

    <a href="#" onclick="window.showHelp('c:\\Help\\HelpSample.chm')">Help!</a>

The HTML Help Viewer displays differently, depending on whether the Help file is compiled, as shown in Figure 14-14.

Figure 14-14 Compiled vs. non-compiled Help

As you can see from Figure 14-14, compiled Help displays Contents, Index, and Search features, while non-compiled Help displays only the requested Help topic file. By using compiled Help, you get the full features of the HTML Help system; however, the user must first download the compiled Help file.

To download a compiled Help file to the user' s machine, create a hyperlink to the compiled Help file and provide the user with instructions on where to save the Help file. For example, the following HTML creates a hyperlink that downloads the HelpSample.chm file and instructs the user to save the file to a specific folder:

<a href="HelpSample.chm">Click here</a> to download the sample Help file and
then save the file to the C:\Help folder on your computer.</p>

When the user clicks the preceding hyperlink, the browser asks the user whether he or she wants to open or save the Help file. When the user saves the file to the specified directory, the showHelp method can display topics from the file.

Displaying HTML Help in the Browser

The showHelp method isn' t the only way to display compiled Help files. You can also display topics in the browser using URLs from within the Help file itself. The browser can' t display HTML Help' s Contents, Index, or Search features, but it can open topics from the compiled file across the Internet, which is something showHelp can' t do!

To display a compiled Help file in the browser, create a hyperlink using the topic file' s URL within the compiled file. For example, the following hyperlink displays Topic1.htm in a new browser window:

<a href="ms-its:http://www.mycompany.com/Help/HelpSample.chm::/topic1.htm"
target="HelpWin">Show Help</a>

The preceding hyperlink uses the ms-its: pluggable protocol and the Help path syntax to display a topic file from within a compiled Help file stored on the Web. The ms-its: protocol is provided by Internet Explorer version 4.0 and later. The HTML Help path syntax has the form compiledfile::/topicfile, where compiledfile is the URL for the compiled Help file and topicfile is the HTML topic file name within the compiled Help file.

Displaying Context-Sensitive Help

If you are displaying compiled HTML Help in the HTML Help Viewer, you can use context IDs to identify topics within the Help file and associate those topics with tasks in your Web application. This type of Help is called context-sensitive because the topic displayed depends upon the current context (or task) within the application.

Trying to display context-sensitive Help within a Web application presents a couple of challenges:

  • Although you can capture the Help key (F1) using the onkeydown event and the window.event.keyCode property, you can' t cancel the default action for F1 within Internet Explorer. The default action is to display Internet Explorer Help.

  • The contextID argument for the showHelp method doesn' t work from within a Web form or an HTML page.

To solve these problems, you must choose a mechanism other than F1 to display Help within your Web application, and you must use the HTML Help path syntax to display the appropriate Help topic. The following HTML uses the onfocus event to display different Help topics as the user moves between controls on a Web form:

VBScript
<HTML>
   <HEAD>
   <title>Context Help</title>
<script language="vbscript">
Dim LastTopic

Sub ShowContextHelp(Topic)
   If (document.all("chkHelp").checked And LastTopic <> Topic) Then
         ' Display Help for this context.
         window.showHelp "c:\help\HelpSample.chm::/" & Topic
         ' Store topic name.
         LastTopic = Topic
   End If
End Sub
</script>
   </HEAD>
   <body onfocus="ShowContextHelp('Topic1.htm')>
         <form id="WebForm2" method="post" runat="server">
               <H2>Context Sensitive Help</H2>
               <INPUT onfocus="ShowContextHelp('Topic2.htm')" type="text">
               <BR>
               <INPUT onfocus="ShowContextHelp('Topic3.htm')" type="button"
               value="Button">
               <BR>
               <INPUT id="chkHelp" type="checkbox" name="chkHelp">Show user
               assistance.
         </form>
   </body>
</HTML>
JScript
<HTML>
   <HEAD>
         <title>Context Help</title>
   <script language="jscript">
   var LastTopic;

   function ShowContextHelp(Topic)
   {
         if ((document.all("chkHelp").checked) && (LastTopic != Topic))
         {
               // Display Help for this context.
               window.showHelp("c:\\help\\HelpSample.chm::/" + Topic);
               // Store topic name.
               LastTopic = Topic;
         }
   }
   </script>
   </HEAD>
   <body onfocus="ShowContextHelp('Topic1.htm')">
         <form id="WebForm2" method="post" runat="server">
               <h2>Context Sensitive Help</h2>
               <INPUT type="text" onfocus="ShowContextHelp('Topic2.htm')">
               <br>
               <INPUT type="button" value="Button"
               onfocus="ShowContextHelp('Topic3.htm')">
               <br>
               <INPUT id="chkHelp" type="checkbox">Show user assistance.
         </form>
   </body>
</HTML>

The CheckBox control allows the user to turn on context-sensitive Help. At run time, the ShowContextHelp procedure displays the Help topic for each item on the page when the focus changes to a new item, as shown in Figure 14-15.

Figure 14-15 Context-sensitive Help


Summary

  • You can provide user assistance within Web applications by using ToolTips, Web forms or HTML pages, or HTML Help.

  • HTML Help provides built-in Contents, Index, and Search capabilities.

  • Use the window object' s showHelp method in client-side scripts to display the HTML Help Viewer.

  • Use the HTML Help Workshop to create HTML Help projects and to compile HTML Help.

  • Use the HTML Help ActiveX object to add index keywords, related topic links, and associative links to Help topics.

  • To display compiled Help files from a Web application, you must first download the file to the user' s machine.


Lab: Building and Using an HTML Help File

In this lab, you will create a compiled HTML Help file containing several topics, Contents, Index, and Search features. Then you will link the Help file to a Web application.

Estimated lesson time: 30 minutes
Exercise 1: Creating a Help Project

In this exercise, you will use the skills you learned in Lesson 2 to create a new HTML Workshop Help project, create five Help topic files, and add those files to the Help project. To keep this exercise simple, the content of the Help topic files is minimal, although you can supply any additional content you feel like creating.

To create a new HTML Help Project

  1. Start the HTML Help Workshop. Choose New from the File menu, select Project from the New dialog box, and then follow the New Project Wizard pages to create an empty project in a folder named HelpDemo on your computer.

  2. Create five new HTML files titled Topic1 through Topic5. Add headings and save the files as shown in the following table:

    3. Title | Heading | Save as
<title>Topic1</title> | <h1>Topic1</h1> | Topic1.htm
<title>Topic2</title> | <h2>Topic2</h2> | Topic2.htm
<title>Topic3</title> | <h3>Topic3</h3> | Topic3.htm
<title>Topic4</title> | <h3>Topic4</h3> | Topic4.htm
<title>Topic5</title> | <h2>Topic5</h2> | Topic5.htm

  3. Add each of the topic files to the Help project. When complete, the HTML Help Workshop should appear as shown in Figure 14-16.

    Figure 14-16 Creating a Help project
Exercise 2: Enabling Contents, Index, and Searching

In this exercise, you will continue to use the skills you learned in Lesson 2 to automatically generate Contents, Index, and Search features when the Help project is compiled.

To enable Contents, Index, and Search features

  1. In the Help project Options dialog box, click the Files tab and select check boxes to automatically create a contents file and to include keywords in the index, and then type names for the contents and index files, as shown in Figure 14-17.

    Figure 14-17 Creating contents and index files automatically

  2. In the Help project options, click the Compiler tab and select the Compile Full-Text Search Information check box, as shown in Figure 14-18. Click OK to close the dialog box.

    Figure 14-18 Enabling full-text searches

  3. On the Project tab of the HTML Help Workshop, click Save All Files And Compile. The Workshop builds the contents, index, and search information and then compiles the file and displays a log of the results, as shown in Figure 14-19.

    Figure 14-19 Compiler log

  4. Click View Compiled File on the HTML Help Workshop toolbar, enter the location and name of the HelpDemo.chm file in the View Compiled File dialog box, and then click View. The Workshop displays the compiled Help file, as shown in Figure 14-20.

    Figure 14-20 Compiled Help
Exercise 3: Adding Index Keywords

In this exercise, you will add indexed keywords to the Help topic files and then recompile the Help file to create automatic index entries.

To add index keyword to topic files

  1. In the HTML Help Workshop, open the Topic2.htm topic file and insert a new line between the <body> </body> tags.

  2. From the Edit menu, choose Compiler Information. The Workshop displays the Compiler Information dialog box.

  3. Click Add and type “Item1” as the name of the index keyword to add to the topic, and then click OK. Click OK again to close the Compiler Information dialog and add the index entry to Topic2.htm. The following HTML shows the generated text that is added to Topic2.htm:

    <Object type="application/x-oleobject"
classid="clsid:1e2a7bd0-dab9-11d0-b93a-00c04fc99f9e">
   <param name="Keyword" value="Item1">
</OBJECT>

  4. Copy the preceding HTML to each of the other topic files. Add or change the keyword parameters to create different keywords. For example, the following HTML inserts three index keywords in a topic file:

    <Object type="application/x-oleobject"
classid="clsid:1e2a7bd0-dab9-11d0-b93a-00c04fc99f9e">
   <param name="Keyword" value="Item1">
   <param name="Keyword" value="new">
   <param name="Keyword" value="improved">
</OBJECT>

  5. Recompile the Help project and view the resulting index. It should appear similar to Figure 14-21.

    Figure 14-21 Keywords in the index
Exercise 4: Linking Help to a Web Form

In this exercise, you will use the skills you learned in Lesson 3 to link the compiled Help you created in the preceding exercises to a simple Web form.

To link compiled Help to a Web form

  1. Create a new, empty Web application project named vbHelpDemo or csHelpDemo.

  2. Add the following HTML to Webform1 to instruct the user how to download the Help file and to provide a way to display Help on the Web form:

    <p>To use Help with this Web form:</p>
<ol>
   <li><a href="HelpDemo.chm">Click here</a> to download Help.</li>
   <li>Save the file to the c:/help folder on your computer.</li>
   <li>Then use the buttons below to display Help topics.</li>
</ol>
<INPUT type="button" value="Show Help" onclick="window.showHelp('c:\\help\\HelpDemo.chm')">
<br>
<INPUT type="button" value="Show Topic3" onclick="window.showHelp('c:\\help\\HelpDemo.chm::/Topic3.htm)">
<brL
<INPUT type="button" value="Show Topic5" onclick="window.showHelp('c:\\help\\HelpDemo.chm::/Topic5.htm)">

  3. Copy the compiled Help file created in the preceding exercises to the Web application project directory.

  4. Run the Web application. After you download the Help file, clicking the HTML Button controls should display Help, as shown in Figure 14-22.

    Figure 14-22 Displaying Help from a Web form


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. Which HTML attribute does the server control' s Tooltip property map to in the HTML rendered by ASP.NET?

  2. How does displaying HTML Help using the window object' s showHelp method differ from displaying HTML Help using a browser window?

  3. How do you add index keywords to a Help topic?


Globalizing Web Applications

Chapter 15
Globalizing Web Applications
Lesson 1: Choosing an Approach
Lesson 2: Creating and Using Satellite Assemblies
Lesson 3: Globalization Issues
About This Chapter

Globalization is the process of creating an application that meets the needs of users from multiple cultures. This process involves more than just translating the user interface elements of an application into multiple languages—it also includes using the correct currency, date and time format, calendar, writing direction, sorting rules, and other issues. Accommodating these cultural differences in an application is called localization.

Fortunately, the Microsoft .NET Framework simplifies localization tasks substantially by making its formatting, date/time, sorting, and other classes culturally aware. Using classes from the System.Globalization namespace, you can set your application' s current culture and much of the work is done for you automatically!

In this chapter, you will learn how to detect a user' s culture and create appropriate responses, ranging from redirecting the user to a separate, localized Web application, to handing cultural differences within code and displaying a localized user interface.

Before You Begin

To complete this chapter, you must

  • Be familiar with HTML

  • Be comfortable creating Web forms in HTML mode


Lesson 1: Choosing an Approach

Before you begin the process of localization, you need to understand the advantages and disadvantages of the different approaches to globalizing Web applications. In this lesson, you will learn about three different ways to structure your application for globalization. These approaches are organized from simplest to most complex. The last approach is covered in more detail in Lesson 2.

After this lesson, you will be able to

  • Choose a globalization approach that best meets your needs

  • Detect and respond to a user' s culture

  • Create a culture-specific Web application using settings in Web.config

  • Change the culture used within your application to evaluate dates, format currencies, and handle other culture-dependent tasks

  • Detect the culture currently in use by the application

  • Explain the difference between the CurrentCulture and CurrentUICulture properties

Estimated lesson time: 30 minutes
Ways to Globalize Web Applications

There are several different approaches to creating Web applications that support multiple cultures. Each approach is based on the globalization tools that the .NET Framework provides. Choose a single approach or a combination of approaches based on your applications needs, as described in the Table 15-1.

Table 15-1 Globalization Approaches
Approach | Description | Best for
Detect and redirect | Create a separate Web application for each supported culture, and then detect the user' s culture and redirect the request to the appropriate application. | Applications with lots of text content that requires translation and few executable components.
Run-time adjustment | Create a single Web application that detects the user' s culture and adjusts output at run time using format specifiers and other tools. | Simple applications that present limited amounts of content.
Satellite assemblies | Create a single Web application that stores culture-dependent strings in resource files that are compiled into satellite assemblies. At run time, detect the user' s culture and load strings from the appropriate assembly. | Applications that generate content at run time or that have large executable components.

All three of the preceding approaches depend on detecting the user' s culture at run time and then forming a response based on that information. The following sections show in greater detail how to detect the user' s culture and respond by using each of these approaches. These sections also provide more detail on the relative advantages of each approach.

Detecting the User' s Culture

The .NET Framework provides components for supporting multiple cultures and language in the System.Globalization namespace. The CultureInfo, Calendar, and comparison classes that you use throughout this chapter are all part of that namespace. Use the following line to import that namespace so that you can use its classes without qualifying the references:

Visual Basic .NET
Imports System.Globalization
Visual C#
using System.Globalization;

ASP.NET uses the Request object' s UserLanguages property to return a list of the user' s language preferences. The first element of the array returned by UserLanguages is the user' s current language. You can use that value to create an instance of the CultureInfo class representing the user' s current culture.

To get the user' s culture at run time, follow these steps:

  1. Get the Request object' s UserLanguages property.

  2. Use the returned value with the CultureInfo class to create an object representing the user' s current culture.

For example, the following code gets the user' s culture and displays the English name and the abbreviated name of the culture in a label the first time the page is displayed:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
   ' Run the first time the page is displayed
   If Not IsPostBack Then
      Dim sLang As String
      ' Get the user's preferred language.
      sLang = Request.UserLanguages(0)
      ' Create a CultureInfo object from it.
      Dim CurrentCulture As New CultureInfo(sLang)
      lblCulture.Text = CurrentCulture.EnglishName & ": " & _
        CurrentCulture.Name
   End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
   // Run the first time the page is displayed
   if (!IsPostBack)
   {
         // Get the user's preferred language.
         string sLang = Request.UserLanguages[0];
         // Create a CultureInfo object from it.
         CultureInfo CurrentCulture = new CultureInfo(sLang);
         lblCulture.Text = CurrentCulture.EnglishName + ": " +
           CurrentCulture.Name;
   }
}
tip | The CultureInfo class' s Name property and the Request object' s UserLanguages array values use different capitalization schemes. If you are comparing the two values, be sure to convert the Name property to lowercase.
Redirecting to Culture-Specific Web Applications

Conceptually, redirecting is the simplest way of dealing with multiple cultures. When a user requests the default page for your application, you detect his or her culture and then redirect the response to the appropriate Web application, as shown in Figure 15-1.

Figure 15-1 Detect and redirect

This approach has some advantages in addition to conceptual simplicity:

  • Content is maintained separately, so this approach allows the different applications to present very different information, if needed.

  • Users can be automatically directed to sites that are likely to be geographically close, and so can better meet their needs.

  • Content files (Web forms and HTML pages, for example) can be authored in the appropriate natural language without the complexity of including resource strings.

Maintaining content separately is best suited for Web applications that present large amounts of content that must be translated or otherwise changed for other cultures. Using this approach requires that the executable portion of the Web application be compiled and deployed separately to each culture-specific Web site. This requires more effort to maintain consistency and debug problems across Web sites.

Redirecting Based on Primary Language

Both the Request object' s UserLanguages array and the CultureInfo class' s Name property return culture information in two parts: The first two letters are the language code; the last two letters contain a region code. For example, English US (en-US) and English United Kingdom (en-GB) both use English, but display different currencies and date order.

To redirect requests based on language alone, follow these steps:

  1. Get the user' s preferred language or CurrentCulture, as shown previously.

  2. Get the first two letters of the returned value.

  3. Compare those letters to the list of language codes.

For example, the following code detects the user' s language, then redirects the user to one of several different language-specific Web sites:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
   Dim sLang As String
   ' Get the user's preferred language.
   sLang = Request.UserLanguages(0)
   ' Get the first two characters of language.
   sLang = sLang.Substring(0, 2)
   ' Redirect user based on his/her culture.
   Select Case sLang
         Case "en"
            ' Use US site.
            Response.Redirect("http://www.contoso.com/usa")
         Case "es"
            ' Use Spanish site.
            Response.Redirect("http://www.contoso.com/es")
         Case "de"
            ' Use German site.
            Response.Redirect("http://www.contoso.com/de")
         Case "zh"
            ' Use Chinese site.
            Response.Redirect("http://www.contoso.com/zh")
         Case Else
            ' Use US site.
            Response.Redirect("http://www.contoso.com/usa")
   End Select
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
   // Get the user's preferred language.
   string sLang = Request.UserLanguages[0];
   // Get the first two characters of language.
   sLang = sLang.Substring(0, 2);
   // Redirect user based on his/her culture.
   switch (sLang)
   {
         case "en":
               // Use US site.
               Response.Redirect("http://www.contoso.com/usa");
               break;
         case "es":
               // Use Spanish site.
               Response.Redirect("http://www.contoso.com/es");
               break;
         case "de":
               // Use German site.
               Response.Redirect("http://www.contoso.com/de");
               break;
         case "zh":
               // Use Chinese site.
               Response.Redirect("http://www.contoso.com/zh");
               break;
         default:
               // Use US site.
               Response.Redirect("http://www.contoso.com/usa");
               break;
         }
   }

See the CultureInfo Class topic in the Microsoft Visual Studio .NET online Help for a listing of the language and culture codes.

Setting Culture in Web.config

Use the Web.config file' s globalization element to create a culture-specific Web application. The culture attribute of the globalization element determines how the Web application deals with various culture-dependent issues, such as dates, currency, and number formatting.

Because a Web application can have only one Web.config file, setting the culture attribute makes that Web application culture-specific. All requests and responses to that Web application will be handled using the specific culture entered in the culture attribute.

For example, the following Web.config entry handles the Web application' s requests and responses using the Arabic (Saudi Arabia) [ar-SA] culture:

<globalization requestEncoding="utf-8" responseEncoding="utf-8"
 culture="ar-SA" />

At run time, the Web application displays dates, currency, and number formats according to the culture attribute setting in the Web.config' s globalization element, as shown in Figure 15-2.

Figure 15-2 A culture-specific response

In Figure 15-2 the dates, times, numbers, and currencies are displayed using the Arabic (Saudi Arabia) settings. However, the entire page is not displayed in right-to-left formatting, as you might expect for Arabic. To display the page correctly, you must set the HTML dir attribute for the page' s body element, as shown here:

<body dir="rtl">

You can use the dir attribute individually on panels, text boxes, or other controls as well. Setting the dir attribute on the body element applies right-to-left formatting to the entire page, as shown in Figure 15-3.

Figure 15-3 Right-to-left formatting
Adjusting to Current Culture at Run Time

By default, Web applications run on the server using a neutral culture. Neutral cultures represent general languages, such as English or Spanish, rather than a specific language and region. When you set the culture attribute for a Web application in Web.config, ASP.NET assigns that culture to all the threads running for that Web application. Threads are the basic unit to which the server allocates processor time—ASP.NET maintains multiple threads for a Web application within the aspnet_wp.exe worker process.

Using Web.config to set the culture creates a static association between the application and a specific culture. Alternatively, you can set the culture dynamically at run time using the Thread class' s CurrentCulture property, as shown here:

Visual Basic .NET
Imports System.Globalization
Imports System.Threading

Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
   Dim sLang As String
   ' Get the user's preferred language.
   sLang = Request.UserLanguages(0)
   ' Set the thread's culture to match the user culture.
   Thread.CurrentThread.CurrentCulture = New CultureInfo(sLang)
End Sub
Visual C#
using System.Globalization;
using System.Threading;

private void Page_Load(object sender, System.EventArgs e)
{
   // Get the user's preferred language.
   sLang = Request.UserLanguages[0];
   // Set the thread's culture to match the user culture.
   Thread.CurrentThread.CurrentCulture = new CultureInfo(sLang);
}

The preceding code detects the user' s culture and then sets the culture of the current thread to match. ASP.NET will then format date, currency, and numeric strings using the user' s culture.

Setting the culture dynamically at the thread level has the following advantages over creating separate Web applications for each culture:

  • All cultures share the same application code, so the application doesn' t have to be compiled and deployed for each culture.

  • The application resides at a single Web address—you don' t need to redirect users to other Web applications.

  • The user can choose from a full array of available cultures.

Setting the culture dynamically is best suited for simple Web applications that don' t contain large amounts of text that must be translated into different languages. To provide large amounts of translated content through a single Web application, use the technique describe in the section “Using Satellite Assemblies” later in the chapter.

Setting the Current Culture

The following code illustrates the main advantages of setting the culture dynamically at the thread level. The first time the page loads, the code builds a list of all available cultures and stores them in a drop-down list box. When the user chooses a culture from the drop-down list, the code assigns that culture to the current thread and ASP.NET displays the Web form using the selected culture.

Visual Basic .NET
Imports System.Globalization
Imports System.Threading

Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
   ' Add cultures to drop-down list box the first time the page is displayed.
   If Not IsPostBack Then
      ' Create an array of specific cultures.
      Dim arrCultures() As CultureInfo = _
        CultureInfo.GetCultures(CultureTypes.SpecificCultures)
      Dim item As CultureInfo
      ' For each specific culture
      For Each item In arrCultures
            ' Add the name of the culture as an item in the list box.
            drpCulture.Items.Add(New ListItem(item.EnglishName, item.Name))
            ' If the item is the thread's current culture, select it.
          If item.Name = Thread.CurrentThread.CurrentCulture.Name Then
                ' Select this list item
                drpCulture.SelectedIndex = drpCulture.Items.Count - 1
            End If
      Next
  End If
End Sub

Private Sub drpCulture_SelectedIndexChanged(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles drpCulture.SelectedIndexChanged
    ' Change the culture of the current thread to match selection.
    Thread.CurrentThread.CurrentCulture = New _
      CultureInfo(drpCulture.SelectedItem.Value)
    ' Get the culture for the selected item.
    Dim SelectedCulture As New CultureInfo(drpCulture.SelectedItem.Value)
End Sub

Private Sub Page_PreRender(ByVal sender As Object, _
  ByVal e As System.EventArgs) Handles MyBase.PreRender
    ' Display the culture's native name in the heading.
    head1.InnerHtml = Thread.CurrentThread.CurrentCulture.NativeName
    ' Display Date, Currency, and Numeric strings formatted for the culture.
    Label1.Text = DateTime.Now.ToString("F")
    Label2.Text = 1234567890.ToString("C")
    Label3.Text = 1234567890.ToString("N")
End Sub
Visual C#
using System.Globalization;
using System.Threading;

private void Page_Load(object sender, System.EventArgs e)
{
   // Add cultures to drop-down list box the first time the page is displayed.
   if (!IsPostBack)
   {
         // Create an array of specific cultures.
         CultureInfo[] arrCultures =
           CultureInfo.GetCultures(CultureTypes.SpecificCultures);
         // For each specific culture
         foreach (CultureInfo item in arrCultures)
         {
               // Add the name of the culture as an item in the list box.
               drpCulture.Items.Add(new ListItem(item.EnglishName,
                 item.Name));
               // If the item is the thread's current culture, select it.
               if (item.Name == Thread.CurrentThread.CurrentCulture.Name)
                     // Select this list item
                     drpCulture.SelectedIndex = drpCulture.Items.Count - 1;
         }
   }
}

private void drpCulture_SelectedIndexChanged(object sender,
  System.EventArgs e)
{
   // Change the culture of the current thread to match selection.
   Thread.CurrentThread.CurrentCulture = new
     CultureInfo(drpCulture.SelectedItem.Value);
   // Get the culture for the selected item.
   CultureInfo SelectedCulture = new
     CultureInfo(drpCulture.SelectedItem.Value);
}

private void Page_PreRender(object sender, System.EventArgs e)
{
   // Display the culture's native name in the heading.
   head1.InnerHtml = Thread.CurrentThread.CurrentCulture.NativeName;
   // Display Date, Currency, and Numeric strings formatted for the culture.
   Label1.Text = DateTime.Now.ToString("F");
   Label2.Text = 1234567890.ToString("C");
   Label3.Text = 1234567890.ToString("N");
}

The preceding code sets the heading and label text in the PreRender event so that the culture change takes place before the text is stored in those controls. The ToString method in the preceding code uses format specifiers to format each string according to the current culture. For more information about format specifiers, see the “Formatting Types” topic in the Visual Studio .NET online Help. Figure 15-4 shows the preceding code in action on a Web form.

Figure 15-4 Setting the culture at the thread level

When the user selects a new culture from the drop-down list box in Figure 15-4, the heading, calendar, date, currency, and numeric strings are translated automatically.

Responding to the Thread' s Culture

You can also get the culture that a Web application is running under by using the thread' s CurrentCulture property. For example, the following code displays a panel on a Web form using right-to-left formatting if the current culture is Arabic or Hebrew:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
   ' Get culture of current thread.
   Dim CurrentCulture As CultureInfo = Thread.CurrentThread.CurrentCulture
   ' Get the primary language.
   Dim sLang As String = CurrentCulture.Name.Substring(0, 2)
   ' If it's Arabic or Hebrew, display right-to-left.
   If sLang = "ar" or sLang = "he" Then
         panel1.Attributes.Add("dir", "rtl")
   End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
        // Get culture of current thread.
        CultureInfo CurrentCulture = Thread.CurrentThread.CurrentCulture;
        // If culture is Arabic or Hebrew, use right-to-left layout.
        string sLang  = CurrentCulture.Name.Substring(0, 2);
        if ((sLang == "ar") ││(sLang == "he"))
         {
               Panel1.Attributes.Add("dir", "rtl");
         }
}
Using Satellite Assemblies

The preceding section showed you how to handle cultural differences within a single Web application, but it omitted one important detail: how do you handle translated content? That is a job for satellite assemblies.

Satellite assemblies allow you to store the translated strings for each culture in a separate, resource-only assembly file that you can load automatically based on the setting of the CurrentUICulture property. Using satellite assemblies provides the same advantages as adjusting to the culture at run time, plus satellite assemblies simplify displaying content from multiple translated sources. The next lesson describes how to create and use satellite assemblies in detail.


Lesson 2: Creating and Using Satellite Assemblies

Satellite assemblies are assembly files (.dll) that contain localized resources for an application. Each satellite assembly file contains the resources for one culture. An application can have many satellite assemblies, depending on how many cultures the application supports.

In this lesson, you will learn how to prepare a Web application to use satellite assemblies, how to store localized user-interface elements within a satellite assembly, and how to load and display elements from the satellite assembly at run time.

After this lesson, you will be able to

  • Create localized resources and add them to satellite assemblies

  • Enable HTML elements within your application to display localized strings from satellite assemblies

  • Load resources into a ResourceManager object

  • Understand how ASP.NET selects the resources to load based on the CurrentUICulture property

  • Get strings from the ResourceManager object and display them in the user interface

Estimated lesson time: 30 minutes
How Satellite Assemblies Work

Web application projects use satellite assemblies to store the translated strings, graphics, and other culture-dependent aspects of an application' s user interface. To create the assemblies themselves, you use the Resource Manager. At run time, the Web application loads the translated strings into the Web form based on the current thread' s CurrentUICulture property, as shown in Figure 15-5.

Figure 15-5 Satellite assemblies at work

To use satellite assemblies, follow these steps:

  1. Set the id and runat attributes for all of the user-interface elements of your application that require translation. Server controls have these attributes by default, but you will need to add them to HTML elements such as heading, paragraph, and span so that you can load translated strings into those elements.

  2. Create a fallback resource file containing the default strings to display in the user interface if the user' s culture is not specified or not recognized. Name the fallback resource file filename.resx. For example: strings.resx.

  3. Create resource files containing the translated strings to display for each general language that your Web application supports. Name the translated resource files filename.languagecode.resx. For example: strings.es.resx.

  4. Optionally, create resource files containing the translated strings to display for each specific culture that your Web application supports. Name the resource file filename.languagecode-regioncode.resx. For example: strings.ex-MX.resx.

  5. Write code to load the resources for the Web form using the ResourceManager class.

  6. Write code to detect the user' s culture and set the Thread class' s CurrentCulture and CurrentUICulture properties to match. The CurrentCulture property determines how ASP.NET formats dates, currency, and numbers; the CurrentUICulture property determines which satellite assembly is used when loading translated strings.

  7. Write code to get strings from the resource files and display them in elements on the Web form.

The following sections describe these steps in more detail.

Enabling HTML Elements for Resources

Resources from satellite assemblies are loaded into Web form elements on the server side. Therefore, you need to make sure that all the elements on your Web form have id and runat attributes that make them available from server-side code. By default, ASP.NET server controls have these elements; however, HMTL elements do not.

For example, the following HTML shows a simple Web form with a heading, some text, and some controls:

<HTML>
   <HEAD>
         <title>WebForm1</title>
   </HEAD>
   <body>
         <form id="Form1" method="post" runat="server">
               <h1>Sample heading</h1>
               <p>Some introductory text.</p>
               <span>Enter a number: </span>
               <asp:TextBox ID="txtCurrency" Runat="server" />
               <br>
               <span>Amount in local currency: </span>
               <asp:Label ID="lblCurrency" Runat="server" />
               <br>
               <br>
               <asp:Button ID="butOK" Text="OK" Runat="server" />
               <input type="button" onclick="txtCurrencty.value=''"
                 value="Cancel">
         </form>
   </body>
</HTML>

To enable the preceding Web form for using resources, you need to change the HTML elements, as shown in the following code in bold:

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm1.aspx.vb" Inherits="vbSatelliteSnippet.WebForm1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
   <HEAD>
         <title>WebForm1</title>
   </HEAD>
   <body>
         <form id="Form1" method="post" runat="server">
               <h1 id="head1" runat="server">Sample heading</h1>
               <p id="p1" runat="server">Some introductory text.</p>
               <span id="sp1" runat="server">Enter a number: </span>
               <asp:TextBox ID="txtCurrency" Runat="server" />
               <br>
               <span id="sp2" runat="server">Amount in local currency: </span>
               <asp:Label ID="lblCurrency" Runat="server" />
               <br>
               <br>
               <asp:Button ID="butOK" Text="OK" Runat="server" />
               <input id="butCancel" runat="server" type="button"
                 onclick="txtCurrencty.value=''" value="Cancel">
         </form>
   </body>
</HTML>

The preceding id and runat attributes cause Visual Studio .NET to handle those elements as server controls. When you switch from HTML view to Design view, Visual Studio .NET adds the following declarations to the Web form' s code file:

Visual Basic .NET
Protected WithEvents head1 As System.Web.UI.HtmlControls.HtmlGenericControl
Protected WithEvents p1 As System.Web.UI.HtmlControls.HtmlGenericControl
Protected WithEvents sp1 As System.Web.UI.HtmlControls.HtmlGenericControl
Protected WithEvents sp2 As  System.Web.UI.HtmlControls.HtmlGenericControl
Protected WithEvents butCancel As System.Web.UI.HtmlControls.HtmlInputButton
Visual C#
protected System.Web.UI.HtmlControls.HtmlGenericControl head1;
protected System.Web.UI.HtmlControls.HtmlGenericControl p1;
protected System.Web.UI.HtmlControls.HtmlGenericControl sp1;
protected System.Web.UI.HtmlControls.HtmlGenericControl sp2;
protected System.Web.UI.HtmlControls.HtmlInputButton butCancel;

These declarations allow you to set the value and innerHTML attributes of these HTML elements at run time.

Creating Resource Files

You can create three types of resource files when using satellite assemblies to provide translated strings within a Web application, as described in Table 15-2.

Table 15-2 Resource File Types for Satellite Assemblies
Type | Named | Compiled into | Provides
Fallback | file.resx. For example: strings.resx | The executable assembly. | User-interface strings when culture is not specified or not recognized by the Web application.
Language- specific | file.langcode.resx. For example: strings.es.resx | Resource-only assembly stored in a subfolder of the bin folder identified by language code. For example: bin\es\. | Translated strings for cultures using a particular language.
Culture-specific | file.langcode- regioncode.resx. For example: strings.es-MX.resx | Resource-only assembly stored in a subfolder of the bin folder identified by the language and region code. For example: bin\es-MX\. | Translated strings for cultures using a specific dialect of a language.

The culture codes used to identify language and culture-specific resource files are listed in the CultureInfo class' s online Help topic. Those codes are also used in the Name property of the CultureInfo class and the Request object' s UserLanguages array.

To add resource files to a Web application project in Visual Studio .NET, follow these steps:

  1. From the Project menu, choose Add New Item and then select Assembly Resource File from the Templates list.

  2. Type the name of the resource file you want to create, and then click Open. Visual Studio .NET creates a new resource file and adds it to the project, as shown in Figure 15-6.

    Figure 15-6 A new resource file

To enter translated strings for the Web form' s user interface into a resource file, follow these steps:

  1. Type an identifier for the Web form element in the Name field of the resource file. The identifier must be unique within the scope of the application, so a convention such as formname.elementname works well. For example: webform1.butok.

  2. Type the translated string for that element in the Value field of the resource file.

Figure 15-7 shows translated strings entered for the server control and HTML elements created in the preceding section.

Figure 15-7 Translated strings in a resource file
tip | The name field within a resource file is case sensitive by default. To avoid errors locating resources at run time, use a consistent capitalization scheme when naming resource elements. For example, this lesson uses the convention of all lowercase letters for resource names.
Loading Resource Files

The .NET framework provides resource management as part of the System.Resources namespace. To use classes from that namespace without fully qualifying their names, include an Imports/using statement at the beginning of your Web form, as shown here:

Visual Basic .NET
Imports System.Resources
Visual C#
using System.Resources;

To load resources into a Web form, create an instance of the ResourceManager class. For example, the following line loads the resource file for the Web application:

Visual Basic .NET
' Load resources.
Protected gStrings As New ResourceManager("vbSatelliteSnippet.strings", _
  GetType(WebForm1).Assembly)
Visual C#
// Load resources.
protected ResourceManager gStrings = new
  ResourceManager("csSatelliteSnippet.strings", typeof(WebForm1).Assembly);

The preceding example loads the resources stored in the Strings assembly file associated with Webform1' s parent assembly, and stores the returned object in the gStrings variable. The arguments for the ResourceManager class' s constructor are described in Table 15-3.

Table 15-3 ResourceManager Arguments
Argument | Description
baseName | The namespace of the resource within the project. For Visual Basic .NET projects, this is projectname.resourcebasename. For Visual C# projects, this is rootnamespace.resourcebasename. The resourcebasename part omits culture codes, so “MyProj.Strings” includes Strings.resx, Strings.en.resx, Strings.es.rex, and so on.
assembly | The object representing the current executable assembly. Use the Type class' s Assembly property to get the value to pass to this argument. For example: GetType(“Webform1”).Assembly.
Getting and Setting User-Interface Culture

As shown in the preceding lesson, you get the user' s culture from the Request object' s UserLanguages array, and you set the current thread' s culture using the Thread class' s CurrentCulture property. When working with satellite assemblies, you need to add one more step: setting the CurrentUICulture property. This property determines which satellite assembly is used when loading strings from the ResourceManager object.

To set the current user interface culture for a thread, set the Thread object' s CurrentUICulture property. For example, the following code gets the user' s preferred language and then sets both the culture and user-interface culture for the current thread:

Visual Basic .NET
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
   If Not IsPostBack Then
      ' Get the user's perferred language.
      Dim sLang As String = Request.UserLanguages(0)
      ' Set the thread's culture for formatting, comparisons, etc.
      Thread.CurrentThread.CurrentCulture = _
         CultureInfo.CreateSpecificCulture(slang)
      ' Set the thread's UICulture to load resources
      ' from satellite assembly.
      Thread.CurrentThread.CurrentUICulture = New CultureInfo(slang)
   End If
End Sub
Visual C#
private void Page_Load(object sender, System.EventArgs e)
{
   if (!IsPostBack)
   {
         // Get the user's perferred language.
         string sLang = Request.UserLanguages[0];
         // Set the thread's culture for formatting, comparisons, etc.
         Thread.CurrentThread.CurrentCulture =
           CultureInfo.CreateSpecificCulture(sLang);
         // Set the thread's UICulture to load resources
         // from satellite assembly.
         Thread.CurrentThread.CurrentUICulture = new CultureInfo(sLang);
   }
}

In the preceding code, the CurrentCulture and CurrentUICulture properties are set differently. CurrentCulture uses the CreateSpecificCulture method to ensure that the returned object is not a neutral culture. Neutral cultures represent general languages rather than a specific language and region. The CurrentCulture property cannot be set to a neutral culture because it uses region information to format currencies, among other things.

When you have set the thread' s CurrentUICulture, ASP.NET automatically selects the resources that match in the following order:

  1. If a satellite assembly is found with an exactly matching culture, the resources from that assembly are used.

  2. If a satellite assembly is found with a neutral culture that matches the Current- UICulture, resources from that assembly are used.

  3. If a match is not found for the CurrentUICulture, the fallback resources stored in the executable assembly are used.

Displaying Resource Strings

After you have completed all of the preceding tasks, displaying strings from a resource file on the Web form is straightforward.

To display strings from the resource file on a Web form, use the ResourceManager object' s GetStrings method to retrieve strings and assign them to the Text, value, or innerHTML properties of the Web form' s elements.

The following code brings the preceding steps together to load a resource file, get the user' s culture, set the current thread' s culture, and display strings from the resource file in the Web form server control and HTML elements:

Visual Basic .NET
Imports System.Resources
Imports System.Globalization
Imports System.Threading

' Load resources.
Protected gStrings As New ResourceManager("vbSatelliteSnippet.strings", _
  GetType(WebForm1).Assembly)

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
If Not IsPostBack Then
   ' Get the user's preferred language.
   Dim sLang As String = Request.UserLanguages(0)
   ' Set the thread's culture for formatting, comparisons, etc.
   Thread.CurrentThread.CurrentCulture = _
  CultureInfo.CreateSpecificCulture(sLang)
   ' Set the thread's UICulture to load resources from satellite assembly.
   Thread.CurrentThread.CurrentUICulture = New CultureInfo(sLang)
   ' Get strings from resource file.
   head1.InnerHtml = gStrings.GetString("webform1.head1")
   p1.InnerHtml = gStrings.GetString("webform1.p1")
   sp1.InnerHtml = gStrings.GetString("webform1.sp1")
   sp2.InnerHtml = gStrings.GetString("webform1.sp2")
   butOK.Text = gStrings.GetString("webform1.butOK")
   butCancel.Value = gStrings.GetString("webform1.butCancel")
End If
End Sub
Visual C#
using System.Globalization;
using System.Threading;
using System.Resources;

private void Page_Load(object sender, System.EventArgs e)
{
   if (!IsPostBack)
   {
         // Get the user's perferred language.
         string sLang = Request.UserLanguages[0];
         // Set the thread's culture for formatting, comparisons, etc.
         Thread.CurrentThread.CurrentCulture =
           CultureInfo.CreateSpecificCulture(sLang);
         // Set the thread's UICulture to load resources
         // from satellite assembly.
         Thread.CurrentThread.CurrentUICulture = new CultureInfo(sLang);
         // Get strings from resource file.
         head1.InnerHtml = gStrings.GetString("webform1.head1");
         p1.InnerHtml = gStrings.GetString("webform1.p1");
         sp1.InnerHtml = gStrings.GetString("webform1.sp1");
         sp2.InnerHtml = gStrings.GetString("webform1.sp2");
         butOK.Text = gStrings.GetString("webform1.butOK");
         butCancel.Value = gStrings.GetString("webform1.butCancel");
   }
}

At run time, the preceding code displays the translated strings from the resource files based on the user' s preferred language, as shown in Figure 15-8.

Figure 15-8 Displaying strings from a resource file


Lesson 3: Globalization Issues

The .NET Framework handles many globalization issues automatically, based on the setting in the CurrentCulture property. However, globalization issues also affect some basic programming techniques. In this chapter, you will learn about some general programming issues you need to think about when developing global applications. You will also learn about issues that arise when storing and presenting the extended character sets used by some languages.

After this lesson, you will be able to

  • Discuss some of the general programming issues affected by globalization

  • Save Web forms containing special characters from other languages

  • Use Web forms stored in different character encodings

  • Detect the user' s requested character encoding

  • Create responses using different character encodings

Estimated lesson time: 10 minutes
General Programming Issues

The “Best Practices for Developing World-Ready Applications” topic in the Visual Studio .NET online Help provides a helpful list of items that you should consider when developing Web applications that will be translated into different languages. The following sections provide more detail on points from that topic that are relevant to Web applications.

Sorting and Comparing Strings

Sorting and string comparison you perform using the .NET Framework classes perform correctly based on the current culture. You only really need to worry about globalization issues when implementing your own techniques for those tasks, such as when you are implementing custom sorting procedures. In those cases, use the System.Globalization namespace' s CompareInfo class to perform culturally aware string comparisons.

Custom Validation Controls

ASP.NET server controls acquire specific properties based on the current culture. This includes the validation controls. You do need to consider globalization issues when creating custom validation controls, however.

For example, if you are writing a custom validation procedure that verifies that input is numeric, don' t rely on the numeric value of the input characters. Instead, use a culturally aware test such as using the Convert class within an exception-handling structure to test if the value is numeric.

Building Strings

Because some languages read from right to left, and because some words change meaning when in proximity to other words, it is difficult to build strings using concatenation in a way that works for all cultures. Instead, you store completed strings in a resource file and retrieve the strings as needed by using the resource manager.

Getting Substrings

In some languages, character symbols (called glyphs) are composed of two characters. In those cases, you might need to adjust how you get substrings. For example, getting the first character of a user' s response would be meaningless in those cases.

Character Encoding

Characters within strings can be represented many different ways within a computer. These different ways of representing characters are called character encodings because they encode characters into numbers that the computer can understand.

The common language runtime uses the UTF-16 character encoding, which maps characters to 16-bit numbers. 16 bits provides enough room to accommodate the characters for almost all written languages. Unfortunately, that' s about double the size that other character encoding schemes use, which makes it less than optimal for transmitting text over the Internet.

Therefore, ASP.NET uses UTF-8 character encoding by default when interpreting requests and composing responses. UTF-8 is optimized for the lower 127 ASCII characters, which means it provides an efficient way to encode languages that use the Latin alphabet. UTF-8 is also backward-compatible with the ASCII character encoding, meaning that UTF-8 readers can interpret ASCII files.

The common language runtime handles the conversion between encodings by using classes from the System.Text namespace. You usually don' t need to be aware that any of this is going on, with the following exceptions:

  • If you go to save a Web form or HTML file that includes extended characters from the UTF-8 encoding

  • If you need to support specific encodings other than UTF-8

Saving Encoded Files

When you create a Web form that contains characters other than the first 127 ASCII characters, Visual Studio .NET prompts you to save the file using encoding, as shown in Figure 15-9.

Figure 15-9 Saving files with character encoding

To save a file with encodings, follow these steps:

  1. From the File menu, choose Save As, click the Save drop-down arrow, and then select Save With Encoding. Visual Studio .NET displays the Advanced Save Options, as shown in Figure 15-10.

    Figure 15-10 Specifying encoding while saving a file

  2. Choose the encoding you want use for saving the file. Click OK.

Saving the file using UTF-8 encoding with signature allows ASP.NET to automatically detect the encoding of the file. If you don' t choose to include the signature, or if you use another encoding, you must specify the fileEncoding attribute in the Web.config file, as shown here:

<globalization requestEncoding="utf-8" responseEncoding="utf-8"
  fileEncoding="utf-8" />
Using Other Encodings

By default, Web applications interpret requests and compose responses using the UTF-8 character encoding. These defaults are set in the globalization element of the Web.config file, as shown in the preceding section.

If a Web application receives a request for an encoding other than UTF-8, ASP.NET interprets that request using the request' s encoding. However, ASP.NET does not automatically generate the response in that encoding.

To detect when a request has a specific encoding, get the Request object' s ContentEncoding property. For example, the following code displays the encoding of a request on a Web form:

Visual Basic .NET
Response.Write(Request.ContentEncoding.WebName)
Visual C#
Response.Write(Request.ContentEncoding.WebName);

To specify a specific encoding for a response, set the Response object' s ContentEncoding property using the System.Text namespace' s Encoding class, or use the value returned from the Request object' s ContentEncoding property. For example, the following code creates a response using the shift_JIS character encoding:

Visual Basic .NET
Imports System.Text
Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
   Response.ContentEncoding = Encoding.GetEncoding("shift_JIS")
   Response.Write("This response uses the character encoding: ")
   Response.Write(Response.ContentEncoding.WebName)
End Sub
Visual C#
using System.Text;
private void Page_Load(object sender, System.EventArgs e)
{
   Response.ContentEncoding = Encoding.GetEncoding("shift_JIS");
   Response.Write("This response uses the character encoding: ");
   Response.Write(Response.ContentEncoding.WebName);
}

For more information about character encoding, see the following online Help topics:

“Unicode in the .NET Framework”

“Encoding Base Types”

“System.Text Namespace”

You can also find information about character encoding on the following Web sites:

http://www.unicode.org

http://www.w3.org/TR/REC-html40/charset.html


Summary

  • Use the Request object' s UserLanguages array to detect the user' s culture.

  • Set the culture and uiCulture attributes of the globalization element in Web.config to create culture-specific Web applications.

  • The .NET Framework identifies cultures using the language and region codes listed in the CultureInfo class' s online Help topic.

  • Set the culture used by the application for formatting dates, currencies, numbers, and determining sort order using the Thread class' s CurrentCulture property.

  • Use the Thread class' s CurrentUICulture to determine which satellite assembly is used to load localized resources.

  • Add id and runat attributes to HTML elements to be able to display localized strings from resource files in those elements at run time.

  • Elements within a resource file are case sensitive and must be uniquely named within the scope of the application, so use a naming convention such as webform.id.

  • When creating Web forms that use non-ASCII characters, save the file using the UTF-8 encoding with a signature. Including the signature allows ASP.NET to automatically detect the file' s encoding.


Lab: Create a Currency Converter

In this lab, you will create a simple Currency Converter Web application that uses the user' s culture to determine the type of conversion to perform:

  • An unrecognized culture converts euros to dollars and pesos, and displays text in English as the fallback interface.

  • An English-US culture converts dollars to euros and pesos and displays an English-language interface.

  • A Spanish-Mexican culture converts peso to dollars and euros and displays a Spanish-language interface.

When complete, the Web application will appear as shown in Figure 15-11.

Figure 15-11 Currency converter (fallback interface)
Estimated lesson time: 20 minutes
Exercise 1: Creating the Currency Converter Web Form

The Currency Converter Web application contains a single Web form that receives input from a text box and displays output in two labels when the user clicks OK. The Web form also includes a Cancel HTML button that clears the text box.

Because the application will be localized, the HTML elements on the Web form must have id and runat attributes so that the localized resource strings can be loaded into those controls from server-side code.

To create the Currency Converter Web form

  1. Create a new Web application project named CurrencyConverter.

  2. Switch to HTML view and enter the following HTML between the <form> and </form> tags of the Web form:

    <h1 id="head1" runat="server">Head1</h1>
<span id="sp1" runat="server">Enter an amount: </span>
<asp:TextBox ID="txtCurrency" Runat="server" />
<br>
<span id="sp2" runat="server">Amount in: </span>
<asp:Label ID="lblCurrency1" Runat="server" />
<br>
<span id="sp3" runat="server">Amount in: </span>
<asp:Label ID="lblCurrency2" Runat="server" />
<br><br>
<asp:Button ID="butOK" Text="OK" Runat="server" />
<input id="butCancel" runat="server" type="button"
  onclick="txtCurrency.Text=''" value="Cancel">

  3. When you are done, switch the Web form back to Design view. This is an important step, because it generates the correct control declarations in the code file.

Exercise 2: Creating the User-Interface Resource Files

The Web form created in the preceding step contains only dummy entries for the text that will actually be displayed in the user interface. For instance, the Web form' s heading is “Head1.” In this exercise, you will create the strings that are displayed in place of those dummy entries at run time.

To create the user-interface resource files

  1. From the Project menu, choose Add New Item and then select Assembly Resource File from the list of templates. Name the resource file strings.resx.

  2. Add the entries shown in Figure 15-12 to the resource file to create the fallback values for the user interface.

    Figure 15-12 Fallback values for the UI

  3. Create a new resource file named strings.en-US.resx, and add the entries shown in Figure 15-13 to create the interface for users with the English-US culture:

    Figure 15-13 English-US culture values

  4. Create another new resource file named strings.es-MX.resx, and add the entries shown in Figure 15-14 for the Spanish-Mexican culture:

    Figure 15-14 Spanish-Mexican culture values

  5. When you are done, click Save All to save your work.

You can create additional resource files for other cultures if you want. Just include the culture code in the resource file name, as shown in the preceding steps. Visual Studio .NET will automatically compile the resources as satellite assemblies and store them in the appropriate subfolder when you build the application.

Exercise 3: Loading Resources Based on User Culture

In this exercise, you will detect the user' s culture, load resources, and display the resources matching the user' s culture.

To load resources based on the user' s culture

  1. Add the following Imports or using statements to the beginning of the Web form' s code file:

    Visual Basic .NET
    Imports System.Resources
Imports System.Globalization
Imports System.Threading
    Visual C#
    using System.Resources;
using System.Globalization;
using System.Threading;

  2. Add the following code at the class level of the Web form to load the resources into the ResourceManager object:

    Visual Basic .NET
    Protected gStrings As New ResourceManager("CurrencyConverter.strings", _
  GetType(WebForm1).Assembly)
    Visual C#
    protected ResourceManager gStrings = new
  ResourceManager("CurrencyConverter.strings", typeof(WebForm1).Assembly);

  3. Add the following code to the Page_Load event procedure to detect the user' s culture, set the application thread' s user interface culture to match, and display the localized resource strings in the user interface:

    Visual Basic .NET
    Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
   If Not IsPostBack Then
         ' Get the user's preferred language.
         Dim sLang As String = Request.UserLanguages(0)
         ' Set the thread's UICulture to load resources.
         Thread.CurrentThread.CurrentUICulture = New CultureInfo(sLang)
         ' Set the thread's culture for date/string/currency formats.
         Thread.CurrentThread.CurrentCulture = _
           CultureInfoCreateSpecificCulture(sLang)
         ' Get strings from resource file.
         head1.InnerHtml = gStrings.GetString("webform1.head1")
         sp1.InnerHtml = gStrings.GetString("webform1.sp1")
         sp2.InnerHtml = gStrings.GetString("webform1.sp2")
         sp3.InnerHtml = gStrings.GetString("webform1.sp3")
         butOK.Text = gStrings.GetString("webform1.butOK")
         butCancel.Value = gStrings.GetString("webform1.butCancel")
   End If
End Sub
    Visual C#
    private void Page_Load(object sender, System.EventArgs e)
{
   if (!IsPostBack)
   {
         // Get the user's preferred language.
         string sLang  = Request.UserLanguages[0];
         // Set the thread's UICulture to load resources
         //from satellite assembly.
         Thread.CurrentThread.CurrentUICulture = new CultureInfo(sLang);
         // Set the thread's culture for date/string/currency formats.
         Thread.CurrentThread.CurrentCulture =
           CultureInfo.CreateSpecificCulture(sLang);
         // Get strings from resource file.
         head1.InnerHtml = gStrings.GetString("webform1.head1");
         sp1.InnerHtml = gStrings.GetString("webform1.sp1");
         sp2.InnerHtml = gStrings.GetString("webform1.sp2");
         sp3.InnerHtml = gStrings.GetString("webform1.sp3");
         butOK.Text = gStrings.GetString("webform1.butOK");
         butCancel.Value = gStrings.GetString("webform1.butCancel");
   }
}

You have to load the localized user interface strings only the first time the page is displayed, as shown in the preceding code. ASP.NET retains the value of the controls between page displays automatically.

Exercise 4: Performing Culture-Dependent Conversion

Just as the Currency Converter displays a different interface for different cultures, it also performs different conversions based on the current culture. In this exercise, you will write the code that performs the culture-dependent conversions.

To perform the culture-dependent conversions

Add the following code to the butOK_Click event procedure to detect the current culture and perform different conversions based on that information:

Visual Basic .NET
Private Sub butOK_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles butOK.Click
   ' Get the amount.
   Dim SrcAmount As Double = Convert.ToDouble(txtCurrency.Text)
   Dim DestAmount As Double
   ' Culture variables for currency formatting.
   Dim Europe As New CultureInfo("fr-FR")
   Dim USA As New CultureInfo("en-US")
   Dim Mexico As New CultureInfo("es-MX")
   ' Perform conversions based on current user-interface culture.
   Select Case Thread.CurrentThread.CurrentCulture.Name
         Case "en-US"
               ' Convert from dollars.
               DestAmount = SrcAmount * 1.1329
               lblCurrency1.Text = DestAmount.ToString("C", Europe)
               DestAmount = SrcAmount * 9.0655
               lblCurrency2.Text = DestAmount.ToString("C", Mexico)
         Case "es-MX"
               ' Convert from pesos.
               DestAmount = SrcAmount * 0.125
               lblCurrency1.Text = DestAmount.ToString("C", Europe)
               DestAmount = SrcAmount * 0.1103
               lblCurrency2.Text = DestAmount.ToString("C", USA)
         Case Else
               ' Convert from euros.
               DestAmount = SrcAmount * 8.0021
               lblCurrency1.Text = DestAmount.ToString("C", USA)
               DestAmount = SrcAmount * 0.1103
               lblCurrency2.Text = DestAmount.ToString("C", Mexico)
   End Select
End Sub
Visual C#
private void butOK_Click(object sender, System.EventArgs e)
{
      // Get the amount.
      double SrcAmount = Convert.ToDouble(txtCurrency.Text);
      double DestAmount;
      // Culture variables for currency formatting.
      CultureInfo Europe = new CultureInfo("fr-FR");
      CultureInfo USA = new CultureInfo("en-US");
      CultureInfo Mexico = new CultureInfo("es-MX");
      // Perform conversions based on current user-interface culture.
      switch (Thread.CurrentThread.CurrentCulture.Name)
      {
            case "en-US":
                  // Convert from dollars.
                  DestAmount = SrcAmount * 1.1329;
                  lblCurrency1.Text = DestAmount.ToString("C", Europe);
                  DestAmount = SrcAmount * 9.0655;
                  lblCurrency2.Text = DestAmount.ToString("C", Mexico);
                  break;
            case "es-MX":
                  // Convert from pesos.
                  DestAmount = SrcAmount * 0.125;
                  lblCurrency1.Text = DestAmount.ToString("C", Europe);
                  DestAmount = SrcAmount * 0.1103;
                  lblCurrency2.Text = DestAmount.ToString("C", USA);
            break;
            default:
                  // Convert from euros.
                  DestAmount = SrcAmount * 8.0021;
                  lblCurrency1.Text = DestAmount.ToString("C", USA);
                  DestAmount = SrcAmount * 0.1103;
                  lblCurrency2.Text = DestAmount.ToString("C", Mexico);
                  break;
            }
   }

The preceding code creates CultureInfo objects (Europe, United States, and Mexico) for each cultural conversion. These objects provide the appropriate currency formatting in the ToString methods. The Europe object uses the culture code for France, because France uses the euro as currency. There isn' t a culture code for the European Union.

The preceding example also hard-codes the exchange rates into the procedure. You wouldn' t do that in a real-world situation, of course. Getting current exchange rates is an appropriate task for Web Services. See Chapter 7, “Advanced Web Forms Programming,” for a lesson on how to use XML Web Services.


Review

The following questions are intended to reinforce key information presented in this chapter. If you are unable to answer a question, review the appropriate lesson and then try the question again. Answers to the questions can be found in the appendix.

  1. What is the difference between the CurrentCulture property and the Current- UICulture property?

  2. How do you detect the user' s culture?

  3. What is a neutral culture?

  4. How does character encoding affect file formats?


Questions and Answers

Appendix
Questions and Answers
Chapter 1:Introduction to Web Programming

  1. Give three examples of how an ASP.NET Web application is different from a traditional Windows application.

    Web applications and traditional Windows applications have the following differences:

    Feature

    ASP.NET Web application

    Unmanaged Windows application

    Architecture

    Client/server.

    Local user.

    Execution

    Runs under common language runtime using managed code.

    Runs under Windows using unmanaged code.

    User interface

    User interface is displayed in an Internet browser using Web Forms.

    User interface is displayed by the application using the Win32 API.

  2. What are the two main parts of the .NET Framework?

    • The common language runtime

    • The .NET Framework class library

  3. How do you restore the default window settings in Visual Studio .NET?

    From the Tools menu, choose Options and then click Reset Window Layout.

  4. Why doesn' t the Visual Studio .NET Code Editor automatically complete the following partial line of code (Visual C# users only)?

    int intX = system.math

    The namespace System.Math must be capitalized in Visual C# for IntelliSense to recognize the namespace.

  5. When can' t you use ASP.NET to create a Web application?

    When you are developing for non-Microsoft Windows Web servers, such as Linux/Apache.

Chapter 2:Creating Web Forms Applications

  1. Explain where Visual Studio .NET stores Web application projects.

    Web application projects are stored in a new virtual folder for each project. The properties of that virtual folder determine where the files are physically stored. These properties can be viewed in IIS.

  2. List the four major differences between Web and Windows applications.

    • Web forms cannot use the standard Windows controls. Instead, they use server controls, HTML controls, user controls, or custom controls created specially for Web forms.

    • Web applications are displayed in a browser. Windows applications display their own windows and have more control over how those windows are displayed.

    • Web forms are instantiated on the server, sent to the browser, and destroyed immediately. Windows forms are instantiated, exist for as long as needed, and are destroyed.

    • Web applications run on a server and are displayed remotely on clients. Windows applications run on the same machine they are displayed on.

  3. Describe the life cycle of a Web application: When are Web forms instantiated and how long do they exist?

    A Web application starts with the first request for a resource within the application' s boundaries. Web forms are instantiated when they are requested. They are processed by the server and are abandoned immediately after the server sends its response to the client. A Web application ends after all client sessions end.

  4. How do you preserve persistent data, such as simple variables, in a Web application?

    You can preserve data in state variables, such as ApplicationState, SessionState, or ViewState.

  5. What determines the boundaries of a Web application?

    IIS determines Web application boundaries by the structure of the application' s virtual folders. A Web application boundary starts in the folder containing the start page of the application and it ends at the last subordinate folder or when it encounters another start page in a subordinate folder.

Chapter 3:Working with Web Objects

  1. How does the .NET Framework organize its classes?

    The .NET Framework uses namespaces to organize its classes.

  2. In Visual Basic .NET, what is difference between a class module and a code module?

    Class modules are instantiated at run time to create objects that provide separate storage for variables and properties in each instance. Code modules do not have instances, so any module-level variables they use are shared among calls to the module' s procedures.

  3. In Visual C#, how do you declare a method to make it available without having to first instantiate an object from the class?

    To create a method that can be called without instantiating an object, declare that method as static.

  4. How do you call a member of a base class from within a derived class?

    To refer to a member of a base class in Visual Basic .NET, use the MyBase keyword. To refer to a member of a base class in Visual C#, use the base keyword.

  5. What are the four main objects used in Web application programming?

    The four main objects in Web application programming are the Application, Page, Request, and Response objects.

  6. Where would you save the following data items so that they persist between requests to a Web form?

    • A control created at run time

      Save controls created at run time in the Page object' s ViewState.

    • An object that provides services to all users

      Save objects that provide services to all users in the Application state.

    • User preferences

      Save user preferences in SessionState.

Chapter 4: Creating a User Interface

  1. What is the main difference between the Button server control and the Button HTML control?

    When clicked, the Button server control triggers an ASP.NET Click event procedure on the server. The Button HTML control triggers the event procedure indicated in the button' s onclick attribute, which runs on the client.

  2. How do you get several RadioButton controls to interoperate on a Web form so that only one of the RadioButtons can be selected at once?

    Set the GroupName property of each RadioButton to the same name.

  3. Why does ASP.NET perform validation on both the client and the server?

    Client-side validation helps avoid round trips to the server. Validating on the client makes sure that the data is valid before it is submitted, in most cases. However, because validation might be turned off (or maliciously hacked) on the client, data must be revalidated on the server side. This provides full assurance that the data is valid while avoiding as many round trips as possible.

  4. What types of validation would you use to verify that a user entered a valid customer number?

    You would use a RequiredFieldValidator and a RegularExpressionValidator. If you have access to a list of expected customer numbers, you could replace the RegularExpressionValidator with a CustomValidator that checked the list.

  5. What is wrong with the following line of code?

    Visual Basic .NET
    Server.Transfer("Default.htm")
    Visual C#
    Server.Transfer("Default.htm");

    You can' t use the Transfer method with HTML pages. It works only with .aspx pages.

  6. Why can' t you open a new browser window from within server code?

    Server code executes on the server, whereas the new window is created on the client. You need to use client-side code to do things that affect the client, such as upload files, display new windows, or navigate back in history.

Chapter 5: Storing and Retrieving Data with ADO.NET

  1. What steps would you follow and what objects would you use to quickly find the number of records in a database table?

    There are two ways to accomplish this task:

    • Use a database connection and a command object to execute a SQL command that returns the number of rows in the table.

    • Use a database connection and data adapter object to create a data set for the table, and then get the number rows in the data set.

  2. How do typed data sets differ from untyped data sets, and what are the advantages of typed data sets?

    Typed data sets use explicit names and data types for their members, whereas untyped data sets use collections to refer to their members. The following examples show a typed reference vs. an untyped reference to a data item:

    Visual Basic .NET
    ' Typed reference to the Contacts table's HomePhone column.
DataSet1.Contacts.HomePhoneColumn.Caption = "@Home"
' Untyped reference to the Contacts table's HomePhone column.
DataSet1.Tables("Contacts").Columns("HomePhone").Caption = "@Home"
    Visual C#
    // Typed reference to the Contacts table's HomePhone column.
DataSet1.Contacts.HomePhoneColumn.Caption = "@Home";
// Untyped reference to the Contacts table's HomePhone column.
DataSet1.Tables["Contacts"].Columns["HomePhone"].Caption = "@Home";

    Typed data sets do error checking at design time. This error checking helps catch typos and type mismatch errors, which would be detected only at run time with untyped data sets.

  3. How do you reuse a database connection component throughout a Web application?

    Add the database connection component to the Global.asax file, then create a state variable in the Application_Start or Session_Start event procedures to share the connection with the Web forms in the application.

  4. Explain the difference between handling transactions at the data set level and at the database level.

    Data sets provide implicit transactions, since changes to the data set aren' t made permanent in the database until you call the Update method. To handle transactions in a data set, process the Update method and check for errors. If errors occur during Update, none of the changes from the data set is made in the database. You can try to correct the error and resubmit the update, or you can roll back the changes to the data set using the RejectChanges method.

    Databases provide explicit transactions through the Transaction object. You create a Transaction object from a database connection, and then assign that Transaction object to the commands you want to include in the transaction through the command object' s Transaction property. As you perform the commands on the database, you check for errors. If errors occur, you can either try to correct them and resubmit the command, or you can restore the state of the database using the Transaction object' s RollBack method. If no errors occur, you can make the changes permanent by calling the transaction object' s Commit method.

Chapter 6: Catching and Correcting Errors

  1. Explain why exception handling is important to a completed application.

    When an unhandled exception occurs in an application, the application stops—the user can' t proceed, and any work he or she did immediately prior to the exception is lost. Exception handling provides a way to intercept and correct unusual occurrences that would otherwise cause these problems.

  2. List two different exception-handling approaches in ASP.NET Web applications.

    Exceptions can be handled in exception-handling blocks using the Try, Catch, and Finally keywords in Visual Basic .NET or the try, catch, and finally keywords in Visual C#. They can also be handled using Error event procedures at the Global, Application, or Page levels using the Server object' s GetLastError and ClearError methods.

  3. Describe the purpose of error pages and why they are needed.

    Because Web applications run over the Internet, some exceptions occur outside the scope of the application. This means that your application cannot respond directly to these exceptions. These types of exceptions are identified by HTTP response codes, which IIS can respond to by displaying custom error pages listed in your application' s Web.config file.

  4. Explain why tracing helps with exception handling.

    Tracing allows you to record unusual events while your application is running, without users being aware of it. If an unanticipated exception occurs, your application can write a message to the trace log, which helps you diagnose problems during testing and after deployment.

Chapter 7: Advanced Web Forms Programming

  1. Write the HTML for a hyperlink that will send mail when the user clicks the link.

    <a href="mailto:you@microsoft.com?SUBJECT=Sending from a client&BODY=Some
message text.">Send mail</a>

  2. Show the code that writes a cookie containing the user name “Rob Young” and the current date to the user' s computer. Set the cookie to remain on the user' s computer for 30 days.

    Visual Basic .NET
    Dim cookUserInfo As New HttpCookie("UserInfo")
cookUserInfo("Name") = "Rob Young"
cookUserInfo("Time") = DateTime.Now.ToString()
cookUserInfo.Expires = DateTime.Now.AddDays(30)
Response.Cookies.Add(cookUserInfo)
    Visual C#
    HttpCookie cookUserInfo = new HttpCookie("UserInfo")
CookUserInfo["Name"] = "Rob Young"
CookUserInfo["Time"] = DateTime.Now.ToString()
cookUserInfo.Expires = DateTime.Now.AddDays(30)
Response.Cookies.Add(cookUserInfo)

  3. Why can' t you open a new browser window using server-side code? How would you display a page in a new window with a client-side script?

    Server-side code can execute tasks only on the server. To perform a task on the client' s computer, such as opening a new browser window, the code needs to run on the client.

    You can open a new window using the DOM window object. For example, the following HTML Button control opens a Help page in a new window:

    <button id="butHelp"
onclick="window.open('help.aspx', 'help', 'height=200,width=300')">Help</
button>

  4. How do you display a page in one frame from a hyperlink in another frame?

    Use the <a> element' s target attribute to specify the frame to display the page. For example, the following hyperlink displays a page in the main frame of a frameset:

    <a href="AllTheAnswers.aspx" target="main">Show the answers!</a>
Chapter 8: Maintaining Security

  1. Which ASP.NET authentication mode is best suited to identifying and authorizing users who belong to a corporate network?

    Windows integrated authentication is best suited to authenticating users of a corporate network because it uses the accounts and permissions that already exist for network users.

  2. What is the difference between Windows and Forms authentication users lists in Web.config?

    Users lists for Windows authentication are included in the <authorization> element of Web.config.

    Users lists for Forms authentications are included in the <credentials> element of Web.config or as part of an external users database or file.

  3. How do you require authentication using the Web.config file? (The answer is the same for all ASP.NET authentication modes.)

    Include the following <authorization> element to require authentication:

    <authorization>
   <deny users="?" />
</authorization>

  4. How does the Secure Sockets Layer (SSL) provide security in a Web application?

    SSL protects data exchanged between a client and a Web application by encrypting the data before it is sent across the Internet.

  5. How do you begin and end secure communication via SSL?

    To begin secure communication, specify the https protocol in an address. For example:

    <a href="https://www.contoso.com/secure.htm">Secure page.</a>

    To end secure communication, specify the http protocol. For example:

    <a href="http://www.contoso.com/Default.htm">Not secure.</a>
Chapter 9: Building and Deploying Web Applications

  1. What permissions do Web applications run under by default?

    By default, Web applications run as the ASPNET user, which has limited permissions equivalent to the Users group.

  2. Why is the Machine.config file important to deployed Web applications?

    The Machine.config file controls many aspects of how Web applications run, including how processes are recycled, what types of request queue limits are imposed, and what interval is used when checking if users are still connected.

  3. How do you set cache options for a Web application?

    Use the FrontPage Server Extensions in IIS to set the page caching options for a Web application.

  4. How does deploying to a Web farm or a Web garden affect Session state in a Web application?

    Web applications that are deployed to a Web farm or a Web garden need to identify a Session state provider in their Web.config file. This is because a single client' s requests can be directed to different processes over the course of his or her session. The Session state provider allows each different process to access the client' s Session state.

Chapter 10: Testing Web Applications

  1. How do unit, integration, and regression testing relate to each other?

    Unit testing is the foundation that ensures that each piece of code works correctly. Integration testing extends this concept by verifying that pieces work together without errors. Regression tests are made up of the existing unit and integration tests, which are run on a regular schedule to assure that new code did not break previously working code.

  2. Why is load testing likely to be more important for a Web application than for a stand-alone Windows application?

    Because Web applications can be public on the Internet, they can have hundreds, thousands, or even millions of users. Load tests let you simulate the expected demand to locate resource conflicts, performance bottlenecks, and other problems that aren' t apparent in single-user tests.

  3. What is the difference between the Debug and Trace classes?

    Under the default environment settings, code using the Debug class is stripped out of release builds, while code using the Trace class is left in. The classes are otherwise equivalent.

  4. What are the two special steps you need to take to ensure that a COM component can use a component from a .NET assembly?

    • You must register the .NET assembly in the system registry using RegAsm.exe.

    • You must make sure that the COM component can find the .NET assembly, either by placing the .NET assembly in the global assembly cache, by placing the two components in the same folder, or by following the other assembly-probing rules.

Chapter 11: Creating Custom Web Controls

  1. Briefly describe the best uses for each of the three types of Web controls by completing the following sentences:

    Create a user control when you want to…

    …quickly create a group of controls that can be reused throughout a project to perform some logical unit of work.

    Create a composite custom control when you want to…

    …combine one or more existing controls into a compiled assembly that can be easily reused in many different projects.

    Create a rendered control when you want to…

    …build an entirely new control that can be compiled into an assembly for use in multiple projects.

  2. How is deriving important to creating custom Web controls?

    Both composite and rendered custom controls are derived from the WebControl base class. That class provides the methods that you override to create the appearance of the custom control.

  3. What is the most important method to override when creating a composite custom control?

    You override the CreateChildControls method to add existing controls to a composite custom control.

  4. What is the most important method to override when creating a rendered control?

    You override the Render method when creating a rendered custom control.

  5. How is raising a post-back event in a composite control different from raising a post-back event in a rendered control?

    In a composite control, you can use one of the contained controls to raise a post-back event. In a rendered control, you must implement the IPostBackEventHandler interface and write a client-side script to raise a post-back event.

Chapter 12: Working with Multimedia

  1. Why do you usually control multimedia from client-side scripts, rather than from server-side code?

    Intercepting events on the server requires the page to be redisplayed. Redisplaying a page restarts most media, such as sounds and video.

  2. Which HTML sound and video elements are supported by most browsers?

    Most browsers support these HTML elements: bgsound, img, and embed.

  3. How do you support Netscape Navigator users within an ActiveX object element?

    Nest an HTML embed element containing an equivalent Netscape plug-in within the ActiveX HTML object element. You will also need to modify or disable client-side code that controls the ActiveX object if the user' s browser is Netscape Navigator.

  4. What central feature does HTML+TIME provide that is not available with other animation techniques?

    HTML+TIME allows you to synchronize timed events on a page.

  5. List two ways to create a new timeline using HTML+TIME.

    • Use one of the timeline elements, such as t:SEQ, t:EXCL, or t:PAR.

    • Add a TIMECONTAINER attribute to a standard HTML element.

Chapter 13: Formatting Web Application Output

  1. What is the advantage of using CSS rather than in-line styles for formatting a Web application?

    Using CSS allows you to maintain formatting separately from the content of your Web forms, so changes are easier to make and consistency is easier to maintain.

  2. Why would you create a style for a class rather than for an HTML element?

    You create style classes when you want to apply the same formatting to different types of elements within an application. For example, you might want to create an emphasis style class that applies the same formatting to any text, control, heading, or image that you want to draw the user' s attention to.

  3. How do CSS and XSL relate to each other when it comes to formatting a Web application?

    CSS and XSL are complementary techniques for formatting. CSS determines the font, color, size, background, and other appearance aspects of HTML elements on a Web page. XSL is used to transform XML files into HTML output. In this way, XSL controls the position and appearance of items based on their content. Used together, XSL performs the high-level tasks of composition and layout while CSS performs the low-level tasks of applying fonts, colors, and other appearance features.

  4. What are the differences between HTML and XML?

    • HTML uses predefined element names, such as <p> and <br>. In XML, you create your own element names to identify hierarchical nodes of data.

    • XML syntax is much stricter than HTML: elements must always have end-tags, element names are case sensitive, attribute values must always be enclosed in quotation marks, and nested elements must be terminated within their parent elements.

    • XML identifies data conceptually based on the data' s content, rather than based on the type of formatting to apply.

Chapter 14: Providing Help

  1. Which HTML attribute does the server control' s Tooltip property map to in the HTML rendered by ASP.NET?

    The Tooltip property is rendered as the title attribute at run time.

  2. How does displaying HTML Help using the window object' s showHelp method differ from displaying HTML Help using a browser window?

    The showHelp method cannot open compiled HTML Help from a network address; the compiled file must first be downloaded to the user' s machine. You can display HTML Help from a network address within a browser window, provided you use the ms-its: protocol. However, the HTML Help Contents, Index, and Search features are not automatically available from a browser window.

  3. How do you add index keywords to a Help topic?

    Insert an object element in the Help topic using the HTML Help ActiveX control. For example, the following object element adds the "numbers" index keyword:

    <Object type="application/x-oleobject" classid="clsid:1e2a7bd0-dab9-11d0-
b93a-00c04fc99f9e">
   <param name="Keyword" value="numbers">
</OBJECT>
Chapter 15: Globalizing Web Applications

  1. What is the difference between the CurrentCulture property and the CurrentUICulture property?

    The CurrentCulture property affects how the .NET Framework handles dates, currencies, sorting, and formatting issues. The CurrentUICulture property determines which satellite assembly is used when loading resources.

  2. How do you detect the user' s culture?

    Use the Request object' s UserLanguages array. The value at element 0 corresponds to one of the culture codes used by the CultureInfo class. For example:

    SLang = Request.UserLanguages(0)

  3. What is a neutral culture?

    Neutral cultures represent general languages without region-specific differences, such as currency. For example, the “es” culture code represents Spanish.

  4. How does character encoding affect file formats?

    When using non-ASCII characters in a Web form, you must save the file with a specific character encoding, such as UTF-8. ASP.NET can automatically detect UTF-8 file encoding if the file is saved with a signature; otherwise, you need to specify the file encoding used in the fileEncoding attribute of the globalization element in Web.config.


Glossary
A

abstract class A class that cannot be instantiated but is used as a base from which other classes can be derived. In Microsoft Visual Basic .NET, abstract classes are declared using the MustInherit keyword. In Microsoft Visual C#, abstract classes are declared using the abstract keyword.

abstract member A member of base class that cannot be invoked, but instead provides a template for members of a derived class. In Visual Basic .NET, abstract members are declared using the MustOverride keyword. In Visual C#, abstract members are declared using the abstract keyword.

ad hoc testing Testing that relies on the uncoordinated efforts of developers or testers to ensure that code works. This type of testing contrasts with automated testing, which uses test scripts or programs to systematically check that all parts of an application work correctly.

additive The process of combining two or more things.

alias The name you use to identify resources on the Web. Aliases represent physical resources on the Web server, such as a Web form, an HTML page, or a graphic.

ASP.NET The portion of the Microsoft .NET Framework used to create Web applications and XML Web services. ASP.NET is an evolution of Microsoft Active Server Pages (ASP).

assembly The executable component of an application created using the .NET Framework. Web applications, as well as other types of .NET applications, compile their executable code into an assembly file with a .dll filename extension. The compiled code consists of IL assembly language (ILAsm), which is then compiled into its final state at run time by the common language runtime.

authentication The process of determining the identity of a user. In effect, authentication validates that the user is who he says he is.

authorization The process of granting access privileges to resources or tasks within an application. Applications typically authenticate users and then authorize them based on their identities or roles within an organization.

automated testing A type of testing that uses tests written in a scripting or programming language to systematically check that all parts of an application work as expected. Automated testing checks that an application' s components work separately (called unit testing), that all components work together (called integration testing), and that changes haven' t broken existing features (called regression testing).

B

base class A class that provides properties and methods as a foundation for a derived class. In object-oriented programming, one class can be based on another through inheritance. Using this technique, the base class provides characteristics (such as properties and methods) to a derived class. The derived class can reuse, modify, or add to the base class' s members.

behaviors Components that encapsulate specific functionality or actions on a Web form or HTML page. When applied to a standard HTML element on a page, a behavior modifies that element' s default behavior. Behaviors are implemented as styles.

C

caching The process of storing frequently requested documents in the server' s memory to speed up access to those items. Web applications provide automatic caching features through the Microsoft FrontPage Server Extensions and manual caching features through the Cache object.

cascading See cascading style sheets (CSS).

cascading style sheets (CSS) Web application project files (.css) that collect and organize all of the formatting information applied to elements on a Web form or HTML page. Because they keep this information in a single location, cascading style sheets make it easy to adjust the appearance of Web applications. Web applications can have multiple style sheets and can switch style sheets at run time to dynamically change the appearance of a Web application.

certificate authority An independent third-party that provides server certificates to enable secure communications across the Web through the secure sockets layer (SSL). These server certificates must be purchased and installed on your Web server to use secure sockets layer (SSL) and the HTTPS protocol.

class A data structure that groups properties and methods used to perform tasks in programs. Classes are the fundamental building blocks of object-oriented programs. Objects are instances of classes.

cookie A small file that a Web application can write to the client' s computer. Cookies are used to identify a user in a future session or to store and retrieve information about the user' s current session.

CSS See cascading style sheets (CSS).

D

data binding A way to link data (such as a data set) in your application to the properties of a control.

default page A page that IIS displays if the user navigates to the Web application directory without specifying a specific page to view. IIS uses the name Default.htm and Default.aspx as the default page unless you change it using the IIS property settings for the application.

delegation A programming technique by which a member of one class uses a member from another class to perform some task. Delegation differs from inheritance in that the first class implements a property or method and then calls (or delegates to) the other class' s property or method. The two classes do not have to be related to each other. In other words, the first class does not have to be derived from the second class.

deploying Installing an application on the computer where it will run.

derived class A class that is based on another class (called a base class) through inheritance. A derived class inherits the members of its base class and can override or shadow those members.

DHTML behaviors See behaviors.

dirty read The process of reading database records without locking the records being read. This means that an uncommitted change can be read and then rolled back by another client, resulting in a local copy of a record that is not consistent with what is stored in the database.

drivers In the context of testing, drivers are test components that make sure two or more components work together. Drivers are necessary to test components during development when an application is being developed from the bottom up.

E

error See exceptions.

exception handling The process of dealing with unusual occurrences within code so that they do not cause the program to crash or lose data.

exception log A record of exceptions that occurred while an application was running.

exceptions Unusual occurrences that happen within the logic of an application.

F

frames Regions of a Web page that you can use to display other Web pages. You use frames to display multiple regions that scroll and behave independently.

G

GAC See global assembly cache (GAC).

global assembly cache (GAC) A special subfolder within the Microsoft Windows folder that stores the shared .NET assemblies. Assemblies stored in the GAC are shared with all other applications installed on the computer.

Globally Unique Identifier (GUID) A 128-bit integer that serves as a unique identifier across networks. GUIDs are used throughout Windows and the .NET Framework to identify components.

GUID See Globally Unique Identifier (GUID).

I

image map A graphic containing multiple regions that the user can click to cause different actions to occur.

impersonation The process of assigning one user identity to another user. ASP.NET uses impersonation to authorize anonymous users to access resources on the Web server. By default, anonymous users impersonate the ASPNET user account.

inheritance The process of basing one class on another. In this process, a base class provides class members to a derived class. The advantage of inheritance is that you can write and maintain code once in the base class and reuse it multiple times in the derived classes.

interfaces In the context of object-oriented programming, an interface is a contract that defines the members that a group of classes provides. Once you implement a particular interface in a class, instances of that class can be used for any argument or variable declared as that interface.

L

localization The process of accommodating cultural differences within an application. Localized applications can support multiple languages, currencies, writing direction, and calendars based on the cultures that they support.

M

managed code Code that runs under the common language runtime. The common language runtime takes care of many tasks that would have formerly been handled in the application' s executable. Managed code solves the Windows programming problems of component registration and versioning (sometimes called DLL Hell) since managed code contains all the versioning and type information that the common language runtime needs to run the application. The common language runtime handles registration dynamically at run time, rather than statically through the system registry, as is done with applications based on the Component Object Model (COM).

multiple inheritance The programming technique of deriving a class from two or more base classes. Visual Basic .NET and Visual C# do not support multiple inheritance.

N

namespace collisions The problem that occurs when two items use the same name at a single level of the namespace hierarchy.

nodes See XML nodes.

neutral cultures Cultures that map to a specific language but not a specific region.

O

One-Click Hosting A feature ASP.NET Web service providers offer that allows developers to upload completed Web applications directly from the Microsoft Visual Studio .NET Start page' s Web Hosting pane.

optimization Writing code in a way that executes more quickly or consumes fewer resources.

overloading In the context of object-oriented programming, the programming technique of providing versions of a method that takes different types of arguments. The ToString method is a good example of an overloaded method.

overriding In the context of object-oriented programming, the programming technique of replacing a member inherited from a base class with a different member in the derived class.

P

project The collection of Visual Studio .NET source files that make up an application.

R

regression A problem in which a new or modified component breaks some previously working component. Regression is uncovered during testing.

reproducibility The ability to produce the same result again and again.

S

save point A point within a database transaction from which you can restore the database state.

scalability The ability to add capacity to an application as user demand increases.

Secure Sockets Layer (SSL) The standard means of ensuring that data sent over the Internet cannot be read by others. When a user requests a secure Web page, the server generates an encryption key for the user' s session and then encrypts the page' s data before sending a response. On the client' s side, the browser uses that same encryption key to decrypt the requested Web page and to encrypt new requests sent from that page.

SEH See structured exception handling.

server certificate A file installed through IIS that provides an encryption key for use with the secure sockets layer (SSL). Server certificates are obtained from a certificate authority, which licenses server certificates for a fee and acts as a clearinghouse to verify your server' s identity over the Internet.

server controls Visual components used on a Web form to create the user interface of a Web application.

session The sum of interaction between an instance of a client browser and a Web application. A session begins when the browser first requests a resource from within the application. A session ends either when the browser closes on the client' s machine or when the session times out after a period of inactivity. (The default is 20 minutes.)

shadowing The programming technique of replacing a member of a base class with a new member in a derived class. Shadowing differs from overriding in that the base class' s shadowed member is no longer available from the derived class.

shared members Methods that can be called directly without first creating an instance of a class. These members are called static members in Visual C#.

signature The name, parameter list, parameter types, and return type of a member.

solution A group of Visual Studio .NET projects that make up a single functional unit.

SSL See Secure Sockets Layer (SSL).

Start page The first Web form displayed when you run a Web application project from within Visual Studio .NET. The Start page is also the first page the Visual Studio .NET development environment displays in the Document pane. This Visual Studio .NET Start page contains various panes to help simplify some common tasks, such as opening a recent file and making information easier to find.

start-up project The first project Visual Studio .NET starts when you run a multi-project solution. The start-up project is shown in bold within the Solution Explorer window.

static members Methods that you can call directly without first creating an instance of a class. These members are called shared members in Visual Basic .NET.

step in Moving from a calling procedure to a called procedure during debugging. Also used in reference to setting breakpoints to stop execution in a specific procedure during debugging.

step over Executing a procedure call as a single statement during debugging.

structured exception handling (SEH) The programming technique of using exception-handling blocks or exception events to handle unusual occurrences in an application.

stubs Nonfunctional components that provide the class, property, or method definition used by another component during testing. Stubs are necessary to test components during development when an application is being developed from the top down.

subweb A virtual folder that contains a Web site.

superclassing The programming technique of deriving a new class from an existing class using inheritance. Superclassing generally refers to a situation in which most of the derived class' s behavior and members come directly from the base class.

T

template See XSL template.

testing interface A set of public properties and methods that you can use to control a component from an external testing program.

thread The basic unit to which the server allocates processor time. A single process can have multiple threads.

ToolTip A short, descriptive message that is displayed when a user places the mouse pointer over a control and leaves it there for a couple of seconds. These messages are used throughout Windows applications to provide helpful information about toolbar buttons and other graphic controls whose meaning might not otherwise be obvious.

tracing A programming technique for recording events, such as exceptions, in an application. Tracing is used during debugging and in the testing phase of application deployment.

transaction A group of commands (treated as a single unit) that change the data stored in a database. The transaction assures that the commands are handled in an all-or-nothing fashion—if one of the commands fail, all of the commands fail, and any data that was written to the database by the commands is backed out. In this way, transactions maintain the integrity of data in a database.

tuning The process of making adjustments to a deployed application that don' t affect code.

U

unhandled exceptions Exceptions that have not been dealt with in code. Unhandled exceptions cause applications to stop executing and appear to the user as errors in the application.

user control page A Web application project file with the .ascx filename extension that combines one or more server controls into a single, visual component that can be used on Web forms.

V

view state The current property settings of server controls on a Web form. By default, ASP.NET automatically maintains view state between post-back events.

virtual folder A shared resource identified by an alias that represents a physical location on a server.

W

Web farm A Web application running on multiple servers.

Web form The central user-interface component within a Web application.

Web garden A Web application running on a single server using multiple processors.

X

XML nodes Uniquely named elements within an XML file. XML nodes are organized hierarchically with parent-child relationships.

XML schema A description of the data elements contained in the XML file. The XML schema provides the names of the elements, their types, whether or not they are key fields, and other information.

XSL template An element within an XSL file that provides the information used to format an XML node during an XSL transformation.


System Requirements

To get the most out of the MCAD/MCSD Training Kit: Developing Web Applications with Microsoft Visual Basic .NET and Microsoft Visual C# .NET, you will need a computer equipped with the following minimum configuration:

  • Microsoft Windows 2000 operating system or Microsoft Windows XP Professional Edition

    note

    The default installation configuration for these operating systems includes the Internet Information Services (IIS) software that is required for developing Web applications.

  • Microsoft Visual Studio .NET Professional Edition or Visual Studio .NET Enterprise Developer Edition

    note

    A 60-day evaluation copy of Visual Studio .NET Professional is included on the DVD.

  • 3.5 GB on the installation drive, including 500 MB on the system drive

  • 160 MB physical memory; 256 MB is recommended

  • 450 MHz or higher Intel Pentium II–compatible processor

  • Super VGA monitor (800 x 600 resolution or higher with 256 colors)

  • CD-ROM drive or DVD drive, 12x or faster recommended

    note

    A DVD drive is required to install the Evaluation Edition software, and a CD-ROM drive is required to install the practice files and the eBook.

  • Microsoft Mouse or compatible pointing device

  • Internet connection and networking card (56K modem is the minimum; a higher connection speed is recommended)