ASPnet Step by Step PDF
ASPnet Step by Step PDF
Table of Contents
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
Duthie, G. Andrew, 1967-
Microsoft ASP.NET Step by Step / G. Andrew Duthie.
p. cm.
Includes index.
ISBN 0-7356-1287-0
1. Internet programming. 2. Active server pages. 3. Web servers. I. Title.
1 2 3 4 5 6 7 8 9 QWT 7 6 5 4 3 2
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 [email protected].
The list of people to whom I am grateful for their assistance and support in the
production of this book is long, and I hope that I don’t leave anyone out. If I missed
anyone, please know that your contributions are appreciated.
First I would like to thank my mother for her unceasing support and encouragement.
During the process of writing this book, I made the transition from working as a
consultant for someone else to starting and running my own company, something that
my mother assured me I would eventually have to do. Once again, Mom, you were right.
Several members of the ASP.NET development team provi ded invaluable support along
the way. My thanks to Scott Guthrie and Susan Warren, for their willingness to take time
out of their schedules early and often to share information about this exciting new
technology with authors and helping us to share it with readers. Thanks also to Erik
Olson for his assistance in reviewing this book and helping me make it better, and
especially to Rob Howard, for cheerfully making himself available to answer my many
questions and for his hard work in making sure that authors and developers alike have
the best possible information. To the extent that this book is successful in helping you
learn ASP.NET, they deserve a good deal of credit. Any shortcomings of the book are
mine alone.
I appreciate the support and assistance provided by my acquisitions editor, David Clark,
and the team at Microsoft Press. Writing about beta software is never easy, but David
and the folks at Microsoft Press did everything in their power to make it easier, making
sure that I always had the latest versions of the software and whatever other resources I
needed.
I’d also like to thank Rob Caron for his assistance and his hard work on documenting
these technologies. Rob and I worked at the same company while I was writing my first
book, and I’m glad that even though he’s now 3,000 miles away, we still manage to keep
in touch from time to time.
Finally, I must express my deep and eternal gratitude for the continued support and love
of my wife, Jennifer. She is a blessing to me in the best possible sense of the word, and
by being there for me day in and day out in countless ways, she makes it possible for me
to bring these words to you. My wish for the world is that everyone might be blessed with
such a partner and friend!
G. Andrew Duthie
November, 2001
About the Author
G. Andrew Duthie is the founder and Principal of Graymad Enterprises, Inc., providing
training and consulting in Microsoft Web development technologies. Andrew has been
developing multitier Web applications since the introduction of Active Server Pages. He
wrote about developing scalable n-tier applications in Microsoft Visual InterDev 6.0
Developer’s Workshop, also from Microsoft Press.
In addition to his writing, consulting, training, and speaking, Andrew enjoys playing
music, smoking fine cigars, and most recently, playing Dead or Alive 3 tag-team matches
with his wife, Jennifer, on their new XBOX.
You can reach Andrew by e-mail at [email protected].
Hammer
Most of the hand tools used today have changed little since the Middle Ages, the only
major improvement being the use of steel instead of iron for cutting edges. The most
common hand tools include saws, planes, and chisels, and such miscellaneous tools as
hammers and screwdrivers, which are used in conjunction with fasteners. A hammer is a
hand tool consisting of a shaft with a metal head at right angles to it, used mainly for
driving in nails and beating metal.
At Microsoft Press, we use tools to illustrate our books for software developers and IT
professionals. Tools are an elegant symbol of human inventiveness, and a powerful
metaphor for how people can extend their capabilities, precision, and reach. From basic
calipers and pliers to digital micrometers and lasers, our stylized illustrations of tools give
each book a visual identity and each book series a personality. With tools and
knowledge, there are no limits to creativity and innovation. Our tag line says it all: The
tools you need to put technology to work.
[1] * Microsoft Encarta® Reference Library 2002. © 1993–2001 Microsoft Corporation.
All rights reserved.
The manuscript for this book was prepared, galleyed, and composed using Adobe
FrameMaker 6. Pages were composed by TIPS Technical Publishing, Inc., with text in
Sabon and display type in Syntax. Composed pages were delivered to the printer as
electronic prepress files.
Cover Designer: Patricia
Bradbur
y
Indexer: Ariel
Tupelan
o
Afterword
When I was first exposed to ASP+, which would become ASP.NET, I was immediately
reminded of the ’80s song by a band called Timbuk 3. The refrain was “The future’s so
bright, I gotta wear shades.” In the 18 months since then, I think the future of Web
development, and ASP.NET’s place in it, has only grown brighter.
In many ways, ASP.NET and the .NET Framework have opened up new possibilities to
developers who have existed solely in the world of Notepad and scripting languages. In
some development circles, ASP developers have been treated as second class
citizens— no more. Whether you use a text editor or a rich IDE like Visual Studio .NET,
the .NET Framework and ASP.NET offer you the power of fully compiled languages and
object- oriented programming.
As with any big step forward, there are costs associated with the move to this new
platform. The learning curve for some of the technologies used in ASP.NET may be a
little steeper than with classic ASP; but the reward for this learning is greater power,
greater productivity, and substantially better applications.
I hope that this book helps developers get up to speed with this great new technology. I
also hope that you, the reader, will help me by letting me know whether I have met that
goal. I welcome all reader feedback at [email protected].
G. Andrew Duthie
If you have problems, comments, or ideas regarding this book or the sample files on the
CD-ROM, please send them to Microsoft Press.
Microsoft Press
Redmond, WA 98052-6399
Please note that product support is not offered through the above addresses. For help
with ASP.NET, you can connect to Microsoft Technical Support on the Web at
support.microsoft.com/directory, or for additional developer information about ASP.NET,
go to www.microsoft.com/net and search on ASPNET.
Visit the Microsoft Press World Wide Web Site
You are also invited to visit the Microsoft Press World Wide Web site at the following
location:
http://mspress.microsoft.com/
You’ll find descriptions for the complete line of Microsoft Press books (including others
by G. Andrew Duthie), information about ordering titles, notice of special features and
events, additional content for Microsoft Press books, and much more.
System Requirements
Before you break the seal on the ASP.NET Step by Step CD-ROM package, be sure that
you have the correct version of the .NET Framework installed and that your operating
system meets the minimum requirements for running ASP.NET applications.
This book was written to work with the Beta 2 or later release of ASP.NET and the .NET
Framework. If you are using the Beta 1 or earlier release, you will need to upgrade in
order to make effective use of the examples in the book. The sample files require
approximately 500 Kb of hard disk space.
ASP.NET applications can be run only on Windows 2000 or later with Internet
Information Services installed. The examples in this book were written and tested on
Windows 2000, but they should work without modification on Windows XP Professional
or Windows .NET Server.
In addition to installing the sample files by using the set-up program, you can also
browse the files directly. The files are organized by chapter number for easy reference.
Note Some of the sample files need to be compiled using the
command-line compilers for Visual Basic .NET or C#. Batch files
have been included on the CD to simplify the compilation process,
but in order to use these batch files you need to add the path to
the folder containing the command-line compilers to your PATH
environment variable. By default, the compilers are located in the
%windir%\Microsoft.NET\Framework\%version% folder, where
%windir% is the Windows directory, and %version% is the version
number of the .NET Framework. To find the actual values for your
system, locate the Microsoft.NET directory under the Windows
directory in Windows Explorer, and expand the Microsoft.NET
node and its Framework child node. The version number should
be the name of the folder underneath Framework.
The procedure for adding the path to this folder in Windows 2000
is as follows.
ASP.NET is not just an upgrade-not by a long shot. ASP.NET provides the most
advanced Web development platform created to date. What's more- ASP.NET has been
rebuilt from the ground up to create an entirely new and more flexible infrastructure for
Web development.
What makes ASP.NET so revolutionary is that it's based on Microsoft's new .NET
platform- or more accurately the .NET Framework. In order to understand clearly where
and when to use ASP.NET- let's take some time to go over the Microsoft .NET platform-
the products that it comprises- and where ASP.NET fits within Microsoft .NET.
The individual language compilers compile the code written by developers into an
intermediate language called Microsoft Intermediate Language (IL or MSIL). This IL is
then either compiled to native code by the runtime at install time or compiled Just-In-
Time (JIT) at first execution.
Code that is compiled to IL and managed by the runtime is referred to as managed code.
It’s called this because the runtime takes responsibility for managing the execution of the
code, including the instantiation of objects, allocation of memory, and garbage collection
of objects and memory.
Components written in managed code and executed by the runtime are referred to as
.NET managed assemblies, or just assemblies for short. Assemblies are the basic unit of
deployment in the .NET world and are quite similar to COM components. The difference
is that, whereas a COM component contains or has an associated type library to
describe how clients should interact with it, an assembly contains a manifest, which is
the set of metadata that describes the contents of the assembly. Among other
advantages, the self-describing nature of .NET components means that they don’t need
to be registered on a computer in order to work!
This metadata also describes the dependencies and version information associated with
an assembly. Not only does this make it much easier to ensure that all necessary
dependencies of an assembly are fulfilled, but it also means that multiple versions of the
same assembly can be run side by side on the same computer without conflict. This is a
major step in resolving “DLL Hell,” the bane of many developers’ existence. Just ask any
Web developer who’s worked with more than one version of ActiveX Data Objects
(ADO), and you’re sure to get an earful about applications being broken by a new
version of ADO. With .NET, this should be a thing of the past. As long as the consuming
application knows which version of an assembly it’s designed to use, it can locate the
correct version among multiple versions of the same assembly by querying the
assembly’s metadata.
There’s a great deal more to the runtime, and you’ll learn about it in future chapters. If
you need information on the runtime, do a search on “common language runtime” in
either the .NET Framework SDK documentation or the MSDN Library documentation for
Visual Studio .NET.
Inheritance
Inheritance is a central concept in the .NET Framework. It provides a way for
developers to use existing code in classes. A class can expose both properties and
methods that clients can use. Classes that are inherited from a particular base class
are said to be derived from that class. By inheriting from this class, a developer can
reuse the functionality that it exposes without having to rewrite the code.
In addition (and more importantly), a developer using the inherited class can override
one or more of the methods exposed by the class in order to provide a specialized
implementation of that functionality. This capability will come in handy when you learn
about custom server controls.
Enterprise Servers
The .NET Enterprise Servers are the first step in the evolution of the Microsoft
development platform. Although the .NET Enterprise Servers don’t explicitly take
advantage of the runtime and the class library, they do form a solid foundation on which
you can begin building enterprise-class business solutions.
Note When you’re developing classic ASP applications using the .NET
Enterprise Servers, you should do so with ASP.NET in mind. For
example, because the default parameter type for Visual Basic
.NET is ByVal, you should write your classic ASP applications
such that they will work without modification under ASP.NET. See
Appendix A, “Migrating from ASP to ASP.NET,”for more
information on coding practices that will make your classic ASP
applications easier to migrate.
Together, these products provide much of the functionality needed by most large
businesses, right out of the box. This section will discuss these products and their
features.
Using the Commerce Server 2000 starter sites, developers can create a fully functional
business-to-consumer e-commerce site simply by importing their product catalog.
Although the starter site provides only a basic user interface (UI), as shown in the
following figure, it supplies all of the logic necessary to run the navigation, the product
searching and browsing, and the shopping cart.
With the additional step of providing a custom UI, developers can very quickly and easily
run an e-commerce site with rich UI and back-end functionality, including data and traffic
analysis tools (based on the SQL Server 2000 OLAP engine) that allow organizations to
track sales, site traffic, and so on.
BizTalk Server lets you send and receive documents (such as purchase orders) in any
format using the XML-based transformation functionality of the BizTalk Mapper. The
BizTalk Orchestrator lets you quickly and easily diagram a business process and then
map each step to scripts or components that will execute that step. Once a process has
been diagrammed, BizTalk Orchestrator can create an XML-based document that
describes the business process. This document is then used by the BizTalk Orchestrator
runtime to execute the process. A distinct advantage of this approach is that it allows
rapid development and significantly easier modification of business processes.
For the .NET platform, languages (and the tools with which you’ll use them) are probably
one of the most important topics to discuss, and they’re covered throughout this book.
For now, let’s take a high-level look at some of the languages and tools that will be
available for developing .NET applications.
Notepad “.NET”
Believe it or not, many developers, particularly ASP developers, still do much of their
development in Microsoft Notepad, which I used to lovingly refer to as “Visual” Notepad.
Now I guess I’ll have to change to Notepad “.NET”. While Notepad has the substantial
advantage of being ubiquitous, it’s not exactly what you’d call a robust development
environment. That said, if you’re working with the .NET Framework SDK (rather than
Visual Studio .NET), there’s no reason you can’t use Notepad to do all of your .NET
development. The .NET Framework SDK includes command-line compilers for Visual
Basic .NET, C# (pronounced “C sharp”), and JScript .NET. So you can create your
classes, ASP.NET pages, and so on in Notepad, and then you can either compile them
explicitly using the command-line compilers or, in the case of ASP.NET, allow the
ASP.NET runtime to dynamically compile the page when it’s requested for the first time.
This means that developers of Visual Basic, Visual C++, and C# will all share the same
IDE, including the capability to perform debugging and error-handling across languages
in the same environment.
There are many new features in Visual Basic .NET, but users of VBScript and Visual
Basic 6.0 should have little trouble using it once they’re familiar with the .NET
programming model. There will be plenty of code examples throughout the book to help
you get started.
C#
A new member of the Visual Studio family, C#, is a descendent of C. It’s much like C++,
but designed with greater simplicity and ease of use in mind. Although C# isn’t
necessarily as easy to learn as Visual Basic, it’s far easier than C++ and provides nearly
all of the power available to C++ developers. It also doesn’t require you to manage the
allocation and deallocation of memory, as C++ does. Because C#, like Visual Basic
.NET, is a managed language, all of the memory management is taken care of by the
runtime. This is an important advantage because memory management is one of the
most troublesome areas of C++ development and is responsible for many application
crashes.
Developers familiar with C, C++, and Java will quickly become productive using C#. This
book will have some code examples in C# that will give you a taste of this exciting new
language.
Visual Studio .NET also provides an extremely flexible plug-in architecture that allows
other languages that are written for or ported to the .NET platform to easily use the
power of the Visual Studio IDE.
The current list of third-party languages planned for Visual Studio .NET includes
§ APL
§ COBOL
§ Pascal
§ Eiffel
§ Haskell
§ ML
§ Oberon
§ Perl
§ Python
§ Scheme
§ Smalltalk
§ Many others (17 languages at the time of this writing)
Between the languages that will ship with Visual Studio .NET and the third-party
languages that will be available, there should be something to please just about any
developer.
ASP.NET Architecture
While there are plenty of familiar features in ASP.NET- there have also been some
significant changes made to the ASP.NET architecture- including many improvements
and new features. The following section will take a high-level look at what's new in
ASP.NET.
Familiar Features
It's important to note that many things in ASP.NET will be familiar to Web developers
who've used classic ASP. The much-used Request and Response objects are still there-
as are the Application- Session- and Server objects- albeit with some new properties and
methods. You can still use either <SCRIPT RUNAT= 'SERVER> blocks or the <% %>
ASP script delimiters to denote server-side script. In fact- for the most part you can write
an ASP.NET page exactly the same way you would write a classic ASP page. Once you
get used to the new programming model of ASP.NET- though- you'll never go back to
coding your ASP applications the way you do today.
Also- you don't need to migrate all of your existing ASP applications at once. ASP.NET is
designed to run side by side with classic ASP. So while you're working on your first new
ASP.NET application- your current ASP applications can still be running right alongside.
What's New
There's a lot of new stuff in ASP.NET- and it will take time to learn all of it. But once
you've learned it- your productivity will be far greater than it was with classic ASP. Let's
look at a list of some of the new features of ASP.NET.
§ Web Forms This is the new programming model of ASP.NET. Web
Forms combines the best of ASP with the ease of development and
productivity of Visual Basic. You can drag controls onto a page and then
write code to provide interactivity- call business objects- etc. You'll learn
about Web Forms in Chapter 9.
§ Server controls A major component of the Web Forms programming
model- the ASP.NET server controls map approximately to HTML
elements (plus some additional controls you'll learn about later) and
provide powerful server-side programmability. Server controls are run on
the server and can output HTML that's tailored for uplevel browsers-
such as Internet Explorer 5.x or later- or for any HTML 3.2'compliant
browser. Chapter 10 and Chapter 12 will cover server controls in depth.
§ Web Services This is a key part of ASP.NET that allows developers to
make programmatic services available to other developers over the
Internet (or a local intranet). Web Services are based on the emerging
SOAP (Simple Object Access Protocol) standard- so they will allow
relatively painless interoperation across diverse platforms. You'll learn
more about Web Services in Chapter 13.
§ Caching ASP.NET includes a powerful new caching engine that will
allow developers to improve the performance of their applications by
reducing the Web server and database server processing loads. You'll
learn more about caching in Chapter 14.
§ Configuration Improvements ASP.NET uses a new method of storing
configuration information for Web applications. Instead of having IIS
store this information in a hard-to-access database- it's stored in XML-
based human- and machine- readable configuration files. You'll look at
how these configuration files work in Chapter 7.
§ State Management Improvements If you've had to build an ASP
application to run on a Web farm- you know all too well that there were
major limitations to state management in classic ASP. ASP.NET
overcomes these limitations- providing support for distributing session
state across Web servers- persisting state information in a SQL Server
database- and providing state management without the use of cookies.
You'll learn how to take advantage of these features in Chapter 6.
§ Security This is an extremely important function in today's Web
applications. The security model in ASP.NET has been substantially
improved- including new and improved authentication methods- code
access security- and role-based authorization. You'll look at the
ASP.NET security model and how to implement security in your
ASP.NET applications in Chapter 8.
§ Improved Reliability ASP.NET contains new features aimed at
improving the reliability of Web applications- including proactive
restarting of applications and automatic process recycling to resolve
deadlock conditions and memory leaks. You'll learn more about these
features in Chapter 7.
Conclusion
We’ve only scratched the surface in describing some of the new offerings of the .NET
platform, and the substantial advantages offered by ASP.NET. In subsequent chapters
you’ll get detailed information on using ASP.NET to create faster, more robust, and more
functional Web applications. In the next chapter, you’ll learn about the various
development tools that you can use to create your Web applications, from simple text
editors, to powerful IDEs like Visual Studio .NET.
Now that you’ve learned a little bit about some nifty new features of ASP.NET, the next
question is: how do you take advantage of them? Clearly, you’re going to need some
development tools. Conveniently enough, that’s precisely what you’re going to learn
about in this chapter.
In the world of Visual Basic 6.0 programming, you had only one development tool: the
Visual Basic 6 Integrated Development Environment (IDE). You could get it as a stand-
alone product or as a part of Visual Studio 6.0, but if you wanted to develop Visual Basic
applications, it was pretty much your only choice.
One distinct advantage of classic ASP was that, because the code was interpreted and
executed by an ActiveX scripting engine built into Internet Information Services (IIS), you
didn’t need a compiler or an IDE to create Active Server Pages. All you really needed
was a text editor.
What’s the point of all this history? Well, ASP.NET pages can also be created with a
simple text editor because the language compilers are installed with ASP.NET in the
.NET Framework SDK. But instead of being interpreted at runtime, ASP.NET pages are
compiled, which means substantially improved performance.
So, if you can write ASP.NET pages with a simple text editor, why would you bother to
buy an IDE like Visual Studio .NET? There are many good reasons, including advanced
features like IntelliSense statement completion, advanced project file management, rich
designer support, and many others.
Still, some people may want to learn about ASP.NET without incurring the expense of a
full-featured IDE, while others will settle for nothing less.
Notepad '.NET
It's not really called Notepad '.NET- but just as there were those who affectionately
referred to this ubiquitous tool as 'Visual Notepad- calling it Notepad '.NET is a way of
reminding ourselves that sometimes keeping things simple is a good thing. Given that
you can find Notepad on just about any Microsoft Windows platform (with the exception
of some versions of Microsoft Windows CE)- you'd be hard-pressed to name a more
convenient development tool.
One other major advantage of Notepad is its price-it is free. You don't even need to go
out and download it. To get started developing with ASP.NET and Notepad- all you really
need to do is install the .NET Framework SDK (or the ASP.NET runtime redistributable
package).
Once you've installed the SDK- creating an ASP.NET page can be as simple as creating
an HTML document using Notepad and saving it with the extension .aspx. That's really
all it takes. For example- consider the following HTML code for a very simple ASP.NET
page:
<html>
<head>
<title>First ASP.NET Page!</title>
</head>
<body>
<p>Hello World!</p>
</body>
</html>
If you create a text file in Notepad with this code and save it with an .aspx extension in a
valid IIS virtual directory- you'll have a perfectly valid ASP.NET page. (As it happens- this
is also a perfectly valid ASP page.) Of course- it doesn't do much yet. You'll fix that soon
enough. But first- let's create a virtual directory in IIS for testing ASP and ASP.NET
pages.
4. On the first page of the Virtual Directory Creation Wizard- click Next.
5. On the second page- enter an alias (name) for the new virtual
directory. This name will be used to access the content in the virtual
directory from a Web browser. For this example- use the name
ASPNETTest. Click Next.
6. Enter the path to the directory in the file system where you'd like to
keep the content for the virtual directory. This can be an existing folder
or a new folder. If you're using an existing folder or you've already
created the new folder for the content- you can use the Browse button
to find the folder you want. For this example- use
C:\Inetpub\WWWRoot\ASPNETTest. Click Next.
Note In most cases- the file system directories for virtual directories of the
default Web site are stored under the C:\InetPub\WWWRoot folder.
InetPub is a special folder set up by the IIS installation process- and
WWWRoot is a folder set up as the root of all Web server content.
This doesn't mean that you have to use WWWRoot as the parent
folder for your Web content folders- but it can be a convenient way of
organizing all of your content in a single easy-to-find location. You
should keep in mind- however- that using WWWRoot (particularly on
the default install location of the C drive) puts all of your applications in
a well-known location- which can make life easier for hackers
attempting to deface your sites or locate important information to steal.
7. On the Access Permissions page- you can alter the settings that
determine how users can access the content in your new virtual
directory. The default settings are fine in this case- so click Next.
Important Use caution when altering the access permissions for a virtual
directory. Allowing Write- Execute- and/or Browse permissions
on your virtual directory can allow unfriendly individuals to alter
your content or execute damaging code on your server. Only
alter these settings if you understand the implications of doing
so.
8. That's it! Your virtual directory is complete. Click Finish.
Now that you've created your ASPNETTest virtual directory- go ahead and save the
HTML document listed earlier.
1. Open Notepad. Click Start- Programs- Accessories- Notepad.
2. Type in the HTML code shown on page 18.
3. Save the document to the file system folder that you created to hold
the contents of your test virtual directory. Name the file
ASPNETHello.aspx.
Now that you've saved the document- you should be able to view it. Open a Web
browser and enter the following URL:
http://localhost/ASPNETTest/ASPNETHello.aspx.
Assuming that your Web server is correctly installed and you've created your virtual
directory based on the steps earlier in the chapter- you should see something like the
following figure.
While this page may not be terribly exciting and doesn't do much yet- you've taken the
first step toward developing a successful ASP.NET application using Notepad.
Note Localhost is a name that is used in URLs to refer to the local Web
server. It's equivalent to the Internet Protocol (IP) address
127.0.0.1- which is the default loopback address for a machine.
You can also browse local HTML- ASP- and ASP.NET pages by
using the following syntax:
http://<machine name>/<vdir name>/<page name>
where <machine name> is the name of the computer you're
browsing from- <vdir name> is the name of the virtual directory
that contains the page you want to browse- and <page name> is
the name of the page you want to browse. <page name> is
optional. If <page name> is omitted- the Web server will deliver
the default page for that virtual directory (usually default.htm-
index.htm- or variants of these with the .asp extension)- if a default
has been specified.
Notepad is probably the most common text editor in the Windows world- but it's by no
means the only one. In addition to Notepad- there are many third-party text editors that
offer many features that were once only available in high-end development tools. Some
of these features include the following:
§ Syntax coloring The use of color to indicate various types of keywords
in the language you're using for development.
§ Auto-completeSuggests possible completions for statements as you
type- based on what you've already typed- and how they match with a
custom file of the syntax for the language with which you're working.
§ Available source codeFor developers who want to know how to build
their own code editor- there are even open-source editors- including one
built in the newest .NET language- C#.
If you're only beginning in the world of ASP.NET- one of these text editors may be a
good starting point for you. It will allow you to use features you won't find in Notepad
without immediately taking on the expense of a professional development tool like Visual
Studio .NET.
If you won't settle for anything less than full power- or you want the many extra added
features of a true IDE- Microsoft has created Visual Studio .NET- the redesigned version
of their Visual Studio development suite. The next section will walk through several
simple procedures using the Visual Studio .NET IDE.
That’s just a brief list. There’s much more to the tool than can be covered in a single
chapter. So without further ado, let’s look at how to create projects and pages in the
Visual Studio .NET environment.
Creating an ASP.NET Web Application
One of the first things you’re going to want to do in order to work with ASP.NET in Visual
Studio .NET is create a new project, or in Visual Studio .NET parlance, a Web
application. Here are the steps necessary to create a new Web application:
1. Open Visual Studio .NET.
2. There are three methods of opening the New Project dialog box:
§ Click the Create a New Project link on the Visual Studio
.NET Start Page (displayed by default when you first
open Visual Studio .NET).
§ Click the New Project button, located on the toolbar.
That’s it! You’ve now created your first ASP.NET Web application. Next we’ll look at how
to add new pages.
In your new Web application, you’ll notice that Visual Studio .NET has already added a
page to the project for you, named WebForm1.aspx, and opened it in the editor.
However, since one page is rarely enough for most sites, let’s look at how to add a new
page to your Web application.
1. As with creating a new project, there are several ways to add a new
ASP.NET page (also referred to as a Web Form) to your application.
Which one you should use is largely a matter of how you like to work.
The methods are
§ In the Solution Explorer window (see the following
figure), right -click the application name and then select
Add, Add Web Form. You can also select Add New
Item and then select Web Form from the Templates
selection in the Add New Item Dialog.
§ On the Visual Studio .NET toolbar, click the Add New
Item button. You can also click the down arrow next to
this button and then choose Add Web Form from the
pop-up menu.
§ From the Project menu, select Add Web Form (or Add
New Item).
Any of these methods will open the Add New Item dialog box, shown in the following
figure.
2. In the Add New Item dialog box, select the Web Form template and
specify a name for the new page. Since you’re going to use this page
as the start page for this project, call it index.aspx. Before finishing up,
you may want to take a look at some of the other template types that
are available, both for Web Projects and for Local Projects. Once
you’re done looking, click Open. Visual Studio .NET creates the page,
adds it to the project, and opens it in the Web Forms Designer (see
the following figure).
Adding Controls
Now that you’ve created a start page for your new application, what can you do with it?
Well, let’s start by making it do the same thing as the page that you created in Notepad.
Only this time, instead of using plain HTML text, use the Label control (one of the
ASP.NET server controls) to display the Hello World greeting to the client. Here are the
steps to add the Label control to the Web Form:
1. With the Web Form open in design mode, place your mouse over the
Toolbox tab. (By default, it’s found to the left of the code
editor/designer window.)
2. When the Toolbox appears, ensure that the Web Forms palette is
active. (The title bar of the active palette is shown immediately above
the controls displayed in the Toolbox.) If it isn’t active, you can click on
its title bar to activate it.
3. With the Web Forms palette active, double-click the Label control entry
(see the following figure). Once you’ve added the label, it should be
selected by default.
4. To make the Label control display the text you want, you need to
change its Text property. Click to the right of the Text entry in the
Properties window, and then change the text (by default, Label) to
Hello World!, as shown in the following figure.
Saving and Browsing Your Page
Now that you’ve added your Server Control, go ahead and save the page by clicking the
Save button on the toolbar. You can also save by selecting Save <filename> from the
File menu.
Because the Web Form page you used automatically adds a code-behind module for
your Web Form, you need to build your project before you can browse the page. (You’ll
learn more about code-behind in later chapters.) Building is the process of compiling all
of the code modules in the project so they’ll be available to the pages and modules that
call them. To build the project, select Build from the Build menu.
Once you’ve saved the Web Form page and built the application, you can view the page
in an embedded browser window by right-clicking the page and selecting View in
Browser. The result should look like the following figure.
Fundamentals
Chapter List
Chapter 3: ASP.NET Development Overview
Chapter 4: Understanding Programming Basics
ASP.NET applications- at their simplest- are much like classic ASP applications. A
simple ASP.NET application consists of the following four things:
§ A virtual directory in IIS - configured as an application root - to hold the
files that make up the application and to control access to the files
§ One or more .aspx files
§ A Global.asax (analogous to the Global.asa file in classic ASP) file to
deal with Session and Application start-up and clean-up logic (optional)
§ A Web.config file used to store configuration settings (new in ASP.NET
and optional)
For Visual Studio .NET users- the good news is that all of the preceding files are created
for you when you create a new Web application project.
Web Forms in Visual Studio .NET allow you to create rich- interactive applications simply
by dragging and dropping controls onto a page and then writing minimal code to handle
user interaction- events- etc. In addition- the Visual Studio .NET environment lets you
work on your pages visually- using the Web Forms Designer- or textually- using Visual
Studio .NET's powerful source-code editor.
The code that you write in your Web Forms can be written in one of two ways: inline in
the .aspx file (as is typical of a classic ASP page)- or using a code-behind module. While
it's possible to write your application with code in the actual .aspx file and still take
advantage of compiled code and the other improvements of .NET- it's recommended that
you get in the habit of using code-behind modules.
Code-Behind
Code-behind is a new feature in ASP.NET that allows developers to truly separate the
HTML UI code from the code in Visual Basic .NET- C#- or other languages that is used
to provide user interaction- validation- etc. There are a number of advantages to using
code-behind modules- including the following features.
§ Clean separation of HTML and code Code-behind allows HTML
designers and developers to do what they do best independently-
while minimizing the possibility of messing up one another's work
(something that happens all too frequently when developing classic
ASP applications).
§ Easier reuse Code that isn't interspersed with HTML in an .aspx page
can be more easily reused in other projects.
§ Simpler maintenance Because the code is separated from the
HTML- your pages will be easier to read and maintain.
§ Deployment without source code Visual Studio .NET projects using
code- behind modules can be deployed as compiled code (in addition
to the .aspx pages)- allowing you to protect your source code if you
wish. This can be very useful if you're creating applications for clients
but wish to retain control of your intellectual property.
All in all- it's worthwhile to get into the habit of using code-behind. You'll see examples of
the use of code-behind throughout the book. Part IV of the book will discuss Web Forms
in detail.
An XML Web service- at its simplest- is a chunk of programming code that is accessible
over the Web. XML Web services are based on the World Wide Web Consortium's
(W3C) SOAP specification. This allows computers on varying platforms- from Windows
servers to UNIX workstations- to offer and consume programmatic services over the
HTTP protocol.
Note SOAP can use other protocols- such as FTP or SMTP- but HTTP
is the most common protocol used with SOAP and XML Web
services because most firewalls allow communication via the
HTTP protocol.
ASP.NET makes it remarkably easy to implement XML Web services. In fact- all it takes
is adding a declaration to any methods you wish to make available as XML Web
services. Visual Studio .NET makes it even easier by taking care of all the work
necessary to make your XML Web service available to potential clients. Part V will
discuss XML Web services in detail.
If you’ve worked with Visual Basic or Visual Basic Scripting Edition (VBScript) but don’t
have experience with C, C++, or JScript, you’ll likely be most comfortable working in
Visual Basic .NET. Although there have been substantial changes to the language since
Visual Basic 6.0, Visual Basic .NET is similar in syntax. This book isn’t intended to be a
language tutorial for Visual Basic .NET, but we’ve made an effort to point out where
there are significant differences between the syntax of Visual Basic .NET and its
predecessor.
C#
If you’ve worked with C, C++, JScript, or Java, you may be most comfortable with C#.
Like Java, C# is derived from C and C++, so you should have no trouble becoming
productive in C# in fairly short order. Unlike C and C++ (and, to an extent, Java), the C#
learning curve isn’t terribly steep, so developers who are familiar with Visual Basic may
wish to take a look at C# as well.
Unlike Visual Basic .NET, C# does allow you to work with unsafe code (pointers and
memory manipulation, and so on), although this isn’t recommended for beginners. After
all, it’s not called unsafe code for nothing! If you need to perform direct memory access
or work with legacy C or C++ modules, C# will be the better choice for you.
For example, let’s say your primary language is Visual Basic .NET, but you need to
access functionality in a DLL written in C. You can create a class library in C# to access
the DLL, and then inherit from that class in Visual Basic .NET (or just use the class from
Visual Basic .NET).
IDE Enhancements
Some of the new enhancements you'll find in the Visual Studio .NET IDE include the
following features.
§ Start PageThis is the default page that's displayed each time you start
Visual Studio .NET. It allows you to set up your preferences for the IDE-
access recent and existing projects- and create new projects.
§ Multilanguage IDE Unlike Visual Studio 6.0- which used different IDEs
for each language (although Visual InterDev and Visual J++ shared an
IDE)- in Visual Studio .NET- all languages share the same IDE. This
means that standard features like Find and Replace- debugging- and so
on work consistently across different languages. This alone will be a big
productivity enhancer.
§ Command window A cross between Visual Basic's Immediate window
and a command line- the Command window lets you execute Visual
Studio commands or code statements- depending on the mode of the
window. The following figure shows a Command window that's been
switched to immediate mode using the immed command. The Command
window has two modes:
o Command mode Allows you to execute Visual Studio
commands without using the menu system- or to execute
commands that don't appear in any menu.
o Immediate mode Used in debugging. Allows you to
evaluate expressions- check the value of variables -
execute program statements and functions - etc.
§ Tabbed documents Designed to simplify the management of multiple
files being edited simultaneously- the Tabbed Documents interface
allows you to see all of the files you're editing at once. This makes it
much simpler to switch back and forth between open editing windows.
You can still set up Visual Studio .NET to use the old method used by
Visual Studio 6- however. Just select Options from the Tools menu-
select the General option in the Environment folder- switch from Tabbed
Documents to MDI environment- and then click OK. You'll need to restart
Visual Studio .NET for this change to take effect.
§ Auto-hide My personal favorite- auto-hide works much like the
feature of the same name in the Windows toolbar. To enable
auto-hide for a window- click on the pushpin icon (shown in the
margin) in the window's title bar. Now the window will hide itself at
the side of the IDE where it's docked when the mouse moves
away from the window- leaving only a tab with the window title
visible. Mousing over the tab will cause the window to reappear.
This is a great feature for preserving the maximum amount of
screen real estate for the code window- and it can make life much
easier in terms of managing multiple windows in the IDE.
§ Improved HTML editor Like Visual InterDev before it- the Visual
Studio .NET HTML editor provides both a design view and an
HTML (source) view. Visual Studio .NET has done away with the
Quick View window provided by Visual InterDev. Instead- you
preview pages in an embedded browser window- which provides
a truer view of how a page will really look. The improved editor
also supports specifying the HTML schema you're writing for via
the targetSchema property. Setting targetSchema determines
which elements will be made available via the editor's statement
completion features and allows the IDE to provide you with
feedback on syntax that's incorrect in the context of your chosen
target schema.
New Features
In addition to the IDE enhancements- there are a number of entirely new features in the
Visual Studio .NET IDE.
§ XML editorThis allows you to edit XML data (.xml) and schema
(.xsd) files in source- data- or schema views- depending on the
type of XML file you're editing.
§ Autogenerated documentation An exciting feature that's
currently available only in C#- this allows you to generate
documentation from comments in your C# code using a special
comment delimiter (///) and syntax. Visual Studio can also
generate HTML documentation for projects and solutions
regardless of the language used by the project.
§ Dynamic Help A feature that provides context-sensitive help
while you work in the IDE- it suggests topics of interest as you
add files- controls- and code to your project. The following figure
shows the Dynamic Help window that appears when you're
editing the <html> element of a Web Form.
Windows
During your time working with Visual Studio .NET- you'll encounter a wide variety of
windows in the IDE- used for a wide variety of purposes. Some are new- like the
Dynamic Help window described in the previous section- while some will be familiar to
users of previous versions of Visual Studio. This section will take a look at the most
commonly used windows.
§ Designer/Source Editor The following figure shows the
Designer/Source Editor window in the HTML Editing mode. This
is where you'll spend most of your time in the Visual Studio
environment. This window integrates almost all of the designers
and source-code editors that you'll use in Visual Studio- including
the Web Forms- XML schema- and HTML designers - as well as a
unified source-code editor that provides support for XML- HTML-
SQL- Cascading Style Sheets (CSS)- and all of the .NET
languages. The editor provides enhanced features specific to
each language. Two new features of the HTML and CSS editors
that are particularly exciting are IntelliSense statement completion
for both HTML and CSS- and better control over how (or if) the
editor modifies the format of your HTML and CSS documents. To
change the formatting settings- select Options from the Tools
menu- select the Text Editor folder- select the HTML (or CSS)
folder- and select the Format option.
§ Solution Explorer The Solution Explorer window should be
familiar to anyone who's used Visual InterDev 6. It's one of the
primary tools you'll use to manage project files and resources-
including adding- removing- opening- renaming- and moving files-
as well as setting a start-up page or project- switching between
code and design view for a file- and viewing status information
(for example- Source Code Control status) on your files. The
following figure shows the Solution Explorer and identifies many
of its elements.
Toolbars
To accomplish tasks in Visual Studio .NET- you'll most likely use a combination of the
IDE's toolbars and menus. This section will take a look at the most commonly used
toolbars- and the next section will look at the most commonly used menus. You can view
the full list of available toolbars by right -clicking any toolbar (or empty toolbar area). In
keeping with the customizable nature of the Visual Studio .NET IDE - all toolbars may be
customized by adding- removing- or rearranging buttons- moving toolbars- and showing
or hiding toolbars.
Note Given the flexibility of the Visual Studio .NET toolbars - it's easy to
end up with your toolbars looking nothing like they did when you
installed Visual Studio. For some this may be a good thing- but if
you want to restore your toolbars to their original configuration-
click the toolbar Options button found at the right end of each
toolbar- click Add or Remove Buttons- click the menu item for the
toolbar name- and finally click Reset Toolbar.
§ Standard The Standard toolbar- shown in the following figure-
contains buttons for common file and project commands-
including opening files and projects- creating new files and
projects- and accessing various windows in the IDE.
Menus
There are a great many menus available in Visual Studio .NET- depending on the task
you're working on at any given time. While we won't go over all of them here- the menus
you'll encounter most frequently in your Visual Studio travels are listed below.
§ File menu The File menu is used to create- open- and save files
and projects- as well as to print files and to exit the program.
§ Edit menu The Edit menu is used for working with text and
objects- such as Cut- Copy - and Paste- as well as text-specific
commands- such as Find and Replace- and formatting
commands- such as Make Uppercase or Make Lowercase.
§ View menu The View menu is used to access windows or views
that are currently hidden. Use this menu to switch from source
code to design view or to open up windows such as the Task List-
as well as to choose which toolbars are displayed.
§ Project menu The Project menu is used to add items to a
project- add references to assemblies or XML Web services- and
set the start page and start-up project used for debugging.
§ Build menu The Build menu is used for building and rebuilding a
project or projects- as well as commands for deploying projects.
§ Debug menu The Debug menu is used to start- stop- and pause
(break) debugging- and to set breakpoints and access debugging
windows.
§ Table menu The Table menu is used for working with HTML
tables. Use this menu to insert or delete tables- rows- columns-
and cells- as well as to merge or split cells.
§ Tools menu The Tools menu contains commands related to
customizing the IDE and to external tools such as the OLE/COM
Object Viewer and Spy++. You can use this menu to access the
Customize dialog box discussed earlier- as well as the Options
dialog box- discussed in the next section.
§ Query menu The Query menu is used for creating and running
database queries using Visual Studio's database tools.
§ Window menu The Window menu is used to navigate and
manage the open document windows being used by the
application.
§ Help menu The Help menu is used to access the Visual Studio
.NET documentation- as well as to access product support. This
menu also contains a link to the Visual Studio .NET start page
that appears by default when you open Visual Studio. So if you
accidentally close it- you can use this menu item to get it back.
Note In addition to these menus- you can create your own custom
menus. To create a custom menu- right-click anywhere in the
menu bar and select Customize. In the Customize dialog box- click
the Commands tab. Under Categories- select New Menu. Under
Commands- click and drag the New Menu item to the desired
location in the menu bar. Next- right-click the new menu heading
and use the Name entry to give your new menu a name. Now you
can drag items from the other menu categories to your new menu.
To create a submenu- drag another copy of the New Menu item
into the desired location on your menu.
Options
One of the most dramatic areas of improvement in Visual Studio .NET is in the area of
customization. Much of the customization available in Visual Studio .NET is controlled
from the Options dialog box - shown in the following figure. As mentioned earlier- you can
access this dialog by selecting Tools- Options. Not only has the number of options
increased significantly- but the degree of control over particular options has increased as
well.
One good example of this increased control is in the area of code formatting. Visual
InterDev developers will no doubt remember the frustration of having the VI editor
reformat their ASP code when switching from Design to Source view. Visual Studio .NET
still performs code formatting- but the developer has language-by-language control over
how this formatting is done. (Note that not all languages use auto-formatting- so they
won't all have these options.) Go to the Text Editor option folder- choose the language
(for example- HTML or CSS)- and set the options to your preferred setting. In this way-
you can determine how formatting is applied to your code or- for some languages- you
can turn off reformatting entirely.
Note One new option that will appeal to longtime BASIC users is having
the IDE display line numbers in the text editor. Unlike BASIC-
however- the line numbers are only for reference; they're not
actually a part of the code file. This option can be turned on or off
for individual languages - or it can be turned on globally for all
languages.
Chapter 3 Quick Reference
To Do This
This chapter will discuss how these basic programming concepts apply to ASP.NET, and
how you can use them to create effective ASP.NET applications. Although most of these
concepts aren’t language-specific, there are some subtle differences in how they’re
implemented in different languages. Where appropriate, we’ll point out these differences
using code samples in both Visual Basic .NET and C#.
Note In ASP.NET, all code is contained in either Web Forms pages,
code-behind modules, or modules that make up class libraries that
are external to your ASP.NET applications. The term module in
this sense refers to the .cs or .vb file that contains the code, while
module as referred to by the Visual Basic .NET documentation is a
container of code that is made available to other classes and
modules within the same namespace. Unless otherwise specified,
the term module in this book has the former meaning rather than
the latter.
A class, as you’ll see in “Using Classes as Containers for Code”
on page 74, is a special type of code container that provides a
number of useful features. Classes are contained within modules
(that is, files with the extension .cs or .vb).
Expressions
Expressions are central to virtually all computer programs. Expressions let you
§ Compare values to one another
§ Perform calculations
§ Manipulate text values
An expression like this isn’t very useful by itself, however. Unlike people, who can easily
recognize “one plus one” and fill in the blank (“equals two”), computers aren’t capable of
that kind of leap of logic. In order for the expression to be useful, it needs to tell a
computer not just to add one and one, but to store the result somewhere so that we can
make use of it later (either by displaying it to the user or using it in another expression
later). This is where variables come in.
Variables
As with the preceding example, at some point during the execution of most programs,
you’ll need to store values of some sort. Examples include the results of mathematical
operations (as in the preceding example), accepting text input from users, and the
results of comparisons. Simply put, variables are storage areas for data. This data can
be numeric, text, or one of a number of special types. The kind of data that can be stored
by a variable is determined by its data type.
Data Types
The data type of a variable defines the type of data that can be stored in it, as well as the
format in which that data is stored (and the amount of memory that the system needs to
allocate for the variable). The following table lists the data types supported by Visual
Basic .NET and C#, as well as the .NET Framework SDK types to which they map. The
data types marked with an asterisk don’t have a native representation. You can still
access these data types by using the appropriate System type when declaring the
variable.
The data type of a variable is determined at the time the variable is declared. This will be
discussed further in the section entitled “Declaring Variables” on page 56.
* sbyte System.SByte 1
byte
* uint System.UInt32 † 4
byte
s
Long long System.Int64 8
byte
s
* ulong System.UInt64 † 8
byte
s
* ushort System.UInt16 2
byte
s
Single float System.Single 4
byte
s
String string System.String 10
byte
s,
Data Type Comparison
* These data types do not have a language-specific representation. If you need to use
these types, you can use the equivalent .NET data type.
† These data types do not comply with the Common Language Specification (CLS).
Important ASP.NET contains both framework -specific data types and
language-specific data types for specific .NET languages,
such as Visual Basic .NET and C#. In order to take
advantage of some of the multilanguage features of the .NET
environment, you need to limit your use of data types to
those supported by the Common Language Specification
(CLS), a subset of the data types supported by the common
language runtime. In the preceding table, data types marked
with a are not CLS-compliant. Avoid using them in classes
that you wish to make available for use with other .NET
languages.
Declaring Variables
Before you can use variables in your programs, you need to declare them. Variable
declaration is the process of specifying the characteristics of the variable (data type,
lifetime, scope, and visibility) so that the runtime system knows how much storage space
to allocate for the variable, which actions to allow on the variable, and who can take
those actions. A variable declaration takes the following form in Visual Basic .NET:
Dim x As Integer ‘Declares a variable of type Integer
Lifetime
Lifetime refers to the span of time from when the variable is declared to when it is
destroyed. The lifetime of a variable depends on where it is declared. For example, the
lifetime of a variable declared inside a procedure is limited to the execution of the
procedure. Once the procedure has finished executing, the variable is destroyed and the
memory it occupied is reclaimed. An example is shown here:
‘Visual Basic.NET Sub procedure
Sub HelloWorld()
‘Declare procedure-level string variable
Dim HelloString As String
HelloString = "Hello World!"
‘Write HelloString to browser
Response.Write(HelloString)
‘Lifetime of HelloString will end after next line
End Sub
When this procedure completes, the variable HelloString no longer exists. Its lifetime has
ended. If HelloString had been declared outside the procedure, its lifetime would be until
the instance of the class containing it was destroyed.
‘Declare module-level string variable
Dim HelloString As String
‘Visual Basic.NET Sub procedure
Sub HelloWorld()
HelloString = "Hello World!"
‘Write HelloString to browser
Response.Write(HelloString)
‘Lifetime of HelloString will not end after next line
End Sub
Note The Visual Basic .NET documentation recommends using whole
words when naming your variables. You should use mixed case,
with the first letter of each word capitalized, as in HelloString. This
is one of many possible naming conventions you can use for
variables and other elements in your programs. Naming
conventions are simply agreed-upon standards for how elements
will be named in a program. The particular naming convention you
choose isn’t important, but you must choose one and use it
consistently. Doing so will make it easier for you to maintain your
code and will help others understand what it’s doing.
You can extend the lifetime of a procedure-level variable through the substitution of the
Visual Basic .NET Static keyword for the Dim keyword used earlier. (In C#, you would
use the static modifier.) In the following example, HelloString is declared within the
procedure, but because it’s declared as Static, its lifetime continues until the class or
module containing the procedure has finished running:
‘Visual Basic.NET Sub procedure
Sub HelloWorld()
‘Declare static string variable
Static HelloString As String
HelloString = "Hello World!"
‘Write HelloString to browser
Response.Write(HelloString)
‘Lifetime of HelloString will not end after next line
End Sub
Scope
Related to the lifetime of a variable is its scope. Scope, also known as visibility, refers to
the region of code in which a variable may be accessed. The scope of a variable
depends on where the variable is declared, and in Visual Basic .NET it can include the
following levels.
§ Block-level Variables declared within an If…Then, For…Next, or
Do…Loop block. Although the scope of block-level variables is limited
to the block in which they’re declared, their lifetime is that of the
procedure in which the block appears.
§ Procedure-level Also known as local variables, these are visible only
within the procedure in which they’re declared.
§ Module-level In modules, classes, or structures, any variable declared
outside of a procedure is referred to as a module-level variable. The
accessibility of module- level variables is determined by any
accessibility keywords used in their declaration (see “Accessibility” on
page 59).
§ Namespace-level Variables declared at module level, but given public
accessibility (Public or Friend), are referred to as namespace-level
variables. They’re available to any procedure in the same namespace
that contains the module in which the variable is declared.
The important distinction between lifetime and scope is that a variable may be out of
scope (that is, unavailable) without having reached the end of its lifetime. The preceding
example of a Static variable that’s declared within a procedure is a good example of this.
In that example, the variable HelloString will continue to exist even after the procedure in
which it’s declared has completed, but because it has procedure-level scope, it’s only
accessible within the procedure. Here are some good programming practices to follow:
§ Limit your variables to the narrowest possible scope that will allow you
to accomplish your objectives.
§ Avoid using the same name for variables of different scope within the
same module. Using the same name in such situations can lead to
confusion and errors.
§ Namespaces
§ A namespace is a container used for scoping. Namespaces can
contain classes, structures, enumerations, interfaces, and delegates.
By using namespaces, you can have more than one class in a given
program with the same name, so long as the classes reside in
different namespaces.
§ Namespaces are declared in Visual Basic .NET using the
Namespace…End Namespace syntax, where these statements
bracket the classes, structures, and so on to be contained within the
namespace. C# uses the namespace { } syntax, where the classes
and other types to be contained within the namespace are bracketed
by the curly braces.
Accessibility
The last important concept in variable declaration is accessibility. The accessibility of a
variable determines whether it can be accessed from outside the module (or application)
in which it’s declared. In Visual Basic .NET, accessibility is determined through the
substitution of one the following keywords for the Dim keyword.
§ Public Variables declared with the Public keyword are accessible from
anywhere in the same module (or class) in which they’re declared, the
namespace containing that module, and any other applications that
refer to the application in which they’re declared. They can only be
used at the module or class level.
§ Friend Variables declared with the Friend keyword are accessible
from anywhere in the same module (or class) in which they’re
declared, as well as the namespace containing that module.
§ Protected Variables declared with the Protected keyword are
accessible only within the class in which they’re declared or a class
that inherits from that class. (See “Using Inheritance” on page 74.) The
Protected keyword can be combined with the Friend keyword and can
be used only to declare class members.
§ Private Variables declared with the Private keyword are accessible
only from the module, class, or structure in which they’re declared. In
classes or modules, declaring a variable with Dim or Private has the
same effect on the variable’s accessibility, but Private makes your
intentions more explicit. The Private keyword cannot be used within a
procedure.
Note In C#, all variables must be declared before they can be used.
Failure to declare a variable will result in a compiler error. In Visual
Basic .NET, it’s possible (but not advisable) to use variables
without first declaring them. Using variables that have not been
explicitly declared is a major source of bugs in Visual Basic
programs and should be avoided.
The Visual Basic Option Explicit statement (placed at the top of a
module or class) requires that all variables in that module must be
explicitly declared.
Option Explicit On ‘turns Option Explicit on
Option Explicit Off ‘turns Option Explicit off
To further protect your code from inadvertent bugs, you can also
use the new Option Strict statement. When Option Strict is on,
conversions between data types that would result in data loss are
disallowed, as are conversions between strings and numeric
types.
Option Strict On ‘turns Option Strict on
Option Strict Off ‘turns Option Strict off
Constants
Constants are similar to variables, except for one important detail: Once a constant has
been declared and initialized, its value may not be modified. Constants are very useful
when you have a literal value that you want to refer to by name. For example, if you’re a
fan of Douglas Adams, you might want to create a constant to refer to the literal value
42.
Const TheAnswer As Int = 42
Anywhere within your program (subject to the same scope, lifetime, and accessibility
rules as variables) you can use the constant name TheAnswer instead of the literal value
42.
Constants are particularly handy in place of literal values used as the arguments to
methods of components you may use. For example, in classic ADO (ActiveX Data
Objects), the data types of stored procedure parameters were represented by numeric
values. Trying to remember all of those values would be unrealistic, so the developers of
ADO also made it possible to use constants (adInteger, adVarChar, etc.) in place of the
literal values.
In other words, constants let you substitute easy-to-remember names for difficult-to-
remember literal values. This can make your code easier to write and maintain.
Procedures
Procedures are an important tool in ASP.NET development because they allow you to
determine at the time that you write the code the order in which sections of code will run
at runtime. Procedures also allow you to better organize your code into discrete units
based on the functionality provided by each one. For example, if you write code that
multiplies two numbers and returns the result, that code is much more useful if it’s
wrapped in a procedure. Using a procedure allows the code to be called from more than
one place in your application as necessary, which allows you to reuse the code rather
than rewriting it each time you need to multiply two numbers.
In Visual Basic .NE T, there are two main types of procedures that you’ll use: Sub
procedures and Function procedures. (C# has functions only.)
Sub Procedures
A Sub procedure executes code but doesn’t return a value. Code contained in a Sub
procedure is delimited by the Sub and End Sub statements, as follows:
‘Sub procedure that writes output to the browser
Sub WriteHello()
Response.Write("Hello World!)
End Sub
A Visual Basic .NET Sub procedure is equivalent to a C# function declared with the void
modifier. The same procedure would look like the following in C#:
// C# procedure that writes output to the browser
void WriteHello()
{
Response.Write("Hello World!);
}
You can pass more than one parameter to a procedure simply by separating each
parameter with a comma, as in the following example:
Sub WriteHello(ByVal Name1 As String, ByVal Name2 As String)
Response.Write("Hello to both " & Name1 & " and " & Name2)
End Sub
Important In Visual Basic 6, the default for a parameter definition
without the ByVal or ByRef keywords is to pass the
parameter by reference. Because this behavior is the
opposite of that in the vast majority of languages, the Visual
Basic development team has changed this behavior in Visual
Basic .NET.
In Visual Basic .NET, parameters defined without the ByVal
or ByRef keywords are passed by value.
Whether you’re writing Visual Basic 6 code that may need to
be upgraded to Visual Basic .NET or writing native Visual
Basic .NET code, it’s a good programming practice to always
use ByVal and ByRef to explicitly declare how your
parameters should be passed.
Note that you explicitly type the parameters by declaring the data type of each parameter
as String. If any data type other than String is passed to the procedure, an exception will
be thrown.
You can make one or more of the parameters of your procedure optional (meaning the
caller decides whether the parameter will be passed or not) by preceding them with the
Optional keyword.
Sub WriteHello(Optional ByVal Name1 As String = "Bob")
Response.Write("Hello, " & Name1 & "!")
End Sub
When you use optional parameters, you must supply a default value for each one, as in
the preceding example. Also, once you’ve declared an optional parameter, all
subsequent parameters for that procedure must also be optional.
Finally, you can change the accessibility of Sub procedures using the Public, Private,
Friend, and Protected keywords. Sub procedures are public by default, which means
they can be called from anywhere within your application.
Function Procedures
Function procedures in Visual Basic .NET are just like Sub procedures, except for one
important detail—they can return a value. This allows your procedure to
§ Return a numeric value representing an error or success code
§ Return data in the form of an array or an ADO.NET dataset
§ Return True or False to indicate the results of some conditional test
The following code example takes two string parameters, concatenates them, and then
returns the result to the caller as a string:
Function ConcatStrings(ByVal String1 As String, _
ByVal String2 As String) As String
ConcatStrings = String1 & String2
End Function
Notice that since the Function procedure itself is declared as a string, you don’t need to
create a local variable to hold the result of concatenating the two strings. Instead, simply
assign the result to the function name to return it to the caller.
Flow Control
Another important piece of any program is flow control, which allows you to determine at
runtime which sections of your code will run, and in what order. Flow control statements
are the foundation of business logic and consist of conditional logic (including If and
Case statements), looping structures (For… and Do… loops), and error-handling
statements.
If Statements
If statements are the form of conditional logic, also known as decision structures, that
you will probably use most in your programs. They allow you to look for a defined
condition in your program and execute a specific block of code if that condition is true.
The following code checks a Session variable for the logged-in username (set in the
login page) to prevent a user who has not logged into your ASP.NET application from
viewing pages that require a login:
If Session("LoggedInUserName") = "" Then
Response.Redirect("Login.aspx")
End If
In Visual Basic .NET, an If statement always requires an accompanying End If statement
to denote the close of the If block. You can also provide code that will execute if the
defined condition is false by adding the Else statement, and you can test for more than
one condition by using one or more ElseIf statements. These techniques are shown in
the following example:
If Session("LoggedInUserName") = "" Then
Response. Redirect("Login.aspx")
ElseIf Session("LoggedInUserName") = "SuperUser" Then
Response.Write("You are a superuser!")
Else
Response.Write("Hello, " & Session("LoggedInUserName") & "!")
End If
Note Visual Basic .NET also supports single-line If statements, such as
the following:
If 1 < 2 Then Response.Write("1 is less than 2")
This syntax should only be used for very simple If statements, as it
is inherently harder to read and debug single-line If statements.
Looping Statements
Looping statements are useful features that allow you to perform actions repeatedly, by
either specifying explicitly at design time the number of times the code should loop,
deciding at runtime how many times to loop, or looping through a collection of objects
and taking a specific action on each item. There are several different types of looping
statements, each of which has its own particular syntax and is useful for a different
situation. Loop types include
§ For… loops
§ For Each… loops, a special case of For… loops
§ Do… loops, which include Do While… and Do Until… loops
§ While…End While loops
Note Visual Basic 6 supported the While…End syntax for While… loops.
In Visual Basic .NET, this syntax is no longer supported. You
should use the While…End While syntax instead.
Using For… Loops
For… loops are useful for repeating a given set of statements a number of times. The
number of times the statements are executed can be determined either explicitly or by
evaluating an expression that returns a numeric value. The following example (from the
ASP.NET QuickStart Tutorial) uses a For… loop that counts from 1 to 7 and, for each
pass of the loop, outputs a message with a font size equal to the counter variable:
<% For i = 0 To 7 %>
<font size="<%=i%>">Welcome to ASP.NET</font><br>
<% Next %>
You can also use the Step keyword to loop by steps greater than 1, or even to loop
backward by specifying a negative number, as follows:
<% For i = 7 To 0 Step -1 %>
<font size="<%=i%>">Welcome to ASP.NET</font><br>
<% Next %>
You can also use a For… loop to loop through the elements of an array by specifying the
length of the array as the loop count expression, as follows:
<%
Dim s(5) As String
s(0) = "A"
s(1) = "B"
s(2) = "C"
s(3) = "D"
s(4) = "E"
For i = 0 To s.Length - 1 %>
<font size="<%=i%>"><%=s(i)%></font><br>
<% Next %>
Note In Visual Basic .NET, all arrays are zero based. Therefore, to use
the Length property of the array for a looping statement, you
should use Length—1.
Most programs will use a variety of loop types. No matter which loop type you choose,
the important thing is that you use it correctly and consistently for a given task. This will
make your code easier to read and maintain.
Error Handling
In any application, one challenge faced by developers is dealing with the inevitable
errors that pop up. Application errors come in three varieties:
§ Syntax Errors These include misspelled or missing keywords (such as a
missing End If statement), or other mistakes relating to the syntax of the
language you’re using. Syntax errors are typically flagged by the IDE and
may be easily fixed at design time.
§ Runtime Errors These are errors that appear once your code is compiled and
running. Runtime errors are caused by code that appears correct to the
compiler, but that cannot run with certain values. For example, code that
divides one integer variable by another will appear to be correct to a
compiler, but it will cause a runtime error if the second variable is zero.
§ Logic Errors These are errors in which a program that works correctly under
some (or most) circumstances gives unexpected or unwanted results based
on certain input values. This type of error can be extremely difficult to track
down and fix because it doesn’t stop the execution of the program.
One thing that all three types of errors have in common is that they must all be handled
in some fashion, lest they provoke grave dissatisfaction on the part of your users. An
important first step is prevention. In Visual Basic .NET, two steps you can take to prevent
errors are using Option Explicit and Option Strict in your applications. (They’re both
turned on by default in the Visual Studio .NET IDE.) Using Option Explicit will raise a
syntax error if you attempt to use a variable before declaring it, which can help greatly if
you have a habit of misspelling variable names. Using Option Strict will raise a syntax
error if you attempt an implicit data type conversion that would result in a loss of data.
Option Strict also implicitly includes Option Explicit, so Option Strict will also raise a
syntax error on any attempt to use an undeclared variable.
The following example demonstrates the use of Option Strict:
Option Strict On
Dim MyInt As Integer
Dim MyDouble As Double
MyDouble = 3.14159
MyInt = MyDouble ‘This line will cause an error at compile time
Both Option Explicit and Option Strict must come before any other code in the module in
which they appear.
By using classes as containers for code and namespaces, the .NET architecture makes
it very easy to create a rich, yet intuitive application hierarchy that can make your code
easier to use, debug, and maintain.
Note When you develop applications in the Visual Studio .NET
environment, a root namespace will be created with the same
name as your project. If you specify additional namespaces within
your code modules, these will become child namespaces of the
root namespace. You can change the root namespace, if desired,
by right-clicking your project in the Solution Explorer window and
selecting Properties. The Root Namespace property appears
under Common Properties, General.
Using Inheritance
Now that you know a bit about object-oriented programming, let’s take a look at a simple
example of how to put it to use. First, let’s define a class based on a real-world entity, an
animal.
Public Class Animal
Overridable Public Sub Eat()
Console.WriteLine("Yum!")
End Sub
Overridable Public Sub Sleep()
Console.WriteLine("Zzzzz…")
End Sub
End Class
Next, create a more specific animal class, Cat, that derives from the Animal class. Note
the use of the Inherits keyword on the second line. Inheriting from another class is really
that simple. This class will override the behavior of the Eat method, but not the Sleep
method, as follows:
Public Class Cat
Inherits Animal
Overrides Public Sub Eat()
Console.WriteLine("Yum, Yum…Meow, Meow!")
End Sub
End Class
You’ll also create a Dog class that derives from Animal and that overrides the Sleep
method, but not the Eat method, as follows:
Public Class Dog
Inherits Animal
Overrides Public Sub Sleep()
Console.WriteLine("Zzzzzz…woofwoofwoofwoof…zzzzzz!")
End Sub
End Class
Finally, use the Cat and Dog classes in the following code. Note that you use the Imports
statement to access the members of the Animals namespace without fully qualified
names (such as Cat instead of Animal.Cat ).
Imports Animals
Public Class UseAnimals
Public Shared Sub Main()
Dim MyCat As New Cat
Dim MyDog As New Dog
MyCat.Eat
MyCat.Sleep
MyDog.Eat
MyDog.Sleep
End Sub
End Class
This code, when run from a command line, provides the following output:
Yum, Yum…Meow, Meow!
Zzzzz…
Yum!
Zzzzzz…woofwoofwoofwoof…zzzzzz!
Even though you called the same two methods on both the Cat and Dog classes, when
you overrode a method of the parent Animal class, you got the behavior that you
specified for the particular animal. When you didn’t override, the output shows that you
got the behavior inherited from the parent Animal class. You can find the full source code
for this example, as well as a batch file for compiling the classes used, in the source
folder for this chapter on the CD-ROM accompanying the book.
This chapter will walk you through the steps necessary to create an ASP.NET Web
application, using both the Microsoft .NET Framework SDK and Visual Studio .NET.
While it’s simple to create a Web application in Visual Studio .NET, it’s also a good idea
to know how to create a Web application manually if the need arises.
3. Right -click the Web site underneath which you want to create the
application root, select New, and choose Virtual Directory.
4. Click Next on the first page of the Virtual Directory Creation Wizard, as
shown in the following figure.
5. On the second page of the wizard, shown in the following figure, enter
an alias for the application root. The alias is the name that you’ll use to
access the application. (If you create an application root on the default
Web site with an alias of MyWebApp, you could access that
application with the URL http://localhost/MyWebApp .) Once you’ve
entered an alias, click Next.
6. On the third page of the wizard, shown in the following figure, enter or
browse to the path for the file system folder that will contain the files in
the application. If this folder doesn’t exist yet, create it now. Content
folders for virtual directories or application roots under the default Web
site are most commonly stored in the \InetPub\ wwwroot folder, which
is usually located on the C: drive. While it’s possible to put your
content anywhere within the file system, keeping your content folders
under \InetPub\wwwroot makes it easier to keep track of all of your
Web content. Once you’ve entered the path to your content folder,
click Next.
7. On the fourth page of the wizard, shown in the following figure, select
the actions you wish to allow on this virtual directory. For most
applications, the default of Read and Run Scripts (Such As ASP) is
sufficient. When you’re finished selecting permissions, click Next.
Once you’ve finished with the wizard, you should make sure that your application root
has been created correctly. The application root should be represented by the icon
shown at left.
If you accidentally create a virtual directory when you wanted to create an application
root, you can turn the virtual directory into an application root with a few simple steps.
1. Right -click the virtual directory name in the Tree pane of Internet
Services Manager, and then choose Properties.
2. On the Virtual Directory tab, shown in the following figure, click the
Create button.
If all you need is a subfolder, rather than a virtual directory, you can create a new folder
in the file system underneath the folder of an existing virtual directory and then add
content to it. Your folder and content will be displayed under your application root (or
virtual directory) when you refresh the Internet Services Manager window.
Access Permissions
Before you select Execute, Write, or Browse permissions, you should make sure that
you understand the security implications of each one. These settings can make your
application vulnerable to attacks by hackers if used improperly. The following list
explains the purpose of each available access permission and when it’s appropriate to
use.
§ Read Clients can read files with this permission set. This is selected
by default.
§ Run Scripts This allows scripts in files such as ASP files to be
executed, but does not allow executables (.exe, .dll) to be run.
§ Execute This allows executable file types such as .exe and .dll
applications to be executed, as well as Common Gateway Interface
(CGI) applications.
§ Write Clients can write to a directory with this permission set. Use this
permission only when necessary.
§ Browse Clients entering a URL that doesn’t specify a file name or a
default document (such as index.htm or default.asp) will receive a
listing of the files in the directory they’re browsing. Because this
information can help malicious users compromise your application,
the Browse permission should be limited to situations where
browsing is the intended behavior.
<body>
<p>Hello World!</p>
</body>
</html>
Any code located in either <script> blocks or <% %> render blocks in the preceding code
will be assumed to be C# code.
Code Location Options
Another important choice to make when creating ASP.NET pages from scratch is
whether to place the code in the .aspx file itself or use a code-behind module. Using
code-behind modules has the advantage of providing much better separation of code
from presentation (HTML). This can make it easier to debug your code, and it also
makes it easier for teams of UI designers and developers to work together without
breaking one another’s work. Since code-behind allows you to create .aspx files that
consist solely of HTML tags and tag-based server and user controls, it’s much easier for
UI designers to work with the .aspx files without breaking the code behind them.
Note While the theoretical benefit of separating code from presentation
using code- behind modules is substantial, the practical benefit
may be limited by the tools being used by the UI design team. It’s
reasonable to expect that many, if not most, Web design tools will
eventually recognize and support custom ASP.NET tags (such as
those used for server controls and user controls). However, at the
time of this writing, many of these tools don’t recognize these tags
and may respond with error messages when you try to edit the
files.
In addition to creating your application root and adding subfolders and content, you can
also add a file called Global.asax to your Web application. Global.asax (which is added
to Visual Studio .NET Web applications by default) is a file that is primarily used to
provide application and session (per-user) start-up and clean-up code, as well as to set
options that are to apply to the application as a whole. In Global.asax, you can
§ Respond to selected application and session events.
§ Respond to events of custom HttpModules you have created for your
application.
§ Import namespaces into the application using the @ Import directive.
You can then use the members of the namespace from any code within
your application without needing to import the namespace on each page.
§ Register assemblies for use in your application using the @ Assembly
directive.
§ Create instances of application-level objects using the <object
runat=“server”> tag syntax.
A typical set of Global.asax event handlers in Visual Basic .NET would look like the
following:
<script language="VB" runat=server>
Sub Application_OnStart()
' Application start-up code goes here…
End Sub
Sub Session_OnStart()
' Session start-up code goes here…
End Sub
Sub Session_OnEnd()
' Session clean-up code goes here…
End Sub
Sub Application_OnEnd()
' Application clean-up code goes here…
End Sub
Overrides Sub HandleError(ErrorInfo as Exception)
' Application error occurs
End Sub
</script>
You can also handle events exposed by custom HttpModules, which are classes that
inherit the IHttpModule interface and participate in processing ASP.NET requests.
Handling events exposed by custom HttpModules is essentially the same as handling the
application and session events. Events exposed by the HttpModule must use the naming
pattern modulename_eventname(eventargs).
The following are some things to keep in mind with components you instantiate in
Global.asax.
§ Limit the number of components you instantiate at Application or
Session level to those you absolutely need. Remember that these
objects will be consuming resources (such as memory) for the entire
lifetime of the Application (or Session). This consumption level can be
a problem for applications requiring high scalability.
§ Any components instantiated by the <object> tag should be
multithreaded. In the .NET world, this isn’t a major difficulty, but you
should avoid things like instantiating classic COM components written
in Visual Basic 6 with this syntax.
§ Components created with the <object> tag syntax are not instantiated
immediately, but at the time when they’re first called on a page.
Another optional file you can add to your application is called Web.config. This is an
XML-based, human- and machine-readable file that contains the configuration options
for your application. The reason that this Web.config file is optional is that if you don’t
include it, your application will inherit the settings of the machine-level configuration file
Machine.config. If you add a Web.config file to your application root, the configuration
settings contained in that file will apply throughout your application. You can override
these settings in specific areas of your application by setting up child folders in your
application that contain their own Web.config files. In this way, you can set up a
hierarchy of configuration settings for your application.
Create a new Click the New Project button, select the project language
project in and template, and then provide the name and location for
Visual Studio the new project. Alternatively, you can go to the File menu,
.NET select New, and choose Project.
Create a Virtual Start the Internet Services Manager, right-click the Web site
Directory in IIS you want to add the Virtual Directory to, and then select New
and choose Virtual Directory. Follow thwe instructions in the
Virtual Directory Creation Wizard.
Create a new Add the @ Page directive with a Language= attribute to the
top of an HTML page and save it with the .aspx extension.
Web Forms
page Add code using either <script> blocks with the
runat=“server” attribute or <% %> render blocks.
Handle Create a Global.asax file in the application root with the
application and appropriate event handlers.
session-level
events
Configure Create a Web.config file with the appropriate configuration
application sections. You can modify confi guration settings for sections
settings of your application by partitioning it into separate folders and
placing a Web.config file in each folder that needs its own
configuration settings.
Information stored in application state can be easily shared between all users of an
application. This can make it very tempting to use application state to store all manner of
data, from application settings such as database connection strings to cached datasets
containing frequently used data. In many cases, there are more efficient means of
storing this data than application state. The following table shows some examples of
when you may or may not want to store information in application state, as well as
alternatives for storing such information.
Application state has several limitations that you should consider when deciding how to
manage your state information.
§ Durability Application state lasts only as long as the Web application is
running. If the Web application or Web server is shut down or crashes,
any state information stored at the application level will be destroyed.
Any information that needs to persist between application restarts should
be stored in a database or other persistent storage.
§ Web farms Application state is not shared across multiple servers in a
Web farm (nor across multiple processors in a Web garden). If you need
to store values that must be available to all users in these scenarios,
application state is not an appropriate choice.
§ Memory Any given server has only a limited amount of physical memory
available. Overuse of application state may result in information being
swapped to virtual memory (a location on a hard drive used to provide
supplemental “memory” storage). This can reduce performance
significantly.
Note Web farms are groups of identically configured servers that share
the load of serving user requests for a given Web application.
Each server contains the same content and can fulfill any request.
Requests are routed to individual servers by a hardware- or
software-based load-balancing algorithm that either determines
which server is least loaded or assigns requests to a given server
randomly.
Unfortunately, session state in classic ASP had several inherent limitations that made it
less than ideal, particularly for applications requiring high scalability. These included the
following:
§ Web farms In classic ASP, session state could not be scaled across multiple
servers in a Web farm, limiting its usefulness in high-scalability situations.
§ Durability In classic ASP, session state would be destroyed by a server
restart or crash. This made it a poor choice for such uses as shopping
carts, whose contents should survive such events.
§ Cookie reliance Classic ASP offered no inherent solution for supporting
session state with browsers that could not or would not accept cookies.
Although aftermarket solutions were available, they often involved
unacceptable performance trade- offs.
ASP.NET solves each of these limitations, providing per-user state storage that’s
scalable, reliable, and available on browsers that don’t support cookies (or for users who
choose not to accept cookies). For more information on the available options for
ASP.NET session state and how they solve these limitations, see “Configuring Session
State Storage” on page 108.
As with the ASP.NET Application object, the ASP.NET Session object is exposed as a
property of the Page class, from which all ASP.NET pages inherit. This allows access to
the Session object using the Session keyword.
‘ VB.NET
Session("MySessionVar") = "MyValue"
MyLocalVar = Session("MySessionVar")
// C#
Session["MySessionVar"] = "MyValue";
MyLocalVar = Session["MySessionVar"]
Note When accessing the Session or Application collections directly
using a key, you must use square brackets with C#, as shown in
the preceding code . When calling methods, such as the Add or
Remove methods, parentheses are used for VB.NET and C#.
The Session object functionality is provided by the HttpSessionState class, an instance
of which is created for each user session for which session state has been enabled.
Like the Application object, the Session object exposes a Contents collection for
backward-compatibility with classic ASP. Any values stored in the Session collection can
also be accessed through the Contents collection alias.
‘ VB.NET
Session.Contents("MySessionVar") = "MyValue"
MyLocalVar = Session.Contents("MySessionVar")
The Session collection can also be used to store references to object instances, using
similar syntax to that used to store object references at the application level. These
objects then become part of the session’s StaticObjects collection and can be referenced
in your ASP.NET Web Form pages by referring to the id attribute associated with the
object.
‘Global.asax
<object runat="server" id="MyClassInstance" class="MyClassName"
scope="Session">
</object>
‘Web Forms page
Response.Write("Value = " & MyClassInstance.MyValue)
The following table lists some of the additional properties and methods provided by the
Session object to retrieve and manipulate Session collection values.
Method or Use
Property
Keys property Returns a collection of all of the keys by which Session
collection values can be accessed.
Count property Returns the number of objects stored in the Session
collection.
SessionID Returns a string containing the session ID for the current
property
session.
Timeout Returns an Int32 value representing the current Session
property Timeout setting.
Abandon Destroys the current user session.
method
Clear method Removes all items from the Session collection.
RemoveAt Removes a specific item from the Session collection, based
method on its index within the collection.
ToString Returns a string that represents an item in the Session
method collection. Useful when a string value is required rather than
an object reference.
To change its setting, select the enableSessionState entry in the Properties window,
click the drop-down list containing the values, and select the desired value.
Session state’s ease of use makes it easy to overuse or abuse. The following table lists
examples of when you may or may not want to store information in session state, as well
as alternatives for storing such information.
Thanks to a number of new features in ASP.NET, the primary limitation of session state
in ASP.NET is that of memory. As with application state, session state is limited by the
memory available on the Web server. Once the available physical memory on the Web
server has been exhausted, information will be stored in much slower virtual memory.
Some of the factors affecting application scalability that result from poor decisions in
state management include the following conditions.
§ Failure to disable session state when not used If you’re not using session
state within your application, disable it by changing the mode attribute of
the sessionState configuration section of Web.config to “off”. For
applications that make limited use of session state, ensure that any pages
that don’t use session state include the EnableSessionState=“false”
attribute as a part of the page’s @ Page directive.
§ Misuse of Application.Lock() The Application.Lock() method prevents
access to all application values for any user other than the user whose
request resulted in the call to Application.Lock(). This continues until the
Application.Unlock() method is called. You should minimize the time that
these values are locked by calling Application.Lock() immediately prior to
updating a value and calling Application.Lock() immediately afterward.
§ Storing references to single (or apartment) threaded objects Storing
references to non-free-threaded objects at application or session level can
interfere with IIS thread management capabilities and can result in severe
scalability problems. Avoid storing non-free-threaded objects at application
or session level.
§ In-process session state In order to scale a Web application beyond a
certain point, it’s often necessary to use a Web farm to allow multiple
servers to handle requests. In-process session state, the default, is not
available to all servers on a Web farm. To use session state with a Web
farm, you should set your application’s Session mode to either
“StateServer” or “SQLServer” as described in the next section.
§ Overuse of session or application storage Storing numerous object
references or large datasets at the application level, and particularly at the
session level, can rapidly exhaust the physical memory on a Web server,
causing it to substitute virtual memory for storing additional information.
This can have a dramatic effect on the performance of an application. You
should always ensure that any use of application state (and more
importantly, session state) will not exceed the physical memory resources
available on the Web server. Also remember that other applications will be
using some of these resources.
ASP.NET will automatically connect to the specified state server to store session state
for your application. If the state service on the specified server is not running, you will
receive an error message.
Keep in mind that although storing session state in a dedicated server process can
improve the overall scalability of your application, there are inherent performance
implications of moving session state out of process. Retrieving state information from a
different process (and especially from a different machine) is significantly more costly
than retrieving it from within the same process. You should test the impact on the type
and amount of session data you plan to store before implementing this type of session
state storage in a production application.
The second solution to the problems of scalability and durability for session state is to
store it out-of-process in a SQL Server database. One advantage of this method is that
the specified SQL Server can service requests from multiple servers, making it possible
to scale session state across a Web farm. Also, the session state information is stored in
a SQL Server database, so state information can survive restarts or crashes of any Web
application process, any Web server, or even the SQL Server itself.
One ongoing challenge for Web developers using classic ASP is how to handle session
state for users whose browsers can’t or won’t accept cookies. Classic ASP provided no
intrinsic solution for this situation. In ASP.NET, it’s relatively simple.
1. Open the Web.config configuration file for your application and locate
the sessionState configuration section.
2. Change the cookieless attribute from “false” to “true”.
3. The complete sessionState configuration section would look like the
following (note that the stateConnectionString, sqlConnectionString,
and timeout attributes have been omitted).
<sessionState
cookieless="true"/>
When the cookieless attribute is set to “true”, ASP.NET will automatically embed the
SessionID value in the URL for all requests. For best results, always use relative URLs
for internal links within your application. Relative URLs contain only the path and file
information for the requested resource, and not the protocol and domain, which are
assumed to be the same as the current page.
Store sensitive data only if you must- and then only for the
minimum length of time necessary.
ASP.NET provides a solution to this challenge with the server control architecture. All
ASP.NET server controls are capable of maintaining their own state through a
mechanism known as ViewState. ViewState is maintained on a page-by-page basis as a
hidden form field that contains all of the state information for all of the form elements on
that page.
ViewState and server controls are discussed in detail in Chapters 10 and 12.
One of the most important new features of ASP.NET, given the advantages it provides
developers, is the new configuration system it provides ASP.NET. This configuration
system uses human- and machine-readable XML-based files to store configuration
information. This chapter will look at how these configuration files work and how you can
use them in your applications.
Introducing Web.config
Like the Machine.config file, Web.config is XML based. This means that each Web.config
file is made up of tags and attributes, similar to HTML. (XML is a markup language
based on Structured Generalized Markup Language or SGML, the same language on
which HTML was based.) Unlike Machine.config, however, most Web.config files will not
contain elements for every available configuration setting. In fact, an ASP.NET
application doesn’t actually require a Web.config file in order to function. If Web.config is
omitted from an application, it simply inherits its configuration settings from the master
configuration file, Machine.config.
At the time of this writing, the Visual Studio .NET environment’s tools for editing
configuration files are limited to syntax coloring, XML validation, and code formatting.
Because the ASP.NET configuration files are XML-based, you can use your favorite XML
editor (or text editor) to edit ASP.NET configuration files.
In addition to syntax coloring and validation, Visual Studio .NET also provides a default
Web.config file for each new Web application project that you create. This default file
contains the most commonly used elements, as well as comments that explain the
available options for each element. The default Web.config file is useful as a template for
any additional Web.config files you want to place in subfolders of your Web application.
Keep in mind that when you’re using configuration files in subfolders of your application,
it’s a good idea to only include the elements for the configuration settings from the parent
file that you want to override. This helps prevent overriding a configuration setting by
accident, and it may help reduce parsing overhead for the configuration of your
application.
At the time of this writing there are no GUI tools available for editing the Web.config files
and modifying configuration settings. Fortunately, the default Web.config file created by
Visual Studio .NET Web Applications, shown in the following figure, contains comments
specific to the most commonly used settings. These comments provide guidance for
using the available parameters for a given configuration element (although not all
configuration elements appear in the default Web.config file).
In addition to the comments found in the Web.config file generated by Visual Studio
.NET, the Machine.config file also contains comments with specific settings for certain
elements. These comments can guide you when you make configuration changes.
These elements and their settings are described in “ASP.NET Configuration Elements”
on page 123.
Overriding Configuration Settings for Subdirectories
Once you’ve created the application-level Web.config file and modified its settings to suit
your needs, you may want to modify the configuration settings for a subset of your
application. For example, you may want to apply more stringent security requirements to
a particular set of files. In ASP.NET, it’s simple to override the application-level
configuration settings. It takes only the following three steps:
1. Create a subfolder in your application, and place in it the content to
which the new configuration settings will apply.
2. Create a new Web.config file in the new folder.
3. Add the configuration settings you want to override to the new
Web.config file.
Include only the configuration settings that you want to ove rride in the new Web.config
file. All other settings are inherited from the Web.config file of the parent folder or, if that
file doesn’t exist (or doesn’t contain settings for all available configuration elements),
from the Machine.config file for your Web server.
<trace>
The <trace> element allows you to enable or disable application-wide tracing (see
Chapter 16 for more on this useful feature), as well as set the parameters for the tracing
functionality. When tracing is enabled, you can review information about requests
received by the Web server with the special URL http://<servername>/<appname>/
trace.axd. The <trace> element has the following syntax:
<trace
enabled="true|false"
localOnly="true|false"
pageOutput="true|false"
requestLimit="integer"
traceMode="SortByTime|SortByCategory" />
For information on <trace> element attributes, consult the following table.
<globalization>
The <globalization> element controls globalization settings for ASP.NET applications.
This includes the encoding used for requests, responses, and files, as well as settings
for specifying the culture to be associated with Web requests and local searches. The
<globalization> element has the following syntax:
<globalization
culture="any valid culture string"
fileEncoding="any valid encoding string"
requestEncoding="any valid encoding string"
responseEncoding="any valid encoding string"
uiCulture="any valid culture string" />
For information on <globalization> element attributes, consult the following table.
<httpRuntime>
The <httpRuntime> element controls several aspects of the ASP.NET HTTP Runtime
engine. The <httpRuntime> element has the following syntax:
<httpRuntime
appRequestQueueLimit="number of requests"
executionTimeout="seconds"
maxRequestLength="kbytes"
minLocalRequestFreeThreads="number of threads"
minFreeThreads="number of threads"
useFullyQualifiedRedirectUrl="true|false" />
Relative URLs provide only the information necessary to locate a resource relative to
the current document (known as document relative) or current server or domain (known
as root relative). A document relative URL used to link to the previously referenced
page from another page in the same virtual directory would look like the following:
default.aspx
/quickstart/aspplus/default.aspx
Because some controls or applications may not know how to use relative URLs, there
may be times when you need to use a fully qualified URL.
<compilation>
With ten attributes and two child elements, the <compilation> element is one of the more
extensive ASP.NET configuration elements and contains settings that determine how
ASP.NET compiles code in your Web applications and Web Services. The settings you’ll
see most frequently are the debug and defaultLanguage attributes, which are placed in
your application’s Web.config file by default when you’re using Visual Studio .NET. Other
settings, such as the <assemblies> and <namespaces> child elements, are equally
important but usually are inherited from the settings in Machine.config, unless overridden
by a developer.
The <compilation> element has the following syntax:
<compilation
batch="true|false"
batchTimeout="seconds"
debug="true|false"
defaultLanguage="language"
explicit="true|false"
maxBatchSize="number of pages"
maxBatchGeneratedFileSize="kbytes"
numRecompilesBeforeAppRestart="number"
strict="true|false"
tempdirectory="directory" >
<compilers>
<compiler
extension="file extension"
language="language"
compilerOptions="compiler options"
type=".NET type"
warningLevel="number" />
</compilers>
<assemblies>
<add assembly="assembly name" />
<remove assembly="assembly name" />
<clear />
</assemblies>
</compilation>
For information on <compilation> element attributes, consult the following table.
<pages>
The <pages> element allows you to set the defaults for the page-level attributes that are
more commonly associated with the attributes of the @ Page ASP.NET directive. The
settings in this element apply to all pages for which specific attributes of the @ Page
directive do not appear. If these attributes do appear in an ASP.NET page, their settings
will override those in either the Machine.config or Web.config configuration files. As
such, the <pages> element provides a great way of configuring the SessionState,
ViewState, and other settings at an application or subfolder level, giving you a great deal
of control over your application.
The <pages> element has the following syntax:
<pages
autoEventWireup="true|false"
buffer="true|false"
enableSessionState="true|false|ReadOnly"
enableViewState="true|false"
enableViewStateMac="true|false"
pageBaseType="typename, assembly"
smartNavigation="true|false"
userControlBaseType="typename" />
For information on <page> element attributes, consult the following table.
<customErrors>
The <customErrors> element allows you to customize how your ASP.NET application
responds to error conditions. In this element, you can specify whether the raw error
messages generated by ASP.NET should be visible to local or remote clients, or whether
to redirect the client to either a custom error page or a page specific to the error that
occurred (based on the status code of the error). The <customErrors> element supports
one child element, <error>, and has the following syntax:
<customErrors
defaultRedirect="url"
mode="on|off|RemoteOnly">
<error
redirect="url"
statusCode="status code"/>
</customErrors>
For information on <customErrors> element attributes, consult the following table.
<authentication>
The <authentication> element controls configuration of authentication in ASP.NET. You
can choose from one of three authentication methods, and you can set appropriate
parameters for the method you choose or choose no authentication at all. The
<authentication> element supports two child elements, <forms> and <passport>.
Additionally, the <forms> element supports one child element, <credentials>, which in
turn supports one child element, <user>, as shown in the following example:
<authentication mode="Windows|Forms|Passport|None">
<forms
loginUrl="url"
name="name"
path="/"
protection="All|None|Encryption|Validation"
timeout="number">
<credentials passwordFormat="Clear|MD5|SHA1">
<user name="username" password="password" />
</credentials>
</forms>
<passport redirectUrl="url" />
</authentication>
For information on <authentication> element attributes, consult the following table.
<identity>
By default, requests made by ASP.NET applications for resources requiring
authentication, such as files secured by NT Access Controls Lists (ACLs), are made in
the context of either the IUSR_MACHINENAME or IWAM_MACHINENAME accounts,
depending on whether the application is configured to run in-process or out-of-process
relative to IIS. The <identity> element allows ASP.NET applications to use
impersonation, in which an application takes on the security context of the user making a
request, or of a specified account. The <identity> element has the following syntax:
<identity
impersonate="true|false"
userName="username"
password="password" />
For information on <identity> element attributes, consult the following table.
<authorization>
The <authorization> element lets you specify which accounts or roles (groups) are
authorized to access resources within the scope of the configuration file. The
<authorization> element supports two child elements, <allow> and <deny>, each of
which has three attributes. The <authorization> element has the following syntax:
<authorization>
<allow
users="userlist"
roles="rolelist"
verbs="verblist" />
<deny
users="userlist"
roles="rolelist"
verbs="verblist" />
</authorization>
For information on <authorization> element attributes, consult the following table.
<machineKey>
The <machineKey> element allows you to specify the keys used for encryption and
decryption of cookie data in Forms-based authentication. This element can be used at
the machine level through Machine.config, as well as at the site and application levels
through Web.config files, but it may not be used at the subdirectory level. The
<machineK ey> element has the following syntax:
<machineKey
decryptionKey="autogenerate|value"
validation="autogenerate|value"
validationKey="3DES|MD5|SHA1" />
For information on <machineKey> element attributes, consult the following table.
<securityPolicy>
The <securityPolicy> element allows you to specify one of several named security
policies, or a custom policy, for code-access security based on the name and policyFile
attributes of its <trustLevel> child element. The <trust> element, described in the next
section, specifies which of the named policies is implemented for a given site or
application. The <securityPolicy> element supports one child element, <trustLevel>, with
two attributes, and has the following syntax:
<securityPolicy>
<trustLevel
name="value"
policyFile="value" />
</securityPolicy>
For information on <securityPolicy> element attributes, consult the following table.
<trust>
The <trust> element is used to implement one of the named security policies created by
the <securityPolicy> element. This element can be used at the machine level through
Machine.config, as well as at the site and application levels through Web.config files.
However, it can’t be used at the subdirectory level. The <trust> element has the following
syntax:
<trust
level="Full|High|Low|None|custom name"
originUrl="url" />
For information on <trust> element attributes, consult the following table.
<sessionState>
The <sessionState> element is used to configure the Session State HttpModule,
including the type of state management to be used (in-process, out-of-process, or SQL
Server), the default session timeout, and whether or not to use cookies for associating
requests with user sessions. The <sessionState> element has the following syntax:
<sessionState
connectionString="IP address:port number"
cookieless="true|false"
mode="Off|Inproc|StateServer|SQLServer"
sqlConnectionString="sql connection string"
timeout="number" />
For information on <sessionState> element attributes, consult the following table.
<httpHandlers>
The <httpHandlers> element allows you to assign requests of certain types or for certain
resources to specific handler classes. For example, in Machine.config, the handling of
ASP.NET pages (requests with the .aspx extension) is assigned to the
System.Web.UI.PageHandlerFactory class. The <httpHandlers> element can also be
used to prevent HTTP access to certain types of files by assigning them to the
System.Web.HttpForbiddenHandler class, as is done by default for configuration files
(*.config) and source files (*.vb and *.cs, for example).
The <httpHandlers> element supports three child elements, <add>, <remove>, and
<clear>, and has the following syntax:
<httpHandlers>
<add
path="path"
type="type, assembly name"
validate="true|false"
verb="verblist" />
<remove
path="path"
verb="verblist" />
<clear />
</httpHandlers>
For information on <httpHandlers> element attributes, consult the following table.
<httpModules>
HttpModules are classes that implement the IHttpModule interface and are used to
provide functionality to ASP.NET applications. For example, by default, the
Machine.config file adds HttpModules for output caching, session-state management,
authentication, and authorization. The <httpModules> element allows you to add
HttpModules to ASP.NET applications.
The <httpModules> element supports three child elements, <add>, <remove>, and
<clear>, and has the following syntax:
<httpModules>
<add
name="name"
type="type\ assembly name" />
<remove
name="name"
<clear />
</httpModules>
For information on <httpModules> element attributes, consult the following table.
<processModel>
The <processModel> element configures settings related to how ASP.NET applications
run, and it provides access to a number of features geared towards improving the
availability of applications. These include automatic restart (which can be configured
based on elapsed time or number of requests), allowed memory size, and Web garden,
in which applications can be associated with specific processors in a multiprocessor
machine. Note that when ASP.NET is running under IIS 6.0 in native mode, the settings
in the <processModel> element are ignored in favor of the settings configured by the IIS
administrative UI.
The <processModel> element has the following syntax:
<processModel
clientConnectedCheck="time"
comAuthenticationLevel="Default|None|Connect|Call|Pkt| PktIntegrity|PktPrivacy"
comImpersonationLevel="Default|Anonymous|Identify|Impersonate| Delegate"
cpuMask="number"
enable="true|false"
idleTimeout="time"
logLevel="loglevel"
maxIoThreads="number"
maxWorkerThreads="number"
memoryLimit="number"
pingFrequency="hh:mm:ss"
pingTimeout="hh:mm:ss"
requestLimit="number"
requestQueueLimit="number"
responseDeadlockInterval="Infinite|hh:mm:ss"
responseRestartDeadlockInterval="Infinite|hh:mm:ss"
restartQueueLimit="number"
serverErrorMessageFile="filename"
shutdownTimeout="time"
timeout="time"
webGarden="true|false"
username="user name"
password="password" />
For information on <processModel> element attributes, consult the following table.
<webControls>
The <webControls> element allows you to specify the location of script files used by
client-side implementations of ASP.NET Server Controls, such as the validation controls.
The <webControls> element has the following syntax:
<webControls
clientScriptsLocation="path" />
For information on <webControls> element attributes, consult the following table.
<clientTarget>
The <clientTarget> element allows you to set up aliases to be used by the ClientTarget
property of the Page class. The <clientTarget> element supports one child element,
<add>, and has the following syntax:
<clientTarget>
<add
alias="aliasname"
userAgent="true|false" />
</clientTarget>
For information on <clientTarget> element attributes, consult the following table.
<browserCaps>
The <browserCaps> element contains settings used by ASP.NET to provide the
functionality of the browser capabilities component (accessible via the Request.Browser
property). It provides filtering processes that allow the browser capabilities component to
populate its properties with information on the capabilities of a user’s browser, based on
the information contained in the user agent string passed by the browser. The
<browserCaps> element supports three child elements, <result>, <use>, and <filter>.
The <filter> element supports one child element, <case>. The <browserCaps> element
has the following syntax:
<browserCaps>
<result type="System.Web.HttpBrowserCapabilities" />
<use var="HTTP_USER_AGENT" />
list of default property values
<filter>
<case match="string1|string2|stringN">
property=value
</case>
</filter>
</browserCaps>
For information on <browserCaps> element attributes, consult the following table.
Attribute Description
key Specifies the key by which the value in the appSettings hash
table will be retrieved.
value Specifies the value to be retrieved by the specified key.
The Internet has changed all that forever. Applications that are exposed to the Internet
are inherently vulnerable to a host of issues, ranging from attempts at stealing data to
the defacing of Web sites to denial of service attacks. No matter what operating system
or other software you run, that vulnerability will never go away entirely. Software is an
imperfect science, and unfortunately, there has yet to be an operating system that is
invulnerable to attack.
The good news is that most software, including Windows 2000, Windows XP, and IIS,
can be made quite secure if you follow best practices (a recognized set of recommended
procedures and policies) for security, such as keeping track of and installing security
patches as soon as they are released. One of the remarkable things about security
practices in our industry is just how many servers (both Microsoft-based and otherwise)
are sitting out there exposed to the Internet, without patches installed that have been
available for months, or even years!
That said- there are different levels and types of security. The type and level you need
for your application will vary depending on what your application does - the type and
value of data (if any) that you store- the amount of risk you are comfortable with- and the
amount of time- effort- and money you are willing to expend to have a secure application.
The security needs of a personal home page- for example- are very different from those
of a corporate intranet site or a retail e commerce site. The following table describes the
kinds of threats that are out there and the consequences of being underprepared for
them.
Note A more complete discussion of this topic is available in the article
'Web Security by William Stallings on Microsoft TechNet
(http://www.microsoft.com/technet/security/website/chaptr14.asp)-
excerpted from Stallings' book- Cryptography and Network
Security: Principles and Practice- Second Edition- published by
Prentice Hall- PTR (1998).
Security Threats
§ Installation of
Trojan or
Distributed
Denial of
Service
(DDoS) code
Denial of Service Higher- A denial of service attack can
profile prevent users from using your site
sites by flooding it with illegitimate
requests- among other
techniques. These attacks can be
difficult to prevent.
§ Server data
compromised
user
impersonatio
n or data
forgery
Security Basics
With so many potential threats against Internet applications, it’s often difficult to know
just where to start in designing a secure application. This section will discuss some of the
strategies you can use to get started.
§ Server set-up and application design Preventing Web server and/or data
compromise due to insecure server settings and poor application design
§ Patching Preventing Web server compromise due to vulnerabilities in server
software
§ Access control Preventing Web server and/or data compromise due to
inappropriate access settings
§ Auditing and logging Tracking who is hitting your site, what they’re doing,
and when
§ Using SSL and other cryptographic security tools Preventing data
compromise
Choosing an OS
When you’re choosing an operating system, the first thing to ask yourself is just how
much security you need. As with many questions related to the design of a Web
application, there are trade-offs to be made between security and cost. Although client
operating systems such as the Windows 9x series may be able to act as Web servers on
a limited basis through Personal Web Services (PWS), they are not acceptable when
security is an important factor.
Workstation or server operating systems, such as Windows NT 4.0 and Windows 2000,
offer more robust scalability and better security features, including better access control
(see “Access Control” on page 186), logging, and encryption.
Important One reason to choose Windows NT or Windows 2000 to host
a Web application is that these server operating systems can
use the NTFS file system, where the Windows 9x series
cannot. NTFS allows you to provide robust access control at
both the file and folder level, and it also provides built-in
support for file encryption (in Windows 2000). FAT and
FAT32 (available in the 9x series) are poor choices for Web
applications requiring robust security.
Server operating systems offer the most robust security features, as well as better
scalability, greater ease of configuration, and better features for developers.
Choosing between Windows NT and Windows 2000 is fairly easy for a new application.
Windows 2000 should be the hands-down choice because of its improved reliability,
security features, and performance over NT 4.0. If you’re working in an existing NT 4.0
environment, however, you may have to take that into account when deciding whether to
continue development on that platform or migrate to Windows 2000 for new projects and
upgrades of existing Web applications.
Ultimately, your choice of operating system comes down to the following three factors:
§ Features (in this case, security features)
§ Cost
§ Existing environment
Your evaluation should include an analysis of all of these factors, with the goal of
determining which OS meets your security needs (features), while allowing you to work
within the constraints of cost and existing environment (if any).
Important Windows 2000 provides a number of major security
improvements, including built-in support for file system
encryption, security policies and templates (discussed later in
this section), and a Security Configuration and Analysis tool.
This tool can be very useful in determining whether your
system will meet your security needs as configured, and can
help you easily configure it if it doesn’t. For these reasons,
Windows 2000 (or later) should be the default choice for
secure Web applications on the Microsoft platform.
Choosing a Purpose
Another important point to consider when choosing your operating system is the purpose
of the server. For smaller applications with low scalability requirements, it may be
acceptable to run your Web server, database server, and components all on the same
machine. Because IIS and most databases make significant demands on both RAM and
processor power, this model does not scale particularly well for larger applications. More
importantly, however, placing a database on a Web server that is exposed to the Internet
greatly increases the security risks to the data stored in that database. This can also be
true for other server and application software, from mail server software to productivity
applications such as Microsoft Office. The important point is that as you add more
purposes to a server, you are also adding more security exposure. Keep all of this in
mind as you configure your servers and decide the purpose for each one.
If you are not using a service, you should avoid installing it, or use Add/Remove
Programs to remove it. The following steps show how:
1. Click Start, Settings, Control Panel.
2. Double-click the Add/Remove Programs icon.
3. In the left side of the window, click the Add/Remove Windows
Components button.
4. Review the list of installed components (shown in the following figure).
Of particular interest is the IIS node. Select that node and click the
Details button.
Also, if you need to install services that are not used all the time, you should set them to
be started manually rather than automatically. This way, you have control over when
these services are running.
Be a Policy Maker
One unheralded feature of the Windows 2000 operating system family is a robust set of
tools for setting up a machine’s security settings quickly and relatively painlessly. A full
discussion of these tools is beyond the scope of this book (and could take up a book of
its own), but let’s look at a couple of them.
The Security Templates tool and the Security Configuration and Analysis tool, when
used together, let you create, edit, and apply templates for defining security policies,
from minimum password length to file system auditing policy. Both tools, shown in the
following figure, are implemented as Microsoft Management Console (MMC) snap-ins.
Note You can also define security policies manually by using the Local
Security Policy editor, which you can find by clicking Start,
Programs, Administrative Tools, Local Security Policy. This tool
allows you to adjust individual local security policy settings, as well
as apply security templates to the local machine.
The advantage of using the Security Templates tool to create and edit your security
templates is that it allows you to create security policy templates separately from
applying the template to the local machine. To create a new security template, perform
the following steps:
1. Right -click the template path folder for the path where you want to
store your new template. Then select New Template.
2. Enter a name and description for the new template and click OK. The
new template will be displayed under the path folder you selected in
step 1.
3. Expand the node for your new template by double-clicking it, or click
the + sign next to it to display the options available for configuration
with the template.
4. Using the same technique as in step 3, expand the policy area you
want to customize, such as the Password Policy (which can be
found under the Account Policies node). Select the policy area to
view its available attributes in the right pane of the console window.
5. Double-click the attribute you want to modify.
6. Check the Define This Policy Setting in the template check box, as
shown in the following figure. Then edit the value to your desired
value.
Of course, once you’ve created your custom template, you may want to use it to
configure security for one or more machines. This can be accomplished using the
Security Configuration and Analysis tool, which can also be used to determine which
settings on the local machine are not in compliance with the template you’ve defined (or
one of the predefined templates).
You can also use templates to define security settings for development, staging, and
production security requirements. A development or staging server’s security
requirements are usually less restrictive than those for a production server environment,
where the completed application ultimately will be deployed. Unfortunately, when an
application is moved to the more restrictive environment of the production server, the
application may not work due to security restrictions. By defining security templates for
development, staging, and production environments, you can apply those templates to
your development and/or staging servers to test whether the application will work with
the greater restrictions of those envi ronments. Once testing is complete, you can restore
the previous template to continue development work.
Important Because the Security Templates tool can affect a large
number of security settings on a machine, it is very important
that you configure and test your templates on development
or staging systems before applying them to production
systems. Certain security restrictions can prevent Web
applications from functioning properly (for example, by
restricting access to accounts used by the application), so
you should always make sure your application works properly
under the security policy defined by the template before
applying the template to a production system.
Keep in mind that only settings for which a value has been defined in the template will be
applied. All other settings will remain as they were previously configured. Also note that
when a server that you’re configuring is part of a Windows 2000 domain, any settings
configured in the domain-wide security policy will override settings in the local security
policy.
Passwords, Please
One of the most common (and dangerous) areas overlooked in Web server security is
password protection. Problems in this area include weak or nonexistent passwords for
sensitive information or services, and placing passwords in plain-text files such as ASP
and ASP.NET pages, Global.asa or Global.asax files, or configuration files in the Web
space.
You can also use the SQL Server Enterprise Manager utility, if it is available, to modify
the login accounts and passwords for an MSDE database.
Almost as bad as blank passwords are passwords that are weak (easily guessable),
such as
§ Names or places
§ Dates, such as birthdays or anniversaries
§ Words found in a dictionary
§ Short passwords (8 characters or fewer)
§ All letters, all lowercase, all uppercase, or all numeric
Weak passwords make it much easier for someone trying to hack a server to guess the
password for an account. So-called dictionary attacks use a dictionary of common terms
or words to rapidly attempt to log in to an account. Brute force attacks attempt every
possible value until they find the correct one. While a strong password won’t necessarily
eliminate brute force attacks, it can increase the time needed for such an attack to
succeed. In combination with appropriate auditing and logging, this can give you time to
deal with the attack.
Strong passwords meet minimum requirements for length and complexity. You can
require secure passwords using a security template, such as the hisecweb.inf template,
using the Security Configuration and Analysis tool as described earlier in this chapter.
The hisecweb.inf template sets minimum and maximum password age, enforces
password history (preventing users from reusing old passwords), sets minimum
password length to eight characters, and requires passwords to meet minimum
complexity requirements, including requiring passwords to include three of the following
four categories of characters:
§ Uppercase characters
§ Lowercase characters
§ Numeric characters
§ Non-alphanumeric symbols (such as punctuation, special characters
like *, #, and $, etc.)
Note that these settings only control passwords for NT security accounts. If you use your
own authentication credentials in your application, you will need to implement your own
solution for enforcing strong passwords, such as using the RegularExpressionValidator
Server Control to perform pattern matching. (See Chapter 10 for more information on
validation controls.)
Unfortunately, this is true only if the Web server is not compromised by a security
vulnerability. If a server is compromised, a malicious user may be able to read any file in
the Web space for an application and gain the password(s) stored there.
Sensitive information, such as database passwords, can be protected using one of the
following options.
§ If you’re using SQL Server or MSDE, use a trusted connection to
connect to your database. This method uses the Trusted_Connection
attribute of a connection string to tell SQL Server to use the current
user’s NT login information to log in. This is most useful in intranet
scenarios where users log in to your application via NTLM with an NT
username and password. This method has the advantage of not
needing to store a password at all.
§ Store the connection string information in the Machine.config file,
which is not directly in the Web space of the application, using the
appSettings configuration section (described in Chapter 15). Although
this method is still not ideal because password information is still being
stored in plain text, the fact that the Machine.config file is stored
outside of the Web space makes it that much harder for a malicious
user to get to this file. For better security with this method, the
directory containing Machine.config and the directory containing your
Web application should reside on different drives.
In addition, you can also use such methods as encryption to make a would-be hacker’s
job more difficult. The bottom line is that there is really no place that you can store
passwords that is 100% secure, but some methods are more secure than others.
Balance your need for security against other factors when choosing how and where to
store sensitive information.
Note Using a trusted connection with SQL Server requires either using
Windows authentication and impersonation, as described in “Using
Impersonation” on page 203, or setting up the default ASPNET
account (the account used to run the ASP.NET worker processes)
as a login account in the SQL Server database being accessed.
This process is described in Chapter 11.
Account limitations are an important security strategy from the standpoint of Windows
2000 accounts, database accounts, and any custom accounts you may create for your
application. You should configure each account to have only the capabilities necessary
for the type of user it represents. For example, it is usually a good idea to set up a
database account with read-only access for pages (or components) in your application
that only need to read and display data.
A good example of this practice is Microsoft’s decision to change the default account for
running ASP.NET worker processes. The default account used to be the SYSTEM
account, which has numerous privileges and can perform almost any action on a
machine. Now the default is the ASPNET account (specified by the MACHINE value for
the username attribute of the <processModel> configuration element in Web.config),
which has very few privileges on the system. This change makes certain techniques
more difficult to use, but it also reduces the likelihood that a single compromised
application would compromise an entire system or an entire network.
Sample applications are not designed to run on production servers, so typically they do
not use best practices to prevent servers or applications from being compromised. One
example is that samples installed with one version of IIS included a utility that allowed
users to view the source of ASP pages. Unfortunately, if this utility was installed on a
server containing production applications, it could be used to view the source code and
make them vulnerable to attack.
As mentioned earlier, the .NET QuickStart samples install an instance of the MSDE
database software. In the Beta 2 release, this instance was installed with a blank
password for the sa account. If these samples were installed on a server that is exposed
to the Internet, this would result in a major vulnerability.
Additionally, some sample applications demonstrate extremely poor practices when it
comes to security. In earlier versions, the QuickStart samples used the sa account with a
blank password for database connections. This is an extremely bad practice. Not only
does it reinforce the bad habit of leaving the sa account with no password, but it also
uses an account for data access that has much wider permissions than are necessary
for data access alone. The good news is that the current versions of the QuickStart
samples follow the best practice of using an MSDE account created especially for the
sample applications. This account only has permission to access the databases used by
the samples, and only the permissions on those databases necessary for the samples.
Although the changes in the .NET Framework QuickStart samples indicate that Microsoft
is committed to demonstrating better security practices in sample applications, they still
should not be installed on a production server (or any other server that is exposed to the
Internet, for that matter) without a very clear understanding of the risks entailed, and
without undertaking efforts necessary to mitigate those risks.
Validation is another area that is often overlooked in Web application security. For
example, developers may include validation code to ensure that users enter an e-mail
address or phone number in the correct format. But they might not consider that other
text fields, particularly large text fields or those that may be used for display elsewhere in
the application, may expose the application to unacceptable risks.
The problem with not validating all text input by the user is that a malicious user can
enter text, such as script commands, ASP.NET <% %> render blocks, or other text that,
in the wrong context, could be allowed to execute rather than being treated as plain text
to be displayed. Additionally, input fields used to construct SQL statements for data
access may be vulnerable to unexpected commands being entered. Validation can help
you prevent this problem, as well as prevent nuisance problems such as users
attempting to post profanity in guest book or discussion list applications built with
ASP.NET. In fact, ASP.NET makes it very easy to implement robust validation using a
set of server controls designed specifically for this purpose. These controls are
discussed further in Chapter 10.
The important thing to remember is that you should always treat any input from a user as
suspect until it has been proven otherwise. Validate that the input from the user is what
you’re expecting before you use, store, or display it.
Mind Those Ports!
Internet applications communicate via the TCP/IP protocol, which is the basis for all
communication between computers on the Internet. TCP/IP uses two pieces of
information to find the endpoints for a given communication: the IP address, which is a
unique number assigned to a given machine, and a TCP port number, which for most
applications is a well-known number used consistently by all applications of that type.
For example, all Web servers use TCP port 80 as the default port for HTTP (Web)
communications. The well-known nature of these port numbers and the services found
on them makes it much easier for Web servers and clients to find one another.
Other well-known ports include File Transfer Protocol (FTP, port 21), Simple Mail
Transport Protocol (SMTP, port 25), and POP3 (port 110). The full list of well-known
ports and the services assigned to them can be found at
http://www.iana.org/assignments/port-numbers.
Why is this important to the discussion of security and Web server set-up? Because the
very ease of discovery that well-known port numbers makes possible also presents a
security risk for the unwary. For example, on Windows 2000, the ports for services such
as FTP, SMTP, and POP3 (among others) are left open by default. This gives hackers
an engraved invitation to probe these ports and see if the software behind them is
vulnerable to attack. Given the many vulnerabilities discovered in FTP, SMTP, and other
Internet-based services, it is essential to close all ports that are not in use by your
application.
Closing ports can be accomplished in a number of ways, the most common being packet
filtering (either through firewall software or a hardware router) or using the Windows
2000 IPSecurity Policy Management MMC snap-in. For many applications, the preferred
solution is to set up a hardware firewall between the Web server and the Internet that
only allows traffic on port 80 (HTTP), and optionally port 443 (HTTPS) if secure sockets
Web traffic is required. (See “Using SSL to Protect Communications” on page 188 for
more information on Secure Sockets Layer communication.) Then a second firewall is
added between the Web server and the internal network that only allows traffic on ports
necessary for the Web server to reach other servers (such as the database server), and
that blocks ports 80 and 443 (and any other ports open in the other firewall). This method
places the Web server in what is referred to as a DMZ (demilitarized zone), which is
designed to prevent direct communication between the Internet and an internal network.
This protects servers on the internal network from attack.
Important Whichever method you use to close unused ports on your
Web server, it is imperative that you block traffic to any ports
that your application or applications do not use. Remember,
however, that the ports that remain open are still a security
risk. Effective logging of server activity, frequent monitoring
of logs, and prompt patching of vulnerabilities in software
operating on the open ports are all important means of
defending your server(s) from attacks.
A full discussion of packet filtering, routing, and IPSec management is beyond the scope
of this chapter. Consult the manual for your firewall or router, or the Windows 2000 Help
files, for more information on implementing these solutions.
Patching
Once your server is set up correctly and securely, and you’ve considered how the design
of your application affects security, you might think you’re home free. Not so! Even the
most securely configured server and securely designed application can be compromised
by a lax attitude toward ongoing maintenance. One of the most important aspects of
ongoing maintenance of servers and applications is staying on top of patches released
by vendors of any software you’re using.
In an ideal world, the software we use would be perfect from the start. However, the
reality is that there are few, if any, programs that do not contain vulnerabilities. When
these vulnerabilities are discovered, typically the vendor of the program will issue a patch
designed to correct the problem. Unfortunately, many server administrators do not apply
these patches consistently, leaving their servers vulnerable to attack.
This is inexcusable. Most patches can be applied easily, and there are many ways that
you can be notified automatically about new ones for Microsoft software. These include
the following sources of information about patches.
§ The Windows Update site (http://windowsupdate.microsoft.com/) lets you
analyze the updates available for a given system and determine which of
them are currently installed. Windows 98 and later and Windows 2000
and later install a link to Windows Update in the Start menu by default.
§ The Microsoft Product Security Notification service lets you sign up for e
mail notification of vulnerabilities and available patches. This method is
useful for administrators who need to keep track of patches without
visiting multiple machines. You can sign up for this service at
http://www.microsoft.com/technet/treeview/default.asp?url=/technet/secu
rity/bulletin/notify.asp.
§ The Network Security Hotfix Checker is a command-line utility that will
scan the local machine, or machines on the local network, for uninstalled
patches for Windows NT 4.0, Windows 2000, IIS 4.0 and 5.0, SQL
Server 7.0 and 2000 (and MSDE), and IE 5.01 and later. The Network
Security Hotfix Checker is downloadable from
http://www.microsoft.com/downloads/release.asp?releaseid=31154. You
can find more information on using the Network Security Hotfix Checker
at http://support.microsoft.com/directory/article.asp?ID=KB;EN-
US;q303215.
Access Control
Access control is the process of determining who can access resources on your server.
This includes both authentication (determining the identity of a user making a request)
and authorization (determining whether that user has permission to take the action
requested). For an ASP.NET application, there are several different authentication and
authorization methods. You’ll learn how to implement authentication and authorization in
ASP.NET later in this chapter.
It is important not to forget that access control also includes physical access to the
machine being secured. The best authentication, authorization, and password practices
won’t help you a bit if someone can gain physical access to a machine and circumvent
your security barriers, or simply damage the machine beyond repair. Any machine that
has value to you should be secured physically, as well as via software, from
unauthorized use.
As mentioned earlier in this chapter, even the best security practices and strongest
passwords cannot provide 100 percent protection against attacks. For this reason, it is
imperative to enable auditing and logging on exposed servers.
Auditing is the process of monitoring certain activities, such as logon attempts, for
success or failure, and then logging the results of this monitoring to the Windows event
log. Auditing (and proper monitoring of the logs) allows you to determine when someone
is attempting to attack your machine, such as by trying numerous incorrect passwords. In
this case, by auditing login failures, you would be able to see the failed login attempts in
the Security event log and take appropriate action to foil the would-be intruder (such as
disabling the account being attacked). As with many of the other settings discussed in
this chapter, the auditing policy for a Windows 2000 machine can be set using a security
template via the Security Configuration and Analysis MMC snap-in.
Logging is the process of writing information about the activities being performed on a
machine to a known location for later review. For our purposes, the most important
logging (after the logging of audit information) is done by IIS for the Web, FTP, and
SMTP services. Logging is performed at the site level. To enable logging in IIS, use the
following steps. (They’re for enabling logging for a Web site, but the steps for FTP,
SMTP, and other IIS services are similar.)
1. Open the Internet Services Manager MMC snap-in by clicking Start,
Programs, Administrative Tools, Internet Services Manager.
2. Select the server you want to manage, and expand the tree to find the
site you want to manage. Right -click the desired site and select
Properties. The following dialog will be displayed.
By default, information sent via HTTP requests and responses is sent as clear text. This
means that someone could capture the packets that make up these requests and
responses and then recreate them, including any data passed from form fields on a
submitted page. If such a form contained sensitive data, such as passwords or credit
card numbers, this data could be stolen.
Encryption is an important tool for keeping this kind of information secure. The most
commonly used form of encryption in Web applications is the Secure Sockets Layer
(SSL) protocol. SSL is used by sites requiring secure communications between the Web
server and the browser to create and exchange a key used to encrypt communications
between the client and server, which helps protect this information from prying eyes. This
is typically done by e-commerce sites to protect credit card information, for example.
SSL can also be useful for protecting other information, including SessionID cookies and
login information when using basic authentication, or ASP.NET Forms-based
authentication. (See “Enabling Authentication” on page 191 for more information on
basic authentication.)
By default, SSL communications occur on port 443 (as opposed to non-SSL Web
communications, which are on port 80 by default), using the https:// prefix for URLs that
use SSL. Enabling SSL for your Web server requires obtaining a server certificate and
binding that certificate to the Web sites on which you want to use SSL. To request a
server certificate from a certification authority, follow these steps:
1. Open Internet Services Manager.
2. Right -click the site you want to protect via SSL and then select
Properties.
3. On the Directory Security tab, shown in the following figure, click the
Server Certificate button. This will start the Web Server Certificate
Wizard.
Once you’ve received the response from the certificate authority containing your
certificate, follow these steps to install the certificate for use in IIS:
1. Locate the certificate file you received from the certificate authority.
Right -click the file and select Install Certificate.
2. Open Internet Services Manager.
3. Right -click the site you want to protect via SSL and select Properties.
4. On the Directory Security tab, click the Server Certificate button. This
will again start the Web Server Certificate Wizard.
5. Click Next to advance to the second page of the wizard.
6. On the Server Certificate page of the wizard, shown in the following
figure, select Assign an existing certificate and then click Next.
7. The Available Certificates page should list all of the certificates on the
current machine, including the certificate you just installed. Select the
desired certificate and then click Next.
8. Review the Certificate Summary information to ensure you are
assigning the correct certificate. Then click Next.
9. Click Finish to complete the wizard.
Once you’ve installed the certificate for a given site, you can use SSL to encrypt
communications on any of the virtual directories under that site by having users request
pages with https:// rather than http://. To ensure that pages cannot be viewed without
SSL, however, you must require SSL for the page or directory you want to protect.
1. Open Internet Services Manager.
2. Right -click the site, virtual directory, or file you want to protect and
then select Properties.
3. On the Directory Security or File Security tab, click the Edit button,
which should now be available. This will open the Secure
Communications dialog, shown in the following figure.
There are three basic types of authentication available for ASP.NET applications:
Windows -based, Passport, and Forms-based authentication. The authentication method
you choose will depend on the types of clients you’re dealing with, the level of control
you have over the client browser choice, and a number of other factors. The next several
sections will discuss the available methods, how they’re implemented, and why you
might choose one particular method over another.
Important The interaction between IIS and ASP.NET in terms of
authentication and authorization may be somewhat confusing
if you’re inexperienced with these features. Until you
understand how the various options work, you should
practice the techniques and procedures discussed in this
section on a machine that is not exposed to the Internet or
other potential sources of compromise.
If more than one authentication type checkbox is checked, Digest and Integrated
Windows authentication will always take precedence over basic authentication.
Once IIS has authenticated the user, it passes the authenticated identity to ASP.NET,
which can then use that information to allow or deny access to resources within the
application. See “Authorizing Users and Roles ” on page 201 for more information on this
type of authorization.
Once you have set the authentication mode to Windows, you need to set the desired
authorization method to protect the desired resources.
Using Passport Authentication
Passport authentication uses the centralized Passport authentication service provided by
Microsoft. Passport authentication gives users a single login to use with any Passport-
enabled site. In order to use Passport authentication with ASP.NET, you must download
and install the Passport SDK, which is available at http://www.passport.com/business.
ASP.NET provides a wrapper around the Passport authentication service, so you can
use it simply by setting the appropriate value for the Web.config <authentication> tag.
Let’s take a look at an example. First use the following steps to create a new IIS virtual
directory and add a test page and a login page to it:
1. Create a new virtual directory called Chapter8 in IIS. (See Chapter 2
for directions on how to do this.) Leave the Internet Services Manager
open for now.
2. Create two new files in the physical directory associated with the
virtual directory: test.aspx and login.aspx. The content of both of the
files is shown in the following listing.
Test.aspx
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Web.Security " %>
<html>
<head>
<script runat="server">
Sub Page_Load
User.Text = "Username is " & Page.User.Identity.Name & "."
End Sub
Login.aspx
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Web.Security " %>
<html>
<head>
<script runat="server">
Sub Login_Click(Src As Object, E As EventArgs)
'NOTE: The user name and password are stored below for
' the sake of simplicity for our example. In production code,
' these values would be pulled from a database, Active
‘ Directory, or some other secure location.
If Username.Value = "charliebrown" And _
Password.Value = "rats" Then
FormsAuthentication.RedirectFromLoginPage(Username.Value, _
persist.Checked)
Else
Msg.Text = "Incorrect Username or Password: Try again."
End If
End Sub
</script>
</head>
<body>
<form runat="server">
<table>
<tr>
<td>Username:</td>
<td>
<input id="UserName" type="text" runat="server" />
</td>
</tr>
<tr>
<td>Password:</td>
<td>
<input id="Password" type="password" runat="server" />
</td>
</tr>
<tr>
<td>Persist Authentication Cookie:</td>
<td>
<asp:checkbox id="persist" runat="server" />
</td>
</tr>
<tr>
<td>
<asp:button id="login" text="Login"
OnClick="Login_Click" runat="server" />
</td>
<td>
<input type="reset" text="Reset Form" runat="server">
</td>
</tr>
</table>
<asp:Label id="Message" ForeColor="red" runat="server" />
</form>
</body>
</html >
3. Next, add a Web.config file to the directory, with the content shown
here:
<!-- web.config -->
<configuration>
<system.web>
<authentication mode="Forms">
<forms name="formsauth" loginUrl="login.aspx" protection= "All"
timeout="60" />
</authentication>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</configuration>
4. Save all three files.
5. In the Internet Services Manager, locate the test.aspx file, right-click it,
and select Browse to browse the page. You should see something
like the following figure. Note the ReturnUrl querystring argument.
This allows ASP.NET to return the user to the originally requested
page once he’s authenticated.
6. Enter charliebrown for the username and rats for the password, and
then click Login. If you check the Persist Authentication Cookie
checkbox, the cookie will be saved to disk (if your browser settings
allow this) rather than being removed as soon as the browser is
closed. Once you’ve clicked Login, you should see something like
the following figure.
7. Test.aspx uses the User property of the Page object to access the
identity of the current user. From the identity, you use the Name
property to display the name of the authenticated user.
8. Click the Log Out button. This calls the SignOut method of the
FormsAuthentication helper class, which removes the authentication
cookie. Now, if you attempt to browse test.aspx again, you will need
to log in again.
Note In this example Web.config file, the URL for the login page is specified
with the loginUrl attribute of the forms tag. (For more information on all
of the attributes of the form tag, see Chapter 7.) However, if you leave
this attribute out, ASP.NET supplies the same name, login.aspx, as a
default.
ASP.NET also supplies a default value of default.aspx for the redirect
page when FormsAuthentication.RedirectFromLoginPage is called, if
no RedirectUrl querystring argument is found. So in the preceding
example, if you call the login page directly and authenticate
successfully, ASP.NET will attempt to redirect you to default.aspx. If
there’s no such page, you’ll get a “file not found” error.
Using Authorization
After you’ve authenticated the user making the request, the next step is to authorize the
request. Authorization means deciding whether to permit the user to access the
requested resource.
Authorization in ASP.NET takes two major forms: authorization against NTFS Access
Control Lists (ACLs) and URL-based authorization.
When a request for a resource (such as an ASP.NET page) comes in, IIS authenticates
the request, using whichever method (basic, digest, integrated Windows) has been
configured for the application. IIS passes the identity of the logged-in user to ASP.NET,
which then impersonates the identity of the user and checks the ACL for the requested
resource to see if the user has the required permission. If so, ASP.NET fulfills the
request.
Using Impersonation
Impersonation is the process by which ASP.NET takes on the security context of the
user making the request. This allows ASP.NET (and components called by ASP.NET) to
gain access to resources that the user is authorized to access.
In classic ASP, impersonation was used by default. If the user making the request had
not been authenticated by IIS using basic or NTLM authentication, ASP requests ran
with the security context of the IUSR_MACHINENAME built-in account. In ASP.NET,
impersonation must be turned on explicitly by using the impersonation attribute of the
<identity> tag. Without impersonation, ASP.NET runs as the built-in SYSTEM account.
(Although you can use the <processModel> configuration section to configure ASP.NET
to run as a different account that has fewer privileges, such as the MACHINE built-in
account.)
For example, you might want to take advantage of impersonation to use trusted
connections with SQL Server. Trusted connections use Windows security to connect to a
SQL Server database without the need for a SQL Server username and password. As
discussed earlier in this chapter, one of the challenges facing ASP.NET developers is
where to store database login information to reduce the risk of it being compromised.
With trusted connections, this is not a problem because the login information is never
stored by the application.
To make a trusted connection to SQL Server work, you must do the following:
1. Configure your application to use Windows authentication, as described
earlier in this chapter.
2. Add the <identity> tag to your configuration file to make ASP.NET
impersonate the identity of the client making the request.
<identity impersonate="true">
3. Add the desired Windows accounts to the SQL Server security database.
4. In the connection string used to connect to the database, specify
Trusted_Connection=yes. The full connection string would look
something like the following:
"server=(local)\NetSDK;database=pubs;Trusted_Connection=yes"
5. Using one of the accounts that you added to the SQL Server security
database, log in to the ASP.NET application. You should be able to
access the data on the SQL Server database.
Note If you are unable to use Windows authentication and
impersonation in your ASP.NET application, you may still be able
to take advantage of trusted connections by configuring the default
ASPNET account (used to run the ASP.NET worker processes) as
a SQL Server login account. Keep in mind, though, that this will
allow anyone who can access any ASP.NET application to change
the database according to the permissions you have set up for the
ASPNET account. The process of setting up the ASPNET account
as a SQL Server login is described in Chapter 11.
Security Resources
The following are some URLs to keep handy when securing your application.
§ http://www.microsoft.com/technet/security This is the starting point for
finding security-related information for Windows- IIS- and other Microsoft
products. There is a host of good information on improving the security of
your servers and applications to be found here.
§ http://www.microsoft.com/technet/security/iis5chk.asp This is the Secure
Internet Information Services 5 Checklist. Prepared by Michael Howard of
the Windows 2000 and IIS security teams- this document provides
recommendations and best practices for securing IIS 5.
§ http://www.microsoft.com/technet/treeview/default.asp?url=/technet/itsol
utions/security/tools/tools.asp This is a list of security tools available
through the Microsoft Technet Web site.
§ http://nsa2.www.conxion.com/win2k/download.htm This is the download
site for the Windows 2000 Security Recommendation Guides prepared by
the National Security Agency (NSA).
§ http://www.microsoft.com/technet/treeview/default.asp?url=/technet/colu
mns/security/10imlaws.aspThis is the 10 Immutable Laws of Security- a
list of the many ways to lose control of your computer.
§ http://www.microsoft.com/technet/treeview/default.asp?url=/technet/colu
mns/security/10salaws.asp This is the 10 Immutable Laws of Security
Administration- a list of sage advice for those who have the onerous task of
securing their systems.
Enable logging Open the Internet Services Manager, navigate to the site
for a Web site you want to configure, right -click, and then select
Properties. In the Properties dialog for the site, click the
Web Site tab and ensure that the Enable Logging
checkbox is checked.
'Let's start from the very beginning- a very good place to start. When you read you begin
with A… B… C… when you…
No- that won't do at all. But you don't have to channel Julie Andrews in The Sound of
Music to figure out Web Forms. And you will start at the beginning… the beginning of the
page- that is.
Web Forms go beyond what classic ASP pages offered, adding new directives, new
reusability options in the form of user controls and server controls, and a new server-
side data-binding syntax, to name a few. This section will explore how a page is put
together and how you can use these new features in your Web Forms.
The following listing shows an example of a relatively simple Web Forms page.
HelloSimple.aspx
<%-- Example of the @ Page directive --%>
<%@ Page Language="vb" ClassName="Hello" %>
<html>
<head>
<script runat="server">
‘this is a script code declaration block
Private _name As String = "Andrew"
Sub SayHello()
Label1.Text = "Hello, " & _name & "!"
End Sub
</script>
</head>
<body>
<!-- this is a server side form -->
<form runat="server">
<!-- a Server Control -->
<asp:Label id="NameLabel" runat="server">Name: </asp:Label>
<!-- a Server Control -->
<asp:textbox id="NameTextBox" runat="server"/>
<!-- a Server Control -->
<asp:button id="NameButton" text="Submit" runat="server"/>
</form>
<!-- a Server Control -->
<asp:Label id=Label1 runat="server"/>
</body>
</html>
This example contains a directive, HTML markup, a <script> code declaration block, a
server-side form, and several server controls. The following sections describe all of these
elements, as well as other elements that may be used in a Web Form page.
Element Description
Element Description
using code
declaration and
render blocks.
ASP.NET
supports server-
side code in any
language that
targets the
runtime.
Event handlers Event handlers
are procedures in
<script> code
declaration blocks
that handle page
or server control
events, such as
Page_Load or
control Click
events. Most
ASP.NET code
should be written
in or called from
event handlers,
rather than being
written in render
blocks.
<script> code declaration blocks These blocks are
used to contain
page-level
procedures and
to declare
variables that are
global to the
page. Executable
code, other than
global variable
declarations in
code declaration
blocks, must be
contained within
a procedure
declaration.
Server-side code
declaration blocks
must have the
runat=“server”
attribute, as
shown in
HelloSimple.aspx.
<% %> render blocks These blocks are
used to contain
executable code
not contained
within
procedures.
ASP.NET Page Elements
Element Description
Overuse of
render blocks can
result in code that
is difficult to read
and maintain.
Client -side <script> blocks These blocks are
used to contain
script code to be
executed on the
client, usually in
response to a
client-side event.
Choice of
language (set by
the language
attribute) is
dictated by the
languages
supported by the
target browser.
JavaScript is the
most common
choice for cross-
browser
compatibility in
client scripts.
Server-side comments Syntax: <%-- --
%>. Server-side
comments allow
descriptive text to
be added to a
page. Unlike
HTML comments,
this text is not
sent to the client.
User controls These are custom
controls that are
defined
declaratively in
files with the
.ascx extension.
They provide a
simple and
straightforward
mechanism for
reuse of UI and
UI-related code,
and can contain
most of the same
elements as Web
Forms pages.
ASP.NET server controls This set of built-in
controls provides
ASP.NET
ASP.NET Page Elements
Element Description
developers with a
programming
model that
mimics that of
Visual Basic.
Controls are
added to a page,
and programmers
write code to
handle events
raised by users’
interaction with
the controls at
runtime.
ASP.NET
provides two sets
of built-in
controls: the
HTML controls,
which provide a
1-to-1 mapping of
server-side
controls for most
HTML elements;
and the Web
controls, which
provide a set of
controls that are
very similar to the
Visual Basic UI
controls.
Each ASP.NET Web Forms page contains a server-side <form> tag that directs the page
to post back to itself when the form is submitted by the user. Many ASP.NET server
controls also render JavaScript to the client, allowing actions such as selecting an item in
a drop-down list to cause a postback. The ASP.NET runtime also renders a hidden form
field to the page that allows the page to maintain its state between requests.
The postback and hidden field are key, because when the client is interacting with the
page, there is no code running on the server at all. The postback and hidden field allow
the page to be reconstituted on the server. Also, they allow code to be executed in
response to the event raised by the user action, and based on any changes to the form
fields. Once the page has been processed and the output rendered to the browser, the
page and its controls are again discarded. The steps in this process are as follows:
1. The user requests the page from the browser.
2. The page and controls are loaded and initialized.
3. If the page request is the result of a postback, the control state is
loaded from the viewstate (hidden form field), and any changes
submitted by the user are applied. (Note that both the original values
in the viewstate and the updated values are available to server-side
code.)
4. Page event handlers and event handlers for events triggered by user
actions are executed.
5. Control state is saved to viewstate (hidden form field).
6. HTML output from the page is rendered to the browser.
7. The page and controls are unloaded.
It’s important to note that while most server controls save their state to viewstate
automatically, the same is not true for properties that you define in your pages, or in user
controls or custom server controls. You’ll learn how to use viewstate for storing custom
control state in Chapter 12.
Using Directives
If you’ve developed a classic ASP page, you’ve worked with directives. There were few
directives in classic ASP, but they were important. Most prominent were the @
Language directive and the #Include directive. The @ Language directive, which
appeared at the top of every classic ASP page, told the ASP runtime which language
engine to use in interpreting script found in <% %> render blocks in the page. The
#Include directive told the ASP interpreter to include a particular file inline with the
current ASP page.
Directives are simply ways for developers to declaratively determine how certain aspects
of a program will operate. In classic ASP, this was somewhat limited. In fact, there were
only the following four @ directives in classic ASP, in addition to the @ Language
directive:
§ @ Codepage Used in globalization to set the code page for an ASP
page
§ @ EnableSessionState Used to disable session state for a page
§ @ LCID Used to set the locale identifier for a page
§ @ Transaction Used to specify whether and how the page participates
in COM+ transactions
ASP.NET greatly expands the use of directives, adding a number of useful directives for
everything from controlling page behavior and configuration to caching page output. In
addition, @ directives in ASP.NET have attributes, which increase their power and
flexibility. The classic ASP directives listed previously are represented in ASP.NET as
attributes of the @ Page directive, which is described in the next section.
@ Page
The @ Page directive, which is allowed in .aspx files only, defines page-specific
attributes that are used by ASP.NET language compilers and the runtime to determine
how the page will behave. The default values for a few of these attributes are set in the
Pages configuration section in Machine.config. Some of the attributes, including
AutoEventWireup, are set or overridden in pages created by Visual Studio .NET. The
attributes available for the @ Page directive are listed in the following table.
@ Page Attributes
@ Page Examples
In this section, we’ll take a look at a couple of examples of using the @ Page directive.
The first example will show how to enable debugging of ASP.NET pages, and the
second will show how to enable page-level tracing.
Enabling Tracing
By default, the trace functionality is not enabled for ASP.NET pages. As with the default
setting for the debug attribute, this is good for performance, since there is overhead
associated with tracing. Tracing enables you to view information about the current
request, including the collections (cookies, forms, headers, querystrings, and server
variables) associated with the request. Here’s how to enable tracing on a page-by-page
basis:
1. Open the desired page in a text editor, or in Visual Studio .NET.
2. Add the trace attribute to the @ Page directive, with a value of true.
3. <%@ Page trace="true" %>
4. Save and close the file. When you request the file from a browser,
you’ll be able to see the trace information appended to the page
output, as shown in the following figure.
For more information on tracing and its uses in debugging ASP.NET applications, refer
to Chapter 16.
@ Control
The @ Cont rol directive, which is allowed in .ascx files only, performs the same function
as the @ Page directive. However, instead of setting attributes for pages, it sets the
attributes for user controls, which are reusable snippets of code named with the .ascx file
extension. The attributes exposed by the @ Control directive are a subset of those
exposed by the @ Page directive, and their purpose and values are the same as in the
@ Page Attributes table beginning on page 215. The attributes available for the @
Control directive are as follows:
§ AutoEventWireup
§ ClassName
§ Codebehind
§ CompilerOptions
§ Debug
§ Description
§ EnableViewState
§ Explicit
§ Inherits
§ Language
§ Strict
§ Src
§ WarningLevel
@ Import
The @ Import directive is used to import either a .NET Framework namespace or a
custom namespace into a page. Importing a namespace allows you to write code against
the members of that namespace without explicitly specifying the namespace each time.
The @ Import directive has only one attribute, Namespace, which specifies the
namespace to import. Each @ Import directive can have only one Namespace attribute,
so you must use a separate @ Import directive for each namespace you wish to import.
For example, to use the .NET Framework SmtpMail and MailMessage classes to send e-
mail from an ASP.NET page, you would need to add the System.Web.Mail namespace
to your page, as follows:
<%@ Import namespace="System.Web.Mail" %>
Then, to create an instance of the MailMessage class, you would use the following:
Dim myMail As New MailMessage
Without the @ Import directive, you would need to use the following:
Dim myMail As New System.Web.Mail.MailMessage
@ Implements
The @ Implements directive is used to implement a defined interface from within an
ASP.NET page. An interface provides an abstract definition of a set of methods and
properties. When you implement an interface, you commit to supporting the methods and
properties defined by the interface, and you must create matching method and property
definitions in your .aspx file, using <script> blocks. The @ Implements directive can’t be
used to implement interfaces in a code-behind file. It has one attribute, interface, which
specifies the interface being implemented.
@ Register
The @ Register directive is used with both user controls and custom server controls to
register them for use within a page. The @ Register directive’s attributes are listed in the
following table.
@ Register Attributes
@ Assembly
The @ Assembly directive is used to link an assembly into a page at compilation time.
This allows developers to use all of the classes, methods, and so forth exposed by the
assembly as if they were part of the page. The @ Assembly directive’s attributes are
listed in the following table. Only one of the Name and Src attributes of the @ Assembly
directive may be used at a time.
@ Assembly Attributes
@ OutputCache
The @ OutputCache directive is used to specify that the rendered output of the page or
user control in which it appears should be cached, and it specifies the attributes that
determine the duration of caching, the location of the cached output, and the attributes
that determine when a client will receive freshly rendered content rather than the cached
content. Output caching in user controls can be especially useful when some of the
content in a page is relatively static, but other content is frequently updated, making it a
poor candidate for caching. In a case like this, you could move the static content into a
user control and use the @ OutputCache directive to cache its content, while leaving the
rest of the page to be dynamically generated with each request.
The @ OutputCache directive’s attributes are listed in the following table.
@ OutputCache Attributes
@ Reference
The @ Reference directive allows you to dynamically load user controls by referencing
the file name of the desired control and then using the Page.LoadControl method to load
the control at runtime. The @ Reference directive directs the ASP.NET runtime to
compile and link the specified control to the page in which it is declared. You’ll see an
example of using the @ Reference directive later, when this chapter discusses user
controls.
Likewise, you can declare a local member variable in a declaration block, but you may
not assign to it outside of a defined method. So the following code will work:
<script language="vb" runat="server">
Dim Name As String
Sub SetName
Name = "Andrew"
End Sub
</script>
There is one exception to the variable assignment rule. You can initialize the value of a
variable by assigning a value as part of the variable declaration, as follows:
<script language="vb" runat="server">
Dim Name As String = "Andrew"
</script>
The language attribute of the <script> block is optional. If it is omitted, the value will
default to the language specified by the @ Page directive’s language attribute. If no
language attribute has been specified for the @ Page directive, the value will default to
VB.
11. The output should appear similar to that in the following figure.
User controls perform a similar function to server-side includes in classic ASP, but
they’re considerably more powerful because of the level of integration with the page
model. A user control can have its own controls, and it can save the viewstate of its
controls or its own viewstate. This allows the user control to maintain its state across
multiple calls without any effort on the part of the page containing the user control. User
controls can expose both properties and methods, making them easy to understand for
developers who are used to components.
Include files are still available in ASP.NET, mainly for backward compatibility with
classic ASP. For new applications, it makes more sense to use user controls, given
their advantages.
The tag used to invoke the control would look like the following:
<MyPrefix:MyControlClass id="MyControlID" runat="server"/>
For more information on the standard server controls included with ASP.NET, refer to
Chapter 10. Creating and using custom server controls is covered in much greater detail
in Chapter 12.
Event Handling
One of the biggest differences between classic ASP and ASP.NET is the execution
model. In classic ASP, pages were executed in a top-to-bottom fashion. That is, with the
exception of detours to execute functions defined in the page, classic ASP pages were
procedural rather than event-driven, like Visual Basic programs.
ASP.NET changes that by bringing event-driven programming to Web development
through server controls and postbacks. At runtime, the code in a Web Form, as well as in
any code-behind class associated with that Web Form, is compiled into an assembly.
When executed, the code in that assembly fires events that you can handle, both those
exposed by the Page class and those that are fired by server controls that have been
added to the page.
§ Ini tPage and control settings are initialized as necessary for the
request.
§ LoadViewStateS erver control state that was saved to viewstate in an
earlier request is restored.
§ LoadPostData Any data returned in server control form fields is
processed, and the relevant control properties are updated.
§ Load Controls are created and loaded, and control state matches the
data entered by the client.
§ RaisePostDataChangedEvent Events are raised in response to
changes in control data from the previous request to the current
request.
§ RaisePostBackEvent The event that caused the postback is handled,
and the appropriate server-side events are raised.
§ PreRender Any changes that need to be made prior to rendering the
page are processed. Any processing after this point will not be
rendered to the page.
§ SaveViewState Server control state is saved back to the page’s
viewstate prior to tearing down the controls.
§ Render Control and page output is rendered to the client.
§ UnLoad The page and its constituent controls are removed from
memory on the server.
Of these stages, you can add page-level event handlers for the Init, Load, PreRender,
and UnLoad events. These handlers are typically named Page_Init, Page_Load,
Page_PreRender, and Page_UnLoad, respectively. For other stages, such as the
LoadViewState, SaveViewState, and Render stages, you can override the appropriate
method to customize the processing of these stages. Overriding these methods is
discussed in Chapter 12.
Some events, such as those fired by the page, are fired automatically as certain stages
of page processing occur. Others, such as those associated with server controls, are
actually triggered on the client (such as a user clicking a button server control) but are
fired and handled on the server when the page is posted back to the server. Because the
page is reloaded and the control state is restored with each postback, to the client it
appears as though the page is there throughout the client’s interaction with it, and the
application appears to operate much like a Visual Basic form-based application.
Each stage in the processing of a Web Forms page fires an event that allows you to
perform processing of your own during that stage.
To illustrate this concept, let’s finish up the ServerControls.aspx page that you started in
the previous example by adding an event handler to use the text the user enters in the
TextBox server control.
1. If it’s not already open, open the ServerControls.aspx page you
created earlier.
2. Add the following code to the <head> section of the page:
3. <head>
4. <script runat="server">
5. Sub Page_Load(Sender As Object, E As EventArgs)
6. If IsPostBack Then
7. HelloLabel.Text = "Hello, " & _
8. Server.HtmlEncode(NameTextBox.Text) & "!"
9. End If
10. End Sub
11. </script>
</head>
12. Save the page, and then browse to it by entering the appropriate URL
in a browser window, as shown in the following figure, or using Internet
Services Manager.
13. Enter a name in the textbox, and then click the Submit Query button.
After the page is posted back, the output should look similar to the
following figure.
In the preceding code, you added an event handler for the Page_Load event. This event
handler is called when the Load stage of the page processing is reached. In the event
handler, you check the page-level property IsPostBack to determine if the page has been
posted back as a result of a client action (clicking the button in this case). If it has, you
set the Text property of HelloLabel to the desired text, which includes the text the user
entered in the textbox. Notice that you use the Server.HtmlEncode utility method to
encode the text entered by the user. This helps prevent nasty users from entering
<script> tags or other HTML content to do something you don’t want them to do. Using
the HtmlEncode method means that any such content will be written out using HTML
entity equivalents of characters such as less than (<), greater than (>), and quotation
marks.
Important The preceding code assumes that the AutoEventWireup
attribute of the page has been set to true. Normally, you
won’t need to set this manually, since this is the default
setting that is inherited from the <pages> configuration
section in Machine.config. However, pages created by Visual
Studio .NET have the AutoEventWireup attribute in their @
Page directive set to false. This means that all event
handlers in these pages must be manually wired to the
events that they are to handle. You’ll learn about manually
wiring events in Chapter 10.
If you add this code to ServerControls.aspx and run it, the output when the text is
changed will look like the following figure.
Handling Page Errors
While using structured exception handling may be useful for catching exceptions that are
expected (see Chapter 4), there are times when you want to handle errors at the page
level. This is possible through a special event handler called Page_Error. The
Page_Error event is raised whenever a page encounters an unhandled exception. Within
the Page_Error event handler, you can examine the exception that occurred using the
Server.GetLastError method and take appropriate action or display a friendly message to
the user.
To show you how this works, let’s use the ServerControls.aspx page one last time. To
use code-behind with this page, follow these steps:
1. Open ServerControls.aspx in a text editor.
2. Using a text editor, create two new files, ServerControls_cb.aspx and
ServerControls_cb.vb.
3. Copy all of the text from ServerControls.aspx and paste it into
ServerControls_cb.aspx, and then close ServerControls.aspx.
4. Cut all of the code contained within the <script> block in
ServerControl_cb.aspx and paste it into ServerControls_cb.vb, and
then remove the <script> tags.
5. Add Src and Inherits attributes to the @ Page directive in
ServerControls_cb. aspx to help ASP.NET locate the code-behind
class file.
6. <%@ Page Language="vb" Src="ServerControls_cb.vb"
Inherits="ServerControls" %>
7. Save ServerControls_cb.aspx. The final page should look like the
following:
8. <%@ Page Language="vb" Src="ServerControls_cb.vb"
9. Inherits="ServerControls" %>
10. <html>
11. <head>
12. </head>
13. <body>
14. <form runat="server">
15. <asp:Label id="NameLabel" runat="server">Name:
</asp:Label>
16. <asp:TextBox id="NameTextBox"
17. onTextChanged="NameTextBox_TextChanged"
runat="server"/>
18. <input id="NameButton" type="submit" runat="server"/>
19. </form>
20. <asp:Label id="HelloLabel" runat="server"/>
21. </body>
</html>
22. Add the following code to ServerControls_cb.vb, before the text that
you pasted in step 4:
23. Imports System
24. Imports System.Web
25. Imports System.Web.UI
26. Imports System.Web.UI.WebControls
27.
28. Public Class ServerControls
29. Inherits Page
30. Public HelloLabel As Label
Public NameTextBox As TextBox
31. Add an End Class statement to ServerControls_cb.vb, after the text
that you pasted in step 4. The finished code should look like the
following:
32. Imports System
33. Imports System.Web
34. Imports System.Web.UI
35. Imports System.Web.UI.WebControls
36.
37. Public Class ServerControls
38. Inherits Page
39. Public HelloLabel As Label
40. Public NameTextBox As TextBox
41.
42. Sub Page_Load(Sender As Object, E As EventArgs)
43. If IsPostBack Then
44. HelloLabel.Text = "Hello, " & _
45. Server.HtmlEncode(NameTextBox.Text) & "!"
46. End If
47. End Sub
48.
49. Sub NameTextBox_TextChanged(Sender As Object, E As
E ventArgs)
50. HelloLabel.Text += " This text is different!"
51. End Sub
End Class
52. Save both files, and browse ServerControls_cb.aspx. The output
should be the same as in the previous figure.
In this example, you’ve separated the event handling code into a code-behind class file.
To make this work, you’ve had to take some extra steps. You added the Imports
statements to the code-behind file to allow you to use the names of classes such as
Label and Textbox without explicitly adding their namespaces. This is essentially the
same as using the @ Import directive described earlier in this chapter. One difference is
that unlike Web Forms pages, code-behind class files must explicitly import any of the
namespaces for classes to be used in the file (unless all class references use the fully
qualified name for the class).
You also added the Class statement, to define the class ServerControls, and the Inherits
statement, which specifies that you want the ServerControls class to be derived from the
Page class. Then you declared two Public variables, one for each server control that you
want to access in your event-handler code. Finally, you wrapped up the code-behind with
an End Class statement to close out the class.
In the Web Form, you associated ServerControls_cb.vb with ServerControls_cb.aspx by
adding the Src and Inherits attributes to the @ Page directive. The Src attribute tells
ASP.NET where to locate the class file for the code-behind, and the Inherits attribute
tells ASP.NET which class (or namespace and class) you want to inherit from in the
code-behind file.
While there are a few more steps involved in creating a page that uses code-behind
compared to writing all of your code in a Web Form, the resulting code is generally much
easier to maintain and update. For complicated pages, this alone can make it well
worthwhile to use code-behind.
Modify the runtime Add the @ Page directive to the page and set the
behavior of an desired attributes.
ASP.NET Web Form
To Do This
Use classes by name In a Web Form- add the @ Import directive- with the
without explicitly namespace attribute specifying the namespace to
import.
specifying their
namespace In a Visual Basic .NET code-behind file- add the
Imports statement with the desired namespace.
In a C# code-behind file- add the using statement with
the desired namespace (such as using System.Web;).
Make a user control Add the @ Register directive to the page- and set the
appropriate attributes.
or custom server
control available for
use on a Web Form
Enable output Add the @ OutputCache directive to the page- and set
the desired attributes.
caching
Create a user control Create a file with the .ascx extension containing the
desired HTML markup- server controls and code. User
controls should not contain <html>- <body>- or <form>
elements.
Optionally- add the @ Control directive to modify the
runtime behavior of the control.
Write server-side Create a <script> block with the runat='server attribute
and place the functions within it.
functions in a Web
Forms page
Handle page-level Add an event handler with the appropriate signature
events (such as Page_eventname) to the server <script>
block.
Types of Controls
As discussed briefly in the previous chapter, the built-in server controls in ASP.NET are
divided into two groups, HTML controls and Web controls, each of which is contained in
its own namespace. This section will discuss the available controls in each group and
show examples of when and how to use each type of control. It will also discuss some of
the specialized Web controls, including the Calendar and AdRotator controls, as well as
the Validation server controls.
HTML Controls
The HTML controls map 1-to-1 with the standard HTML elements. However, some HTML
elements don’t map directly to a specific HTML control. Why would you want to use an
HTML control instead of just coding HTML? You do this because you can easily
manipulate the look and functionality of the page at runtime by changing the control’s
attributes programmatically.
The HTML controls reside in the System.Web.UI.HtmlControls namespace, which is
available to Web Forms pages automatically. The HTML elements for which there is
direct mapping are shown in the following table.
This allows you to perform actions on the client before the page is
posted back to the server, if desired.
Because of their close mapping to individual HTML elements, HTML controls provide the
fastest and easiest path for moving from static HTML to the power of ASP.NET server
controls.
Note In Web Forms pages, there are three basic rules of the game
when it comes to case sensitivity.
§ Namespaces and class names are case sensitive. For
example, namespaces imported using the @ Import
directive will cause an error if the correct case is not used.
This is a common cause of errors for those just getting
started with ASP.NET.
§ Tag names and attributes are not case sensitive. This also
applies to server control properties expressed as tag
attributes.
§ Control names used in <script> blocks or in code-behind
classes may or may not be case sensitive, depending on
whether the language you’re using is case sensitive. C# is
case-sensitive, for example, so using the incorrect case for
a control name or property in C# code can lead to errors.
When you’re working with a case-sensitive language, or when you
receive errors such as “The Namespace or type <namespace> for
the Import ‘System.<namespace>’ cannot be found,” it’s usually a
good idea to check for incorrect case in control, class, or
namespace names.
Once you’ve converted the elements to HTML controls, it is fairly simple to manipulate
them in server-side code.
<html>
<head>
<script language="vb" runat="server">
Sub Page_Load(Sender As Object, E As EventArgs)
Body.Attributes("bgcolor") = "Red"
HelloFont.Attributes("size") = "7"
HelloFont.Attributes("color") = "Blue"
End Sub
</script>
</head>
<body id="Body" runat="server">
<font id="HelloFont" runat="server">Hello, World!</font>
</body>
</html>
If you save and run this code, the output should look like the following figure.
HTML controls are useful for dynamically adding and removing HTML table rows or
<select> items. In the following example, you’ll see how to dynamically add and remove
items in a drop-down list created by an HtmlSelect control:
1. Create a new text file called HtmlSelect.aspx in your favorite text
editor.
2. Add the following HTML markup to the file:
3. <html>
4. <body>
5. <form>
6. What's your favorite ice cream?<br/>
7. <select id="IceCream" size="1">
8. <option>Chocolate</option>
9. <option>Strawberry</option>
10. <option>Vanilla</option>
11. </select>
12. <input id="Enter" type="button" value="Enter">
13. <h3><span id="Fave"></span></h3>
14. </form>
15. </body>
</html>
16. Add runat=“server” attributes to the <form>, <select>, <input>, and
<span> elements to transform them into HTML controls. After this
step, the page should look like the following:
17. <html>
18. <body>
19. <form runat="server">
20. What's your favorite ice cream?<br/>
21. <select id="IceCream" size="1" runat="server">
22. <option>Chocolate</option>
23. <option>Strawberry</option>
24. <option>Vanilla</option>
25. </select>
26. <input id="Enter" type="button" value="Enter" runat="server">
27. <h3><span id="Fave" runat="server"></span></h3>
28. </form>
29. </body>
</html>
30. Add Page_Load and Enter_Click event handlers to the page to add
new items to the HtmlSelect control and to display the choice of the
user. (The following code should go between the first <html> tag
and the <body> tag that follows it.)
31. <head>
32. <script language="vb" runat="server">
33. Sub Page_Load()
34. If Not IsPostBack Then
35. IceCream.Items.Add("Pistachio")
36. IceCream.Items.Add("Rocky Road")
37. 'We don't like Strawberry
38. IceCream.Items.Remove("Strawberry")
39. End If
40. End Sub
41. Sub Enter_Click(Sender As Object, E As EventArgs)
42. Fave.InnerHtml = "Your favorite is " & IceCream.Value & "!"
43. End Sub
44. </script>
45. </head>
46. Finally add an OnServerClick attribute to the <input> tag to route the
HtmlInputButton control’s ServerClick event to your event handler.
The complete tag is shown here:
47. <input id="Enter" type="button" value="Enter"
OnServerClick="Enter_Click" runat="server">
48. Save the page, and then browse it. The output should look similar to
the following figure.
Web Controls
Web controls are truly the gem in the crown of ASP.NET. These server controls give you
an unprecedented level of productivity by providing a rich, event-driven programming
model, similar to that of Visual Basic. The Web controls reside in the
System.Web.UI.WebControls namespace, which is available to all Web Forms pages
automatically.
The Web controls run the gamut of functionality, from simple controls like the Button,
TextBox, and Label controls, to the richer ListBox and DropDownList controls, to the
even richer Calendar and AdRotator controls. There are even special controls that are
especially designed for data binding, including the DataGrid, DataList, and Repeater
controls. You’ll see examples of these types of controls in later sections.
Note Although the System.Web.UI.WebControls and
System.Web.UI.HtmlControls namespaces are available to Web
Forms pages automatically, the same is not true of code-behind
pages associated with Web Forms pages. If you want to use
controls in either of these namespaces without using the fully
qualified class name, you need to add the appropriate statement
(Imports for Visual Basic; using for C#) to your code-behind class
to import the namespace.
Because there are two distinct sets of server controls to choose from, you may wonder
how to choose the right control for a given application. In general, if you are interested
in handling client-side events or are converting HTML elements to server controls, you
should look at the HTML controls. If you’re familiar with the Visual Basic programming
model, the Web controls provide a model that maps very closely to that. The Web
controls also provide additional functionality, such as validation, and provide a strong
basis for customization.
You could also accomplish the same result by using a single-tag syntax.
<asp:label id="myLabel" text="Hi there!" runat="server"/>
When you add a control to the page declaratively, you are doing two things: giving the
control enough information to be created and initialized properly, and setting the
properties for the control. The first part is accomplished by the asp: prefix and the tag
(control) name, plus the id and runat attributes. Only the prefix/tagname and runat
attribute are required to create a server control, but the id attribute is required if you want
to refer to the control in server-side code.
Once you’ve provided this minimum information, you can set additional properties by
adding attributes to the tag declaration corresponding to the property you want to set.
For example, if you wanted to set the Visible property of a Label control to false (to set
up a message declaratively and show it by making it visible later in server-side code),
you would modify the declaration as follows:
<asp:label id="myLabel" text="Hi there!" visible="false"
runat="server"/>
Some of the more feature-filled controls, such as the DataGrid control, use a
combination of attributes in the main tag definition and subtags that declare additional
controls or set properties (such as the ItemStyle and AlternatingItemStyletags that can
be used with the DataGrid or DataList controls) to define the properties for the control.
You’ll see examples of these in this chapter.
As you can see, the code successfully creates the Label control and sets its text property
to Hi there!. There’s just one problem. As written, the code adds the control to the end of
the page’s Controls collection, which is actually after the closing <body> and <html>
tags, which ASP.NE T treats as Literal controls on the server. The code that is sent to the
browser is shown here:
<html>
<head>
</head>
<body>
</body>
</html>
<span>Hi there!</span>
While this output will still be rendered in most browsers, it’s not ideal because it’s
incorrect HTML syntax. This presents even more of a problem when you are dealing with
both declarative and programmatically created controls. Creating controls out of order
may produce unintended results. So what do you do?
One option is to call the AddAt method of the Controls collection, instead of Add. AddAt
takes two arguments: an integer representing the location in the collection where the
control should be added, and the name of the control to add.
Page.Controls.AddAt(3, myLabel)
The only problem is that in order to render the control where you want it in the page, you
have to know exactly where the control should appear in the collection. This is not
always practical.
An easier way to ensure that programmatically created controls appear where you want
them to is to use a PlaceHolder control to locate the control in the page. The
PlaceHolder control has no UI of its own. All of the content within a PlaceHolder control
is rendered based on the controls in its Controls collection. So, to make sure that the
control in the previous example is displayed within the body of the HTML document sent
to the browser, you can use the following code:
<%@ Page Language="vb" %>
<html>
<head>
<script runat="server">
Sub Page_Load()
Dim myLabel As New Label
myPH.Controls.Add(myLabel)
myLabel.Text = "Hi there!"
End Sub
</script>
</head>
<body>
<asp:placeholder id="myPH" runat="server" />
</body>
</html>
Multiple controls can be added to the same placeholder, and multiple placeholders can
be used in the same page.
Sending Mail
A common task for many Web applications is sending e-mail to customers or visitors.
Often, this e-mail is generated automatically, without the need for specific data entry. But
site operators may also want a way to send a quick e-mail through the site. Web
controls, in combination with the .NET SmtpMail and MailMessage classes, can make it
quick and easy to add this functionality to a Web site.
Note This example assumes that you have access to an SMTP server
from which to send your e-mail. Windows 2000, Windows XP
Professional, and Windows .NET Server come with an SMTP
service that is part of the services provided by IIS. The SMTP
service is relatively easy to install and configure. Details for setting
up the SMTP service can be found at
http://msdn.microsoft.com/library/en-us/dnduwon/html/d5smtp.asp
.
117. The last part of the process is adding the code to send an e-
mail using the data entered by the user. First, add an @ Import
directive to import the System.Web.Mail namespace so you can
refer to its classes without using the namespace name each time.
This directive should go directly below the @ Page directive at the
top of the page.
118. <%@ Import Namespace="System.Web.Mail" %>
119. Finally replace the Response.Write statement in the event
handler with the following code. It creates an instance of the
MailMessage class, sets the necessary properties, and then sends
the message using the Send method of the SmtpMail class.
(Because the Send method is a Static method, it is not necessary to
create an instance of SmtpMail to use the method). Once the mail
has been sent, the code then hides all of the form controls by setting
the Visible property for each control to False, and it adds a
HyperLink control to link back to the original page to send another e-
mail, if desired. Note that you need to set Mail.From to a valid e mail
address and SmtpMail.SmtpServer to the name of an available
server running SMTP (unless the local machine is running SMTP, in
which case this line may be removed).
120. 'Create MailMessage instance, set properties, and send
121. Dim Mail As New MailMessage
122. Mail.To = ToText.Text
123. Mail.CC = CCText.Text
124. Mail.BCC = BCCText.Text
125. Mail.Subject = SubjectText.Text
126. Mail.Body = BodyText.Text
127. Mail.From = <valid email address>
128. SmtpMail.SmtpServer = <local SMTP server>
129. SmtpMail.Send(Mail)
130. ‘Use the first label to display status
131. ToLabel.Text = "Mail Sent"
132. 'Hide the rest of the controls
133. ToText.Visible = False
134. CCLabel.Visible = False
135. CCText.Visible = False
136. BCCLabel.Visible = False
137. BCCText.Visible = False
138. SubjectLabel.Visible = False
139. SubjectText.Visible = False
140. BodyLabel.Visible = False
141. BodyText.Visible = False
142. Send.Visible = False
143. Reset.Visible = False
144. 'Add a Hyperlink control to allow sending another email
145. Dim Link As New HyperLink
146. Link.Text = "Click here to send another email."
147. Link.NavigateUrl = "SmtpEmail.aspx"
148. Page.Controls.Add(Link)
149. Save and refresh the page. You should now be able to send
an e-mail by filling out the fields (at a minimum, you must have an e-
mail address in the To, CC, or BCC field) and clicking the Send
button. The output should look something like the following figure,
and the link should take you back to the form.
This example shows how simple it can be to send e-mail using server controls on a Web
Form, but it does have a couple of shortcomings. One is that if you don’t enter an
address in the To, CC, or BCC field, you’ll get an error. Another is that it takes a fair
amount of code to hide all the controls once you’ve sent the e-mail. You can fix this
second shortcoming by adding another Label control outside the table, and then turning
the table into an HtmlTable control by adding the runat=“server” attribute. You can then
hide all of the controls in the table by setting the Visible property of HtmlTable to False.
Make sure you save the example file because we’ll be taking another look at it later in
this chapter. The full code for this example is shown in the following listing.
SmtpEmail.aspx
<%@ Page Language="vb" %>
<%@ Import Namespace="System.Web.Mail" %>
<html>
<head>
<script runat="server">
Sub Send_Click(Sender As Object, e As EventArgs)
'Create MailMessage instance, set properties, and send
Dim Mail As New MailMessage
Mail.To = ToText.Text
Mail.CC = CCText.Text
Mail.BCC = BCCText.Text
Mail.Subject = SubjectText.Text
Mail.Body = BodyText.Text
Mail.From = <valid email address>
SmtpMail.SmtpServer = <local SMTP server>
SmtpMail.Send(Mail)
Registration Wizard
Another common need in Web applications is providing users with an easy way to
register. Registration allows you to provide personalized services, saved shopping carts,
and other valuable services for your visitors. In order to make this as simple as possible,
you might want to use a multipage format similar to a Windows Wizard interface. This
type of interface will be immediately familiar to Windows users. The following listing
shows the code for a simple registration wizard implemented as an ASP.NET Web Form.
RegWiz.aspx
<%@ Page Language="vb" %>
<html>
<head>
<script runat="server">
Sub Page_Load()
If Not IsPostBack Then
Step1.Font.Bold = True
End If
End Sub
Sub Next_Click(Sender As Object, e As EventArgs)
Select Case Sender.Parent.ID
Case "Page1"
Page1.Visible = False
Step1.Font.Bold = False
Page2.Visible = True
Step2.Font.Bold = True
Case "Page2"
Page2.Visible = False
Step2.Font.Bold = False
Page3.Visible = True
Step3.Font.Bold = True
Revi ewFName.Text += FirstName.Text
ReviewMName.Text += MiddleName.Text
ReviewLName.Text += LastName.Text
ReviewEmail.Text += Email.Text
ReviewAddress.Text += Address.Text
ReviewCity.Text += City.Text
ReviewState.Text += State.Text
ReviewZip.Text += Zip.Text
End Select
End Sub
Sub Previous_Click(Sender As Object, e As EventArgs)
Select Case Sender.Parent.ID
Case "Page2"
Page2.Visible = False
Step2.Font.Bold = False
Page1.Visible = True
Step1.Font.Bold = True
Case "Page3"
Page3.Visible = False
Step3.Font.Bold = False
Page2.Visible = True
Step2.Font.Bold = True
End Select
End Sub
</script>
<style type="text/css">
div
{
background:silver;
width:400px;
border:2px outset;
margin:5px;
padding:5px;
}
</style>
</head>
<body>
<form runat="server">
<asp:label id="RegWiz" text="Registration Wizard"
font-bold="true" font-size="16" font-name="verdana"
runat="server"/>
<br/>
<asp:label id="Step1" text="Step 1: Enter Personal Info"
font-name="verdana" runat="server"/>
<br/>
<asp:label id="Step2" text="Step 2: Enter Address Info"
font-name="verdana" runat="server"/>
<br/>
<asp:label id="Step3" text="Step 3: Review" font-name="verdana"
runat="server"/>
<br/>
<asp:panel id="Page1" runat="server">
<table align="center">
<tr>
<td>
<asp:label id="FirstNameLabel" text="First Name:"
runat="server"/>
</td>
<td>
<asp:textbox id="FirstName" runat="server"/>
</td>
</tr>
<tr>
<td>
<asp:label id="MiddleNameLabel" text="Middle Name:"
runat="server"/>
</td>
<td>
<asp:textbox id="MiddleName" runat="server"/>
</td>
</tr>
<tr>
<td>
<asp:label id="LastNameLabel" text="Last Name:"
runat="server"/>
</td>
<td>
<asp:textbox id="LastName" runat="server"/>
</td>
</tr>
<tr>
<td>
<asp:label id="EmailLabel" text="Email:"
runat="server"/>
</td>
<td>
<asp:textbox id="Email" runat="server"/>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<asp:button id="P1Previous" Text="Previous"
enabled="false" onclick="Previous_Click"
runat="server"/>
<asp:button id="P1Next" Text="Next"
onclick="Next_Click" runat="server"/>
<input id="P1Reset" type="reset" runat="server"/>
</td>
</tr>
</table>
</asp:panel>
<asp:panel id="Page2" visible="false" runat="server">
<table align="center">
<tr>
<td>
<asp:label id="AddressLabel" text="Street Address:"
runat="server"/>
</td>
<td>
<asp:textbox id="Address" runat="server"/>
</td>
</tr>
<tr>
<td>
<asp:label id="CityLabel" text="City:"
runat="server"/>
</td>
<td>
<asp:textbox id="City" runat="server"/>
</td>
</tr>
<tr>
<td>
<asp:label id="StateLabel" text="State:"
runat="server"/>
</td>
<td>
<asp:textbox id="State" runat="server"/>
</td>
</tr>
<tr>
<td>
<asp:label id="ZipLabel" text="Zip Code:"
runat="server"/>
</td>
<td>
<asp:textbox id="Zip" runat="server"/>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<asp:button id="P2Previous" Text="Previous"
onclick="Previous_Click" runat="server"/>
<asp:button id="P2Next" Text="Next"
onclick="Next_Click" runat="server"/>
<input id="P2Reset" type="reset" runat="server"/>
</td>
</tr>
</table>
</asp:panel>
<asp:panel id="Page3" visible="false" runat="server">
<table align="center">
<tr>
<td colspan="2">
<asp:label id="ReviewFName" text="First Name: "
runat="server"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:label id="ReviewMName" text="Middle Name: "
runat="server"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:label id="ReviewLName" text="Last Name: "
runat="server"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:label id="ReviewEmail" text="Email: "
runat="server"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:label id="ReviewAddress" text="Address: "
runat="server"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:label id="ReviewCity" text="City: "
runat="server"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:label id="ReviewState" text="State: "
runat="server"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:label id="ReviewZip" text="Zip: "
runat="server"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:button id="P3Previous" Text="Previous"
onclick="Previous_Click" runat="server"/>
<asp:button id="P3Next" Text="Next" enabled="false"
onclick="Next_Click" runat="server"/>
<input id="P3Reset" type="reset" disabled="true"
runat="server"/>
</td>
</td>
</tr>
</table>
</asp:panel>
</form>
</body>
</html>
The keys to the functionality of the registration wizard are the three Panel controls and
the Next_Click and Previous_Click event handlers. The Panel controls contain Label,
TextBox, and Button Web controls (as well as an HtmlInputButton control for resetting
the form fields), which are formatted using standard HTML table elements. When the
user clicks the Next button or the Previous button (depending on which one is enabled
for that page), the event handlers hide the currently visible panel, show the next (or
previous) panel, and update the Label controls that tell the user which step in the
process they are on currently.
Since ASP.NET takes care of maintaining the state of the controls from request to
request, it is simple to display the data entered by the user on the third panel by simply
accessing the Text property of the TextBox controls on the other panels. Notice that the
caption text of the controls on the third panel is set declaratively in the tag, and then the
text that the user entered is appended to this text programmatically in the event handler
using the &= operator. The first page of the registration wizard is shown in the following
figure.
Note The <style> block that you defined takes care of formatting for the
Panel controls by defining the desired style for the <div> HTML
element. Note that since the Panel control is rendered as a <div>
on Internet Explorer and as a <table> element on Netscape and
other non–Internet Explorer browsers, you may need to find
another way to do the formatting if you want cross-browser
compatibility.
One way to accomplish this is through browser sniffing. The
Request object exposed by the Page class has a Browser property
that provides information on the browser making the current
request. By querying the Browser property (and its subproperties),
you can perform a simple if statement that links to one style sheet
if the browser is IE, and to another style sheet if it is another
browser.
Specialty Controls
In addition to the standard suite of Web controls that provide functionality much like that
of the Visual Basic GUI controls, ASP.NET also offers a set of richer specialty controls
that reside in the System.Web.UI.WebControls namespace. Currently, these include the
AdRotator, Calendar, and Xml controls. This section will describe the purposes of these
controls and show some examples.
AdRotator
The AdRotator control is used to display a random selection from a collection of banner
advertisements specified in an XML-based advertisement file. The advertisement file
contains an <Ad> node for each specified advertisement. This node contains nodes that
configure the path to the image to display for the ad, the URL to navigate to when the ad
is clicked, the text to be displayed when/if the image is not available, the percentage of
time the ad should come up in rotation, and any keyword associated with the ad.
(Keywords can be used to filter ads, allowing the use of a single advertisement file with
multiple AdRotator controls and providing each control with different content.)
Note The random selection for the AdRotator control will be identical for
any AdRotators on the page that have identical attributes. You can
take advantage of this by using the AdRotator control to display a
random selection of header/ footer information that you want to be
the same for both header and footer.
23. Looks good so far, but now you need to relate clicking on a date on
the calendar to setting the text in the text boxes. First, add the
OnSelectionChange attribute to the <asp:calendar> tag to map the
event to the event handler.
<asp:calendar onselectionchange="Set_DateText" runat="server"/>
24. Next add the following <script> block to the <head> section of the
page. The Page_Load event handler simply ensures that the Start
radio button is checked the first time the page is loaded, while the
Set_DateText event handler takes care of setting the dates.
25. <script runat="server">
26. Sub Page_Load
27. If Not Page.IsPostBack Then
28. Start.Checked =True
29. Calendar1.SelectedDate = Now()
30. StartDate.Text = Calendar1.SelectedDate
31. Calendar1.TodaysDate =Calendar1.SelectedDate
32. End If
33. End Sub
34. Sub Set_DateText(Sender As Object,e As EventArgs)
35. If Start.Checked =True Then
36. StartDsate.Text =Calendar1.SelectedDate
37. Else 'End must be checked
38. EndDate.Text = Calendar1.SelectedDate
39. End If
40. End Sub
41. </script>
42. Save and browse the page. At this point, you should be able to use
the calendar to set the value of both text boxes. Which text box is
set depends on which radio button is checked.
43. At this point the calendar is fairly plain, so let’s use some of the
available properties to liven it up a bit. Modify the <asp:calendar>
tag to match the following code. Notice that there’s a closing
</asp:calendar> tag so that you can use the child <titlestyle> tag to
set attributes of the calendar’s TitleStyle property.
44. <asp:calendar id="Calendar1"
45. onselectionchanged="Set_DateText"
46. backcolor="lightgray"
47. borderstyle="groove"
48. borderwidth="5"
49. bordercolor="blue"
50. runat="server">
51. <titlestyle backcolor="blue" forecolor="silver"
52. font-bold="true"/>
</asp:calendar>
53. Save and browse the file again. The output should look similar to
the following figure.
54. Save this file. You’ll be coming back to it later in this chapter.
Xml
The Xml control is a specialty control that lets you display XML code in Web Forms
pages. This is important because raw XML within an ASP.NET Web Form will not be
displayed predictably. The Xml control can read XML from a string, from a provided URL,
or from an object of type System.Xml.XmlDocument.
In addition to simply displaying XML from a given source, the Xml control can also apply
an XSL Transform document to the XML to perform formatting on the document. The
XSL Transform can come from a URL, or it can be provided by an object of type
System.Xml.Xsl.XslTransform.
Validation Controls
Another specialized set of controls that reside in the System.Web.UI.WebControls
namespace are the Validation controls, which perform various kinds of validation on
other controls.
You can use Validation controls to do the following:
§ Ensure that required fields are filled out
§ Ensure that data entered by the user falls within a given range
§ Ensure that data entered by the user matches a specific pattern
§ Compare the values of two controls for a given condition (equality,
greater than, etc.) or compare the value of a control to a specified value
§ Ensure that all controls on a page are valid before submitting the page
Shared Members
There are a number of important properties and methods that all Validation controls
share. These properties and methods are inherited from the BaseValidator class and
include the following:
§ ControlToValidate This property sets or retrieves a value of type
String that specifies the input control to validate.
§ Display This property sets or retrieves a value that determines how
the error message for a Validation control will be displayed. The value
must be one of the values specified by the ValidatorDisplay
enumeration. The default is Static.
§ EnableClientScript This property sets or retrieves a boolean value
that determines whether client-side validation will be performed.
Server-side validation is always performed with the Validation controls,
regardless of this setting. The default is true.
§ Enabled This property sets or retrieves a boolean value that
determines whether the control is enabled. The default is true.
§ ErrorMessage This property sets or retrieves a value of type String
that specifies the error message to be displayed if the input from the
user is not valid.
§ ForeColorThis property sets or retrieves a value of type Color that
represents the color of the error message specified in the
ErrorMessage property. The default is Color.Red.
§ IsValid This property sets or retrieves a boolean value that signifies
whether the control specified by the ControlToValidate property
passes validation.
§ Validate This method causes the control on which it is called to
perform validation, and it updates the IsValid property with the result.
The following table lists the Validation controls available in ASP.NET.
Validation Controls
Validating by Comparison
Another one of the previous examples with the potential for trouble is the Calendar
control example. As coded in the example, the Calendar control can be used to set a
start date that is later than the end date. Clearly, if you’re storing these dates or using
them to drive some programmatic logic, this is not something you want to allow.
Fortunately, the CompareValidator control can help prevent this problem, as illustrated
by the following example:
1. Open Calendar.aspx.
2. Add a CompareValidator control below the EndDate TextBox control:
3. <br/>
4. <asp:comparevalidator
5. id="DateValidator"
6. controltovalidate="StartDate"
7. controltocompare="EndDate"
8. type="Date"
9. operator="lessthan"
10. display="dynamic"
11. errormessage="Start Date must be before End Date!"
srunat="server"/>
12. Add a line of code to the Set_DateText event handler to cause the
Validation control to validate each time the event handler is fired.
The Page.Validate call should go between the End If and End Sub
statements.
13. Page.Validate
14. Save and browse the file. Set the end date to a date earlier than the
start date. The validation error message will appear, as shown in the
following figure. Additionally, the IsValid property of the page will be
set to False. You can test this property before saving the date
values to ensure that they are valid.
Validation controls can also improve the security of your application. For example, you
can use the RegularExpressionValidator control to ensure that passwords entered by
users (such as when establishing account information) meet minimum length and
complexity requirements. This can make it more difficult for the bad guys to crack your
application.
Data-Bound Controls
One of the most significant advances in ASP.NET is in the area of data binding. The
System.Web.UI.WebControls namespace contains a set of data-bound controls that offer
substantial functionality and flexibility, without the necessity of tying front-end UI logic
into back-end database logic, as was the case with the data-bound Design-Time
Controls available in classic ASP through the Visual InterDev 6.0 development
environment. The data-bound controls also allow you to provide data binding regardless
of the browser that your clients are using. This is because the controls run on the server
and return plain HTML to the client, unlike some of the data-binding techniques that were
possible with Internet Explorer versions 4.0 and later.
Another major improvement is that the data-bound controls can use a variety of sources,
not just data from a database. Data-bound controls in ASP.NET can be bound to data
from an array, an ADO.NET DataSet, a DataView, or any data source that implements
the ICollection or IList interfaces. The data-bound controls include the DataGrid,
DataList, and Repeater controls. Chapter 11 will discuss these controls in detail.
Other Controls
In addition to the HTML controls and Web controls that come with the .NET Framework,
there are also some additional controls that are currently available as separate
downloads. These are the ASP.NET Mobile controls and the Internet Explorer Web
controls.
Create a control that can handle both client- Use an HTML control by
and server-side events adding an id attribute and the
runat=“server” attribute to
the desired HTML element
(note that the element must
support the desired client-
side event) and add
attributes to map the events
to the appropriate client- and
server-side event handlers.
Understanding ADO.NET
In classic ASP, the most common way to access data was through ADO. Developers
used ADO Connection objects to connect to a database, and then used ADO Command
and Recordset objects to retrieve, manipulate, and update data. When designing
applications, particularly those with high scalability requirements or those whose back-
end datasource might change at some point, developers needed to be careful not to tie
in their front-end presentation code with their back-end database. Otherwise, they’d end
up having to rewrite everything if there was a change in the back-end database.
The ADO.NET class architecture is factored somewhat differently from classic ADO.
ADO.NET classes are separated into two major categories: datasourcespecific and non-
datasource-specific.
The following figure shows the major classes of the SQL Server .NET Data Provider and
how they relate to one another.
Understanding Datasets
The main class not specific to a datasource is the DataSet class. This is essentially an
in- memory representation of one or more tables of data. This data may be read from an
XML file or stream, or may be the result of a query against a datasource.
One important thing about the DataSet class is that it doesn’t know anything about the
datasource from which it receives its data, other than what that data is and sometimes
the types of the data columns. (See the section on typed datasets later in this chapter.)
The following figure shows the major classes associated with the DataSet class and how
they relate to one another. Note that the Rows , Columns, and Constraints objects are
properties of the DataTable class.
The fact that a dataset knows nothing about the source of its data means that it’s
abstracted from the back-end datasource. This is important because it means that if you
pass a dataset from a component used for data retrieval to your Web Forms page to be
used for data-binding, your Web Forms page neither knows nor cares where the data
comes from, as long as the dataset structure remains the same. The back-end database
can be changed without necessitating any changes in the Web Forms page. This makes
it much easier to maintain a Web application.
Datasets contain a collection of tables, each of which contains a collection of rows, a
collection of columns, and a collection of constraints. You can use these collections to
get information on the objects contained within them, as well as to access and update
individual data values. The dataset can also contain a collection of relationships between
the tables it contains, allowing hierarchical data to be represented. You’ll learn about the
DataSet class and related classes in detail later in this chapter.
XML in ADO.NET
Unlike classic ADO, in which XML support was added after the initial object model had
been created, XML support has been designed into the ADO.NET model from the
ground up. Classes such as SqlCommand, OleDbCommand, and DataSet have built-in
support for reading and writing XML data. Datasets in particular can be built from or
saved as XML, making them ideal for transporting data between tiers in a multi-tier
application, or for temporarily storing data to disk for later retrieval.
SqlConnection
For applications that use SQL Server as the back-end database and are unlikely to
change to a different database in the future, the SqlConnection class is the appropriate
choice. This class is optimized for the best performance when connecting to a SQL
Server database. The SqlConnection class will also provide superior performance when
accessing data in an MSDE database, since MSDE uses the same database engine and
protocols as SQL Server.
Creating a SqlConnection class is simple and can be done in one line of code.
Dim mySqlConn as New SqlConnection(ConnectionString)
This creates a connection called mySqlConn to the SQL Server specified by the
ConnectionString passed to the constructor of the SqlConnection class. Opening the
connection is as simple as calling the following:
mySqlConn.Open
Key Description
Application Name The name of
the
application
from which
the
connection
is being
made.
AttachDBFilename The file
name and
full path to
the primary
file of an
attachable
database.
This key
requires the
Database
key to
specify the
database
name.
Connect Timeout The number
or of seconds
before an
Connection Timeout
attempted
connection
is aborted
and an error
is raised.
The default
is 15.
Connection Lifetime The time, in
seconds,
that a
pooled
connection
should
remain
alive. When
a
connection
is returned
to the
connection
pool, it is
destroyed if
the current
time is more
than this
many
seconds
past its
SqlConnection Connection String Keys
Key Description
creation
time. The
default is 0,
meaning
that the
connection
will not be
destroyed.
Connection Reset A Boolean
value that
determines
whether the
connection
state is
reset when
a
connection
is retrieved
from the
pool. If set
to false, the
connection
state will not
be reset,
which saves
a trip to the
server. But
the
programmer
must then
manually
take any
steps
necessary
to ensure
that the
connection
state is
appropriate
for the
application’s
use. The
default is
true.
Current Language The SQL
Server
language
name.
Data Source The server
or name or
Server network
or address of
Address the SQL
Server
or instance to
SqlConnection Connection String Keys
Key Description
Addr connect to.
or
Network Address
Enlist Determines
whether the
connection
is
automaticall
y enlisted in
the creator’s
current
transaction
context. The
default is
true.
Initial Catalog The name of
or the
Database database to
connect to.
Integrated Security Determines
or whether the
connection
Trusted Connection
uses the
Windows
authenticati
on
credentials
of the caller
to
authenticate
against SQL
Server.
Setting this
to true
alleviates
the need for
authenticati
ng a user ID
and
password,
which
means that
you don’t
need to
worry about
storing
these
values. The
default is
false.
Max Pool Size Determines
the
maximum
number of
SqlConnection Connection String Keys
Key Description
connections
to be
pooled. The
default is
100.
Min Pool Size Determines
the
minimum
number of
connections
that should
be in the
pool. The
default is 0.
Network Library Specifies
or the network
Net library to
use when
connecting
to the
specified
SQL Server.
The default
is
dbmssocn,
which
specifies the
TCP/IP
sockets
library.
Other valid
values are
dbnmpntw
Named
pipes
dbmsrpcn
Multiprotoco
l
dbmsadsn
Apple Talk
dbmsgnet
VIA
dbmsipcn
Shared
memory
dbmsspxn
IPX/SPX
The server
must have
the
appropriate
DLL for the
specified
network
library.
SqlConnection Connection String Keys
Key Description
Packet Size Specifies
the number
of bytes per
network
packet to
use in
communicati
ng with SQL
Server. The
default is
8192.
Password Specifies
or the
Pwd password to
use to log
into the SQL
Server
database.
Persist Security Info If set to
false, this
key
prevents
sensitive
information,
such as
passwords,
from being
returned as
a part of the
connection if
the
connection
is open. The
default is
false.
Pooling Determines
whether
pooling is
enabled for
this
connection.
When true,
a requested
connection
will be
pulled from
the
appropriate
pool. If no
connections
are
available
from the
pool, the
requested
SqlConnection Connection String Keys
Key Description
connection
is created
and then
returned to
the pool
when
closed. The
default is
true.
User ID The name of
the SQL
Server user
account with
which to log
into the
server.
Workstation ID The name of
the machine
connecting
to SQL
Server. The
default is
the local
machine
name.
OleDbConnection
For applications that need to be able to query databases other than SQL Server, such as
Access or Oracle, the OleDbConnection class is the appropriate choice. This class uses
a standard OLE DB Provider string to connect to any OLE DB datasource through the
OLE DB provider for that datasource.
Creating an OleDbConnection is simple and can be done in one line of code.
Dim myOleDbConn as New OleDbConnection(ConnectionString)
This creates a connection called myOleDbConn to the database specified by the
ConnectionString passed to the constructor of the OleDbConnection class. The
ConnectionString argument is a standard OLE DB provider string. The following, from
the MSDN .NET Framework Class Library Reference, shows some examples of OLE DB
Provider strings for connecting to Oracle, MS Access, and SQL Server datasources,
respectively:
Provider=MSDAORA; Data Source=ORACLE8i7; User ID=OLEDB;
Password=OLEDB;
Provider=Microsoft.Jet.OLEDB.4.0;
Data Source=c:\bin\LocalAccess40.mdb;
Provider=SQLOLEDB;Data Source=MySQLServer;Integrated Security=SSPI;
The Provider= clause is required. You can also use other clauses supported by the
particular OLE DB provider to set other properties for the provider. For example, the
Initial Catalog= clause is used by the SQL Server OLE DB Provider to indicate the
database against which queries will be run.
Later in this chapter, you’ll see examples of using connections to perform data access.
In this section you’ll learn how to use the latter technique to enable the use of trusted
connections without the need for Windows authentication.
Important Setting up the ASPNET account in SQL Server will allow any
ASP.NET application on the Web server to access the
database with whatever privileges have been granted to the
ASPNET account. For this reason, this technique should be
used only on development systems or production systems on
which you are in control of all ASP.NET applications. This
technique should generally not be considered for shared
server applications.
To set up the ASPNET account to access the Pubs SQL Server sample database using
SQL Enterprise Manager, follow these steps.
1. Open SQL Enterprise Manager by clicking Start, Programs, Microsoft
SQL Server, Enterprise Manager.
2. Expand the tree and locate the desired SQL Server or MSDE instance.
Expand this instance and locate the Security node.
3. Expand the Security node, right -click the Logins node, and then select
New Login.
4. On the General tab, select the desired domain (or local machine
name) from the Domain drop-down list, then click the elipsis (…)
button next to the Name textbox.
5. Locate and select the account named ASPNET (aspnet_wp account),
click Add, and then click OK.
6. Also on the General tab, change the Database default to pubs. Click
the Database Access tab.
7. On the Database Access tab, scroll to the pubs database, check the
Permit checkbox, and then add the db_datareader and db_datawriter
roles to the ASPNET login account by checking the checkboxes for
these roles.
8. Click OK. You can now use a trusted connection to read from and
write to the pubs sample database.
To set up the ASPNET account to access the Pubs sample database in the NetSDK
MSDE database using the oSql command-line tool, follow these steps.
1. Start the oSql command-line utility by entering the following at a
command prompt. (If using a SQL Server database other than a local
version of the NetSDK MSDE instance, enter that server or instance
name as the -S parameter.)
oSql -S(local)\NetSDK -Usa
2. When prompted, enter the sa password for the instance indicated by
the -S parameter. You should see a 1> prompt.
3. Call the sp_grantlogin system stored procedure to grant login access
to the ASPNET account. The syntax should look like the following, with
<domain> replaced by your domain or local machine name. The go
command entered at the 2> prompt tells oSql to execute the stored
procedure.
4. 1> sp_grantlogin '<domain>\ASPNET'
2> go
5. Call the sp_defaultdb system stored procedure to change the default
database for the ASPNET account to pubs. The syntax should look
like the following, with <domain> replaced by your domain or local
machine name.
6. 1> sp_defaultdb '<domain>\ASPNET', 'pubs'
2> go
7. Call the sp_adduser system stored procedure to add the ASPNET
login account to the pubs database, passing the db_datareader
argument to add the account to the db_datareader role. The syntax
should look like the following, with <domain> replaced by your domain
or local machine name.
8. 1> sp_adduser '<domain>\ASPNET', 'ASPNET', 'db_datareader'
9. 2> go
10. If desired, call the sp_addrolemember system stored procedure to add
the ASPNET account to the db_datawriter role. In this step, ASPNET
is the username added to the pubs database in the previous step. The
syntax should look like the following, with <domain> replaced by your
domain or local machine name.
11. 1> sp_addrolemember 'db_datawriter', 'ASPNET'
2> go </code>
12. Type exit and press the Enter key to exit the oSql utility.
Once you’ve set up the ASPNET account as described above, you should be able to
access the desired database using a trusted connection, as shown in the connection
strings used in the samples later in this chapter.
SqlCommand
The SqlCommand class is the appropriate class to use when you want to run commands
against a SQL Server. Here are the steps to follow when using a SqlCommand object.
Each step outlines one or more ways to initialize or use the SqlCommand class.
1. Create the SqlCommand object. Note that the constructor of the
SqlCommand is overloaded- so you can save steps by passing
arguments (such as the query for the Command and/or the
SqlConnection object to use for the command) to its constructor-
rather than setting the properties after the object is created.
2. ‘Default constructor
3. Dim mySqlCmd As New SqlCommand()
4.
5. ‘Passing in a query
6. Dim SQL As String = "SELECT au_id FROM authors"
7. Dim mySqlCmd2 as New SqlCommand(SQL)
8.
9. ‘Passing in a query and a connection
10. Dim mySqlConn As New _
11. SqlConnection("datasource=localhost\NetSDK;database=pubs;int
egratedsecurity=true")
12. Dim SQL As String = "SELECT au_id FROM authors"
13. Dim mySqlCmd3 as New SqlCommand(SQL- mySqlConn)
14. ‘Passing in a query- a connection- and a transaction
15. Dim mySqlConn As New _
16. SqlConnection("datasource=localhost\NetSDK;database=pubs;int
egratedsecurity=true")
17. Dim mySqlTrans As SqlTransaction =
mySqlConn.BeginTransaction()
18. Dim SQL As String = "SELECT au_id FROM authors"
19. Dim mySqlCmd4 as New SqlCommand(SQL- mySqlConn-
mySqlTrans)
20. If you haven't set them in the constructor- set the CommandText
property to the desired SQL query or stored procedure name and the
Connection property to an open SqlConnection object.
21. mySqlCommand.CommandText = "SELECT au_id FROM
authors"
22. ‘Assumes that mySqlConn has already been created and opened
mySqlCommand.Connection = mySqlConn
23. Call one of the following four methods that execute the command:
24. ‘Use ExecuteNonQuery to execute an INSERT- UPDATE
or‘DELETE query where that query type has been set using
the‘CommandText property
25. mySqlCmd.ExecuteNonQuery()
26.
27. ‘Use ExecuteReader to execute a SELECT command and
28. ‘return a datareader
29. Dim mySqlReader As SqlDataReader =
mySqlCmd.ExecuteReader
30. ‘Use ExecuteScalar to execute a command and return the value
of
31. ‘the first column of the first row. Any additional results
32. ‘are ignored.
33. Dim Result As Object
34. Result = mySqlCmd.ExecuteScalar
35.
36. ‘Use ExecuteXmlReader to execute a SELECT command and
37. ‘fill a DataSet using the returned XmlReader
38. Dim SQL As String = "SELECT * FROM authors FOR XML
AUTO- XMLDATA"
39. Dim mySqlCmd as New SqlCommand(SQL- mySqlConn)
40. Dim myDS As New DataSet()
MyDS.ReadXml(mySqlCmd.ExecuteXmlReader()- XmlReadMode.Fragment)
41. Make sure to close the connection when you're finished with it.
The following listing shows the code necessary to retrieve the contents of the Authors
table of the Pubs sample SQL Server database as XML- and to display that data in a
Web Forms page.
Note The data access samples in this book are written to run against
the MSDE sample database installed by the .NET Framework
SDK. If you want to run the data access samples against a SQL
Server database other than the NetSDK MSDE database- or if you
are unable to use a trusted connection to SQL Server or MSDE-
you must modify the connection string in the examples to match
the appropriate server name and logon credentials.
ExecuteXmlReader.aspx
<%@ Page Language="vb" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Xml" %>
<html>
<script runat="server">
Sub Page_Load(sender As Object- e As EventArgs)
Dim ConnStr As String
ConnStr = "data source=(local)\NetSDK;"
ConnStr &= "database=pubs;integrated security=true"
Dim mySqlConn As New SqlConnection(ConnStr)
mySqlConn.Open
Dim SQL As String = "SELECT * FROM authors FOR XML AUTO- XMLDATA"
Dim mySqlCmd as New SqlCommand(SQL- mySqlConn)
Dim myDS As New DataSet()
myDS.ReadXml(mySqlCmd.ExecuteXmlReader()- XmlReadMode.Fragment)
XmlDisplay.DocumentContent = MyDS.GetXml
End Sub
</script>
<body>
<asp:xml id="XmlDisplay" transformsource="authors.xsl"
runat="server"></asp:xml>
</body>
</html>
This listing creates and opens a SqlConnection to the Pubs database- creates a SQL
query string to retrieve the contents of the Authors table as XML- and creates a new
SqlCommand- passing in the SQL query and the SqlConnection. Then it creates a
dataset and uses the ExecuteXmlReader method of the SqlCommand to pass an
XmlReader to the dataset's ReadXml method- which allows the dataset to populate itself
from the XmlReader. Finally the code sets the DocumentContent property of the
declared Xml server control to the result of the GetXml method of the dataset. The Xml
control uses the XSL Transformation document authors.xsl to format the Xml content
displayed by the Xml control. The following listing shows the content of authors.xsl.
Authors.xsl
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template match="/">
<style>
.header{font -weight:bold;color:white;background-color:black;}
.value{font-family:arial;font-size:.7em;background-color:silver}
</style>
<table border="1" cellspacing="0" cellpadding="1"
bordercolor="black">
<tr class="header">
<th>Author ID</th>
<th>Last Name</th>
<th>First Name</th>
<th>Phone</th>
<th>Address</th>
<th>City</th>
<th>State</th>
<th>Zip</th>
<th>Contract</th>
</tr>
<xsl:for-each select='Schema1/authors'>
<tr>
<td nowrap="true" class="value">
<b>
<xsl:value-of select='@au_id' />
</b>
</td>
<td nowrap="true" class="value">
<xsl:value-of select='@au_lname' />
</td>
<td nowrap="true" class="value">
<xsl:value-of select='@au_fname' />
</td>
<td nowrap="true" class="value">
<xsl:value-of select='@phone' />
</td>
<td nowrap="true" class="value">
<xsl:value-of select='@address' />
</td>
<td nowrap="true" class="value">
<xsl:value-of select='@city' />
</td>
<td nowrap="true" class="value">
<xsl:value-of select='@state' />
</td>
<td nowrap="true" class="value">
<xsl:value-of select='@zip' />
</td>
<td nowrap="true" class="value">
<xsl:value-of select='@contract' />
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
OleDbCommand
For most purposes- using the OleDbCommand class is effectively the same as using the
SqlCommand class. Instead of connecting with the SqlConnection class- you can just
use the OleDbConnection class. One significant difference- however- is that the
OleDbCommand class does not have an ExecuteXmlReader method.
Here's how to use an OleDbCommand to execute an aggregate query against the
Northwind Microsoft Access sample database and return a result (this example assumes
that you have the Northwind.mdb database installed locally):
1. Create and open an OleDbCommand with the appropriate connection
string for connecting to the Northwind database- where <filepath> is
the path to Northwind.mdb on your machine.
2. Dim ConnStr As String
3. ConnStr = "Provider=Microsoft.Jet.OLEDB.4.0;"
4. ConnStr &= "Data Source=<filepath>\northwind.mdb;"
5. Dim myOleDbConn As New OleDbConnection(ConnStr)
myOleDbConn.Open
6. Create a variable to contain the SQL query (note that the query can
also be passed as a literal string to the constructor of the
OleDbCommand class).
Dim SQL As String = "SELECT Count(*) FROM products"
7. Create an OleDbCommand object- passing the SQL query and the
connection object to the constructor.
Dim myOleDbCmd as New OleDbCommand(SQL- myOleDbConn)
8. Create a variable to receive the return value from the command. This
variable is declared as type Object because that is the return type of
the ExecuteScalar method of the OleDbCommand object.
Dim Result As Object
9. Finally call the ExecuteScalar method of the OleDbCommand object-
and use the returned value. Note that the value must be cast to the
correct type before using it because the returned type is Object. This is
especially important if you need to use methods of a particular type
that are not implemented by Object.
10. Result = myOleDbCmd.ExecuteScalar()
Value.Text &= CType(Result- String)
The following listing shows how you would use this example to display the returned
result in a Web Forms page.
ExecuteScalar.aspx
<%@ Page Language="vb" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<html>
<script runat="server">
Sub Page_Load(sender As Object- e As EventArgs)
Dim ConnStr As String
ConnStr = "Provider=Microsoft.Jet.OLEDB.4.0;"
ConnStr &= "Data Source=<filepath>\northwind.mdb;"
Dim myOleDbConn As New OleDbConnection(ConnStr)
myOleDbConn.Open
Dim SQL As String = "SELECT Count(*) FROM products"
Dim myOleDbCmd as New OleDbCommand(SQL- myOleDbConn)
Dim Result As Object
Result = myOleDbCmd.ExecuteScalar()
Value.Text &= CType(Result- String)
End Sub
</script>
<body>
<form runat=server>
<asp:label id="Value" runat="server">The count is
: </asp:label>
</form>
</body>
</html>
ExecuteReader_SP.aspx
<%@ Page Language="vb" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<script runat="server">
Sub Page_Load(sender As Object- e As EventArgs)
Dim ConnStr As String
ConnStr = "Data Source=(local)\NetSDK;"
ConnStr &= "database=pubs;integrated security=true"
Dim mySqlConn As New SqlConnection(ConnStr)
mySqlConn.Open
Dim SQL As String = "byroyalty"
Dim mySqlCmd as New SqlCommand(SQL- mySqlConn)
mySqlCmd.CommandType = CommandType.StoredProcedure
Dim mySqlParam As New SqlParameter("@percentage"- SqlDbType.Int)
mySqlParam.Value = 40
mySqlCmd.Parameters.Add(mySqlParam)
Dim Reader As SqlDataReader
Reader = mySqlCmd.ExecuteReader()
MyGrid.DataSource = Reader
MyGrid.DataBind()
Reader.Close()
End Sub
</script>
<body>
<form runat=server>
<asp:datagrid id="MyGrid" runat="server"></asp:datagrid>
</form>
</body>
</html>
The previous listing creates and opens a connection to the Pubs database- creates a
SqlCommand and sets its CommandType to CommandType.StoredProcedure- and then
creates a SqlParameter- passing the parameter name and datatype to the parameter's
constructor. The code then sets the value of the parameter- adds it to the Parameters
collection of the SqlCommand- and executes the command. The resulting
SqlDataReader is then bound to a DataGrid control- which displays the results.
Using Datasets
Datasets are one of the two main ways of working with data in ADO.NET. (The other one
is datareaders, which you’ve been using and will learn about in more detail later in this
chapter.) Datasets provide a database-independent in-memory representation of data.
This data can be used for display or to update a back-end database through the
appropriate DataAdapter. A dataset can also read data from and write data to an XML
file or a Stream object.
Datasets can be constructed and manipulated programmatically by adding, modifying, or
deleting DataTables, DataColumns, and DataRows in the dataset. You can also use
such datasets to update a back-end database (assuming that the schema of the tables
you’ve constructed is compatible) by calling the Update method of the dataset.
You can work with typed datasets to make your code easier to read and your ADO.NET
code less error-prone. Also, you can use a DataView to filter the contents of a dataset for
use in display (such as for data-binding) or calculations.
Using DataAdapters
DataAdapters allow you to get data from a back-end database into a dataset, and to
update a back-end database from a dataset. There are two DataAdapters that are
installed with the .NET Framework, the SqlDataAdapter and the OleDbDataAdapter.
Each of these DataAdapters uses the appropriate Connection class and Command
classes to either retrieve or update data.
The Fill method of the SqlDataAdapter and OleDbAdapter classes can be used to
populate a dataset with results from a back-end database, using the Command object
specified by the SelectCommand property of the DataAdapter. The following code
creates and opens a connection to the database specified by the ConnStr argument.
Then it creates a new SqlDataAdapter and sets its SelectCommand property to a newly
created SqlCommand object that uses the query specified by the SQL argument. Finally
the code creates a new dataset, populates it using the Fill method of the
SqlDataAdapter, and closes the connection.
Dim mySqlConn As New SqlConnection(ConnStr)
mySqlConn.Open()
Dim mySqlAdapter As New SqlDataAdapter()
mySqlAdapter.SelectCommand = New SqlCommand(SQL, mySqlConn)
Dim myDS As New DataSet
mySqlAdapter.Fill(myDS)
mySqlConn.Close()
At this point, the data in the dataset can be updated or deleted, new rows can be added,
or the entire dataset can be passed to another component because it no longer has a
connection to the back-end database.
AddTitle.aspx
<%@ Page Language="vb" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<head>
<script language="VB" runat="server">
Sub Page_Load
Dim myDS As New DataSet()
Dim TitleTable As DataTable
Dim TitleRow As DataRow
Dim ConnStr As String
ConnStr = "server=(local)\ NetSDK;database=pubs;"
ConnStr &= "Trusted_Connection=yes"
Dim SQLSelect As String
SQLSelect = "SELECT * FROM Titles"
Dim mySqlConn As New SqlConnection(ConnStr)
Dim mySqlDA As New SqlDataAdapter(SQLSelect, ConnStr)
Dim mySqlCB As New SqlCommandBuilder(mySqlDA)
myDS.ReadXmlSchema(Server.MapPath("AddTitle.xsd"))
If IsPostBack Then
TitleTable = myDS.Tables(0)
TitleRow = TitleTable.NewRow()
TitleRow("title_id") = title_id.Text
TitleRow("title") = title.Text
TitleRow("type") = type.Text
TitleRow("pub_id") = pub_id.Text
TitleRow("price") = CDbl(price.Text)
TitleRow("advance") = CDbl(advance.Text)
TitleRow("royalty") = CInt(royalty.Text)
TitleRow("ytd_sales") = CInt(ytd_sales.Text)
TitleRow("notes") = notes.Text
TitleRow("pubdate") = CDate(pubdate.Text)
TitleTable.Rows.Add(TitleRow)
mySqlDA.Update(myDS)
titlegrid.DataSource = myDS.Tables(0).DefaultView
titlegrid.Databind()
End If
End Sub
</script>
<body>
<form runat="server">
<h3>Inserting a Title</h3>
<table width="300">
<tr>
<td colspan="2" bgcolor="silver">Add a New Title:</td>
</tr>
<tr>
<td nowrap>Title ID: </td>
<td>
<asp:textbox id="title_id" text="XX0000" runat="server"/>
</td>
</tr>
<tr>
<td nowrap>Title: </td>
<td>
<asp:textbox id="title" text="The Tao of ASP.NET"
runat="server"/>
</td>
</tr>
<tr nowrap>
<td>Type: </td>
<td>
<asp:textbox id="type" text="popular_comp"
runat="server"/>
</td>
</tr>
<tr>
<td>Publisher ID: </td>
<td>
<asp:textbox id="pub_id" text="1389" runat="server"/>
</td>
</tr>
<tr>
<td>Price: </td>
<td>
<asp:textbox id="price" text="39.99" runat="server"/>
</td>
</tr>
<tr>
<td>Advance: </td>
<td>
<asp:textbox id="advance" text="2000" runat="server"/>
</td>
</tr>
<tr>
<td>Royalty: </td>
<td>
<asp:textbox id="royalty" text="5" runat="server"/>
</td>
</tr>
<tr>
<td nowrap>Year-to-Date Sales: </td>
<td>
<asp:textbox id="ytd_sales" text="0" runat="server"/>
</td>
</tr>
<tr>
<td>Notes: </td>
<td>
<asp:textbox id="notes" textmode="multiline"
text="Philosophy and Code…a perfect mix."
rows="3" columns="30" runat="server"/>
</td>
</tr>
<tr>
<td nowrap>Publication Date: </td>
<td>
<asp:textbox id="pubdate" text="12/01/01"
runat="server"/>
</td>
</tr>
<tr>
<td></td>
<td style="padding-top:15">
<asp:button text="Add Title" runat="server"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:datagrid id="titlegrid" runat="server"/>
</td>
</tr>
</table>
</form>
</body>
</html>
Setting Up Relationships
Since datasets can contain multiple tables of data, it only makes sense that you might
want the dataset to contain information about the relationships between these tables.
This would allow you to treat them as true relational data, as well as to maintain any
hierarchy and foreign key relationships inherent in the data. The DataSet class fulfills this
need with its Relations collection, which contains a collection of DataRelation objects,
each of which describes a parent/child relationship between two matching columns in
different tables in the dataset.
DataRelation objects can be added to a dataset programmatically, or can be inferred
from a supplied XSD schema.
Accessing Values
Values in a dataset are accessed by referring to the DataTables and DataRows
containing the data. The DataTables are available via the Tables collection of the
dataset, and you can reference a particular table either by index (zero-based) or by table
name (key) as follows:
Dim myTable As DataTable = myDS.Tables(0)
Dim myTable As DataTable = myDS.Tables("tablename")
You can also iterate over the DataTableCollection (returned by the Tables property of the
dataset), DataRowCollection (returned by the Rows property of a DataTable), or the
DataColumnCollection (returned by the Columns property of a DataTable). The code in
the following listing iterates over all of the tables in a dataset and all of the rows in each
table. Then it displays the value of each column in the row, using an ASP.NET Literal
control as a placeholder for the text.
DisplayDataSetItems.aspx
<%@ Page Language="vb" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<head>
<script language="VB" runat="server">
Sub Page_Load
Dim myDS As New DataSet()
Dim ConnStr As String
ConnStr = "server=(local)\ NetSDK;database=pubs;"
ConnStr &= "Trusted_Connection=yes"
Dim SQLSelect As String
SQLSelect = "SELECT * FROM Titles "
SQLSelect &= "SELECT * FROM Publishers"
Dim mySqlConn As New SqlConnection(ConnStr)
Dim mySqlDA As New SqlDataAdapter(SQLSelect, ConnStr)
mySqlDA.Fill(myDS)
But what if you edit several values, and then you realize that you want to roll back one or
more of the changes? Fortunately, the dataset provides robust support for accepting and
rejecting changes in the its data by maintaining multiple versions of the data for each
row. Each row contains the following versions.
§ Original This contains the data originally loaded into the row.
§ Default This contains the default values for the row (specified by the
DefaultValue property of the row’s columns). If no defaults have been
specified, this contains the same data as Original.
§ Current This contains updated data for any columns that have been
updated, and the same data as Original for columns that have not been
updated.
§ Proposed After calling BeginEdit on a row and before calling the EndEdit
or CancelEdit methods of the DataRow, the Proposed version contains
changes to data that have not yet been applied to the Current version. If
CancelEdit is called, the Proposed version is deleted. If EndEdit is called,
the changes in the Proposed version are applied to the Current version.
You can determine whether or not changes have been made to an item in a row by
comparing the item’s Original or Default version with the Current version.
If CurrentRow(1, DataRowVersion.Current) Is _
CurrentRow(1, DataRowversion.Proposed) Then
‘Take appropriate action
End If
Typed Datasets
One of the coolest new features of ADO.NET is the ability to create strongly typed
datasets. Typed datasets are special classes, generated by the xsd.exe command-line
utility, that inherit from the DataSet class. They use an XSD schema to create additional
public properties and methods that allow you to access the columns of the dataset’s
tables directly by name, rather than having to use either an index or a late-bound key.
This can improve runtime performance and reduce the likelihood of errors in coding
against the dataset. Creating a typed dataset for the AddTitles example that was shown
earlier requires just a few steps. The following example assumes that you’ve already
created an XSD schema for the table or tables in the dataset, which you can do with the
WriteXmlSchema method of a dataset that’s been loaded with data from the table or
tables:
1. Open a command prompt at the location of the XSD schema file.
2. Create the class for the typed dataset, using the xsd.exe utility. The /d
option specifies that we want to create a dataset, the /l option sets the
language as VB, and the /n option specifies that the class should use
the namespace AddTitle. AddTitle.xsd is the name of your XSD
schema file, which is shown in the AddTitle.xsd listing.
xsd.exe /d /l:vb AddTitle.xsd /n:AddTitle
3. Compile the class, using the vbc.exe command-line compiler. The /t
option specifies that you want to compile as a library component
(DLL), the /r options specify assemblies that you need to reference,
and the /out option directs the compiler to save the compiled assembly
in the bin subdirectory of the current directory.
4. vbc.exe /t:library AddTitle.vb /r:System.dll
5. /r:System.Data.Dll /r:System.Xml.Dll
6. /out:bin/AddTitle.dll
AddTitle.xsd
<?xml version="1.0" standalone="yes"?>
<xsd:schema id="TitleDataSet" targetNamespace="http:// www.aspnetb.com.ns"
xmlns="" xmlns:xsd="http://www.w3.org/2001/ XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="TitleDataSet" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="Titles">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="title_id" type="xsd:string" minOccurs="0" />
<xsd:element name="title" type="xsd:string" minOccurs="0" />
<xsd:element name="type" type="xsd:string" minOccurs="0" />
<xsd:element name="pub_id" type="xsd:string" minOccurs="0" />
<xsd:element name="price" type="xsd:decimal" minOccurs="0" />
<xsd:element name="advance" type="xsd:decimal" minOccurs="0" />
<xsd:element name="royalty" type="xsd:int" minOccurs="0" />
<xsd:element name="ytd_sales" type="xsd:int" minOccurs="0" />
<xsd:element name="notes" type="xsd:string" minOccurs="0" />
<xsd:element name="pubdate" type="xsd:dateTime" minOccurs="0" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
The following listing shows how you would write the AddTitle.aspx example to work with
the typed dataset.
AddTitle_TypedDS.aspx
<%@ Page Language="vb" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="AddTitle" %>
<html>
<head>
<script language="VB" runat="server">
Sub Page_Load
Dim myTDS As New TitleDataSet()
Dim myTitleTable As TitleDataSet.TitlesDataTable
Dim myTitleRow As TitleDataSet.TitlesRow
Dim ConnStr As String
ConnStr = "server=(local)\ NetSDK;database=pubs;"
ConnStr &= "Trusted_Connection=yes"
Dim SQLSelect As String
SQLSelect = "SELECT * FROM Titles"
Dim mySqlConn As New SqlConnection(ConnStr)
Dim mySqlDA As New SqlDataAdapter(SQLSelect, ConnStr)
Dim mySqlCB As New SqlCommandBuilder(mySqlDA)
mySqlConn.Open()
mySqlDA.Fill(myTDS, "Titles")
mySqlConn.Close()
If IsPostBack Then
myTitleTable = myTDS.Titles
myTitleRow = myTitleTable.NewTitlesRow()
myTitleRow.title_id = title_id.Text
myTitleRow.title = title.Text
myTitleRow.type = type.Text
myTitleRow.pub_id = pub_id.Text
myTitleRow.price = CDbl(price.Text)
myTitleRow.advance = CDbl(advance.Text)
myTitleRow.royalty = CInt(royalty.Text)
myTitleRow.ytd_sales = CInt(ytd_sales.Text)
myTitleRow.notes = notes.Text
myTitleRow.pubdate = CDate(pubdate.Text)
myTitleTable.Rows.Add(myTitleRow)
mySqlDA.Update(myTDS, "Titles")
titlegrid.DataSource = myTitleTa ble.DefaultView
titlegrid.Databind()
End If
End Sub
</script>
<body>
<!-- This code is identical to AddTitle.aspx -->
</body>
</html>
Note that you add the AddTitle namespace using the @ Import directive. This lets you
use its types directly. Because the assembly was placed in the bin subdirectory by the
compiler, it will be loaded automatically by your Web application.
Now when you create the dataset, you create an instance of TitleDataSet instead of
DataSet. Similarly, you use strong types for the table and row. To successfully load data
from and update the underlying database table, you also need to pass the name of the
table to the Fill and Update methods of the DataAdapter. Once you’ve created the table
and row objects, you can access the column values via early-bound properties instead of
through an index.
Another useful thing about typed datasets is that when you use them with Visual Studio
.NET, you automatically get IntelliSense code completion for all the members of the
TitleDataSet. This makes coding against a typed dataset much easier and considerably
less error-prone.
Using DataViews
The DataView class provides a means for sorting, searching, editing, filtering, and
navigating DataTables. Dataviews can also be data-bound, as you’ll see in later
examples. Data-binding allows ASP.NET server controls to display bound data
automatically, even when multiple rows or columns of data are contained in the
datasource. This greatly simplifies the display of data in ASP.NET. You can access an
unfiltered view of the data in a table in a dataset as follows:
Dim myDV As DataView
myDV = myDS.Tables(0).DefaultView
Once you have a reference to the DataView, you can filter it by adding an expression to
the RowFilter property.
myDV.RowFilter = "type=’business’"
Search the DataView using the Find method.
Dim RowNum As Integer = myDV.Find("BU1032")
Sort the DataView by adding an expression to the Sort property.
myDV.Sort = "type, title_id DESC"
You’ve already seen some basic examples of binding to a DataView. You’ll look at data-
binding in more detail later in this chapter.
SqlDataReader
SqlDataReader is the class to use when accessing data from a SQL Server database.
You create this class by calling the ExecuteReader method on a SqlCommand object.
Dim mySqlDR As SqlDataReader = mySqlCmd.ExecuteReader()
To access the rows in a SqlDataReader instance, you call the Read method of the
instance, usually in a loop.
While mySqlDR.Read()
Response.Write(mySqlDR.Item(0))
End While
Once you’re finished with the SqlDataReader, you should always call its Close method,
as well as calling the Close method on the associated Connection object.
mySqlDR.Close()
Note To avoid having to explicitly close the connection associated with
the command used to create either a SqlDataReader or an
OleDbDataReader, pass the CommandBehavior.CloseConnection
argument to the ExecuteReader method of the connection.
mySqlDR =
mySqlCmd.ExecuteReader(CommandBehavior.CloseConnection)
The associated connection will be closed automatically when the
Close method of the datareader is called. This makes it all the
more important to always remember to call Close on your
datareaders!
OleDbDataReader
Creating and using an OleDbDataReader is essentially the same as for the
SqlDataReader, with one notable exception. The OleDbDataReader can handle
hierarchical recordsets retrieved using the MSDataShape OLE DB Provider. When an
OleDbDataReader is created based on an OleDbCommand that returns a hierarchical
recordset, the OLE DB chapter is returned as a column in the OleDbDataReader. The
value of the column is an OleDbDataReader representing the child records.
Data-Binding
One exciting advance in the move from classic ASP to ASP.NET is the availability of
server-side data-binding. Data-binding is the process of declaratively tying elements of
the UI for a page (such as controls) to data in an underlying datastore. Data-binding on
the client has been available in Internet Explorer for a number of years, but it requires all
of your users to use the same browser, or else you need to do a massive amount of
testing to ensure compatibility with different browser versions. Server-side data-binding
solves this problem by binding and rendering data on the server and returning cross-
browser-compatible HTML.
ASP.NET allows you to bind against a variety of sources, including properties and
method call results, arrays and collections, and datareaders and dataviews. Typically,
binding is done either declaratively, using the <%#expression%> syntax, or by
programmatically setting the DataSource property of a control to the desired datasource.
The data is bound by calling the DataBind method, either at the Page level (which binds
all controls on the page) or on the control being bound (which binds the control and any
child controls). The DataBind method causes the control and its children to evaluate any
data-binding expression(s) associated with the control and assign the resulting values to
the appropriate control attribute. Data-binding allows controls like the DataGrid control,
which can handle multiple rows and columns, to iterate over and format the data
specified by their DataSource property for display.
Simple Data-Binding
The simplest type of data-binding uses the <%#expression%> syntax to replace the
<%#expression%> construct with the value represented by the expression. For example,
the following listing shows a simple page that binds to a page-level property.
SimpleDataBinding.aspx
<%@ Page Language="vb" %>
<html>
<head>
<script runat="server">
Dim Name As String
Sub btnSubmit_Click(sender As Object, e As EventArgs)
Name = txtHello.Text
lblHello.Visible = True
DataBind()
End Sub
</script>
</head>
<body>
<asp:label id="lblHello" visible="false" runat="server">
Hello, <%# Name %>!
</asp:label>
<form runat="server">
<asp:textbox text="" id="txtHello" runat="server"/>
<br>
<asp:button text="Submit" id="btnSubmit"
OnClick="btnSubmit_Click" runat="server" />
</form>
</body>
</html>
The output of this listing after entering a value and clicking the button is shown in the
following figure.
Binding to Controls
The same syntax can be used to bind to controls. For example, you can bind the
selected item of a DropDownList control to the Text property of an ASP.NET Label
control using the code shown in the following listing.
ControlBinding.aspx
<%@ Page Language="vb" %>
<html>
<head>
<script runat="server">
Dim myArrayList As New ArrayList
Sub Page_Load(sender As Object, e As EventArgs)
If Not IsPostBack Then
myArrayList.Add("Chocolate")
myArrayList.Add("Vanilla")
myArrayList.Add("Strawberry")
myList.DataSource = myArrayList
myList.DataBind()
End If
Page.DataBind()
End Sub
</script>
</head>
<body>
<form runat="server">
<asp:dropdownlist id="myList" runat="server"/>
<asp:button Text="Submit" runat=server/>
<br/>
Favorite Ice Cream:
<asp:label text="<%# myList.SelectedItem.Text %>"
runat=server/>
</form>
</body>
</html>
Using DataBinder.Eval
DataBinder.Eval is a static method that is used to evaluate a data-binding expression at
runtime. This can simplify the process of casting various datatypes to be displayed as
text. The basic syntax of DataBinder.Eval is
<%# DataBinder.Eval(Container, EvalExpression, FormatExpression) %>
Container is the object that contains the expression to be evaluated (for DataGrid,
DataList, or Repeater controls, this is always Container.DataItem), EvalExpression is the
full name of the property or item to be evaluated, and FormatExpression is a string
formatting expression (such as {0:c}) to be used to format the string result. Note that if no
format expression argument is passed, DataBinder.Eval will return an object instead of a
string.
Important Because DataBinder.Eval uses late binding and reflection
(which allows managed code to interrogate other managed
code at runtime), it may be significantly slower than the other
data-binding techniques. For this reason, you should only
use DataBinder.Eval when you need to, such as for
performing string formatting on numeric data.
Using DataGrids
The DataGrid control presents bound data in a tabular format and provides a number of
properties that you can use to format the data. This control can automatically generate
all columns in the table from the datasource, or you can use specialized column controls
to provide additional functionality or formatting for a given column or columns.
At its simplest, there are three basic steps to using a DataGrid control.
1. Add the DataGrid to the page.
2. Set the DataSource property of the DataGrid to an appropriate
datasource. This datasource can be defined in the page, or you can
call a method in an external object to supply the data.
3. Call the DataBind method of the control to automatically bind the data
to the control. You can also call the DataBind method of the Page
object, which will bind (or rebind) all of the data-bound controls on
the page by calling DataBind on each control in turn.
Let’s look at an example. In “HTML Controls” in Chapter 10, there is an example that
used a drop-down listbox to list flavors of ice cream for a user to choose from. Suppose
that you want to show similar information in a table format. The problem with this
approach is that the data is explicitly added to the control in the code (or declaratively by
adding items to the control in its tag declaration). Thus, anytime the data changes, you
have to explicitly change the code that creates the control. Additionally, because the
creation of the data and the creation of the control are tightly coupled, it’s not possible to
reuse this data, either elsewhere on the page or elsewhere in the application.
So in this first example, you’ll create the data that you want to use in a custom procedure
in a <script> block, and then call that procedure in order to populate the datasource of
your DataGrid control. Start with a very simple DataGrid that has no formatting
whatsoever.
1. Create a new file and save it as DataGrid.aspx.
2. Add the @ Page directive, standard HTML elements, and an
HtmlForm control to the page, as in earlier examples.
3. Add a Label control and a DataGrid control to the HtmlForm control,
with the following attributes:
4. <form runat="server">
5. <asp:label id="title" text="DataGrid Example"
6. font-name="Verdana" font-size="18" runat="server"/>
7. <asp:datagrid
8. id="MyGrid"
9. autogeneratecolumns="true"
10. runat="server"/>
</form>
11. Add a <script> block with a function called CreateData to create the
data that you’ll bind to the grid. Remember that the <script> block
goes between the <head> and </head> tags.
12. <script language="vb" runat="server">
13. Function CreateData() As ICollection
14. Dim DataArray As New ArrayList()
15.
16. DataArray.Add("Chocolate")
17. DataArray.Add("Vanilla")
18. DataArray.Add("Strawberry")
19. DataArray.Add("Pistachio")
20. DataArray.Add("Rocky Road")
21.
22. Return DataArray
23. End Function
</script>
24. Finally add a Page_Load event handler and use it to set the
DataSource property of the grid (which can only be set at runtime)
and call the DataBind method of the DataGrid.
25. Sub Page_Load()
26. MyGrid.DataSource = CreateData()
27. MyGrid.DataBind()
28. End Sub
29. Save and browse the page. The output should look something like
the following figure.
Although this is a very simple example, you used two lines of code (setting the
DataSource and calling DataBind) to accomplish what would have taken considerably
more code in classic ASP. Now let’s jazz things up a bit.
1. Before making any changes, save the current file as DataGrid2.aspx.
2. Start by making the data a little richer, substituting an ADO.NET
DataTable for the ArrayList. To make this easier, add an @ Import
directive to import the System.Data namespace, which contains the
DataTable class. This directive should go directly below the @ Page
directive.
3. <%@ Import Namespace="System.Data" %>
4. Next delete all of the code in the CreateData function and add the
following code. It creates a new DataTable, adds three columns to it
(using the constructor for the DataTable class), and then adds five
rows to it. Finally, since you can’t bind directly to a DataTable,
create an instance of the DataView class based on the DataTable
and return that as the datasource for your DataGrid.
5. Dim dt As New DataTable()
6. Dim dr As DataRow
7.
8. dt.Columns.Add(New DataColumn("IceCreamID",
GetType(Int32)))
9. dt.Columns.Add(New DataColumn("Flavor", GetType(String)))
10. dt.Columns.Add(New DataColumn("Price", GetType(Double)))
11.
12. 'Add row 1
13. dr = dt.NewRow()
14. dr(0) = 1
15. dr(1) = "Chocolate"
16. dr(2) = 2.00 'Chocolate is popular, so it costs more
17. dt.Rows.Add(dr)
18.
19. 'Add row 2
20. dr = dt.NewRow()
21. dr(0) = 2
22. dr(1) = "Vanilla"
23. dr(2) = 1.50
24. dt.Rows.Add(dr)
25.
26. 'Add row 3
27. dr = dt.NewRow()
28. dr(0) = 3
29. dr(1) = "Strawberry"
30. dr(2) = 1.00
31. dt.Rows.Add(dr)
32.
33. 'Add row 4
34. dr = dt.NewRow()
35. dr(0) = 4
36. dr(1) = "Pistachio"
37. dr(2) = 1.75
38. dt.Rows.Add(dr)
39.
40. 'Add row 5
41. dr = dt.NewRow()
42. dr(0) = 5
43. dr(1) = "Rocky Road"
44. dr(2) = 1.25
45. dt.Rows.Add(dr)
46.
47. Dim dv As New DataView(dt)
48. Return dv
49. Save and browse the page. The output should look something like
the following figure.
50. Now that you have more interesting data, let’s work on the
formatting. First add the following attributes to the DataGrid tag
declaration:
51. <asp:datagrid
52. id="MyGrid"
53. autogeneratecolumns="true"
54. bordercolor="black"
55. borderwidth="2"
56. cellpadding="3"
runat="server"/>
57. Next, add child tags to the DataGrid to provide row formatting. In
order to do this, you need to remove the / character that closes the
single DataGrid tag, and add a closing DataGrid tag.
58. <asp:datagrid
59. id="MyGrid"
60. autogeneratecolumns="true"
61. bordercolor="black"
62. borderwidth="2"
63. cellpadding="3"
64. runat="server">
65. <headerstyle backcolor="silver" font-bold="true"/>
66. <itemstyle backcolor="black" forecolor="white"/>
67. <alternatingitemstyle backcolor="white" forecolor="black"/>
68. </asp:datagrid>
69. Save and browse the page. The output should look something like
the following figure.
But wait! There’s more. In addition to autogenerating columns from the bound data,
DataGrids also support creating columns declaratively. This gives you greater control
over both the formatting and the behavior of individual columns. For example, you can
use a BoundColumn control to provide customized header text for a column, or to apply
special formatting to the bound text in each row. You can also use a ButtonColumn
control to display a command button in each row of a column, an EditButtonColumn to
provide in-place editing for a row, or a HyperlinkColumn control to provide a hyperlink for
each row of the grid, with the text caption, the URL, or both being data-bound. Finally,
you use a TemplateColumn control to apply complex formatting to each row of a specific
column. Let’s see what this example will look like using EditColumn and BoundColumn
controls.
1. Before making any changes, save the current file as DataGrid3.aspx.
Then change the autogeneratecolumns attribute of the DataGrid tag
declaration to false.
2. Next add a pair of <columns> tags to the Datagrid tag to contain the
column definitions.
3. <asp:datagrid
4. id="MyGrid"
5. autogeneratecolumns="false"
6. bordercolor="black"
7. borderwidth="2"
8. cellpadding="3"
9. runat="server">
10. <headerstyle backcolor="silver" font-bold="true"/>
11. <itemstyle backcolor="black" forecolor="white"/>
12. <alternatingitemstyle backcolor="white" forecolor="black"/>
13. <columns>
14. </columns>
15. </asp:datagrid>
16. Next add the column definitions. The EditCommandColumn isn’t
data-bound, so set its properties explicitly. For the ID column, set
the ReadOnly property to true to prevent the column from being
edited when the Edit link is clicked. Set the DataField property for
the bound columns to the desired fields and use the {0:c} format
string to format the Price column as currency.
17. <columns>
18. <asp:editcommandcolumn
19. canceltext="Cancel"
20. edittext="Edit"
21. updatetext="Update"
22. headertext="Edit?"/>
23. <asp:boundcolumn
24. headertext="Ice Cream ID"
25. readonly="true"
26. datafield="IceCreamID"/>
27. <asp:boundcolumn
28. headertext="Flavor"
29. datafield="Flavor"/>
30. <asp:boundcolumn
31. headertext="Price"
32. datafield="Price"
33. dataformatstring="{0:c}"/>
</columns>
34. Now you need to create event handlers for when the Edit links are
clicked. For this example, since you’re dealing with local data, skip
the Update command. Add the following code to the <script> block,
just after the CreateData function. In the Grid_Edit handler, use the
argument e, which contains information about the DataGrid
command that occurred, to determine which row to edit. Then rebind
the grid.
35. Sub Grid_Edit(Sender As Object, e As
DataGridCommandEventArgs)
36. MyGrid.EditItemIndex = e.Item.ItemIndex
37. MyGrid.DataBind
38. End Sub
39. Sub Grid_CancelEdit(Sender As Object, e As
DataGridCommandEventArgs)
40. MyGrid.EditItemIndex = -1
41. MyGrid.DataBind
42. End Sub
43. Next you need to map the OnEditCommand and
OnCancelCommand events to the event handlers you just created
by adding the appropriate attributes to the DataGrid tag.
44. <asp:datagrid
45. id="MyGrid"
46. autogeneratecolumns="false"
47. bordercolor="black"
48. borderwidth="2"
49. cellpadding="3"
50. oneditcommand="Grid_Edit"
51. oncancelcommand="Grid_CancelEdit"
runat="server">
52. Save and browse the page. When you click the Edit link for a row,
the output should look similar to the following figure. Clicking Cancel
will cancel Edit mode.
In addition to the features demonstrated in the preceding examples, the DataGrid control
also supports the sorting and paging of bound data. Follow these steps to enable sorting:
1. Set the AllowSorting property of the DataGrid control to true.
2. <asp:DataGrid id="ItemsGrid"
3. BorderColor="black"
4. BorderWidth="1"
5. CellPadding="3"
6. AllowSorting="true"
7. OnSortCommand="Sort_Grid"
8. AutoGenerateColumns="false"
9. runat="server">
10. If you have not defined bound columns, set the
AutoGenerateColumns property of the DataGrid control to true.
11. Add an event handler to handle the OnSortCommand event and
map the event to the handler (similar to the preceding example for
editing).
12. <asp:DataGrid id="ItemsGrid"
13. BorderColor="black"
14. BorderWidth="1"
15. CellPadding="3"
16. AllowSorting="true"
17. OnSortCommand="Sort_Grid"
18. AutoGenerateColumns="false"
runat="server">
19. Use the SortExpression passed as part of the
DataGridCommandEventArgs argument to sort the source data.
20. Sub Sort_Grid(sender As Object, e As
DataGridSortCommandEventArgs)
21. SortExpression = e.SortExpression.ToString()
22. ItemsGrid.DataSource = CreateDataSource()
23. ItemsGrid.DataBind()
End Sub
24. Rebind the grid. See the BoundColumn_Sort.aspx sample file on
this book’s CD for the full implementation of sorting in a DataGrid.
When sorting is enabled, links are provided automatically in the header of each column
to sort by that field. Note that if you are using BoundColumn controls, you can skip step
2, but you must explicitly set the SortExpression property of the controls. The value of
the property should be the same as the DataField property (that is, the name of the
column in the datasource to which the column in the DataGrid is bound).
Using DataLists
DataLists provide an excellent mix of built-in functionality and control over display
formatting. Unlike the DataGrid control, in which the rows are displayed one after the
other in a table format, the DataList can display its items in multiple columns and can
display rows horizontally or vertically.
Follow these steps to display information from the Titles table of the Pubs SQL Server
sample database:
1. Create a new text file and save it as TitlesDataList1.aspx.
2. Add the @ Page directive, standard HTML elements, and an
HtmlForm control to the page, as in earlier examples. You should
also add the necessary @ Import statements to import the
System.Data and S ystem.Data.SqlClient namespaces.
3. Add the following code to the <form> section of the page to declare
the DataList control, and to set up some simple formatting.
4. <h3>Binding to a DataList Control</h3>
5. <asp:datalist id="titlelist" repeatcolumns="3"
6. gridlines="both" cellpadding="5" runat="server">
7. <ItemTemplate>
8. <h5>
9. <%# DataBinder.Eval(Container.DataItem, "title")%>
10. <br/>
11. </h5>
12. </ItemTemplate>
</asp:datalist>
13. Add the following code to a server-side <script> block in the <head>
section of the page. This code creates and fills a dataset with data
from the Titles table, and then it binds the DataList to the
DefaultView of the dataset.
14. <script runat="server">
15. Sub Page_Load
16. Dim myDS As New DataSet()
17. Dim ConnStr As String
18. ConnStr = "server=(local)\ NetSDK;database=pubs;"
19. ConnStr &= "Trusted_Connection=yes"
20. Dim SQLSelect As String
21. SQLSelect = "SELECT * FROM Titles"
22. Dim mySqlConn As New SqlConnection(ConnStr)
23. Dim mySqlDA As New SqlDataAdapter(SQLSelect, ConnStr)
24. mySqlDA.Fill(myDS)
25. titlelist.DataSource = myDS.Tables(0).DefaultView
26. titlelist.Databind()
27. End Sub
28. </script>
29. Save and browse the page. The output should look something like
the following figure.
Clearly, this layout isn’t too pleasing visually, so let’s spice it up a bit:
1. Before making changes, save a copy of the file as
TitlesDataList2.aspx.
2. Replace the entire contents of the <ItemTemplate> section with the
following (note that the first table row uses a format string containing
the path to an image file, with a placeholder for the title_id, to bind
the title_id for each item to its graphic).
3. <table>
4. <tr>
5. <td rowspan="4">
6. <img align="top"
7. src='<%# DataBinder.Eval(Container.DataItem, "title_id", _
8. "/quickstart/aspplus/images/title-{0}.gif") %>' >
9. </td>
10. </tr>
11. <tr>
12. <td>
13. <em>Title: </em>
14. </td>
15. <td nowrap>
16. <%# DataBinder.Eval(Container.DataItem, "title")%>
17. <br/>
18. </td>
19. </tr>
20. <tr>
21. <td>
22. <em>Price: </em>
23. </td>
24. <td nowrap>
25. <%# DataBinder.Eval(Container.DataItem, "price", "{0:c}")%>
26. <br/>
27. </td>
28. </tr>
29. <tr>
30. <td>
31. <em>Category: </em>
32. </td>
33. <td nowrap>
34. <%# DataBinder.Eval(Container.DataItem, "type")%>
35. <br/>
36. </td>
37. </tr>
38. </table>
39. Save and browse the page. The output should look similar to the
following figure. Note that for the images to be displayed, the
ASP.NET QuickStart samples must be installed.
The <ItemTemplate> is but one of the templates that can be used with the DataList
control, but they all work similarly. Other templates that you can define include the
following:
§ AlternatingItemTemplate Sets the formatting for alternating items.
§ EditItemTemplate Formats the fields used when an item in the
DataList is switched to Edit mode.
§ FooterTemplate Sets formatting for the footer of the DataList.
§ HeaderTemplate Sets formatting for the header of the DataList.
§ SelectedItemTemplate Sets the formatting for the item in the DataList
that has been selected by the user.
§ SeparatorTemplate Sets the format of the divider between items in
the DataList.
Like a DataGrid, a DataList can be set up for in-place editing of values. This requires
adding a LinkButton or Button control to the ItemTemplate with the CommandName set
to “edit”; implementing an EditItemTemplate; setting up the event handlers for the
EditCommand, DeleteCommand, CancelCommand, and UpdateCommand events; and
mapping the event handlers to the events. When the Edit button for an item is clicked,
the event handler should set the EditItemIndex to the number of the item that was
clicked, which can be retrieved from the DataListCommandEventArgs passed to the
event handler. The EditItemTemplate should include LinkButton or Button controls for the
DeleteCommand, CancelCommand, and UpdateCommand, and event handlers should
be added for each.
Using Repeaters
The Repeater control lets you go “hog-wild” with templates. If you can write it in HTML
and/or server controls, you can put it into a template. The following listing shows how the
previous example can be updated to work with a Repeater instead of a DataList. It adds
a HeaderTemplate and a SeparatorTemplate to further improve the look of the page.
TitlesRepeater.aspx
<%@ Page Language="vb" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<head>
<script runat="server">
Sub Page_Load
Dim myDS As New DataSet()
Dim ConnStr As String
ConnStr = "server=(local)\ NetSDK;database=pubs;"
ConnStr &= "Trusted_Connection=yes"
Dim SQLSelect As String
SQLSelect = "SELECT * FROM Titles"
Dim mySqlConn As New SqlConnection(ConnStr)
Dim mySqlDA As New SqlDataAdapter(SQLSelect, ConnStr)
mySqlDA.Fill(myDS)
titlerepeater.DataSource = myDS.Tables(0).DefaultView
titlerepeater.Databind()
End Sub
</script>
</head>
<body>
<form runat="server">
<h3>Binding to a DataList Control</h3>
<asp:repeater id="titlerepeater" runat="server">
<ItemTemplate>
<table>
<tr>
<td rowspan="4">
<img align="top"
src='<%# DataBinder.Eval(Container.DataItem, _
"title_id", "/quickstart/aspplus/images/title-{0}.gif") %>' >
</td>
</tr>
<tr>
<td>
<em>Title: </em>
</td>
<td nowrap>
<%# DataBinder.Eval(Container.DataItem, "title")%>
<br/>
</td>
</tr>
<tr>
<td>
<em>Price: </em>
</td>
<td nowrap>
<%# DataBinder.Eval(Container.DataItem, "price", _
"{0:c}")%>
<br/>
</td>
</tr>
<tr>
<td>
<em>Category: </em>
</td>
<td nowrap>
<%# DataBinder.Eval(Container.DataItem, "type")%>
<br/>
</td>
</tr>
</table>
</ItemTemplate>
<HeaderTemplate>
<h4 style="background-color:silver;">Titles</h4>
</HeaderTemplate>
<SeparatorTemplate>
<hr>
</SeparatorTemplate>
</asp:repeater>
</form>
</body>
</html>
The output of TitlesRepeater.aspx is shown in the following figure. Remember that for
the images to be displayed, the ASP.NET QuickStart samples must be installed.
All templates available to the DataList control can be used with the Repeater control, and
editing is handled the same with a Repeater as with a DataList.
Having spent the last two chapters studying ASP.NET server controls, ADO.NET, and
data-binding, by now you’re probably thinking, “These server controls sure are cool, but I
wish I could create my own.” Well, wish no longer! In this chapter, you’ll learn how to
create custom ASP.NET server controls.
There are as many reasons for developing custom server controls as there are
developers, but generally they fall into three main categories: reuse, specialization, and
maintenance.
Creating custom server controls makes reuse easy. You can wrap up a bunch of related
functionality and UI elements into a control, and then reuse that control where you need
that functionality. For example, you could create a server control that encapsulates all of
the fields and validation controls necessary for user registration. Then, instead of
rewriting that code anytime you need a user registration screen, you simply add your
control to the page instead.
Finally, custom ASP.NET server controls simplify the maintenance of your applications.
Once a server control has been developed and tested, it can be reused reliably in many
places. If you find a bug in your control, it can be fixed in a single place (the source class
for your control), and all you need to do to distribute the fix is replace the assembly
containing the fixed control. This is a major improvement over reuse through include
files, or having no reuse at all.
Creating a Namespace
Each custom control must belong to a namespace because the @ Register directive,
which makes custom controls available for use in a page, has a Namespace attribute
that is used to locate the control class to instantiate at runtime.
You create a namespace using the namespace keyword. In Visual Basic .NET, you use
both the Namespace keyword and a closing End Namespace keyword to define the
scope of the namespace.
Namespace myNS
‘Class definitions, etc. go inside the namespace
End Namespace
In C#, you use the namespace keyword and a pair of curly braces that define the scope
of the namespace.
namespace myNS
{
//Class definitions, etc.
}
Note that if you are creating your custom control using Visual Studio .NET, the IDE will
create the namespace for you automatically.
Important Although Visual Studio .NET creates a namespace for each
new project automatically, this is implemented differently
depending on the language you choose. For example, in a
Visual Basic .NET Web application, the namespace for the
project is defined by the Root Namespace option set in the
Project Properties dialog for the project. This means that
although you will not actually see the Namespace keyword in
your class files, a namespace will be created when the class
is compiled.
In a Visual C# project, the namespace keyword is added to
class files and code- behind files automatically.
Creating a Class
Each custom server control is defined in a class. You can have more than one class
defined in a single namespace within a file, but the class is what defines the boundary of
the control. You may put multiple controls within a single file, if you want. When you
compile this file, all of the classes (and the controls they represent) are compiled into a
single assembly. When you have multiple related controls, this can make deploying your
controls simpler.
You define classes in Visual Basic .NET and C# with the class keyword. As with
namespaces, classes in Visual Basic .NET are defined with a Class keyword and an End
Class keyword that together define the scope of the class.
Class myControl
‘Variables, procedures, etc.
End Class
C# uses the class keyword with a pair of curly braces to define the scope of the class.
class myControl
{
//Variables, procedures, etc.
}
One of the most important steps in creating a custom server control is choosing the class
from which your control will inherit. This can be a more difficult decision than you might
think, because there are so many choices.
You need to consider both how the control will be used and who will use it. Since one of
the reasons for creating custom controls in the first place is to reuse controls that have
most (but not quite all) of the functionality you need, you may be tempted to simply jump
right into inheriting a very rich control and get the most bang for your buck. But you
should keep in mind that any public property, method, or event exposed by the base
control will be exposed by a derived control automatically. If you don’t plan for this, your
control may do something unexpected, or you may unwittingly introduce bugs when
someone uses the control in a way that you didn’t anticipate.
This may not be a problem if you are creating the control for your own use. If you plan to
share it with others, however, just remember that people can be very creative in finding
ways to break software, and developers are more creative than most. By making
available a lot of public methods that you didn’t write, you increase the chances that
another developer will break your code. To avoid this problem, generally you should
inherit from the class that provides the fewest public members while still providing the
desired functionality.
For example, let’s say you want basic UI plumbing code but don’t want the additional
functionality of a TextBox or Label control. You can inherit from the WebControl class,
which implements such basic UI features as the BackColor and ForeColor properties,
and the ApplyStyle method. If you don’t even need the UI plumbing, you can inherit from
the Control class, which has no UI-related members. Of course, this means that you
have to implement UI-related code yourself.
For this first example, you’ll inherit from the Control class. In Visual Basic .NET, this
looks like the following:
Namespace myNS
Public Class myControl
Inherits System.Web.UI.Control
‘Variables, procedures, etc.
End Class
End Namespace
Of course, if you wanted to avoid typing the System.Web.UI, you could add an Imports
statement to import the namespace as follows:
Imports System.Web.UI
The command for compiling a custom control written in Visual Basic .NET looks like the
following:
vbc /t:library <sourcefile> /r:System.Web.dll /r:System.dll
Note that sourcefile is the name of the file (with a .vb extension) containing the definition
of your control class. The /t parameter specifies that the compiler should create a library
(DLL) assembly. The /r parameter (of which there may be more than one) specifies
assemblies that should be referenced when compiling your control class. Each assembly
whose types are used within your class must be referenced using a separate /r
parameter that points to the physical name of the assembly. You can also add the /out
parameter, which specifies the path and file name to use for the compiled assembly. One
use for this is to direct the compiled output to be placed directly in the bin directory of the
application that will use the control, which saves the step of having to copy the assembly
there later.
The command for compiling a control written in C# looks like the following:
csc /t:library <sourcefile> /r:System.Web.dll /r:System.dll
Important Depending on which version of the .NET Framework you've
installed, you may need to add the path to the folder
containing the Visual Basic .NET compiler to your PATH
environment variable in order for the preceding command to
work. The procedure for locating and adding this path to the
PATH environment variable is detailed on page xv of the
“Installing the Sample Files ” section at the beginning of this
book.
The only things that are different are the name of the compiler (csc.exe instead of
vbc.exe) and the extension of sourcefile, which for C# will be .cs. This highlights one of
the advantages of the .NET platform, which is improved consistency of operations from
language to language. This consistency can make it easier for developers to work in
multiple languages without facing a steep learning curve.
Note When you’re developing controls using Visual Studio .NET, many
of the most commonly used assemblies are referenced for you
automatically by the IDE. This means that when you issue a Build
command, you do not need to explicitly tell the compiler to
reference those assemblies. The IDE has taken care of it already.
The assemblies automatically referenced for a Web Control
Library project are System, System.Data, System.Drawing,
System.Management, System.Web, and System.Xml.
If you use types from other assemblies, you will need to add a
reference to the assemblies. You can do this either by right-
clicking the References folder in the Solution Explorer and clicking
Add Reference, or by selecting Add Reference from the Project
menu. Either one opens the Add Reference dialog, which allows
you to select .NET or COM components or projects to reference
and browse to find assemblies or components not listed. (Only
.NET assemblies that have been placed in the Global Assembly
Cache and COM components that have been registered are
listed.)
Now that you’ve looked at all of the pieces that go into creating a custom ASP.NET
server control, let’s put it all together in a simple working control.
1. Create a new file in your preferred editor and save it as
HelloControl.vb.
2. Add an Imports statement to import the System.Web.UI namespace.
Imports System.Web.UI
3. Add the Namespace and End Namespace keywords to define the
namespace ASPNETSBS.
4. Namespace ASPNETSBS
End Namespace
5. Within the namespace you just created, add a class named
HelloControl. Use the Public modifier to make the class accessible to
clients.
6. Public Class HelloControl
End Class
7. On the first line within the class, add an Inherits statement to inherit
the Control class. Since you imported the System.Web.UI namespace,
you can just use the class name.
Inherits Control
8. Finally, override the Render method of the base Control class to send
a message to the browser.
9. Protected Overrides Sub Render(writer As HtmlTextWriter)
10. writer.Write("Hello, World!</br>")
End Sub
11. Open a command prompt window, navigate to the directory containing
HelloControl.vb, and compile the control using the syntax shown
earlier.
vbc /t:library HelloControl.vb /r:System.Web.dll /r:System.dll
Your control is now compiled and ready to use. Remember that you will need to copy the
assembly (in the case of the preceding command, the assembly name will be
HelloControl.dll) to the bin directory of the application from which you want to use your
control. That is, unless you used the /out parameter to direct the compiler to put the
assembly there for you.
By default, all custom assemblies that you create are private and are made available to
a Web application by placing them in the bin directory of the application, where they
are loaded by ASP.NET automatically. Assemblies may also be shared across all
clients on a machine by placing them in the GAC. Barring any security configuration in
the assembly that prevents it, assemblies in the GAC may be used by any managed
application on that machine.
Private assemblies allow you to limit the use of an assembly to the application in whose
bin subdirectory the assembly resides. Shared assemblies allow you to reuse your
assemblies in multiple applications without needing to maintain a copy of an assembly
for each application that uses it.
To install an assembly into the GAC, you need to give the assembly a strong name
(signing it with a public key, which you can generate with the sn.exe .NET Framework
utility). Then add it to the GAC either by using the gacutil.exe .NET Framework utility or
by dragging and dropping the assembly into the %windir%\assembly folder, which will
add the assembly to the GAC automatically.
The GAC supports versioning of assemblies, so you can install multiple versions of the
same assembly by setting its AssemblyVersionAttribute attribute. Clients can then
specify which version of an assembly to load at runtime.
Once you’ve registered the control, you can use it in a Web Forms page by adding the
appropriate tag to the page. The syntax for the tag for a custom control is
<tagprefix:classname id="identifier" runat="server">
Here, tagprefix is the tag prefix specified in the @ Register directive, classname is the
name of the class that contains the control, and identifier is a unique (to the page) ID that
you will use to refer to the control in server-side code.
Adding a custom control to the page programmatically is essentially the same as adding
a standard ASP.NET server control to the page programmatically, and uses the following
syntax:
<%@ Import Namespace=<namespace name> %>
<script runat="server">
Sub Page_Load
Dim myControl As New ControlClassName()
me.Controls.Add(myControl)
End Sub
</script>
Here, namespace name is the name of the namespace containing the control, and
ControlClassName is the name of the control’s class. In the preceding example, the New
keyword is used to create an instance of the control class, which is then added to the
Controls collection of the page (referenced by the me keyword). Once the control has
been added to the Controls collection, ASP.NET will take care of rendering it for you.
You’ll see examples of both declarative and programmatic use of a custom control in the
next section.
Let’s put your simple control to work. First practice implementing the control
declaratively, as follows:
1. Create a new file in your preferred editor and save it as
HelloControl_Client.aspx. This file should be saved to an IIS
application directory, which also contains a bin directory to which the
compiled assembly containing the custom control has been copied.
2. Add the @ Page directive and standard HTML tags to the file.
3. <%@ Page Language="VB" %>
4. <html>
5. <body>
6. </body>
</html>
7. Next add an @ Register tag with the appropriate attributes for the
control.
8. <%@ Register TagPrefix="ASPNETSBS"
Namespace="ASPNETSBS"
Assembly="HelloControl" %>
9. Finally add the tag to declare the control. This tag should go in the
<body> section of the page.
<ASPNETSBS:HelloControl id="hello" runat="server"/>
10. Save and browse the page. The output from the page should look like
the following figure.
Here’s the full code for the page that uses the HelloControl control:
<%@ Page Language="VB" %>
<%@ Register TagPrefix="ASPNETSBS" Namespace="ASPNETSBS"
Assembly="HelloControl" %>
<html>
<body>
<ASPNETSBS:HelloControl id="hello" runat="server"/>
</body>
</html>
That’s right; just seven lines of code to use the control you created. Just imagine how
much time you’re going to save by encapsulating frequently used code into a custom
control!
Now that you’ve conquered declarative use of your custom control, let’s implement the
control programmatically, as follows:
1. Create a new file in your preferred editor and save it as
HelloControl_Client_Programmatic.aspx. This file should be saved to
the same location as in the previous example.
2. Add the @ Page directive and standard HTML tags.
3. Add the following code to the <head> section of the page:
4. <script runat="server">
5. Sub Page_Load
6. Dim myControl As New HelloControl()
7. me.Controls.Add(myControl)
8. End Sub
</script>
9. Save the page and browse it. The output should look the same as the
preceding figure.
While the output of both HelloControl_Client.aspx and
HelloControl_Client_Programmatic.aspx look the same, there is one significant
difference: the text Hello, World! in the second example appears after the closing </html>
tag. This is because when you add a control to the page’s Controls collection using the
Add method, it is added at the end of the collection.
There are two ways to fix this: use the AddAt method instead of Add, or use an ASP.NET
PlaceHolder control. The AddAt method takes both the name of a control and an integer
representing the location in the collection where the control should be inserted. The
usefulness of this method is limited because you must know by number where you want
your control to be located, and static HTML content is dynamically converted at runtime
into ASP.NET Literal controls. The easiest method of precisely placing a
programmatically created control on the page is with a PlaceHolder control. Add a
PlaceHolder control at the desired location in the page, making sure to provide an id
attribute for the control.
<asp:placeholder id="ph1" runat="server"/>
Then, when you create your control, add it to the Controls collection of the PlaceHolder,
rather than the page’s Controls collection.
ph1.Controls.Add(myControl)
Adding Functionality
As cool as it may be to create and consume a custom server control in a grand total of
16 lines of code (including both the control and the Web Forms page that consumes it), it
provides minimal functionality. Clearly, to be truly useful, you need to add to the control.
The next several sections will look at how to add properties and methods to a control,
create raise and handle events, handle postbacks, maintain state in a control, and create
templated controls. By the end of this section, you’ll be able to add a great deal of
functionality to what is now a very simple control.
Adding a Property
Let’s start with adding a property by following these steps:
1. Open HelloControl.vb.
2. Copy the HelloControl class and paste a copy below the original.
Modify the class name to HelloControlProp. The following code
added to the class creates a private string variable and a public
Property procedure to provide access to the private member
variable. It also modifies the Render method to use the value of the
local variable.
3. Public Class HelloControlProp
4. Inherits Control
5. Dim _name As String = "World"
6. Public Property Name As String
7. Get
8. Name = _name
9. End Get
10. Set(ByVal Value As String)
11. _name = Value
12. End Set
13. End Property
14. Protected Overrides Sub Render(writer As HtmlTextWriter)
15. writer.Write("Hello, " & _name & "!</br>")
16. End Sub
End Class
17. Save and recompile the class file. You can save yourself time by
creating a batch file (saved with a .bat extension) in the same
directory as the class file containing the following commands:
18. vbc /t:library HelloControl.vb /r:System.Web.dll /r:System.dll
pause
19. Copy the compiled assembly to the bin directory of the Web
application that uses the control.
20. Open HelloControl_Client.aspx and add the following tag:
21. <ASPNETSBS:HelloControlProp id="helloprop" runat="server"/>
22. Save and browse HelloControl_Client.aspx. The output should look
like the following figure.
Note that the output in this figure uses the default value of the _name private variable,
which you set to World. This way, if the person using the control does not explicitly set
the property, you still get valid output. You could also have added code to the Get portion
of the Property procedure to check the value of _name, and substitute a default value if it
is blank.
To check that your property works correctly, change the tag definition by adding a name
attribute, with your name for the value. By default, properties defined on custom controls
are available as attributes on the tag used to declare the control, without any additional
coding on your part. The updated tag would look like the following:
<ASPNETSBS:HelloControlProp id="helloprop" name="Andrew"
runat="server"/>
The output with the modified tag should look like the following figure.
Adding a Method
Now let’s look at adding a method.
1. Open HelloControl.vb.
2. Copy the HelloControlProp class and paste a copy below the original.
Modify the class name to HelloControlPropMeth. The following code
added to the class creates another private string variable to hold the
rest of the message you’re outputting, a private boolean variable to
flag whether the Sub has been called, and a public Sub procedure
that formats the variable to show a client-side message box. Added
to the Render method is an If statement, which checks the boolean
variable to determine whether to pop up a client-side message box
or simply render the text message.
3. Public Class HelloControlPropMeth
4. Inherits Control
5. Dim _name As String = "World!"
6. Dim _messageText As String = "Hello, "
7. Dim _clientMessage As Boolean = False
8. Public Property Name As String
9. Get
10. Name = _name
11. End Get
12. Set(ByVal Value As String)
13. _name = Value
14. End Set
15. End Property
16. Public Sub FormatClientMessage()
17. _messageText = "<script>alert('" & _messageText & _name &
_
18. "!');</script>"
19. _clientMessage = True
20. End Sub
21. Protected Overrides Sub Render(writer As HtmlTextWriter)
22. If _clientMessage = True Then
23. writer.Write(_messageText)
24. Else
25. writer.Write(_messageText & _name & "</br>")
26. End If
27. End Sub
End Class
28. Save and recompile the class file.
29. Copy the compiled assembly to the bin directory of the Web
application that uses the control.
30. Open HelloControl_Client.aspx and add the following tag:
<ASPNETSBS:HelloControlPropMeth id="hellopropmeth" runat="server"/>
31. Add a <head> section to the page containing the following code:
32. <script runat="server">
33. Sub Page_Load
34. hellopropmeth.FormatClientMessage()
35. End Sub
</script>
The completed page should look like the following listing.
HelloControl_Client.aspx
<%@ Page Language="VB" %>
<%@ Register TagPrefix="ASPNETSBS" Namespace="ASPNETSBS"
Assembly="HelloControl" %>
<html>
<head>
<script runat="server">
Sub Page_Load
hellopropmeth.FormatClientMessage()
End Sub
</script>
</head>
<body>
<ASPNETSBS:HelloControl id="hello" runat="server"/>
<ASPNETSBS:HelloControlProp id="helloprop" name="Andrew"
runat="server"/>
<ASPNETSBS:HelloControlPropMeth id="hellopropmeth" name="Andrew"
runat="server"/>
</body>
</html>
7. Save and browse HelloControl_Client.aspx. The output should look
like the following figure.
Note that even though you’ve created three separate controls, they’re all in the same
assembly and use the same namespace, so you only need one @ Register directive to
use all three.
Try commenting out the line in the Page_Load event handler that calls the
FormatClientMessage method by adding a single quote (') to the beginning of the line,
and see what that does to the output.
Another important aspect of creating a custom server control is dealing with events.
While the event model in ASP.NET is designed to closely emulate event handling in a
desktop application, such as a Visual Basic forms application, there are some significant
differences in the models.
The most significant difference is that control events are raised and handled only on the
server. Only after a Web Forms page has been posted back to the server can the state
of each control be examined and appropriate events raised and handled.
Another difference is that custom ASP.NET server controls do not handle client events,
such as clicks, mouseovers, and key presses, because these events occur on the client.
In order to communicate a client event to the server, the control must initiate a postback.
ASP.NET supplies a relatively simple method for allowing a control to initiate a postback,
which is described in “Handling Postbacks” on page 371.
There are three parts to dealing with events in a custom control: creating the event,
raising the event, and handling the event. (Handling the event is typically done outside
the control, but the plumbing for making it work is set up in the control.)
You’ll see a full implementation of these principles after the discussion of handling
postbacks.
When you’re handling events, it’s important to understand that the standard control
events and methods occur in a consistent order, including events and methods related
to postbacks. This is important because if you write code in an event handler that
occurs before the objects manipulated by that code have been created and initialized,
the results will be unpredictable at best. The following is the order of the standard
events and methods for controls.
End Sub
Handling Postbacks
As discussed previously, a postback is the process by which a Web Forms page submits
an HTTP POST request to itself in response to some user action, such as clicking a
button. At some point, it is likely that you will want to develop a control that handles
postback data and/or events. In this section, you’ll learn how to handle both.
There are three phases in the execution of an ASP.NET Web Forms page during which
you have the opportunity to work with postback information: the LoadPostData phase,
the RaisePostDataChangedEvent phase, and the RaisePostBackEvent phase. To work
with the associated data or events, you override the associated method for the desired
phase. To override the methods, you need to implement the IPostBackDataHandler
interface, the IPostBackEventHandler interface, or both. IPostBackDataHandler defines
the LoadPostData method and the RaisePostDataChangedEvent method.
IPostBackEventHandler defines the RaisePostBackEvent method.
You implement interfaces in the fashion specified by the language you’re using. For
Visual Basic .NET, interfaces are implemented using the Implements keyword, and you
can implement as many interfaces as you’d like. The Implements statement should
directly follow the Inherits statement for your control. Separate each interface name with
a comma, as follows:
Inherits Control
Implements IPostBackDataHandler, IPostBackEventHandler
In C#, both the class from which the control is derived and any interfaces it implements
are named on the same line as the class definition.
public class classname : Control, IPostBackDataHandler,
IPostBackEventHandler
Note that the class from which the control is derived must come first in the list, because a
control can only inherit from a single class. All items in the list after the first one are
assumed to be interfaces, and a compiler error will result if the items are not in the
correct order.
A Login Control
To demonstrate event handling and postback handling, you’re going to leave the
HelloControl example behind and build a control that can handle user logins. Your
control will render two text boxes, one for the username and another for the password,
and a Submit button. The control will expose two events, AuthenticateSuccess and
AuthenticateFailure, that the client can use to determine whether the user entered the
correct credentials. To raise these events, you’ll use some of the postback methods that
were described earlier. Let’s get started.
1. Create a new file called LoginControl.vb.
2. Add the following Imports statements to the file.
3. Imports System
4. Imports System.Web
5. Imports System.Web.UI
Imports System.Collections.Specialized
6. Add a namespace with the name ASPNETSBS, and a class within the
namespace named LoginControl_VB.
7. Namespace ASPNETSBS
8. Public Class LoginControl_VB
9. End Class
End Namespace
10. Directly below the class definition, add an Inherits statement to
inherit the Control class.
11. Public Class LoginControl_VB
Inherits Control
12. Directly below the Inherits statement, add an Implements statement
to implement the IPostBackDataHandler and
IPostBackEventHandler interfaces.
13. Inherits Control
14. Implements IPostBackDataHandler, IPostBackEventHandler
15. Within the class, add private variables and public property
procedures for the username and password.
16. Private _UserName As String = ""
17. Private _Password As String = ""
18.
19. Public Property UserName As String
20. Get
21. Return _UserName
22. End Get
23. Set(ByVal Value As String)
24. _UserName = Value
25. End Set
26. End Property
27.
28. Public Property Password As String
29. Get
30. Return _Password
31. End Get
32. Set(ByVal Value As String)
33. _Password = Value
34. End Set
End Property
35. Override the Render method of the Control class, and display the
desired controls. The Me.UniqueID property is used to associate the
client controls being rendered with the custom control containing
them. Note that while you set the value of the first text box to the
UserName property to allow it to be prepopulated on a postback,
you don’t do the same with the password, for security reasons.
36. Protected Overrides Sub Render(writer As HtmlTextWriter)
37. writer.Write("<h3>User Name: <input name=" & _
38. Me.UniqueID & _
39. " id = UserNameKey" & _
40. " type=text value=" & _
41. Me.UserName & _
42. "> </h3>")
43. writer.Write("<h3>Password: <input name=" & _
44. Me.UniqueID & _
45. " id = Password" & _
46. " type=password > </h3>")
47. writer.Write("<input type=button " & _
48. "value=Submit OnClick=""jscript:" & _
49. Page.GetPostBackEventReference(Me) & _
50. """> ")
51. writer.WriteLine("")
52. writer.WriteLine("<br>")
53. writer.WriteLine("<br>")
54. End Sub
55. Define events for authentication success and failure.
56. Public Event AuthSuccess As EventHandler
Public Event AuthFailure As EventHandler
57. Override the LoadPostData method defined in the
IPostBackDataHandler interface to load the posted values into the
local variables. The Implements statement declares that this event
handler provides the necessary implementation of LoadPostData.
The return value of this function determines whether the
RaisePostDataChangedEvent method is called on this control.
58. Public Overridable Function LoadPostData(postDataKey As
String, _
59. values As NameValueCollection) As Boolean _
60. Implements IPostBackDataHandler.LoadPostData
61.
62. Dim newValues As String = values(postDataKey)
63. Dim Index As Integer = newValues.IndexOf(",")
64. Dim newUserName As String = ""
65. Dim newPassword As String = ""
66.
67. 'get the UserName
68. newUserName = (newValues.Substring(0, Index))
69. ' get the Password
70. newPassword = (newValues.Substring(Index + 1, _
71. newValues.Length - Index - 1))
72.
73. If((Not newUserName = UserName) Or _
74. (Not newPassword = _Password)) Then
75. _UserName = newUserName
76. _Password = newPassword
77. Return True
78. Else
79. Return False
80. End If
81. End Function
82. Override the RaisePostDataChangedEvent method. In your control,
you do not take any action with this method, but you are still
required to implement it because it is defined on one of the
interfaces you’re implementing.
83. Public Overridable Sub RaisePostDataChangedEvent() _
84. Implements
IPostBackDataHandler.RaisePostDataChangedEvent
85. ' Respond to changes in posted data
End Sub
86. Finally override the RaisePostBackEvent method. In the following
code, you check the _UserName and _Password local variables
(populated with the posted values in LoadPostData) against static
text values. If they both match, you raise the AuthSuccess event
with the RaiseEvent keyword. If not, you raise the AuthFailure
event. In a real authentication control, you would use information
from a database, or from the Microsoft Active Directory service, to
determine if the entered values were valid.
87. Public Overridable Sub RaisePostBackEvent(eventArgument As
String) _
88. Implements IPostBackEventHandler.RaisePostBackEvent
89. If Me._UserName = "SomeUser" _
90. And Me._Password = "password" Then
91. RaiseEvent AuthSuccess(Me, EventArgs.Empty)
92. Else
93. RaiseEvent AuthFailure(Me, EventArgs.Empty)
94. End If
End Sub
95. Save the class file.
96. Compile the control, using the following command line. If you plan to
make changes to the control, you may want to put this command
into a batch file in the same directory as the class file, allowing you
to recompile by double-clicking the batch file. Note that the
command should all go on a single line.
97. vbc /t:library /r:System.Web.dll /r:System.dll
/out:LoginControl_VB.dll LoginControl.vb
98. Copy the compiled assembly to the bin directory of the Web
application that will use the control.
To use the control, you’ll need to create a new Web Forms page, create the control
(either declaratively or programmatically), and set up handlers for its exposed events.
1. Create a new file in the Web application where you copied the control.
Name the file LoginControl_Client_VB.aspx.
2. Add the @ Page directive and standard HTML tags to the file.
3. <%@ Page Language="vb" %>
4. <html>
5. <head>
6. </head>
7. <body>
8. <form runat="server">
9. </form>
10. </body>
</html>
11. Add a @ Register directive to the page to register the custom
control.
12. <%@ Register TagPrefix="ASPNETSBS"
Namespace="ASPNETSBS"
Assembly="LoginControl_VB" %>
13. Declare the control within the <body> of the page, using the tag
prefix defined in the @ Register directive and the class name
defined for the control.
<ASPNETSBS:LoginControl_VB id="Login" runat=server/>
14. Directly after the login control, add an ASP.NET Label control to
display authentication messages.
15. <asp:label id="Message" Font -Bold="True" ForeColor="Red"
runat="server"/>
16. Add a <script> block containing event handlers for the AuthSuccess
and AuthFailure events to the <head> section of the page.
17. <script runat="server">
18. Sub Login_AuthSuccess(Source As Object, e As EventArgs)
19. Message.Text = "Authenticated!"
20. End Sub
21. Sub Login_AuthFailure(Source As Object, e As EventArgs)
22. Message.Text = "Authentication Failed!"
23. End Sub
</script>
24. Finally wire the events to the event handlers by adding the
appropriate attributes to the control’s tag.
25. <ASPNETSBS:LoginControl_VB id="Login"
26. OnAuthSuccess="Login_AuthSuccess"
27. OnAuthFailure="Login_AuthFailure"
28. runat=server/>
29. Save and browse the page. The output should look similar to the
following figure.
If you enter “SomeUser” for the username and “password” for the password,
the output should look similar to this figure.
If you enter any other values, the output should look similar to the following
figure.
The full code for the control is shown in the following listing.
LoginControl_Client_VB.aspx
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections.Specialized
Namespace ASPNETSBS
Maintaining State
In the previous examples, data used by the controls has been stored in local variables.
For many controls, this may work just fine. But unless you explicitly retrieve submitted
values and repopulate the member controls with these values, as you did in the
LoadPostData method of the previous example, the member controls will be blank when
the page is posted back to the server and the results are returned. In many cases, you
may want to maintain the state of the constituent controls of your custom server control,
without explicitly having to write code to test for new values and repopulate the member
controls with each postback.
To solve this problem, ASP.NET provides a state-management facility called ViewState.
This is provided by an instance of the StateBag class of the System.Web.UI namespace.
You store data in ViewState as key/value pairs, which makes working with it quite similar
to working with session state. To add a value to ViewState or modify an existing value,
you can simply specify the item’s key and assign a value as follows:
‘ Visual Basic .NET
ViewState("MyKey") = MyValue
// C#
ViewState["MyKey"] = MyValue;
Important In order to use ViewState, controls must be contained within
a <form> block with the runat=“server” attribute set.
Note that in C#, you use square brackets around the key rather than parentheses. You
can also add items to ViewState using the Add method of the StateBag class, and
remove them using the Remove method. You can clear all ViewState values with the
Clear method.
Retrieving values from ViewState is a bit more involved. Since the Item property of the
StateBag class returns an object, you will need to cast the object to the proper type
before performing any type-specific actions on it or passing it to another procedure that
expects a particular type. The syntax for retrieving a string value is as follows:
‘ Visual Basic .NET
Dim MyString As String
MyString = CType(ViewState("MyKey"), String)
// C#
string MyString;
MyString = (string)ViewState["MyKey"];
The simplest way to automate the process of saving control state to ViewState is to
substitute ViewState items for the local member variables for storing the values of
properties of the control.
Important Using ViewState has both performance and security
implications, because all of the information stored in
ViewState is round-tripped to the client as a base64 encoded
string. The size of the ViewState hidden field can become
quite large with controls that contain a lot of data, which can
have a negative impact on performance. You may want to
disable ViewState for these controls by setting their
EnableViewState property to False.
Because the ViewState field is sent as a text string, you
should never use ViewState to store information such as
connection strings, server file paths, credit card information,
or other sensitive data. Remember that once the ViewState
has been sent to the client, it would be fairly simple for a
hacker to decode the string and retrieve any data contained
within. Don’t count on base64 encoding to protect sensitive
data sent to the client.
Finally, the ViewState of controls is saved between the firing
of the PreRender and Render events, so any changes to
control state that you want to save in ViewState must be
made during the PreRender event. Changes made to control
state after the PreRender event will not be saved to
ViewState.
For the previous example, you would make the following modifications to the UserName
and Password property procedures (in addition to eliminating the _UserName and
_Password local variables):
Public Property UserName As String
Get
Return CType(ViewState("UserName"), String)
End Get
Set(ByVal Value As String)
ViewState("UserName") = Value
End Set
End Property
Public Property Password As String
Get
Return CType(ViewState("Password"), String)
End Get
Set(ByVal Value As String)
ViewState("Password") = Value
End Set
End Property
Overriding CreateChildControls
The CreateChildControls method is substituted for the Render method in compositional
controls. A sample CreateChildControls method is as follows:
Protected Overrides Sub CreateChildControls()
Dim MyLabel As New Label()
MyLabel.Text = "Label: "
Me.Controls.Add(MyLabel)
Dim MyText As New TextBox()
Me.Controls.Add(MyText)
Dim MyButton As New Button()
Me.Controls.Add(MyButton)
End Sub
It is not necessary in a compositional server control to call the Render method, because
all of the individual controls added to the Controls collection will take care of rendering
themselves during the Render stage of page processing.
If you want to manipulate controls after they’ve been added to the Controls collection,
you can access them either by their numeric index, as shown here, or by calling the
FindControl method of the Control class and passing in the ID of the control you want to
manipulate.
Dim MyText As TextBox = Controls(1)
Implementing INamingContainer
Compositional controls should always implement the INamingContainer interface. This
interface does not contain any property or method declarations. It simply creates a new
naming scope for the current control to ensure that the names of child controls are
unique within the page.
In the next section, you’ll see examples of both overriding the CreateChildControls
method and implementing INamingContainer.
You can create a batch file to make the compilation process quicker.
Using the control from a Web Forms page requires the standard steps of registering the
control with the @ Register directive and adding a tag to instantiate the control
declaratively.
1. Create a new file called HelloControlTemplated_Client.aspx, add the @
Page directive and standard HTML controls, and save the file.
2. Add an @ Register directive for your new control, which should look like
the following:
3. <%@ Register TagPrefix="ASPNETSBS"
Namespace="ASPNETSBS"
4. Assembly="HelloControlTemplated" %>
5. Add the tags to instantiate the control. This time, instead of using a single
tag with the closing / at the end to instantiate the control, you’ll use a tag
pair, since you need two tags to contain the NameTemplate tags. Notice
that you set the Name property to World.
6. <ASPNETSBS:HelloTemplate id="hello" Name="World"
runat="server">
7. <%--Template tags and formatting tags go here --%>
</ASPNETSBS:HelloTemplate>
8. Add the NameTemplate tags to the HelloTemplate control. The
NameTemplate tags contain HTML formatting to be applied to the
templated item. In addition to the HTML formatting tags, you also add
literal content to round out your message.
9. <ASPNETSBS:HelloTemplate id="hello" Name="World"
runat="server">
10. <NameTemplate>
11. <h3><font color="Blue">
12. <em>Hello, <%# Container.Name%>!</em>
13. </font></h3>
14. </NameTemplate>
</ASPNETSBS:HelloTemplate>
15. Add another declarative instance of HelloTemplate, this time using the
single-tag syntax. This allows you to test the output of the control without
templates.
16. <ASPNETSBS:HelloTemplate id="hello2" Name="Hello, World!"
runat="server"/>
17. Finally add a <script> block with a Page_Load handler that calls the
DataBind method to evaluate the Container.Name data-binding
expression.
18. <script runat="server">
19. Sub Page_Load()
20. DataBind()
21. End Sub
</script>
22. Save and browse the page. The output should look similar to the
following figure.
HelloControlTemplated.vb
Imports System.Web.UI
Namespace ASPNETSBS
Public Class HelloControlTemplateItem
Inherits Control
Implements INamingContainer
<TemplateContainer(GetType(HelloControlTemplateItem))> _
Public Property NameTemplate As ITemplate
Get
Return _nameTemplate
End Get
Set
_nameTemplate = Value
End Set
End Property
HelloControlTemplated_Client.aspx
<%@ Page Language="VB" %>
<%@ Register TagPrefix="ASPNETSBS" Namespace="ASPNETSBS"
Assembly="HelloControlTemplated" %>
<html>
<head>
<script runat="server">
Sub Page_Load()
DataBind()
End Sub
</script>
</head>
<body>
<form runat="server">
<ASPNETSBS:HelloTemplate id="hello" Name="World" runat="server">
<NameTemplate>
<h3><font color="Blue">
<em>Hello, <%# Container.Name%>!</em>
</font></h3>
</NameTemplate>
</ASPNETSBS:HelloTemplate>
<ASPNETSBS:HelloTemplate id="hello2" Name="Hello, World!"
runat="server"/>
</form>
</body>
</html>
TextBoxPlus.vb
Imports System.ComponentModel
Imports System.Drawing
Imports System.Web.UI
Imports Microsoft.VisualBasic
Imports Microsoft.VisualBasic.Compatibility
Namespace ASPNETSBS
Public Enum LabelLocations
PreviousLine
SameLineBefore
SameLineAfter
End Enum
The command line for compiling the code in the previous listing would look something
like the following (the entire example should be a single command):
vbc /t:library /r:System.Web.dll /r:System.Drawing.dll
/r:Microsoft.VisualBasic.dll
/r:Microsoft.VisualBasic.Compatibility.dll /r:System.dll
/out:bin\TextBoxPlus.dll TextBoxPlus.vb
The following listing, TextBoxPlus_client.aspx, shows the code necessary to instantiate
the TextBoxPlus control and set its properties.
TextBoxPlus_client.aspx
<%@ Page Language="VB" %>
<%@ Register TagPrefix="ASPNETSBS" Namespace="ASPNETSBS"
Assembly="TextBoxPlus" %>
<html>
<head>
<script runat="server">
Sub Page_Load
MyText.LabelLocation = LabelLocations.SameLineBefore
MyText.LabelFontColor = System.Drawing.Color.Blue
MyText.LabelFontSize = 6
MyText.Label = "Name: "
End Sub
</script>
</head>
<body>
<ASPNETSBS:TextBoxPlus id="MyText" runat="server"/>
</body>
</html>
Inherit from a In Visual Basic .NET- add the Inherits keyword on the line
base class after the class definition- specifying the name of the class
to inherit.
In C#- add a colon after the name of the class you are
creating- followed by the name of the class to be inherited.
Render output Override the Render method of the control class from
from a control which the custom control is derived. To take advantage of
the rendered output of controls like TextBox and Label-
call the Render method of the base class when you are
finished rendering custom output.
Handle Implement the IPostBackDataHandler and/or
postbacks IPostBackEventHandler interfaces- and implement the
methods defined by the interfaces.
Maintain state in Store state in and retrieve state from the ViewState object
a custom control in your property procedures.
Previous chapters have focused on the creation of ASP.NET Web Forms, as well as the
technologies that make ASP.NET development possible and those that make it very
simple and quick to create robust, feature-rich Web applications. Another important and
very useful part of ASP.NET that hasn’t been discussed is XML-based Web services.
Today, Web services are not a very well-understood technology. Part of this is because
of inaccurate reporting from the media, who describe Web services as everything from
subscription software to a proprietary plot by Microsoft to take over the Internet. These
stories may sound great in print, but they are based on misunderstandings and
ignorance of this potentially revolutionary technology.
Some of the solutions for these difficulties have included creating custom Web front-
ends for existing functionality, or using proprietary communications and/or packaging
technologies to bridge the gap between systems. The former solution worked well in
some situations, but the organization hosting the application had to create and maintain
a UI for the functionality that they made available, as well as creating a front -end for any
additional functionality added later. Moreover, this UI might or might not fully meet the
needs of their existing and/or future clients. The use of proprietary communications or
bridging software typically meant greater expense, reliance on a single vendor, and
dependence on that vendor’s maintenance and upgrades. XML-based Web services
address both of these issues squarely, allowing programmatic functionality to be
exposed without the need for a custom Web-based UI, and without the need for
proprietary bridging software.
Web services are discrete units of programmatic functionality exposed to clients via
standardized communication protocols and data formats, namely HTTP and XML. These
protocols and formats are both well-understood and widely accepted. (While XML is still
a relatively immature technology, it has rapidly gained acceptance in the industry due to
its promise as a bridging technology.)
Web services are accessed by calling into a listener that is able to provide a contract to
describe the Web service, and that also processes incoming requests or passes them on
to other application logic and passes any responses back to the client. The following
figure shows how a Web service application may be layered. It is also possible to use
Web services to access legacy logic as well as new application logic, or to wrap legacy
logic for access by a Web service.
SOAP and XML-based Web services are too complicated to cover in a single chapter.
Fortunately, one of the advantages of the ASP.NET programming model is that it
abstracts the complexity of SOAP messaging from the development of Web services.
As mentioned earlier in this chapter, a Web service is nothing more than a way to
provide discrete units of application functionality over standard Web protocols, using a
standardized message format. To be truly useful, however, Web services also need to
provide the following:
§ A contract that specifies the parameters and datatypes it expects, as well
as the return types (if any) that it sends to callers, and
§ A facility for locating the Web service, or a means of discovering Web
services offered by a given server or application, and the descriptions of
those services
The next several sections will examine creating, advertising, locating, and consuming
Web services from the standpoint of an ASP.NET Web developer.
Each .asmx file is associated with a single class, either inline in the .asmx file or in a
code-behind class. Either way, the syntax of a Web service class is as follows:
Imports System.Web.Services
Public Class ClassName
Inherits WebService
‘Methods exposed by the Web service
End Class
ClassName is the name of the Web service class.
Metadata Attributes
Prior to this chapter, the term attributes has referred to the key/value pairs added to
HTML or ASP.NET server control tags to define specific properties of those entities or
controls. This chapter introduces the concept of Metadata attributes.
ASP.NET and the .NET Framework make extensive use of attributes to provide
programmatic plumbing for classes, as well as to add descriptive information to
managed assemblies. You can also use your own attributes (by creating classes
derived from System.Attribute) to add your own descriptive information to your classes.
Attributes in ASP.NET Web services, in addition to providing access to the XML
namespace used for the Web service, are used to tell ASP.NET that a given method is
to be exposed as a WebMethod. ASP.NET then provides all of the runtime functionality
necessary for clients of the Web service to access the WebMethod without requiring
the programmer to write additional code.
In C#, metadata attributes use the syntax [AttributeName(PropertyName=value)] and are
placed on the line before the entity they modify.
[ WebService(Namespace="http://www.aspnetsbs.com/webservices/")]
public class ClassName: WebService {
In both Visual Basic .NET and C#, multiple properties may be set in an attribute
declaration and are separated by commas.
WebMethod Properties
Property Description
BufferResponse Determines whether
output from the method
is buffered in memory
before being sent to the
client. The default is
True.
CacheDuration Allows the output of a
Web service method to
be cached on the
server for the specified
duration, in seconds.
The default is 0, which
turns off caching for the
method.
Description Sets a text description
of the Web service
method. This
description is shown in
the documentation
pages auto-generated
by ASP.NET, and it’s
included in the Web
Service Description
WebMethod Properties
Property Description
Language (WSDL)
contract for the Web
service.
EnableSession Determines whether
session support is
enabled for the Web
service method. Default
is False. Enabling
session state requires
the Web service class
to inherit from
WebService. Note that
enabling session state
for a Web service may
have a negative impact
on performance.
MessageName Allows methods to be
given alias names. This
can be useful when you
have an overloaded
method, in which the
same method name
has multiple
implementations that
accept different input
parameters. In this
case, you can supply a
different
MessageName for
each overloaded
method version,
allowing you to
uniquely identify each
one. The default is the
method name.
TransactionOption Allows adding support
for COM+ transactions
in a Web service
method. Allowable
values are Disabled,
NotSupported,
Supported, Required,
and RequiresNew. The
default is Disabled.
Note that this property
requires the use of an
@ Assembly directive
to load the
System.EnterpriseServi
ces assembly into the
Web service
application.
Creating a HelloWorld Web Service
Now let’s use the information you’re learned thus far to put together a working Web
service. This Web service will have one method, whose purpose is to return the string
value Hello, World!
1. In a folder defined as an application in IIS, create a new file called
HelloService.asmx.
2. Add the @ WebService directive to the file, specifying Visual Basic
.NET as the language and “Hello” as the name of the class that
implements the Web service.
<%@ WebService Language="VB" Class="Hello" %>
3. Add an Imports statement to import the System.Web.Services
namespace, since you will be inheriting from the WebService class
contained in this namespace later in this process.
Imports System.Web.Services
4. Add the class definition, including the WebService attribute (so that
you can modify the default namespace), and the Inherits statement
to inherit from the WebService class.
5. <WebService(Namespace:="http://www.aspnetsbs.com/webservi
ces/")> _
6. Public Class Hello
7. Inherits WebService
8. ‘ method declaration(s)
End Class
9. Add a method definition for a method called SayHello, with a return
type of String, that returns the string Hello, World!. Use the
WebMethod attribute to signify to ASP.NET that this method is a
Web service method.
10. <WebMethod()> _
11. Public Function SayHello() As String
12. Return "Hello, World!"
End Function
13. Save the file.
14. Browse the Web service by entering the URL for the .asmx file
(such as http://localhost/appname/HelloService.asmx). ASP.NET
will generate a set of pages that describes the available Web
methods and the syntax that clients can use to access those
methods. The output of the initial request to the Web service is
shown in the following figure.
15. Click the Service Description link. ASP.NET will return the WSDL
contract that describes the Web service. This contract can be used
by clients to determine how to interact with the Web service. WSDL
is discussed further in “Understanding WSDL Files” on page 418. A
portion of the WSDL contract output is shown in the following figure.
Click back in your browser after you finish looking at the WSDL
contract.
16. Click the link for the SayHello operation. ASP.NET will display the
syntax for SOAP, HTTP GET, and HTTP POST requests and
responses, as well as a button that allows you to invoke the
SayHello method. A portion of this output is shown in the following
figure.
17. Click the Invoke button. A new browser window will open in which
the XML result of the method invocation is returned. This output is
shown in the following figure.
The preceding example shows the implementation of a Web service in which the class
for the Web service is defined within the .asmx file. The following listings,
HelloService_CB.asmx and listing HelloService_CB.vb, show the code for implementing
the same Web service using code-behind. Remember that when using code-behind, the
class that implements the Web service must be manually compiled before the Web
service can be used. The following command can be used to compile the
HelloService_CB.vb code-behind class (note that all three lines make up a single
compilation command):
vbc /t:library /r:System.Web.dll /r:System.dll
/r:System.Web.Services.dll /out:bin\HelloServi ce_CB.dll
HelloService_CB.vb
Important Depending on which version of the .NET Framework you've
installed, you may need to add the path to the folder
containing the Visual Basic .NET compiler to your PATH
environment variable in order for the preceding command to
work. The procedure for locating and adding this path to the
PATH environment variable is detailed on page xv of the
“Installing the Sample Files ” section at the beginning of this
book.
HelloService_CB.asmx
<%@ WebService Class="ASPNETSBS.Hello_(B, HelloService_CB"%>
HelloService_CB.vb
Imports System.Web.Services
Namespace ASPNETSBS
<WebService(Namespace:="http://www.aspnetsbs.com/webservices/")> _
Public Class Hello
Inherits WebService
<WebMethod()> _
Public Function SayHello() As String
Return "Hello, World!"
End Function
End Class
End Namespace
Note that in the code-behind implementation, a namespace has been added to wrap the
Hello class. This is to avoid any possible naming conflicts with other assemblies that may
be used in the application.
Important The example Web services in this chapter all use the XML
namespace http://www.aspnetsbs.com/webservices/. As with
the default http://tempuri.org namespace, if you intend to
make your XML-based Web services publicly available over
the Internet, you should not use
http://www.aspnetsbs.com/webservices/ as the namespace
for your Web service. Instead, use a URI that you control,
and one that will allow you to be sure that your Web service
can be uniquely identified.
XML-based Web services in ASP.NET use the XML Schema Definition (XSD) draft
specification to specify the datatypes that are supported by ASP.NET Web services. This
includes primitive datatypes such as string, int, boolean, long, and so on, as well as more
complex types such as classes or structs.
The definition for a Web service method that adds two numbers and returns the result
would look like the following:
<WebMethod()> _
Public Function Add(Num1 As Integer, Num2 As Integer) As Integer
Return (Num1 + Num2)
End Function
30. Click the GetAuthors link. Then click the Invoke button to see the
XML returned by the Web service, which is a serialized dataset
containing both the schema and data for the Authors table.
Note These descriptions of the datatypes supported by XML-based
Web services in ASP.NET are specific to Web services that use
SOAP for requests and responses. When using HTTP GET or
HTTP POST for Web service requests, a more limited set of
datatypes is supported, all of which are represented to the client
as strings. Additionally, input parameters can only be passed by
value, while parameters for SOAP requests can be passed either
by value or by reference.
Once you’ve created a Web service using the techniques in either of the previous
examples, the Web service is ready to be accessed by clients. The challenge at this
point is how to give clients the information necessary to allow them to access your Web
service.
In an intranet-based scenario, this is relatively simple. You can provide potential internal
clients with the URL to your Web service directly, either by e-mailing it to those who need
it or by creating a centrally located Web page that users within your organization can
browse to find the appropriate URL. In an Internet-based scenario, however, you may
not know who your potential clients are. This makes it much more difficult to ensure that
they can find and use your Web services.
There are two solutions to this dilemma: Web services discovery, and Web services
directories using Universal Description, Discovery, and Integration (UDDI).
Chapter13.disco
<?xml version="1.0" encoding="utf-8" ?>
<discovery xmlns="http://schemas.xmlsoap.org/disco/">
<contractRef ref="AuthorsService.asmx?wsdl"
docRef="AuthorsService.asmx"
xmlns="http://schemas.xmlsoap.org/disco/scl/" />
<contractRef ref="HelloService.asmx?wsdl"
docRef="HelloService.asmx"
xmlns="http://schemas.xmlsoap.org/disco/scl/" />
</discovery>
Note that for the sake of readability, relative URLs were used in this listing. Discovery
documents can contain either relative or absolute URLs. When relative URLs are used,
they are assumed to be relative to the location of the URL of the discovery document.
The model for UDDI is for multiple identical versions of the UDDI business registry to be
hosted by multiple participants, providing redundancy and load balancing for the registry.
Web service providers can register their Web services at any node and the information
will be replicated to all registries daily, allowing potential clients to find these services
from any registry.
Currently, both Microsoft and IBM provide online UDDI registries, and both can be
reached from http://www.uddi.org/. You can also find a number of helpful white papers
on UDDI there.
One challenge of implementing a Web service comes when you need to limit who can
access it. You may wish to limit access to only those clients who have paid a
subscription fee, for example. Or, if your Web service stores and uses data that’s specific
to individual users, you might want to implement some sort of login process to allow
clients to view and modify only data that is specific to them.
One potential issue with this is that the SOAP protocol, on which Web services are
based, does not provide any specifications for securing Web services. This means that
it’s up to you to choose a means of securing your Web service that provides the level of
security you need but that’s also relatively easy to implement, both for you and your
clients. The last thing you want is to make your Web service so difficult to use that
potential clients go elsewhere.
The advantage of using one of the IIS authentication mechanisms is that the
infrastructure for them already exists. You do not need to create your own credential
store for user account information. One disadvantage is that each of the authentication
methods offered by IIS requires the creation of NT accounts for each client of your
application. This can be difficult to administer, depending on how your Web server is set
up (whether it is part of an NT domain, whether it uses Active Directory, and so on).
Another disadvantage is that the Integrated Windows authentication method, while
secure, requires the client to belong to the same domain (or a trusted domain) as the
server, making it impractical for Internet use. Basic authentication is suitable for Internet
use, but it sends username and password as clear text, making it easy for them to be
compromised. It can use SSL encryption to prevent this problem, but this may add an
unacceptable performance overhead if user credentials must be passed with each Web
service request.
Finally, you can “roll your own” Web services authentication by implementing a login
function in one or all of your Web services, with the login method returning a unique key
(which should be hashed or encrypted) identifying the logged-in user. This key is then
passed as a parameter of each Web service request, indicating that the user is an
authenticated user. In order to protect the username and password, the login Web
service method should be requested over an SSL connection. Subsequent requests can
be made over a standard HTTP connection, however, so as to avoid the performance
overhead of SSL encryption. To reduce the risk of a user being impersonated by
someone who has intercepted the login key, these keys should be stored in such a way
that they can be easily expired after a predetermined timeout value (in a back-end
database, for example).
While this authentication method is more difficult to implement and maintain than some
of the others, it’s the simplest for your clients to use. Once you have given a client his
account information (username and password), all he needs to do is make the request to
the login Web service (over an SSL connection) and store his login key. With each
subsequent request, he passes the key as one of the parameters of the Web service
method he’s calling. If the timeout value expires between one request and the next, the
user must call the login method again to receive a new key. Therefore, the value should
be set to provide a balance between security and convenience. In addition to the login
method, you could also implement a logout method in your Web service to explicitly
expire the login key.
Important In addition to controlling access to a Web service with
authentication, you should also consider using SSL to
encrypt communications if you are passing sensitive data to
or from the Web service. This includes usernames and
passwords sent when authenticating users.
Nonetheless, you still need to know how to locate and use a Web service, including the
following:
§ Locating a Web service, using either a discovery document or UDDI
§ Using the WSDL.EXE utility to create a proxy class for the Web service
§ Using the proxy class from an ASP.NET page or console application to access
the Web service
The first step in using an XML-based Web service is to locate it. If you’re dealing with a
Web service that you or someone in your organization created, you may already know
the location of the Web service. If so, you can skip this step.
Locating a Web service is essentially the mirror image of advertising a Web service, so
the same techniques apply here. There are two techniques for locating a Web service in
ASP.NET: discovery documents and UDDI.
Locating a Web Service with a Discovery Document
In order to use a discovery document to locate Web services, you need the URL for the
discovery document that describes the services in which you’re interested. You might get
the URL for the document from a listing on an organization’s public Web site, from an e-
mail promotion, or from a catalog.
Once you have the URL for a discovery document, you can use the disco.exe command
line utility to generate WSDL contracts for using the Web services described in the
discovery document. The syntax for the disco.exe utility is
disco [options] URL
Here, options represents one or more command-line options for the utility, and URL
represents the URL to the discovery document. The options for the disco.exe utility are
shown in the following table.
disco.exe Options
Option Description
/d:domain or Sets the
/domain: domain
domain to
use when
connecting
to a site that
requires
authenticati
on.
/nosave Prevents
disco.exe
from saving
files for the
discovered
WSDL and
discovery
documents.
/nologo Prevents the
start-up
banner from
being
displayed.
/o:directory or Provides the
/out:directory
directory
name where
output
should be
saved.
/p:password or Sets the
/password:password password to
use when
connecting
to a site that
requires
authenticati
on.
/proxy:url Sets the
proxy to use
for HTTP
disco.exe Options
Option Description
requests.
/pd:domain or Sets the
/proxydomain:domain
domain to
use when
connecting
to a proxy
server that
requires
authenticati
on.
/pp:password or Sets the
/proxypassword: password password to
use when
connecting
to a proxy
server that
requires
authenticati
on.
/pu:username or Sets the
/proxyusername:username username to
use when
connecting
to a proxy
server that
requires
authenticati
on.
/u:username or Sets the
/username:username
username to
use when
connecting
to a site that
requires
authenticati
on.
/? Provides
help for
command-
line options.
WSDL contracts are similar to COM type libraries or .NET assembly metadata in that
they are used to describe the public members of the Web service, including all publicly
available methods, the parameters (and datatypes) those methods expect when called,
and the return type of any return value. Because this information is encoded in a
standard XML-based format, any client that can read XML and can understand WDSL
and SOAP should be able to effectively consume the Web application that the WSDL
contract describes.
Important The SOAP and WSDL specifications are still in the process
of being standardized. In the short term, this means that
there may be inconsistencies between different
implementations of WSDL on different platforms, since these
implementations may be based on different drafts of the
proposed standard. Until SOAP and WSDL become settled
standards (W3C Recommendations), you may need to tweak
your WSDL contracts to achieve interoperability between
different Web services implementations. This should become
a nonissue once these specifications have received approval
as W3C Recommendations. In the meantime, there is an
interesting article on interoperability between various Web
services implementations on the MSDN Library Web site at
http://msdn.microsoft.com/library/en-
us/dn_voices_webservice/html/service08152001.asp.
Consuming a Web service via SOAP is a fairly complex affair. It involves creating and
formatting a SOAP envelope, a set of SOAP headers, and a SOAP body, all with the
correct elements and attributes for the Web service you’re targeting. Fortunately, thanks
to the built-in support for Web services in the .NET Framework, all of the plumbing
necessary for accessing a Web service is provided for you. The .NET Framework
provides another command-line utility, wsdl.exe, to help you by creating a .NET proxy
class for a Web service described by a WSDL contract.
Using the wsdl.exe Utility
The wsdl.exe utility generates a proxy class in C#, Visual Basic .NET, or JScript .NET for
a WSDL contract specified by URL or path. This proxy class can be instantiated and its
methods called by any client, just like any other .NET class.
wsdl.exe Options
Option Description
/appsettingurlkey:key or Sets the key of the configuration setting
/urlkey:key
from which to read the default URL for
generating code.
/appsettingbaseurl:baseurl or Sets the base URL to use for calculating
/baseurl:baseurl relative URLs when a URL fragment is
specified for the URL argument.
/d:domain or Sets the domain to use when connecting to
/domain: domain a site that requires authentication.
/l:language or Sets the language to use when generating
/language:language the proxy class. Valid values include CS,
VB, JS, or the fully qualified name of any
class that implements the
System.CodeDom.Compiler.CodeDomProvi
der class. Default is CS (C#).
/n:namespace or Sets the namespace to be used when
/namespace:namespace generating the proxy class.
Option Description
same interfaces as specified in the WSDL
contract.
/u:username or Sets the username to use when connecting
/username:username
to a site that requires authentication.
/? Provides help for command-line options.
Take the following steps to create proxy classes in Visual Basic .NET for the
HelloService and AuthorsService example Web services created earlier in this chapter:
1. Open a command line and navigate to the directory where you would
like the proxy classes to be generated. (Or, you can use the /o
option to specify a path and file name for saving the proxy class.)
2. Execute the wsdl.exe utility, using the /l option to specify Visual Basic
.NET as the language and passing the URL to the
HelloService.asmx file, including the ?WSDL parameter, which tells
ASP.NET to return the WSDL contract for the .asmx file. (Both lines
below should form a single command.) The URL you specify should
match the location of the .asmx file on your Web server, and it may
differ from the following URL.
3. wsdl /l:vb /n:ASPNETSBS
http://localhost/aspnetsbs/Chapter13/HelloService.asmx?WSDL
4. Execute the wsdl.exe utility again for AuthorsService.asmx, using the
same syntax as shown in step 2 but modifying the URL to point to
AuthorsService.asmx.
5. Optionally, you can create a batch file (with the .bat extension)
containing both of the preceding commands. If you want to see the
results of the batch execution before the command window closes,
add a pause command at the end of the batch file.
Once you’ve created your proxy class(es), you’ll need to compile the class(es) using the
command-line compiler for the language you chose to generate (in this example, Visual
Basic .NET). The following listing, MakeServices.bat, shows the command-line compiler
syntax to compile these classes and output the resulting assembly to the bin
subdirectory of the directory from which the command is run. Note that this listing
represents a single command and has been wrapped to multiple lines for readability.
MakeServices.bat
vbc /t:library /r:System.Web.dll /r:System.dll
/r:System.Web.Services.dll /r:System.Xml.dll /r:System.Data.dll
/out:bin\Services.dll Authors.vb Hello.vb
The following listing, AuthorConsole.vb, shows all of the code for the console application.
AuthorConsole.vb
Imports ASPNETSBS
Imports System
Imports System.Data
Imports System.Web.Services
Imports System.Xml
Imports Microsoft.VisualBasic
Create an Create a file with the .asmx extension and add the @
XML-based WebService directive to the file. Create a class in the file to
Web implement the Web service, adding the WebMethod attribute to
each method you wish to expose from the Web service.
service
using
ASP.NET
Test a Web Browse the .asmx file containing the Web service from a Web
service browser. ASP.NET will generate documentation pages for the
Web service, as well as allowing you to invoke the Web service.
Advertise a Either create a discovery document for your Web services and
Web publish the document to a public location, such as your
service organization’s Web site, or register your Web services in one of
the publicly available UDDI business registries, making sure to
provide the URL to the WSDL contract for each Web service you
register.
Consume a Use the wsdl.exe utility to create a proxy class based on the
Web WSDL contract for the Web service, compile the proxy class,
service and instantiate the proxy class from your client application as
from you would any other .NET class.
ASP.NET
Performance
In This Chapter, You Will Learn About
§ Taking advantage of output caching for Web Forms pages and user controls
§ Taking advantage of caching for data retrieved with ADO.NET
§ Using the Cache APIs to get fine-grained control over the expiration,
dependencies, and other properties of cached items
The good news about ASP.NET is that its improvements, from the use of compiled
languages rather than interpreted languages to the complete rewrite of the ASP.NET
execution engine, make it easy to get the performance that many applications need.
Well- designed applications written in ASP.NET will almost always be faster than the
equivalent applications written in classic ASP.
But there is still a class of applications for which these performance improvements alone
will not be sufficient. These may be applications whose pages are computationally
intensive (such as pages that use recursive procedures) or access large amounts of
data, or applications that need to scale to large numbers of concurrent users.
ASP.NET offers new features for this class of applications, including a simple API for
output caching and a rich and robust caching engine and API.
Understanding Caching
Caching is the process of temporarily storing expensive resources in memory. These
resources may be expensive because of the amount of processor time necessary to
render them, because they reside on disk, or because they reside in a back-end
database on another machine.
Pages that are expensive to render can consume so much processor time that, beyond a
certain number of users, the Web server becomes unable to fulfill additional requests in
a timely fashion. Retrieving pages and other content from disk is much slower than
serving them from memory, and this performance difference is magnified when the
server needs to fulfill many requests simultaneously. Requests for large amounts of data
from a back-end database often encounter both disk-based and network-based
performance delays. (This makes calls from one server to another substantially more
expensive than fulfilling a request on the same machine.)
For these reasons, Web applications that need high performance have long used
caching to mitigate the performance costs of expensive operations. The output from
recent requests, or requested data, is cached in memory. Subsequent requests are
served from this in-memory cache, rather than rendering a page or querying a database
again. This can substantially improve the performance and scalability of an application.
Unfortunately, in the past this has come with a cost. Developers using classic ASP were
limited to using the Session and Application intrinsic objects for caching, or to rolling their
own caching engine.
ASP.NET now offers a rich, robust framework for caching both page output and arbitrary
data that developers can access easily using both declarative and programmatic means.
ASP.NET output caching allows developers to declaratively cache page and user control
output to avoid wasting resources in repeatedly rendering content that does not need to
change from request to request. The cache API provides a number of methods that allow
developers to add items to the cache, remove items from the cache, and set properties
that determine the lifetime of items stored in the cache.
@ OutputCache Attributes
Attribute Description
Duration Sets the number of seconds to
cache the content. This attribute is
required.
Location Sets the location to be used to cache
content. This can be one of the
following values:
Any
Content can be cached by any
cache-aware application (such as a
proxy server) in the request stream,
by the Web server, or by the client
browser.
Client
Content is cached by the requesting
client’s browser.
Downstream
Content can be cached by any
cache-aware application
downstream of the Web server
fulfilling the request.
Server
Content is cached on the Web
server.
None
No caching.
Default is Any.
This attribute may be used with Web
Forms pages only.
VaryByControl Sets the names of properties of a
user control by which to vary the
caching of the user control output.
Multiple properties are separated by
semicolons.
This attribute may be used with user
controls only.
VaryByCustom Allows varying of cached content by
browser user agent, browser
capabilities, or custom logic. When
set to any value other than browser,
requires that the
HttpApplication.GetVaryByCustomSt
ring be overridden in Global.asax in
order to implement the custom logic.
VaryByHeader Allows varying of cached content
based on the value of one or more
@ OutputCache Attributes
Attribute Description
HTTP request headers. Multiple
properties are separated by
semicolons.
This attribute may be used with Web
Forms pages only.
VaryByParam Allows varying of cached content
based on the value of one or more
querystring values sent with a GET
request, or form fields sent with a
POST request. Multiple key or field
names are separated by semicolons.
You can also set the value of this
attribute to * to vary by all
parameters, or to None to use the
same cached page regardless of
GET/POST paramet ers.
This attribute is required.
Among the most important features of the @ OutputCache directive are the VaryByX
attributes. These attributes make it possible to cache pages and user controls even
when those pages are dynamically generated based on querystring values, form field
values, HTTP header values, or even browser capabilities.
The Duration attribute lets you limit the length of time that output is cached. For pages
that are updated periodically, but not as frequently as every request, the Duration
attribute allows you to fine-tune the output caching so that you can maximize the benefit
gained by caching while being reasonably sure that users will not get stale content. For
example, if you had a page that, on average, was updated every two hours, you could
set the Duration attribute of the @ OutputCache directive to 15. This would cache the
output of the page for 15 minutes from the last time the page was requested (subject to
the VaryBy… attributes). This would ensure that the page would only need to be
rendered once every 15 minutes, regardless of how many requests were received for the
page, and that there would only be a 15-minute window in which users could get a
cached page rather than updated data.
Finally, the Location attribute determines where the output cache is located. Setting
certain values for the Location attribute causes ASP.NET to set the If Modified-Since
HTTP header to allow caching of page or control output.
FragmentCache.aspx
<%@ Page Language="VB" %>
<%@ Register TagPrefix="ASPNETSBS" TagName="UC"
Src="FragmentCache.ascx" %>
<html>
<head>
<script runat="server">
Sub Page_Load()
Label1.Text = Now()
End Sub
</script>
</head>
<body>
<asp:Label id="Label1" runat="server"></asp:Label>
<p>The text above was rendered from the Web Forms page.</p>
<ASPNETSBS:UC runat="server"/>
</body>
</html>
3. Add the following code to FragmentCache.ascx and save the file.
FragmentCache.ascx
<%@ Control Language="VB" %>
<%@ OutputCache Duration="10" VaryByParam="none" %>
<script runat="server">
Sub Page_Load()
Label1.Text = Now()
End Sub
</script>
<asp:Label id="Label1" runat="server"></asp:Label>
<p>The text above was rendered from the User Control.</p>
4. Browse FragmentCache.aspx. The time displayed by the page and
the user control should be identical, as shown in the following figure.
If you click the browser’s Refresh button several times, you’ll notice that the
time displayed by the page is updated with each page request, as shown in
the following figure, while the time displayed by the user control is updated
only once every 10 seconds.
Caching Multiple Versions of Output
If your page uses either querystring values (GET requests) or form fields (POST
requests) to dynamically generate content, you can still use output caching by adding the
VaryByParam attribute to the @ OutputCache directive with the values set to the names
of the parameters by which to vary the cache. ASP.NET will cache a copy of the output
from the page for each unique combination of parameters, so any user who requests the
page within the period specified by the Duration attribute with the same set of
parameters as a previous request will have his request fulfilled from the cache.
Important Because ASP.NET will cache a copy of the output for each
unique combination of parameters, you should avoid using
an excessive number of parameters to vary the cache. This
can result in a great deal of memory use. This caution also
applies to the VaryByHeader and VaryByControl attributes as
well.
To use the VaryByParam attribute, follow these steps:
1. In the same directory as the previous example, create a new file
named VaryByParam.aspx.
2. Add the standard HTML tags, including a <form> tag pair with the
runat=“server” attribute, and @ Page directive.
3. Within the <form> section, add an ASP.NET Label control and a
Textbox control, as well as an HtmlGenericControl (a paragraph tag
with a runat=“server” attribute) with a static message.
4. <form runat="server">
5. <asp:label id="Label1" runat="server">Enter your name:
6. </asp:label>
7. <asp:textbox id="Name" runat="server"/>
8. <p id="P1" visible="false" runat="server">The text above was
9. rendered with an ASP.NET Label Server Control, then the output
10. was cached for 30 seconds.</p>
</form>
11. Add a <script> block to the <head> section of the document
containing a Page_Load event handler. In the event handler, set the
Visible property of the Textbox control to False, set the Visible
property of the HtmlGenericControl to True, and set the Text
property of the Label control to a greeting that includes the name
entered in the Textbox control and the current time/date.
12. <head>
13. <script runat="server">
14. Sub Page_Load()
15. If IsPostBack Then
16. Name.Visible = False
17. P1.Visible = True
18. Label1.Text = "Hello, " & Name.Text
19. Label1.Text &= ". The Current date and time is " &
20. Now()
21. End If
22. End Sub
23. </script>
</head>
24. Add an @ OutputCache directive to the page, specifying the
duration as 30 seconds and the parameter to vary by as the name
of the TextBox control.
25. <%@ OutputCache Duration="30" VaryByParam="Name" %>
26. Save and browse the page. The initial output should look like the
following figure.
Type a name in the text box, and then press the Enter key to submit the page.
The result should look like the following figure.
If you click the browser’s Refresh button (and click Retry or OK to repost the form), the
page will refresh from the cache. If you click Back and enter a different name, you will
get a fresh version of the page. Try entering different names, refreshing, and then going
back and entering the same names as earlier requests. You should see that all requests
with the same name are cached for 30 seconds.
Using Response.Cache
In addition to using the @ OutputCache directive, you can also enable output caching for
a page using the methods of the HttpCachePolicy class, which is exposed by the
Response.Cache property.
The code required to perform the same output caching as shown in the
OutputCache.aspx example is shown in the following listing.
ResponseCache.aspx
<%@ Page Language="VB" %>
<html>
<head>
<script runat="server">
Sub Page_Load()
Label1.Text = Now()
Response.Cache.SetExpires(DateTime.Now.AddSeconds(10))
Response.Cache.SetCacheability(HttpCacheability.Public)
End Sub
</script>
</head>
<body>
<asp:Label id="Label1" runat="server"/>
<p>The text above was rendered with an ASP.NE T Label Server
Control, Then the output was cached for 10 seconds using the
Response.Cache methods.</p>
</body>
</html>
This code is equivalent to an @ OutputCache directive with a duration of 10 and a
location of Any (or no location attribute). To set the cache location to client, you would
use the HttpCacheability.Private enumeration member instead of
HttpCacheability.Public. To turn off caching entirely, you would use
HttpCacheability.NoCache.
In addition to the SetExpires and SetCacheability methods shown in listing
ResponseCache.aspx, you can also use the SetNoServerCaching method in
combination with the code from ResponseCache.aspx to provide the equivalent of an @
OutputCache directive with a duration of 10 and a location of Downstream.
CacheAuthors.aspx
<%@ Page Language="vb" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<head>
<script language="vb" runat="server">
Sub Page_Load()
Dim DS As DataSet = Cache("myDS")
If Not DS Is Nothing Then
Msg.Text = "Dataset retrieved from cache"
Else
DS = GetData()
Cache("myDS") = DS
Msg.Text = "Dataset retrieved from database"
End If
MyGrid.DataSource = DS.Tables(0).DefaultView
MyGrid.DataBind()
End Sub
Function GetData() As DataSet
Dim myDS As New DataSet()
Dim ConnStr As String
ConnStr = "server=(local)\ NetSDK;"
ConnStr &= "database=pubs;Trusted_Connection=yes"
Dim SQLSelect As String
SQLSelect = "SELECT au_id, au_lname, au_fname, "
SQLSelect &= "zip FROM Authors WHERE zip = '94609'"
Dim mySqlConn As New SqlConnection(ConnStr)
Dim mySqlDA As New SqlDataAdapter(SQLSelect, ConnStr)
mySqlDA.Fill(myDS)
Return myDS
End Function
</script>
</head>
<body>
<form runat="server">
<asp:label id="title" text="DataSet Caching Example"
font-name="Verdana" font -size="18" runat="server"/>
<asp:datagrid id="MyGrid" bordercolor="black"
borderwidth="2" cellpadding="3" runat="server">
<headerstyle backcolor="silver" font-bold="true"/>
<itemstyle backcolor="black" forecolor="white"/>
<alternatingitemstyle backcolor="white" forecolor="black"/>
</asp:datagrid>
<asp:label id="Msg" font-style="italic" runat="server"/>
</form>
</body>
</html>
The output of the page on first execution is shown in the following figure.
The output of subsequent requests is shown in the following figure.
First create a trigger on the Authors table that uses the SQL Server Web Assistant to
write a new HTML file when the data in the table changes.
1. Open SQL Server Enterprise Manager and select the Tables node of
the Pubs database. (This should be the Pubs database in your SQL
Server instance, rather than the NetSDK MSDE instance you’ve
used for other examples.)
2. Right -click the authors table in the table list and select All Tasks,
Manage Triggers.
3. Make sure that the <new> item is showing in the Name drop-down
list. Add the following text to the trigger, replacing filepath with the
path to the directory where you have saved the examples for this
chapter and the file name authors.htm, and click OK.
4. CREATE TRIGGER WriteFile ON [dbo].[authors]
5. FOR INSERT, UPDATE, DELETE
6. AS
7. EXEC sp_makewebtask filepath, ’SELECT * FROM authors'
Now you need to follow these steps to create the page that will use the cache:
1. Start by opening the CacheAuthors.aspx example file created earlier
in this chapter and saving a copy as AuthorsTrigger.aspx.
2. Replace the line that reads Cache(“myDS”)=DS with the following
code, which adds a file dependency on the file created by the SQL
Web Assistant:
3. Cache.Insert("myDS", DS, New _
CacheDependency(Server.MapPath("authors.htm")))
4. Modify the connection string in the file to point to the SQL Server
instance where you created the trigger.
5. Save and browse the file. The result should look like the following
figure.
6. If you click the Refresh button on the browser, the data will be served
from the cache and the output will look like the following figure.
Finally to make sure this works, open the Authors table in SQL Enterprise Manager
(right-click the Authors table and select Open Table, Return all rows) and change one of
the values in a row in the table that matches the query criteria for CacheAuthors.aspx
(zip=9469). (Move the cursor off the row being updated to make sure the update occurs.)
Now refresh the browser. The output should look like the first figure.
Expire a cache item when another item changes Insert the item
to be expired
with a key
dependency on
the other cache
item.
Deployment is another one of the areas where the improvements in ASP.NET truly
shine. ASP.NET eliminates many of the deployment shortcomings in classic ASP,
making it possible for the first time to deploy an ASP.NET application simply by copying
all of the necessary files to the IIS application directory where the application will be
deployed.
This chapter will discuss the steps for deploying ASP.NET Web applications, both
manually and through Visual Studio .NET.
Each ASP.NET application needs to have a corresponding IIS application root to function
properly. The application root provides the boundary for the application. The physical file
system directory that maps to the application root is the location where the root
Web.config file for the application, as well as the application’s bin directory (which
contains all managed assemblies related to the application) and the Global.asax file (if
you choose to use one), are deployed. If the IIS directory that is the root of the
application is not configured as an application root, certain configuration settings may
fail, and the assemblies located in the bin directory (if present) will not be loaded.
4. Click OK to update the directory with the new setting. The directory
should now show the icon for an application root, as shown here.
As you can see in these figures, IIS application roots can be nested one beneath the
other. However, each application root defines its own application boundary. One
advantage of this is that it allows you to partition your application as necessary. For
example, you can provide customized configuration settings or use different versions of a
private assembly with different parts of your application. The most important thing to
remember, however, is that the application root is where the bin directory and
Global.asax and Web.config files should be located.
There is only a single file path for a given application root in IIS. However, there may be
more than one directory in IIS that maps to a single physical directory in the file system.
This allows you to set up URLs for both public and private access to a given application,
with different IIS settings for each. If more than one IIS application root points to the
same physical directory in the file system, each IIS application root will have its own IIS -
specific settings. It is important to note, however, that if the physical directory contains
ASP.NET-specific configuration and start-up files (Web.config and Global.asax), the
settings in these files will be shared by both IIS applications.
One important caveat to this sharing is that when a subfolder containing a Web.config
file and/or Global.asax file is defined as an application root in one of the IIS applications,
it’s not configured as an application root in the second IIS application. In the first case,
which is illustrated by the following figure, requests to this subfolder will use the
Global.asax file for start -up code.
In the second case, illustrated by the following figure, the Global.asax file contained
within this subfolder will be ignored.
You should also note that there are certain settings in the Web.config file that are only
applicable at the application root level. For example, if the Web.config file contained in
the Chapter15 folder illustrated in the preceding figure contains an <authentication>
section, a configuration error will result because this section can only be defined at the
machine (Machine.config) or application root level.
The URL for a specific application root in IIS is a product of the domain name (if any)
associated with the IP address that is assigned to the IIS Web site, plus the folder
hierarchy between the IIS Web site and the application root. Thus, in the previous figure,
where the aspnetsbs application root resides under the default Web site, the URL to
reach the Chapter15 directory would be either http://localhost/aspnetsbs/Chapter15/ or
http://servername/aspnetsbs/Chapter15/.
This works because requests for localhost or servername (where servername represents
the name of the Web server) are automatically directed to the default Web site. If you
wanted to make the content of the Chapter15 application root available on the Internet,
you’d need to create a new IIS Web site, assign a publicly available IP address on the
machine to the Web site, and then create (or have a DNS provider create) a DNS entry
that maps your chosen domain name (say, www.aspnetsbs.com) to that IP address.
Then you would create a new application root under the new Web site that maps to the
Chapter15 directory in the file system. Then you could access this application from the
Internet using the URL http://www.aspnetsbs.com/Chapter15/.
Note that if you have directory browsing disabled (the default), you will need to set a
default document on the documents tab of the Properties dialog for the directory, in case
the user does not enter a document name. The name of the default document should
match the name of a page that exists in the directory. This will allow access to content
using a URL that does not contain a document name, such as the preceding URLs.
Otherwise, the name of the desired file must be appended, or an error will occur.
One of the challenges with deploying most non-trivial Web applications in classic ASP is
figuring out where to store application-specific configuration information. In classic ASP,
there were a number of approaches, including using custom keys in the system registry,
storing configuration information in a back-end database, reading configuration settings
from a custom configuration file, and building settings into components. Each approach
has its own set of drawbacks that make it less than ideal.
For example, storing configuration information in the system registry makes that
information relatively secure (particularly if proper registry security procedures are
followed). However, adding that information to the registry or modifying it from a remote
machine may be difficult, if not impossible, due to permission settings designed to
protect the registry from remote tampering. Additionally, accessing settings within the
registry can be expensive from a performance standpoint.
To simplify this situation, ASP.NET provides a special section of the Web.config and
Machine.config files called appSettings, which allows you to store application-specific
configuration settings as a set of key/value pairs.
<appSettings>
<add key="myConfigKey" value="myConfigValue" />
</appSettings>
When your application starts up, ASP.NET caches the values of the appSettings section
in a string collection called ConfigurationSettings.AppSettings. These settings can be
accessed by passing the desired key to this collection, as shown here:
Label1.Text = ConfigurationSettings.AppSettings("myConfigKey")
In addition to automatically loading and caching these values for you at application start-
up, ASP.NET monitors the Web.config file(s) for changes and dynamically reloads the
appSettings if changes are detected.
Important While appSettings provides a storage location for application-
specific configuration settings that is almost ideal in its ease
of use and performance, it should be noted that since the
values are stored as plain text in a file in the Web space,
there is some security risk associated with this approach.
If a vulnerability in the Web server allowed access to the
Web.config file, any sensitive information stored in
appSettings could be compromised. For this reason, it is
important to avoid storing sensitive information, such as
usernames, passwords, or credit card or account numbers, in
the appSettings section of Web.config. For items of medium
sensitivity, one solution would be to add the items to the
appSettings of the Machine.config file, which is not located
within the Web space, making it harder to compromise. Keep
in mind, however, that the appSettings stored in
Machine.config are available to every application on the
server, unless they are locked down using the <location> tag.
(See Chapter 7 for more information on locking down
configuration settings.)
In order to successfully deploy an ASP.NET application, the first thing you need is a
target directory on the server that will host the application. This directory must be set up
in IIS as an application root if you want to use the automatic loading of assemblies in the
bin subdirectory or use a Global.asax file, and if you want to have a root Web.config file
for the application. (Certain configuration sections cannot be set below the application
level, and they will cause errors if used in a Web.config file that resides in a
subdirectory.)
Once you’ve set up the target directory in IIS (mapped to a physical folder on the host
machine), you’re ready to copy all the necessary files to the target directory. This can be
done using any number of commonly available tools, from the XCOPYcommand to
WebDAV. You can even drag and drop files to and from a network share using Windows
Explorer.
If you’re using code-behind in your Web Forms pages and/or user controls, you’ll need to
decide whether or not to deploy the code-behind class files containing your source code
to the target server. If you’re using the Src attribute of the @ Page or @ Control directive
to have ASP.NET dynamically compile your code-behind classes, you will have to deploy
the class files or the application will not function. If you want to avoid deploying the code-
behind class files, you can use the Inherits attribute of the @ Page or @ Control directive
instead of the Src attribute. Recall from Chapter 9 that using the Inherits attribute
requires you to manually compile your code-behind classes and put the resulting
assemblies in the application’s bin subdirectory.
Deploying Content
Deploying static content (such as HTML pages and images), Web Forms pages, user
controls, and code-behind classes is as simple as copying them from the development
application directory to the deployment target directory. For example, you would use the
following command to deploy content from a local directory
C:\inetpub\wwwroot\myDevApp to a remote directory for which a mapped drive, X:, has
been created:
xcopy c:\inetpub\wwwroot\ASPNetApp1 x:\ /E /K /O
You can find out about all of the command-line parameters available with XCOPY by
executing XCOPY /?.
Deploying Assemblies
One of the areas of great improvement in deployment is .NET managed assemblies over
COM components. With classic ASP applications that used custom COM components,
not only did you need to copy the component file to the machine on which the application
was being deployed, but you also needed to register that component in the system
registry, which was difficult to do remotely. Additionally, registering a component made it
available to any application on the server, which is not allowed in a shared server
environment.
Global Assemblies
The exception to the rule of XCOPY deployment applies to assemblies that you want to
make available to more than one application on the server without having multiple copies
of the assembly (one for each application). These assemblies need to be installed in the
GAC. Remember that assemblies installed in the GAC must be strongly named. This can
be done by using the al.exe command-line tool. Once you have a strongly named
assembly, there are several ways to install it into the GAC.
§ Windows Installer 2.0 This is the preferred method for installing
assemblies into the GAC because it performs reference counting and
provides transactional installation, neither of which are available with
the other options. See the Windows Installer documentation for more
information on packaging and installing applications with Windows
Installer 2.0.
§ gacutil.exe This command-line utility lets you add assemblies to the
GAC, remove assemblies from the GAC, and list the contents of the
GAC.
§ Windows Explorer The .NET Framework installs a shell extension
that lets you simply drag and drop strongly named assemblies into the
GAC from any Windows folder. The GAC is located at
%WinDir%\assembly.
Updating Assemblies
In addition to the problems of registering COM components, IIS locked any COM
component as soon as it was called. Because of this, replacing or updating a COM
component often required stopping the IIS Web service, unregistering the old
component, copying over the new component, registering the new component, and
restarting the Web service. For many applications, this meant an unacceptable amount
of downtime.
With private assemblies, this is no longer a problem because of the way ASP.NET loads
assemblies. Unlike IIS under classic ASP, which loaded a component into memory and
locked it on disk until the Web application (or IIS) was shut down, ASP.NET first makes a
shadow copy of each assembly file and then loads and locks the shadow file instead of
the original. ASP.NET then monitors the original files for changes. If changes are
detected, ASP.NET fulfills any existing requests with the current copy of the assembly,
and then it loads the new assembly and fulfills new requests using the updated
assembly, discarding the old assembly. This allows you to easily update any private
assembly used by your application without needing to shut down the Web server, even
temporarily.
Once you’ve copied your content and assemblies to the target directory, it’s a good idea
to double-check the IIS permissions on any subdirectories in the application. Fortunately,
ASP.NET actually takes care of one of the most important checks for you. It
automatically removes all permissions on the bin subdirectory of the application,
effectively preventing the contents of the directory from being listed, read, downloaded,
or executed.
Now, preventing IIS from executing the assemblies on behalf of clients might sound like
a bad thing, but it’s not. In fact, when a request comes in for a resource that ASP.NET
handles, such as an .aspx, .ascx, or .asmx file, IIS simply hands the request over to
ASP.NET, which processes the request from there. Since ASP.NET alone is responsible
for loading assemblies, preventing IIS from attempting to execute assemblies is a good
thing.
If you have subdirectories that contain static content, such as images, you should ensure
that the only permission allowed on these subdirectories is Read.
Most importantly, if you need to allow users to upload content to your Web application, it
is absolutely essential that you set up a separate subdirectory for this purpose, and set
only the Write permission. Read must not be allowed on this subdirectory, nor should the
Execute permissions be set to anything other than None. Failure to follow these
guidelines could allow an attacker to upload and execute hostile code in your application.
You can view and modify the IIS permissions for any directory by right-clicking the
directory name in Internet Services Manager and selecting Properties. The permissions
are set on the Directory (or Virtual Directory) tab, which is shown in the following figure.
Deployment Options in Visual Studio .NET
If you are developing Web applications with Visual Studio .NET- it is still possible to
manually deploy your applications using XCOPY- WebDAV- etc. However- you will
probably find that it is more efficient to take advantage of one of the built-in deployment
features available in the Visual Studio .NET IDE. These include the copy project
command- which provides a fast and simple method for copying a project from one
machine to another or from one directory to another on the same machine- and the
Setup and Deployment Projects type- which provides much more fine-grained control
over how your application is deployed.
You can copy files either to a URL using the FrontPage server extensions- or to a file
share. The Copy Project dialog is shown in the following figure.
Using Copy Project is simple and straightforward- but it offers only limited control over
which project files are copied and how they're copied.
Using a Web Setup Project to Deploy a Web Application
Another method that Visual Studio .NET offers for deploying Web applications is the
Web Setup Project- which is one of the templates available in the Setup and Deployment
Projects project type. The following figure shows the Web Setup project selected in the
Add New Project dialog box.
A Web Setup project allows you to specify a variety of options about how the application
will be deployed- ranging from which files to include to the name of the deployed
application- and the location of the deployed files. When built- the Web Setup project
creates a Windows Installer .msi file that can be copied to the target machine and
executed to deploy the application.
You can use the following steps to use a Web Setup project to deploy a Web application.
1. Open an existing Web application solution in Visual Studio .NET.
2. Right -click the solution name in the Solution Explorer window- and
select Add- New Project. The Add New Project dialog will appear.
(See the figure on page 456. )
3. Select the Setup and Deployment Projects type- select the Web Setup
Project template- and then click OK to accept the default project name.
The File System editor for the Web Setup project will appear.
4. In the Properties window- modify the ProductName property to match
the name of the Web application project you opened in step 1.
5. Right -click the Web Application Folder node in the left pane of the File
System editor- and click Add- Project Output. The Add Project Output
Group dialog will appear- as shown in the following figure.
6. Select both the Primary output and Content Files items from the list.
Use the Ctrl key to select multiple items. Then click OK.
7. Select the Web Application Folder node in the File System editor.
Then locate the VirtualDirectory entry in the Properties window and
change it to the name of a target directory on the deployment target.
This directory does not need to exist; it will be created.
8. Also in the Properties window- set the DefaultDocument property to
the name of the default document for the Web application opened in
step 1.
9. Build the solution by clicking Build- Build Solution.
10. Locate the set-up package created by the Web Setup project. It should
be located in the Debug or Release subdirectory of the directory
containing the Web Setup project. Copy the file to the target server
and execute it.
11. Browse the newly deployed application using the URL
http://machinename/ vdirname/- where machinename is the name of
the machine where you installed the set-up package and vdirname is
the name that you specified for the VirtualDirectory property in step 7.
If the deployment worked correctly- you should see the default page
for the Web application.
In addition to providing fine-grained control over the files and outputs included in the
installation package- this method also allows you to automatically install shared
assemblies in the GAC. It also allows you to uninstall a Web application with a single
command- either by right-clicking the installation package and selecting Uninstall or by
locating the application's entry in the Windows Add/Remove Programs applet- which can
be accessed from the Control Panel.
Create an Add a Web Setup project to the solution for the Web
installation application to be packaged, and set its properties and
package with outputs as desired.
fine-grained
control of
installation
items and
uninstall ability
Applications
In This Chapter, You Will Learn About
§ Tracing
§ Debugging
§ Using the .NET Framework SDK debugger
This book has covered a wide variety of topics related to ASP.NET development.
Hopefully, you’ll be able to avoid too many problems while developing your ASP.NET
Web applications. Inevitably, however, there will be times when your code doesn’t do
what you expect it to, or you’ll run into errors or problems that prevent your application
from working. This final chapter will look at what to do when that happens. It’s not
intended to make you an expert debugger, but rather to give you an overview of the tools
available for debugging in ASP.NET.
To say that classic ASP did not offer developers much support for debugging their
applications would be a colossal understatement. Despite being a very simple and
productive development environment, classic ASP left a lot to be desired in terms of
debugging and error messages.
For example, in classic ASP there was no convenient way to access the state of the
current HTTP request (apart from writing code in each application to access and log, or
write to the page, the contents of the Request.Headers collection) or of the other
collections, such as the QueryString or Forms collections. ASP.NET addresses this
through a new feature called tracing, which is discussed in the first part of this chapter.
Additionally, classic ASP error messages were often cryptic at best. If you were lucky,
they told you the line of the ASP page on which the error occurred. Unfortunately, this
information could be misleading. The error might have occurred in a function called by
that page, but you wouldn’t know that from the error message. ASP.NET provides much
richer error reporting than classic ASP, including a stack trace of the functions that led up
to an error condition, making it much easier to locate the root cause of the error.
The .NET Framework SDK ships with its own debugger, so developers who are not
using Visual Studio .NET can attach it to the process of their ASP.NET application and
step through their code to find and fix problems. This improved error reporting and the
SDK debugger are discussed in the second half of this chapter.
Tracing
As mentioned, one weakness of classic ASP was the lack of an easy way to get
information about the current request. Apart from creating reusable include files to write
out the contents of the various collections of the request object, there was no simple
method of making this information available. ASP.NET provides a solution in the form of
tracing, which provides a substantial amount of information about requests that have
been executed, either at the page level or the application level, as well as a way to get
your own custom debug information without exposing it to the users of your application.
The trace output includes such information as the contents of the cookies, forms, and
querystring collections. Therefore, page-level tracing using the Trace attribute of the @
Page directive should be restricted to nonproduction systems because the output is
visible to any client requesting that page. Although it is possible to use one of the
configuration options detailed in the next section to configure ASP.NET to provide page-
level trace output only for requests from the local system, it is probably best to stick to
application-level tracing on publicly available applications.
In addition to providing trace output at the page level, ASP.NET can be configured to log
trace output for a specified number of requests for later review. The primary advantages
of this approach are that it hides trace output from users of the page, allowing its use on
production or customer-facing systems, and that it allows tracing to be enabled for an
entire site at once.
Application-level tracing is enabled by adding the <trace> section to the Web.config file
for the application and setting its attributes to the desired values. The following listing
shows a <trace> section that enables application-level tracing and tells ASP.NET to log
the last 40 requests. (Logged requests must be cleared manually when the request limit
has been reached.) The attributes for the <trace> section are detailed in Chapter .
Using Trace.Write
The Trace.Write method of the TraceContext class is overloaded, and it can be called in
one of the following three ways:
Trace.Write(message)
Trace.Write(category, message)
Trace.Write(category, message, errorInfo)
message is a string containing the message to be written to the trace output. category is
a string containing a description of the message (variable, statement, or whatever the
developer desires as a category). And errorInfo is an exception object, which allows
exceptions to be logged to the trace output. The category and errorInfo parameters allow
developers to set up sophisticated application-wide tracing by defining specific
categories of logged information and using them consistently across an application.
The following listing shows the code for a page that tests if tracing is enabled for the
page, and then writes a simple message to the trace output.
TraceWrite.aspx
<%@ Page Language="VB" Trace="true" %>
<html>
<head>
<script runat="server">
Sub Page_Load()
If Trace.IsEnabled = True
Label1.Text = "Writing 'Hello from Page!' to Trace Log…"
End If
Trace.Write("Hello from Page!")
End Sub
</script>
</head>
<body>
<asp:label id="Label1" runat="server"/>
</body>
</html>
Using Trace.Warn
Another method offered by the TraceContext class for writing to the trace output is
Trace.Warn. The primary difference between the two methods is that output written with
Trace.Warn appears in the trace output as red text, making it ideal for highlighting
important entries such as exceptions.
The trace output for a page contains a great deal of information. The following table lists
the sections of the trace output and describes the information contained in each.
Section Description
There are three broad categories of bugs- with differing characteristics and differing
levels of difficulty.
§ Syntax errors These errors occur when code breaks the rules of the
language you're using- such as writing a VB Sub statement without a
closing End Sub- or forgetting a closing curly brace (}) in C#. These
errors are the easiest to locate. The language compiler or IDE will alert
you to them and will not allow you to compile your program until they are
corrected.
§ Semantic errors These errors occur in code that is correct according to
the rules of the compiler- but that causes unexpected problems such as
crashes or hanging on execution. A good example is code that executes
in a loop but never exits the loop- either because the loop depends on a
variable whose value was expected to be something different than it
actually was or because the programmer forgot to increment the loop
counter. These bugs are harder to detect and are one type of run-time
error.
§ Logic errors These errors are similar to semantic errors in that they are
run-time errors. That is- they occur while the program is running. But
unlike semantic errors- they do not cause the application to crash or
hang. Instead- logic errors result in unexpected values or output. This
can be due to something as simple as a mistyped variable name that
happens to match another declared variable in the program (an
argument for descriptive variable names rather than simple ones- for
example). This type of error can be extremely difficult to track down and
eliminate- particularly since in complex programs it may be difficult (if not
impossible) to reproduce a logic error reported by a user.
Preventing Bugs
Before we even get into the discussion of how to debug the various errors you're apt to
run into in your applications- let's take some time to look at strategies for preventing
bugs in the first place. Preventing bugs is a much more efficient and much less
expensive way to produce bug-free software.
The following list contains a few strategies that will help. This is not a comprehensive list
by any means- but applying these strategies consistently will help you spend less time
debugging code and more time enjoying the rewards of having written it.
§ Write readable code Choose (or develop) and make consistent use of
naming and coding standards. It's not that important which standard you
use- such as Hungarian notation (txtFirstName) vs. Pascal Casing
(FirstName)- as long as you use one. You should also strive for
consistency in comments and encourage liberal commenting of code.
Anything that makes code more readable and easier to understand will
help eliminate bugs from the get-go.
§ Create effective test plans The only effective way to eliminate logic
errors is to test every path of your application with every possible data
value (or representative range of data) that could be entered by a user.
This is difficult to manage without effective planning. Test plans should
be created at the same time as the application is being designed- and
they should be updated as the application design is modified. To be
effective- the test plans need to describe how to test each piece of
functionality in the application. Test plans and other design documents
can also help highlight design problems before they are implemented-
preventing a wide array of bugs.
§ Use a rich IDE You don't necessarily need to use Visual Studio .NET to
develop your Web applications since ASP.NET does not require it- but
you should consider developing in an IDE that provides syntax checking
as you type. If you develop with Notepad- it is too easy to amass a
number of syntax errors that go unnoticed until you try to run the page.
Then you get to spend the next half-hour or more eliminating the errors-
one at a time- until you finally get the code to run. This is not an efficient
way to write code.
§ Get another pair of eyes Whether you're working on a team or building
an application on your own- it is important to have someone else review
your code (and test it- if possible). Developers are simply too close to
their own code to catch every bug before testing- and sometimes even
after. A review by a different pair of eyes can help a lot.
Other information is only available if the page or application is compiled in debug mode-
which makes debug information available to the runtime- which can then provide
additional information such as the line of code on which an error occurred. Using the
SDK debugger to step through your code also requires that the code be compiled in
debug mode.
To indicate that a page should be compiled in debug mode- add the Debug attribute to
the @ Page directive and set its value to True. To indicate that all pages in an
application should be compiled in debug mode- edit the compilation section of
Web.config (or add it if it doesn't exist)- adding the debug attribute with a value of true.
<configuration>
<system.web>
<compilation debug="true"/>
</system.web>
</configuration>
Setting the debug attribute of the page shown in the previous figure to true results in the
output shown in the following figure.
Anyone who's seen the infamous ASP 015 error probably realizes just what a dramatic
improvement the information in the previous figure represents. ASP.NET will provide
descriptive information for both semantic and syntax errors- including the type of
exception thrown (which allows you to provide effective and specific exception handling
when you cannot completely prevent the exception) and the stack trace for the current
request. As mentioned previously- you can also write to the trace output- and any trace
statements that occur before execution is halted by an exception will be shown in the
trace output.
Attaching Processes
Once you've loaded the source file for the page- you'll want to call up the page in a
browser and attach the debugger to the process of the browser window that is requesting
the page. But first- you'll need to attach to the ASP.NET worker process to allow the
debugger to break the execution of the code.
To attach the ASP.NET worker process and the process for your page- follow these
steps:
1. Open the page to be debugged in a browser.
2. Click Tools- Debug Processes. The Processes window will appear- as
shown in the following figure.
3. Select the aspnet_wp.exe process (on systems with the Beta 2
ASP.NET Premium Edition installed- this will be aspnet_ewp.exe)
and click Attach.
4. Locate the process of the browser that is requesting the page (the
Title column can help you locate the correct process)- select it- and
click Attach.
5. Click Close to close the Processes dialog.
Setting Breakpoints
Once you've loaded the source for a page and attached the necessary processes- you
can step through your code by setting a breakpoint prior to the line of code on which the
error occurs (if known) or on the first line of executable code. Breakpoints halt execution
of your code and allow you to check the value of local variables and step through your
code.
To set a breakpoint - click on the left border of the editor window next to the desired line
of code. Alternatively- you can click Debug- New Breakpoint or press Ctrl+B to open the
New Breakpoint dialog. This allows you to specify additional information about the
breakpoint- such as conditions under which the break should occur or the number of
times the breakpoint is effective. The following figure shows the example page
DivByZero.aspx in the debugger with a breakpoint set at the first line of the Page_Load
event handler. When the browser window containing the program is refreshed- the
debugger will halt execution of the program- allowing local variables to be examined in
the locals window and allowing the code to be stepped through using the commands in
the Debug menu.
Using Debug.Assert
As mentioned- you can add Debug.Assert messages to your code to test certain
conditions. The Assert method is overloaded and takes the following forms:
Debug.Assert(condition)
Debug.Assert(condition- shortmessage)
Debug.Assert(condition- shortmessage- longmessage)
condition is an expression that evaluates to true or false- shortmessage is a string
containing a brief message to display when condition returns false- and longmessage is
a string containing a detailed message to display when condition returns false. The
following figure shows the runtime debugger after stepping past an Assert statement.
Notice that the Output window is displaying the short message defined by the Assert
statement.
In order to call the Debug class without explicitly including the namespace- you need to
import the System.Diagnostics namespace using the @ Import directive- as shown in the
preceding figure.
Enable page-level Add the Trace attribute to the @ Page directive and
tracing set its value to true.
Invoke the runtime Locate the file DbgCLR.exe and execute it.
debugger
Debug a page using Ensure that the page is set to compile in debug
To Do This
the runtime debugger mode. Load the source code for the page into the
debugger, load the page in a browser, and then
attach the ASP.NET worker process and the
process of the browser requesting the page. Set
breakpoints as desired and reload the page in the
browser. Step through the code using the
commands in the Debug menu.
Migration Overview
Almost all of the issues you’ll encounter in migrating your classic ASP applications to
ASP.NET will fall into two categories: page structure changes and language changes.
The next two sections will discuss the types of changes in each of these categories, and
how you’ll need to update your code to work with them.
The page structure changes from classic ASP to ASP.NET can be further broken down
into two areas: changes in the structure of code blocks and changes to the syntax of
page directives.
Code Blocks
As discussed in Chapter 9, there are significant changes between how code could be
structured in classic ASP and how it can be structured in ASP.NET. These changes,
which are designed to make your code more readable and maintainable, are likely to be
the most common issue in migration.
In classic ASP, server-side code could be written in either code render blocks, indicated
by the syntax <% %>, or code declaration blocks, indicated by the <script
runat=“server”></script> syntax. Either syntax could be used anywhere in the page and
could contain either statements or procedures.
One problem is that it could be difficult to tell which code would execute when, leading to
unnecessary bugs. Another problem is that it was easy to write spaghetti code, with
render blocks all over a page, mixing procedures and raw statements. This made the
code difficult to read and to maintain. Excessive mixing of HTML and render blocks also
tended to negatively affect page performance.
In ASP.NET, the purposes of render blocks and code declaration blocks have been
narrowed considerably. Render blocks in ASP.NET can contain only executable
statements, not procedures, while code declaration blocks can contain only global
variable declarations and/or procedures.
Another significant difference is that in classic ASP, multiple <script runat=“server”> code
declaration blocks could be used on a page, and each one could use a different
language through the language attribute, which could be set to VBScript or JScript.
ASP.NET only supports a single language per page, which is Visual Basic .NET by
default.
Page Directives
In classic ASP, the primary directive used in pages was the @ Language directive, which
specified the language to be used for render blocks. Other less commonly used
directives included @ Codepage, @ EnableSessionState, @ LCID, and @ Transaction.
In ASP.NET, these directives are attributes of the @ Page directive, which should
appear at the top of each ASP.NET Web Form page. See Chapter 9 for a full discussion
of the @ Page directive and its attributes.
One new attribute of the @ Page directive that is important to discuss in this context is
the AspCompat attribute. By default, ASP.NET runs as a multi-threaded apartment
(MTA) process. This means that components that run as single-threaded apartment
(STA) components, such as those written in Visual Basic 6, are not compatible with
ASP.NET. This includes the ADO components, which are installed to run as STA
components by default. (You can modify this by running a batch file that changes the
registry settings for ADO, but that is beyond the scope of this discussion.) Setting the
AspCompat attribute to true forces ASP.NET to run in STA mode, making it possible to
use STA components in ASP.NET pages.
Important Setting AspCompat to true should be considered a short-
term solution in migrating from ASP to ASP.NET, because
this setting can have a significant negative impact on the
performance of your application. For best performance, you
should rewrite components written in earlier versions of
Visual Basic using Visual Basic .NET, and you should
migrate existing ADO code to use ADO.NET.
Language Changes
In addition to changes to the structure of pages in ASP.NET, there are some significant
changes to the Visual Basic language that will likely require modifications to your code.
These include the following:
§ Set and Let are no longer needed or supported. Object references can
be set by simple assignment.
Object1 = Object2
§ Parens are now required for calling Sub procedures as well as Function
procedures (including methods that do not have parameters).
Response.Write("Hello, World!")
§ The Variant data type does not exist in Visual Basic .NET. The
replacement is Object.
§ Default properties are no longer supported. All properties must be called
explicitly.
MyString = TextBox1.Text
§ Property declaration syntax has changed. Instead of Property Set,
Property Get, and Property Let, Visual Basic .NET uses the following
syntax (keep in mind that •value is a special keyword that contains the
value submitted to the Set portion of the property statement).
Public Property MyProperty As String
Get
MyProperty = MyInternalVariable
End Get
Set
MyInternalVariable = value
End Set
End Property
§ The default for passing parameters to procedures has changed from
ByRef to ByVal. To pass values by reference, you must explicitly add the
ByRef keyword (reference parameters allow you to change their value in
the procedure to which they’re passed and retrieve their value outside
the procedure).
Sub MySub(ByRef MyValue As String)
MyValue = "Hello!"
End Sub
To demonstrate some of these changes and show you how to deal with them, let’s walk
through the process of migrating a classic ASP page that accesses data in a SQL Server
database through ADO and writes it to the page as an HTML table. First you’ll go
through the process of making the page work in ASP.NET, while still using ADO for data
access and using the same logic for writing the data as an HTML table. Then you’ll
modify that code to access the data through ADO.NET and use an ASP.NET DataGrid to
automatically render the data as a table. The following listing shows the classic ASP
page that you’ll start with.
GetAuthors.asp
<%@ Language=VBScript %>
<html>
<head>
<%
Dim objConn
Dim objCmd
Dim objRS
Dim strConn
strConn = "PROVIDER=SQLOLEDB;INITIAL CATALOG=PUBS;"_
&"SERVER=(local)\NetSDK;uid=sa;pwd=;"
Set objConn = Server.CreateObject("ADODB.Connection")
Set objCmd = Server.CreateObject("ADODB.Command")
Set objRS = Server.CreateObject("ADODB.Recordset")
Sub FormatTable
Dim objField
End Sub
Sub CleanUp()
objConn.Close
Set objConn = Nothing
Set objCmd = Nothing
Set objRS = Nothing
End Sub
%>
</head>
<body>
<%
FormatTable()
CleanUp()
%>
</body>
</html>
Short-Term Migration
In the short term, your goal may be to simply move your pages over to ASP.NET while
still using most of the existing logic in the page. Keeping in mind that this choice has
performance implications, it may allow you to make your migration easier by letting you
do it in stages. Making the code in the previous listing work as an ASP.NET page is
largely a matter of modifying it to comply with the rules for ASP.NET code.
The first thing you should do is save a copy of the page to be migrated with a .aspx
extension and try to run it. Although it’s unlikely that pages of any complexity will run
unaltered, you might be surprised at what will run. More importantly, when you attempt to
run the page and it fails, ASP.NET will give you information about why it failed and which
line caused the failure. (Remember from Chapter 16 that in order to get the most error
information, pages should be set to compile in debug mode. You can do this either by
setting the debug attribute of the @ Page directive or by turning on debug mode in
Web.config.) The following figure shows the page that results from attempting to run
GetAuthors.asp as an ASP.NET page.
As you can see in the preceding figure, you get immediate feedback that the Set
statements need to be removed from your code. Thanks to the improvement in error
information in ASP.NET, you can simply change a page’s extension to .aspx, attempt to
run it, correct the error that is flagged, attempt to run it again, correct the next error to be
flagged, and so on until the page runs without errors.
This example will take a more structured approach and correct all of the structural and
language incompatibilities before attempting to rerun the code.
1. If you haven’t already, save the code in in the listing that starts on
page 480 as GetAuthors.asp (changing the user ID and password to
one that is set up on your machine). Once you have that running,
save a copy as GetAuthors.aspx.
2. Starting from the top of the page, modify the @ Language directive to
@ Page to use Visual Basic as the language, and add the
AspCompat attribute so that you can use the ADO components in
STA mode:
3. <%@ Page Language="VB" AspCompat="True" %>
4. While the code that creates and instantiates the ADO objects will run
in a render block without modification in ASP.NET, the FormatTable
and CleanUp procedures will not. They need to be moved into a
<script> block. Since leaving the ADO code in a render block will not
allow you to control when it executes, move it into a <script> block
as well and run it in the Page_UnLoad event handler. Move the
clean-up code to the Page_Load handler. (You no longer need the
Set obj = Nothing statements because ASP.NET takes care of this
clean-up for you.) Note that you have also removed the Set
statements from the code and added parens to the call to
objConn.Open.
5. <head>
6. <script runat="server">
7. Dim objConn
8. Dim objCmd
9. Dim objRS
10. Sub Page_Load()
11. Dim strConn
12.
13. strConn = "PROVIDER=SQLOLEDB;INITIAL
CATALOG=PUBS;"_
14. &"SERVER=(local)\NetSDK;uid=sa;pwd=;"
15. objConn = Server.CreateObject("ADODB.Connection")
16. objCmd = Server.CreateObject("ADODB.Command")
17. objRS = Server.CreateObject("ADODB.Recordset")
18.
19. objCmd.CommandText = "SELECT * FROM Authors"
20. objConn.Open(strConn)
21. objCmd.ActiveConnection = objConn
22. objRS = objCmd.Execute
23. End Sub
24. Sub FormatTable
25.
26. Dim objField
27.
28. If Not objRS.EOF Then
29. Response.Write("<table border=2 cellspacing=0>")
30. Do While Not objRS.EOF
31. Response.Write("<tr>")
32. For Each objField In objRS.Fields
33. Response.Write("<td>" & objField.Value & "</td>")
34. Next
35. Response.Write("</tr>")
36. objRS.MoveNext
37. Loop
38. Response.Write("</table>")
39. Else
40. Response.Write("No Records!")
41. End If
42. End Sub
43. Sub Page_Unload()
44. objConn.Close
45. End Sub
46. </script>
</head>
47. Save the page and browse it. The output should be the same as in
the figure on page 482.
Long-Term Migration
Techniques such as using AspCompat to allow use of STA components in ASP.NET can
help you get pages up and running in ASP.NET more quickly. But in the long run, it’s
best to fully migrate your code to take advantage of the features offered by ASP.NET
and ADO.NET. The following listing shows the changes necessary to get the equivalent
functionality of GetAuthors.asp using ADO.NET, and using an ASP.NET DataGrid to
display the data rather than writing your own rendering code.
GetAuthors2.aspx
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<head>
<script runat="server">
Sub Page_Load()
Dim myDS As New DataSet()
Dim ConnStr As String
ConnStr = "server=(local)\ NetSDK;database=pubs;"
ConnStr &= Trusted_Connection=yes"
Dim SQLSelect As String
SQLSelect = "SELECT * FROM Authors"
Dim mySqlConn As New SqlConnection(ConnStr)
Dim mySqlDA As New SqlDataAdapter(SQLSelect, ConnStr)
mySqlDA.Fill(myDS)
MyGrid.DataSource = myDS.Tables(0).DefaultView
DataBind()
End Sub
</script>
</head>
<body>
<form runat="server">
<asp:label id="title" text="ASP to ASP.NET Migration: Step 2"
font-size="18" runat="server"/>
<asp:datagrid
id="MyGrid"
autogeneratecolumns="true"
border="2"
cellpadding="0"
runat="server"/>
</form>
</body>
</html>
Note To use a trusted connection to connect to SQL Server as shown in
the preceding listing, you will need to either enable Windows
authentication and impersonation or set up the ASPNET worker
process account as a SQL Server login, as described in Chapter
11.
The code in the preceding listing offers better performance because it does not rely on
AspCompat to run. It takes advantage of ADO.NET instead of using ADO through COM
Interop, which carries some performance overhead. The code could be made even more
efficient by using an ADO.NET DataReader to access the data. The code in the
preceding listing also provides greater flexibility for modifying the look of the rendered
table. Now you can modify this by adding attributes to the DataGrid tag. Using an
ASP.NET DataGrid also provides built-in support for paging, editing, and filtering. See
Chapter 11 for more information on these features.
As you can see from this appendix, migrating from classic ASP to ASP.NET does not
need to be terribly painful. However, a lot depends on how your classic ASP code is
written in the first place. Code that is written with a great deal of intermingled HTML and
render blocks will be more difficult to migrate, as will code that does not follow good
coding practices. The following are several coding practices you can put in place in your
ongoing classic ASP development to make migrating it to ASP.NET easier:
§ For procedures that take parameters, use ByVal or ByRef to explicitly
state which type of parameter is desired. This will prevent code that
relies on the default assumption of ByRef in classic ASP from breaking
when migrated to ASP.NET.
§ Write all procedures in <script> code declaration blocks, rather than in
render blocks.
§ Use render blocks sparingly, particularly when intermingled with HTML
tags.
§ Do not rely on default properties. Instead, explicitly name properties such
as objRS.Value.
§ Do not use multiple languages in server-side <script> blocks. Choose the
language you’re most comfortable with and use it exclusively on a per-
page basis.
Namespace Animals
End Namespace
UseAnimals.vb
Imports Animals
MyCat.Eat
MyCat.Sleep
MyDog.Eat
MyDog.Sleep
End Sub
End Class
Chapter 11
BoundColumn_Sort.aspx
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<html>
<script runat="server">
Dim SortExpression As String
Function CreateDataSource() As ICollection
Dim dt As New DataTable()
Dim dr As DataRow
Dim i As Integer
For i = 0 To 8
dr = dt.NewRow()
dr(0) = i
dr(1) = "Item " + i.ToString()
dr(2) = 1.23 * Rnd * (i + 1)
dt.Rows.Add(dr)
Next i
<form runat=server>
<h3><font face="Verdana">BoundColumn Example</font></h3>
<b>Product List</b>
<asp:DataGrid id="ItemsGrid"
BorderColor="black"
BorderWidth="1"
CellPadding="3"
AllowSorting="true"
OnSortCommand="Sort_Grid"
AutoGenerateColumns="false"
runat="server">
<HeaderStyle BackColor="#00aaaa">
</HeaderStyle>
<Columns>
<asp:BoundColumn
HeaderText="Number"
SortExpression="IntegerValue"
DataField="IntegerValue">
</asp:BoundColumn>
<asp:BoundColumn
HeaderText="Description"
SortExpression="StringValue"
DataField="StringValue">
</asp:BoundColumn>
<asp:BoundColumn
HeaderText="Price"
SortExpression="CurrencyValue"
DataField="CurrencyValue"
DataFormatString="{0:c}">
</asp:BoundColumn>
</Columns>
</asp:DataGrid>
</form>
</body>
</html>
Chapter 12
LoginControl.cs
using System;
using System.Web;
using System.Web.UI;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI.WebControls;
namespace ASPNETSBS {
if((newUserName != UserName) ||
(newPassword != _Password)) {
_UserName = newUserName;
_Password = newPassword;
return true;
} else {
return false;
}
}
LoginControl_Client_CS.aspx
<%@ Page Language="vb" %>
<%@ Register TagPrefix="ASPNETSBS" Namespace="ASPNETSBS"
Assembly="LoginControl_CS" %>
<html>
<head>
<script runat="server">
Sub Auth_Success(Source As Object, e As EventArgs)
Message.Text = "Authenticated!"
End Sub
Sub Auth_Failure(Source As Object, e As EventArgs)
Message.Text = "Authentication Failed!"
End Sub
</script>
</head>
<body>
<h3><font face="Verdana" color="black">
Login Control</font>
</h3>
<form runat=server>
<ASPNETSBS:LoginControl_CS id="Login"
OnAuthSuccess="Auth_Success"
OnAuthFailure="Auth_Failure"
runat=server/>
<asp:label id="Message" Font-Bold="True" ForeColor="Red"
runat="server"/>
</form>
</body>
</html>
List of Sidebars
Chapter 1: ASP.NET Overview
Inheritance
Chapter 4: Understanding Programming Basics
Namespaces
Chapter 5: Creating an ASP.NET Web Application
Access Permissions
Chapter 7: Configuring an ASP.NET Application
Fully Qualified vs. Relative URLs
Chapter 9: Creating Web Forms
Comparing User Controls to Include Files
Server-Side Forms, Postbacks, and ViewState
Page Processing Stages
Chapter 10: Using Server Controls
Choosing the Right Control
Chapter 11: Accessing and Binding Data
XML in ADO.NET
Chapter 12: Creating Custom Server Controls
Private vs. Shared Assemblies
Control Execution Order
Chapter 13: Creating and Using Web Services
Metadata Attributes