Programming Microsoft
Programming Microsoft
PUBLISHED BY
Microsoft Press
A Division of Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399
Copyright 2002 by Jeff Prosise
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
Prosise, Jeff.
Programming Microsoft .NET (core reference) / Jeff Prosise.
p. cm.
Includes index.
ISBN 0-7356-1376-1
1. Microsoft.net framework. 2. Microsoft software. 3. Internet programming. I. Title.
QA76.625 .P76 A73 2002
005.2'76-dc21
2002016626
Printed and bound in the United States of America.
1 2 3 4 5 6 7 8 9 QWE 7 6 5 4 3 2
Distributed in Canada by Penguin Books Canada Limited.
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] .
Active Directory, ActiveX, BizTalk, IntelliSense, JScript, Microsoft, Microsoft Press, MSDN,
Visual Basic, Visual C++, Visual Studio, Win32, Windows, and Windows NT are either
registered trademarks or trademarks of Microsoft Corporation in the United States and/or other
countries. Other product and company names mentioned herein may be the trademarks of their
respective owners.
The example companies, organizations, products, domain names, e-mail addresses, logos,
people, places, and events depicted herein are fictitious. No association with any real company,
organization, product, domain name, e-mail address, logo, person, place, or event is intended or
should be inferred.
Acquisitions Editor: Ann Hamilton
Project Editor: John Pierce
Technical Editor: Marc Young
Dedication
To Abby
Acknowledgments
Id like to take a moment to thank the numerous people who contributed to the production of
this book and lent their unselfish support to the person who wrote it.
First, a tip of the hat to John Pierce, wordsmith par excellence, and Marc Young, who is the most
thorough technical editor Ive ever had the pleasure of working with. Both are veterans of
many books and are among the best in the business at what they do. I hate to even think about
what this book would look like had it not been for them.
Next, a profound thanks to the colleagues whom I pressed into service to read chapters and
provide technical feedback: Francesco Balena, Jason Clark, John Lam, John Robbins, Kenn
Scribner, and Dave Webster. Im especially indebted to Jeffrey Richter for sharing with me
his deep knowledge of the .NET Framework.
Special thanks also to the many people inside and outside Microsoft who answered my questions
and helped me overcome technical obstacles. The list includes, but is not limited to, Peter
Drayton, Rob Howard, Erik Olson, and Brent Rector.
And lest I forget, a heartfelt thanks to Microsoft Press illustrator Rob Nance, who got me out of a
jam when I needed comic book covers for the sample programs in Chapter 6. Also to
Microsofts Anne Hamilton, who allowed me the opportunity to write this book and waited
patiently for me to deliver, and Claudette Moore, my agent and longtime friend, who pressed me
to get this project off the ground.
Finally, to my family: my wife, Lori, and my children, Adam, Amy, and Abby. Writing a book is
hard work. This time around I proved that its harder on the authors family than it is on
the author himself. These four endured a lot while I spent endless days and nights laboring over
the manuscript, and words cant express how good it feels to know that theyre always
there for me, rooting for me every step of the way.
Look, kids. Im done!
Introduction
Yes, its that time againtime to throw away everything you know and start all over. The
era of Microsoft .NET has arrived, and with it comes a promise to change software development
as we know it. Microsoft .NET is many things, but first and foremost its a better way to
write software in an Internet-centric world. To benefit from .NET, youll find it helpful to let
go of any preconceived notions and prepare yourself to think about software in a whole new
light. That means shedding comfortable clothing such as the Windows API, MFC, and COM,
and immersing yourself in new ways of developing and architecting software that are unlike
anything youve seen before.
When I began writing this book in July 2001, I had been working with the .NET Framework
SDK for more than a year. The .NET Framework was in beta at the time and was still months
away from emerging as a released product. When I first laid eyes on it, I expected to see
something that resembled COM. What I saw instead was a radical departure from anything
Microsoft had done before and a better way to write software. If your companys plans
include Web apps, Web services, or other applications that use the Internet as their platform,
there simply is no better way to write those applications than to use Microsoft .NET. I would no
more consider writing a Web app today with ASP than I would consider using a wrench to drive
nails. The first ingredient for a successful software project is picking the right tool for the job. If
your job involves Web programming (and maybe even if it doesnt), Microsoft .NET is just
the tool that you need.
This book is about Microsoft .NETwhat it is, how it works, and how to write software that
uses it. Among other things, youll learn about the common language runtime (CLR) and the
highly structured environment that it provides for executing code compiled from C#, Visual
Basic .NET, and other languages. Youll learn about the .NET Framework class library
(FCL), the stunningly comprehensive class library that provides the API managed applications
write to. Youll become acquainted with the programming models embodied in the FCL,
including Windows Forms, Web Forms, and XML Web services. And just as important,
youll learn how to make all the pieces work together to write sophisticated applications that
leverage the power of Microsoft .NET.
System Requirements
To compile and run the more than 75 sample programs included in this book, you must have the
.NET Framework Software Development Kit (SDK) installed on your machine. The SDK runs
on Windows NT 4.0, Windows 2000, Windows XP, and presumably on later versions of
Windows as well. The CD that comes with this book includes version 1.0 of the .NET
Framework SDK as well as Service Pack 1. When newer versions become available, you can
download them by pointing your browser to http://msdn.microsoft.com/downloads/default.asp?
url=/downloads/sample.asp?url=/msdn-files/027/000/976/msdncompositedoc.xml. We all know
that URLs change. If you go to this one and find that its no longer valid, visit the Microsoft
.NET home page at http://www.microsoft.com/net for the latest information on where to find the
SDK.
Chapters 5 through 11 of this book, which cover ASP.NET, impose another requirement on your
system. In addition to being outfitted with the .NET Framework SDK, your PC needs to have
Microsofts Web server, Internet Information Services (IIS), installed. Because ASP.NET
requires Windows 2000 or Windows XP, you need one of those operating systems, too. On the
Professional editions of these operating systems, IIS isnt part of the default installation. To
install IIS, open Add or Remove Programs in Control Panel and select Add/Remove Windows
Components. Youll find a check box for IIS. Be sure to install IIS before installing the SDK
to make sure ASP.NET gets installed, too.
Some of the chapters in this book include sample programs built with Visual Studio .NET and
provide Visual Studio .NETspecific instructions. You dont have to have Visual Studio
.NET to build code that targets the .NET Framework; the SDK comes with command-line
compilers. However, Visual Studio .NET offers a highly integrated development environment
that makes writing, testing, and debugging code easier. If you dont already own a copy of
Visual Studio .NET, you can purchase one from Microsoft. For more information, visit
http://msdn.microsoft.com/vstudio/howtobuy.
Whats on the CD
Programmers like goodies. The CD that comes with this book contains the following delectable
delights:
All of the books sample programs, source code included
A fully searchable electronic version of the book
The .NET Framework SDK version 1.0 and Service Pack 1
Youll find instructions on the CD for installing the components that come on it. If AutoPlay
is enabled on your PC, simply pop the CD in a drive to get started. In addition to being included
on the CD, the sample files are available for download from
http://www.microsoft.com/mspress/books/5200.asp.
Support
If you have comments about this book, questions you want answered, or errors to report, please
post them at http://www.wintellect.com/forum/forum.asp?FORUM_ID=16. Ill check the
message board regularly and do my best to respond. Others will monitor message traffic also and
jump in as appropriate. If you have a question, chances are someone else has one just like it. The
answer may already be posted. If not, by posting your question in a public forum, you enable
others to benefit from the answer as well.
I'd like to tell you that this book contains no errors, but of course I'd be lying. I've done
everything humanly possible to verify the accuracy of every sentence and every code sample, but
errors will inevitably surface. Youll find an up-to-date errata list at
http://www.wintellect.com/about/instructors/prosise/netbook.asp. If you don't want to fuss with a
long URL, simply go to http://www.prosise.com, and youll find a link there.
If you'd like to contact Microsoft Press directly about this book or need to resolve packaging
problems (such as a defective or missing CD), you can contact them on line at
http://www.microsoft.com/mspress/support. If you prefer paper mail, the address is:
Microsoft Press
Attn:Programming Microsoft .NET Editor
One Microsoft Way
Redmond, WA 98052-6399
Part 1
Essential Concepts
Chapter 1
Hello, .NET
negative effects are mitigated by the fact that a method is compiled only once during the
applications lifetime and also by the fact that the CLR team at Microsoft has gone to
extraordinary lengths to make the JIT compiler as fast and efficient as possible. In theory, JIT
compiled code can outperform ordinary code because the JIT compiler can optimize the native code
that it generates for the particular version of the host processor that it finds itself running on. JIT
compiled code is not the same as interpreted code; dont let anyone tell you otherwise.
The benefits of running code in the managed environment of the CLR are legion. For starters, as the
JIT compiler converts CIL instructions into native code, it enacts a code verification process that
ensures the code is type safe. Its practically impossible to execute an instruction that accesses
memory that the instruction isnt authorized to access. Youll never have problems with stray
pointers in a managed application because the CLR throws an exception before the stray pointer is
used. You cant cast a type to something its not because that operation isnt type safe.
And you cant call a method with a malformed stack frame because the CLR simply wont
allow it to happen. In addition to eliminating some of the most common bugs that afflict application
programs, code verification security makes it eminently more difficult to write malicious code that
intentionally inflicts harm on the host system. In the unlikely event that you dont want code
verification security, you can have it turned off by a system administrator.
Code verification security is also the chief enabling technology behind the CLRs ability to host
multiple applications in a single processa feat of magic that it works by dividing the process into
virtualized compartments called application domains. Windows isolates applications from one
another by hosting them in separate processes. An unfortunate side effect of the one-process-perapplication model is higher memory consumption. Memory efficiency isnt vital on a stand-alone
system that serves one user, but its paramount on servers set up to handle thousands of users at
once. In certain cases (ASP.NET applications and Web services being the prime examples), the CLR
doesnt launch a new process for every application; instead, it launches one process or a handful
of processes and hosts individual applications in application domains. Application domains are secure
like processes because they form boundaries that managed applications cant violate. But
application domains are more efficient than processes because one process can host multiple
application domains and because libraries can be loaded into application domains and shared by all
occupants.
Another benefit of running in a managed environment comes from the fact that resources allocated by
managed code are garbage collected. In other words, you allocate memory, but you dont free it;
the system frees it for you. The CLR includes a sophisticated garbage collector that tracks references
to the objects your code creates and destroys those objects when the memory they occupy is needed
elsewhere. The precise algorithms employed by the garbage collector are beyond the scope of this
book, but they are documented in detail in Jeffrey Richters book Applied Microsoft .NET
Framework Programming (Microsoft Press, 2002).
Thanks to the garbage collector, applications that consist solely of managed code dont leak
memory. Garbage collection even improves performance because the memory allocation algorithm
employed by the CLR is fastmuch faster than the equivalent memory allocation routines in the C
runtime. The downside is that when a collection does occur, everything else in that process stops
momentarily. Fortunately, garbage collections occur relatively infrequently, dramatically lessening
their impact on performance.
Programming Languages
Yet another benefit of running applications in a CLR-hosted environment is that all code reduces to
CIL, so the programming language that you choose is little more than a lifestyle choice. The
common in common language runtime alludes to the fact that the CLR is language-agnostic.
In other environments, the language you use to implement an application inevitably affects the
applications design and operation. Code written in Visual Basic 6, for example, cant easily
spawn threads. To make matters worse, modern programming languages such as Visual Basic and
Visual C++ use vastly different APIs, meaning that the knowledge you gain programming Windows
with Visual Basic is only marginally helpful if your boss asks you to write a DLL in C++.
With the .NET Framework, all that changes. A language is merely a syntactic device for producing
CIL, and with very few exceptions, anything you can do in one language, you can do in all the others,
too. Moreover, regardless of what language theyre written in, all managed applications use the
same API: that of the .NET Framework class library. Porting a Visual Basic 6 application to Visual
C++ is only slightly easier than rewriting the application from scratch. But porting a Visual Basic
.NET application to C# (or vice versa) is much more reasonable. In the past, language conversion
tools have been so imperfect as to be practically useless. In the era of the .NET Framework, someone
might write a language converter that really works. Because high-level code ultimately compiles to
CIL, the framework even lets you write a class in one language and use it (or derive from it) in
another. Now thats language independence!
Microsoft provides CIL compilers for five languages: C#, J#, C++, Visual Basic, and JScript. The
.NET Framework Software Development Kit (SDK) even includes a CIL assembler called ILASM, so
you can write code in raw CIL if you want to. Third parties provide compilers for other languages,
including Perl, Python, Eiffel, and, yes, even COBOL. No matter what language youre most
comfortable with, chances are theres a CIL compiler that supports it. And you can sleep better
knowing that even if you prefer COBOL, you can do almost anything those snobby C# programmers
can do. That, of course, wont prevent them from ribbing you for being a COBOL person, but
thats another story.
Managed Modules
When you build a program with the C# compiler, the Visual Basic .NET compiler, or any other
compiler capable of generating CIL, the compiler produces a managed module. A managed module is
simply an executable designed to be run by the CLR. It typically, but not always, has the file name
extension EXE, DLL, or NETMODULE. Inside a managed module are four important elements:
A Windows Portable Executable (PE) file header
A CLR header containing important information about the module, such as the location of its
CIL and metadata
Metadata describing everything inside the module and its external dependencies
The CIL instructions generated from the source code
Every managed module contains metadata describing the modules contents. Metadata is not
optional; every CLR-compliant compiler must produce it. Thats important, because it means
every managed module is self-describing. Think about it this way. If someone hands you an ordinary
EXE or DLL today, can you easily crack it open and figure out what classes are inside and what
members those classes contain? No way! If its a managed module, however, no problem.
Metadata is like a COM type library, but with two important differences:
Type libraries are optional; metadata is not
Metadata fully describes a module; type libraries sometimes do not
Metadata is important because the CLR must be able to determine what types are present in each
managed module that it loads. But its also important to compilers and other tools that deal with
managed executables. Thanks to metadata, Visual Studio .NET can display a context-sensitive list of
the methods and properties available when you type the name of a class instance into the program
editor, a feature known as IntelliSense. And thanks to metadata, the C# compiler can look inside a
DLL containing a class written in Visual Basic .NET and use it as the base class for a derived class
written in C#.
Metadata
A modules core metadata is stored in a collection of tables. One table, the TypeDef table, lists all
the types defined in the module. (Type is a generic term for classes, structs, enumerations,
and other forms of data understood by the CLR.) Another table lists the methods implemented by
those types, another lists the fields, another lists the properties, and so on. By reading these tables,
its possible to enumerate all the data types defined in the module as well as the members each
type includes. Additional tables list external type references (types and type members in other
modules that are used by this module), the assemblies containing the external types, and more.
Additional metadata information is stored outside the tables in heaps containing items referenced by
table entries. For example, class names and method names are stored in the string heap; string literals
are stored in a separate heap called the user-string heap. Together, metadata tables and heaps define
everything you (or the CLR) could possibly want to know about a modules contents and external
dependencies.
The portion of a managed module that holds the modules CIL is sprinkled with metadata tokens
that refer to entries in the metadata tables. Each row in each table is identified by a 32-bit metadata
token consisting of an 8-bit table index and a 24-bit row index. When a compiler emits the CIL for a
method, it also emits a metadata token identifying the row containing information about the method.
When the CLR encounters the token, it can consult the table to discover the methods name,
visibility, signature, and even its address in memory.
The metadata format is interesting from an academic point of view, but its rare that an
application developer finds use for such knowledge. Most applications dont manipulate metadata
directly; they leave that to the CLR and to compilers. Applications that need to read and write
metadata can do so by using either of two APIs provided by the .NET Framework. Called the
reflection APIs, these APIs insulate the developer from the binary metadata format. One is an
unmanaged API exposed through COM interfaces. The other is a managed API exposed through
classes in the FCLs System.Reflection namespace.
Using the reflection APIs to write a tool that lists the types and type members in a managed module is
relatively easy. But if inspecting metadata is your goal, you dont have to write a tool because the
.NET Framework SDK has one already. Called ILDASM, it uses reflection to reveal the contents of
managed executables. Figure 1-1 shows ILDASM displaying ImageView.exe, one of the sample
programs in Chapter 4. The tree depicts the one class defined in the module (MyForm) and all the
members of that class. But heres the cool part. If you start ILDASM with a /ADV (for
Advanced) switch and select the View/MetaInfo/Show command (or press Ctrl+M, which
works regardless of whether you started ILDASM with a /ADV switch), a window pops up detailing
all the rows in all the metadata tables (Figure 1-2). By checking and unchecking items in the
View/MetaInfo menu, you can configure ILDASM to display metadata in different ways.
Figure 1-1
The ILDASM view of ImageView.exe.
Figure 1-2
Metadata displayed by ILDASM.
Common Intermediate Language
CIL is often described as a pseudo-assembly language because it defines a native instruction set for a
processor. In this case, however, the processor is the CLR, not a piece of silicon. You dont have
to know CIL to program the .NET Framework any more than you have to know x86 assembly
language to program Windows. But a rudimentary knowledge of CIL can really pay off when a
method in the FCL doesnt behave the way you expect it to and you want to know why. You
dont have the source code for the FCL, but you do have the CIL.
In all, CIL includes about 100 different instructions. Some are the typical low-level instructions found
in silicon instruction sets, like instructions to add two values together (ADD) and to branch if two
values are equal (BEQ). Other instructions work at a higher level and are atypical of those found in
hardware instruction sets. For example, NEWOBJ instantiates an object, and THROW throws an
exception. Because the CIL instruction set is so rich, code written in high-level languages such as C#
and Visual Basic .NET frequently compiles to a surprisingly small number of instructions.
CIL uses a stack-based execution model. Whereas x86 processors load values into registers to
manipulate them, the CLR loads values onto an evaluation stack. To add two numbers together, you
copy them to the stack, call ADD, and retrieve the result from the stack. Copying a value from
memory to the stack is called loading, and copying from the stack to memory is called storing. CIL
has several instructions for loading and storing. LDLOC, for example, loads a value onto the stack
from a memory location, and STLOC copies it from the stack to memory and removes it from the
stack.
For an example of CIL at work, consider the following C# source code, which declares and initializes
two variables, adds them together, and stores the sum in a third variable:
int a = 3;
int b = 7;
int c = a + b;
Heres the CIL that Microsofts C# compiler produces, with comments added by hand:
ldc.i4.3 // Load a 32-bit (i4) 3 onto the stack
stloc.0 // Store it in local variable 0 (a)
ldc.i4.7 // Load a 32-bit (i4) 7 onto the stack
stloc.1 // Store it in local variable 1 (b)
ldloc.0 // Load local variable 0 onto the stack
ldloc.1 // Load local variable 1 onto the stack
add // Add the two and leave the sum on the stack
stloc.2 // Store the sum in local variable 2 (c)
As you can see, the CIL is pretty straightforward. Whats not obvious, however, is how the local
variables a, b, and c (locals 0, 1, and 2 to the CLR) get allocated. The answer is through metadata.
The compiler writes information into the methods metadata noting that three local 32-bit integers
are declared. The CLR retrieves the information and allocates memory for the locals before executing
the method. If you disassemble the method with ILDASM, the metadata shows up as a compiler
directive:
.locals init (int32 V_0, // Local variable 0 (a)
int32 V_1, // Local variable 1 (b)
int32 V_2) // Local variable 2 (c)
This is a great example of why metadata is so important to the CLR. Its used not only to verify
type safety, but also to prepare execution contexts. Incidentally, if a C# executable is compiled with a
/DEBUG switch, ILDASM will display real variable names instead of placeholders such as V_0.
The .NET Framework SDK contains a document that describes the entire CIL instruction set in
excruciating detail. I wont clutter this chapter with a list of all the CIL instructions, but I will
provide a table that lists some of the most commonly used instructions and describes them briefly.
Common CIL Instructions
Instruction Description
BOX
Converts a value type into a reference type
CALL
Calls a method; if the method is virtual, virtualness is ignored
CALLVIRT Calls a method; if the method is virtual, virtualness is honored
CASTCLASSCasts an object to another type
LDC
Loads a numeric constant onto the stack
LDARG[A] Loads an argument or argument address [A] onto the stack
LDELEM
Loads an array element onto the stack
LDLOC[A] Loads a local variable or local variable address [A] onto the stack
LDSTR
Loads a string literal onto the stack
NEWARR Creates a new array
NEWOBJ
Creates a new object
RET
Returns from a method call
STARG
Copies a value from the stack to an argument
STELEM
Copies a value from the stack to an array element
STLOC
Transfers a value from the stack to a local variable
THROW
Throws an exception
UNBOX
Converts a reference type into a value type
The same ILDASM utility that lets you view metadata is also a fine CIL disassembler. For a
demonstration, start ILDASM and use it to open one of the System.*.dll DLLs in the
\%SystemRoot%\Microsoft.NET\Framework\v1.0.nnnn directory. These DLLs belong to the .NET
Framework class library. After opening the DLL, drill down into its namespaces and classes until you
find a method you want to disassemble. Methods are easy to spot because theyre marked with
magenta rectangles. Double-click the method and youll see its CIL, complete with compiler
directives generated from the methods metadata. Better still, ILDASM is a round-trip
disassembler, meaning that you can pass the disassembled code to ILASM and reproduce the CIL that
you started with.
When speaking to groups of developers about CIL, Im often asked about intellectual property
issues. If you and I can disassemble the FCL, whats to prevent a competitor from disassembling
your companys product? Reverse-engineering CIL isnt trivial, but its easier than
reverse-engineeringx86 code. And decompilers that generate C# source code from CIL are freely
available on the Internet. So how do you protect your IP?
The short answer isit depends. Code that runs only on serversXML Web services, for
exampleisnt exposed to users, so it cant be disassembled unless someone breaches your
firewall. Code distributed to end users can be scrambled with third-party code obfuscation utilities.
Obfuscators cant guarantee that no one can read your code, but they can make doing so harder. In
the final analysis, someone who has physical access to your CIL and wants to reverse-engineer it
badly enough will find a way to do it. If its any consolation, the Java community has grappled
with this problem for years. There is no perfect solution other than sticking strictly to server-side
apps.
Assemblies
You now know that compilers that target the .NET Framework produce managed modules and that
managed modules contain CIL and metadata. But you might be surprised to learn that the CLR is
incapable of using managed modules directly. Thats because the fundamental unit of security,
versioning, and deployment in the .NET Framework is not the managed module but the assembly.
Anassembly is a collection of one or more files grouped together to form a logical unit. The term
files in this context generally refers to managed modules, but assemblies can include files
that are not managed modules. Most assemblies contain just one file, but assemblies can and
sometimes do include multiple files. All the files that make up a multifile assembly must reside in the
same directory. When you use the C# compiler to produce a simple EXE, that EXE is not only a
managed module, its an assembly. Most compilers are capable of producing managed modules
that arent assemblies and also of adding other files to the assemblies that they create. The .NET
Framework SDK also includes a tool named AL (Assembly Linker) that joins files into assemblies.
Multifile assemblies are commonly used to glue together modules written in different languages and
to combine managed modules with ordinary files containing JPEGs and other resources. Multifile
assemblies are also used to partition applications into discrete downloadable units, which can be
beneficial for code deployed on the Internet. Imagine, for example, that someone with a dial-up
connection wants to download a multi-megabyte application housed in a single-file assembly.
Downloading the code could take forever. To mitigate the problem, you could divide the code into
multiple files and make the files part of the same assembly. Because a module isnt loaded unless
its needed, the user wont incur the cost of downloading the portions of the application that
arent used. If youre smart about how you partition the code, the bulk of the application
Figure 1-3
Multifile assembly.
In the absence of information directing them to do otherwise, compilers produce assemblies that are
weakly named. Weakly named means that the assembly is not cryptographically signed and
that the CLR uses only the name stored in the assembly manifest (which is nothing more than the
assemblys file name without the file name extension) to identify the assembly. But assemblies
can be strongly named. A strongly named assembly contains the publishers public key and a
digital signature thats actually a hash of the assembly manifest where the public key is stored.
The digital signature, which is generated with the publishers private key and can be verified with
the public key, makes the assemblys manifest (and, by extension, the assembly itself)
tamperproof. A strongly named assemblys identity derives from the assembly name, the public
key, the version number, and the culture string, if present. Any difference, no matter how small, is
sufficient to distinguish two otherwise identical assemblies.
The SDKs AL utility can be used to create strongly named assemblies. Most language compilers,
including the C# and Visual Basic .NET compilers, can also emit strongly named assemblies. Its
up to you whether to deploy weakly or strongly named assemblies. The right choice is dictated by the
assemblies intended use. If an assembly is to be deployed in the global assembly cache
(GAC)a global repository used by assemblies designed to be shared by multiple applicationsit
must be strongly named.
An assembly must also be strongly named if you want to take advantage of version checking. When
the CLR loads a weakly named assembly, it does no version checking. That can be good or bad.
Its good if you replace an old version of the assembly with a new one (perhaps one that has
undergone bug fixes) and want applications that use the assembly to automatically use the new one.
Its bad if youve thoroughly tested the application against a specific version of the assembly
and someone replaces the old assembly with a new one thats riddled with bugs. Thats one
symptom of the DLL Hell that Windows developers are all too familiar with. Strong naming can fix
that. When the CLR loads a strongly named assembly, it compares the version number in the
assembly to the version number that the application doing the loading was compiled against. (That
information, not surprisingly, is recorded in the modules metadata.) If the numbers dont
match up, the CLR throws an exception.
Strict version checking, of course, has pitfalls of its own. Suppose you elect to use strong naming, but
later you find a bug in your assembly. You fix the bug and deploy the revised assembly. But guess
what? Applications that use the assembly wont load the new version unless you rebuild them.
Theyll still load the old version, and if you delete the old version, the applications wont run
at all. The solution is to modify the CLRs binding policy. Its relatively simple for an
administrator to point the CLR to a new version of a strongly named assembly by editing a
configuration file. Of course, if the newer version has bugs, youre right back to square one.
Thats why you dont let just anyone have administrator privileges.
Working with assemblies sounds pretty complicatedand at times, it is. Fortunately, if youre
not building shared assemblies or assemblies that link to other assemblies (other than the FCL, which,
by the way, is a set of shared assemblies), most of the issues surrounding naming and binding fall by
the wayside. You just run your compiler, copy the resulting assembly to a directory, and run it.
Couldnt be much simpler than that.
The .NET Framework Class Library
Windows programmers who code in C tend to rely on the Windows API and functions in third-party
DLLs to get their job done. C++ programmers often use class libraries of their own creation or
standard class libraries such as MFC. Visual Basic programmers use the Visual Basic API, which is
an abstraction of the underlying operating system API.
Using the .NET Framework means you can forget about all these anachronistic APIs. You have a
brand new API to learn, that of the .NET Framework class library, which is a library of more than
7,000 typesclasses, structs, interfaces, enumerations, and delegates (type-safe wrappers around
callback functions)that are an integral part of the .NET Framework. Some FCL classes contain
upward of 100 methods, properties, and other members, so learning the FCL isnt a chore to be
taken lightly. The bad news is that its like learning a brand new operating system. The good
news is that every language uses the same API, so if your company decides to switch from Visual
Basic to C++ or vice versa, the investment you make in learning the FCL isnt lost.
To make learning and using the FCL more manageable, Microsoft divided the FCL into hierarchical
namespaces. The FCL has about 100 namespaces in all. Each namespace holds classes and other
types that share a common purpose. For example, much of the window manager portion of the
Windows API is encapsulated in the System.Windows.Forms namespace. In that namespace
youll find classes that represent windows, dialog boxes, menus, and other elements commonly
used in GUI applications. A separate namespace called System.Collections holds classes representing
hash tables, resizable arrays, and other data containers. Yet another namespace, System.IO, contains
classes for doing file I/O. Youll find a list of all the namespaces present in the FCL in the .NET
Framework SDKs online documentation. Your job as a budding .NET programmer is to learn
about those namespaces. Fortunately, the FCL is so vast and so comprehensive that most developers
will never have to tackle it all.
The following table lists a few of the FCLs namespaces and briefly describes their contents. The
term et al refers to a namespaces descendants. For example, System.Data et al refers to
System.Data,System.Data.Common,System.Data.OleDb,System.Data.SqlClient, and
System.Data.SqlTypes.
A Sampling of FCL Namespaces
Namespace
Contents
System
Core data types and auxiliary classes
System.Collections
Hash tables, resizable arrays, and other containers
System.Data et al
ADO.NET data access classes
System.Drawing
Classes for generating graphical output (GDI+)
System.IO
Classes for performing file and stream I/O
System.Net
Classes that wrap network protocols such as HTTP
System.Reflection et al
Classes for reading and writing metadata
System.Runtime.Remoting et alClasses for writing distributed applications
System.ServiceProcess
Classes for writing Windows services
System.Threading
Classes for creating and managing threads
System.Web
HTTP support classes
System.Web.Services
Classes for writing Web services
System.Web.Services.ProtocolsClasses for writing Web service clients
System.Web.UI
Core classes used by ASP.NET
System.Web.UI.WebControls ASP.NET server controls
System.Windows.Forms
Classes for GUI applications
System.Xml et al
Classes for reading and writing XML data
The first and most important namespace in the FCLthe one that every application usesis
System. Among other things, the System namespace defines the core data types employed by
managed applications: bytes, integers, strings, and so on. When you declare an int in C#, youre
actually declaring an instance of System.Int32. The C# compiler recognizes int as a shortcut simply
because its easier to write
int a = 7;
than it is to write
System.Int32 a = 7;
TheSystem namespace is also home for many of the exception types defined in the FCL (for
example,InvalidCastException) and for useful classes such as Math, which contains methods for
performing complex mathematical operations; Random, which implements a pseudo-random number
generator; and GC, which provides a programmatic interface to the garbage collector.
Physically, the FCL is housed in a set of DLLs in the
\%SystemRoot%\Microsoft.NET\Framework\v1.0.nnnn directory. Each DLL is an assembly that can
be loaded on demand by the CLR. Core data types such as Int32 are implemented in Mscorlib.dll;
other types are spread among the FCL DLLs. The documentation for each type lists the assembly in
which its defined. Thats important, because if a compiler complains about an FCL class
being an undefined type, you must point the compiler to the specific assembly that implements the
class for you. The C# compiler uses /r[eference] switches to identify referenced assemblies.
Obviously, no one chapter (or even one book) can hope to cover the FCL in its entirety. Youll
become acquainted with many FCL classes as you work your way through this book. Chapter 3 kicks
things off by introducing some of the FCLs coolest classes and namespaces. For now, realize that
the FCL is the .NET Framework API and that its an extraordinarily comprehensive class library.
The more you use it, the more youll like it, and the more youll come to appreciate the sheer
effort that went into putting it together.
This command invokes the C# compiler and produces an executable named Hello.exe. The /target
switch, which can be abbreviated /t, tells the compiler to produce a console application. Because a
console application is the default, and because the default EXE file name is the name of the CS file, you
can save wear and tear on your fingers by compiling the program with the command
csc Hello.cs
Once the compilation process is complete, run Hello.exe by typing helloat the command prompt.
Hello, world should appear in the window, as shown in Figure 1-5.
Hello.cs
using System;
class MyApp
{
static void Main ()
{
Console.WriteLine ("Hello, world");
}
}
Figure 1-4
Hello, world in C#.
Figure 1-5
Output from Hello.exe.
So what happened when you ran Hello.exe? First, a short x86 stub emitted by the compiler transferred
control to the CLR. The CLR, in turn, located and called the programs Main method, which
compiled to three simple CIL instructions. The JIT compiler transformed the instructions into x86
machine code and executed them. Had you compiled and run the EXE on another type of machine, the
same CIL instructions would have compiled to instructions native to the host processor.
Inside Hello.cs
Lets talk about the code in Figure 1-4. For starters, every application has to have an entry point. For
a C# application, the entry point is a static method named Main. Every C# application has one. Because
C# requires that all methods belong to a type, in Hello.cs, Main is a member of MyApp. Theres
nothing magic about the class name; you could name it Foo and it would work just the same. If an
application contains multiple classes and multiple Main methods, a /main switch tells the C# compiler
which class contains the Main method thats the entry point.
The one and only statement in MyApp.Main is the one that writes Hello, world to the console
window. If Hello were a standard Windows application written in C or C++, youd probably use
printf to write the output. But this isnt a standard Windows application. Its a .NET Framework
application, and .NET Framework applications use the FCL to write to console windows. The FCLs
System namespace features a class named Console that represents console windows. Look up Console in
the FCL reference, and youll find that it contains a static method named WriteLine that writes a line
of text to the window (or to wherever standard output happens to point at the time).
The statement
using System;
at the beginning of the program is a syntactical simplification. Remember that the FCL contains more
than 7,000 members divided into approximately 100 namespaces. Without the using directive, youd
have to qualify the reference to Console by prefacing the class name with the namespace name, as
shown here:
System.Console.WriteLine ("Hello, world");
But with the using directive, you can abbreviate the reference by specifying only the class name. Its
not a big deal here, but when you write programs containing hundreds, perhaps thousands, of references
to FCL classes, youll appreciate not having to type the namespace names over and over. Most
languages support the using directive or an equivalent. In Visual Basic .NET, its called Imports.
What happens if two namespaces contain identically named classes and you use both of those classes in
your application? Simple: you have to qualify the references one way or another. The following code
wont compile because both namespaces contain classes named ListBox:
using System.Windows.Forms;
using System.Web.UI.WebControls;
Consequently, you have two choices. The first is to fully qualify the class names:
System.Windows.Forms.ListBox winLB =new System.Windows.Forms.ListBox ();
System.Web.UI.WebControls.ListBox webLB =
new System.Web.UI.WebControls.ListBox ();
The second is to use an alternative form of the using directive to create aliases for the fully qualified
class names:
using winListBox = System.Windows.Forms.ListBox;
using webListBox = System.Web.UI.WebControls.ListBox;
This example is admittedly contrived because youll probably never use a Web Forms ListBox and a
Windows Forms ListBox in the same program (not for any reason I can think of, anyway). But the
principle is valid just the same.
More About the Main Method
HellosMain method accepts no parameters and returns no data, but thats just one of four
different ways that you can prototype Main. All of the following are legitimate:
static
static
static
static
void Main ()
int Main ()
void Main (string[] args)
int Main (string[] args)
Theargs parameter is an array of strings containing the programs command-line arguments. The
string at index 0 is the first argument, the string at index 1 is the second, and so on. If you rewrote the
program this way:
using System;
class MyApp
and started it by typing hello .NET, the output from the program would be Hello, .NET. Use the
form of Main that receives an args parameter if you want to accept command-line input in your program.
The modified form of Hello.cs in the previous paragraph has one little problem: if you run it without any
command-line parameters, it throws an exception because the 0 in args[0] constitutes an invalid array
index. To find out how many command-line parameters were entered, read the string arrays Length
property, as in:
int count = args.Length;
This statement works because an array in the framework is an instance of System.Array, and
System.Array defines a property named Length. You can use Length to determine the number of items
in any array, no matter what type of data the array stores.
Inside Hello.exe
If youd like to see Hello.exe as the CLR sees it, start ILDASM and open Hello.exe. Youll see
the window shown in Figure 1-6. The first red triangle represents the assembly manifest. Double-click
it, and youll see a list of assemblies that this assembly depends on (.assembly extern
mscorlib), as shown in Figure 1-7. Youll also see the assembly name (.assembly Hello)
and a list of the modules that make up the assembly. Because Hello.exe is a single-file assembly, its
the only module that appears in the list.
Figure 1-6
ILDASM displaying the contents of Hello.exe.
Figure 1-7
Hello.exes manifest.
ILDASM also reveals that Hello.exe contains a class named MyApp and that MyApp contains two
methods: a constructor (.ctor) and Main. The constructor was generated automatically by the compiler;
its a default constructor that takes no arguments. The S in the box next to Main indicates
thatMain is a static method. Double-clicking Main displays the CIL generated for that method by the
C# compiler. (See Figure 1-8.) The statement
.entrypoint
isnt CIL but is simply a directive noting that this method is the programs entry point. The entry
points identity came from Hello.exes CLR header. Next, the statement
.maxstack 1
is information obtained from the method prologuedata bytes preceding the method in the
CILindicating that this method requires no more than one slot in the CLRs evaluation stack. The
CLR uses this information to size the evaluation stack prior to invoking the method.
Mains CIL consists of just three simple instructions:
A LDSTR instruction that places Hello, world on the evaluation stack
A CALL instruction that calls System.ConsolesWriteLine method in Mscorlib
A RET instruction that ends the method
As you can see, its relatively easy to use ILDASM to figure out how a method works. Early .NET
Framework developers spent copious amounts of time figuring out how the FCL works by poring over
the CIL for individual methods.
Figure 1-8
TheMain method disassembled.
Chapter 2
Types and Exceptions
Before you begin drilling down into the Microsoft .NET Framework class library (FCL) and the
various programming models that it supports, its helpful to understand what the FCL is
made of. The FCL is a library of types, which is a generic way of referring to classes,
structs, interfaces, enumerations, and delegates. This chapter defines these terms and will make
Chapter 3 more meaningful to developers who are new to the .NET Framework. This chapter
also introduces some potential pitfalls related to types, including common errors that arise when
using types that encapsulate file handles and other resources that arent managed by the
garbage collector.
Understanding the .NET Frameworks type system and the differences between the various
kinds of data types that it supports is important, but so is understanding how types are loaded,
versioned, and deployed. Types are packaged in assemblies. The FCL is a set of many different
assemblies, each one of them shared so that any application can use it. Applications, too, are
deployed as assemblies. You already know that an assembly is a group of one or more files. You
even created a single-file assembly (Hello.exe) in Chapter 1. What you dont
knowyetis how to create assemblies of your own that contain types that can be used by
other programs. Youll remedy that in this chapter by building and deploying a multifile,
multilanguage assembly, and then building a client that dynamically links to it. In addition to
gaining valuable insight into how the FCL works, youll see what it takes to build class
libraries of your own and learn how to use the assembly-based versioning mechanism in the
common language runtime (CLR) to avoid DLL Hellthe term used to describe what happens
when a fix made to a DLL for the benefit of one application breaks another application (or
perhaps breaks the very application that the fix was intended to help).
Finally, youll learn about exception handling. Applications that use the .NET Framework
employ a C++-like try/catch mechanism to achieve robustness without having to check the return
value from each and every method call. In fact, checking return values does little good because
the CLR and FCL dont flag errors by returning error codes; they throw exceptions. A
working knowledge of how exception handling works and how languages such as C# expose the
CLRs exception handling mechanism is essential to becoming a proficient .NET Framework
programmer.
The C in FCL stands for class, but the FCL isnt strictly a class library; its a library of types.
following:
Classes
Structs
Interfaces
Enumerations
Delegates
Understanding what a type is and how one type differs from another is crucial to understanding the FCL. The
sections will not only enrich your understanding of the FCL, but also help you when the time comes to build d
Classes
A class in the .NET Framework is similar to a class in C++: a bundle of code and data that is instantiated to fo
object-oriented programming languages such as C++ contain member variables and member functions. Frame
contain the following members:
Fields, which are analogous to member variables in C++
Methods, which are analogous to member functions in C++
Properties, which expose data in the same way fields do but are in fact implemented using accessor (get
Events, which define the notifications a class is capable of firing
Here, in C#, is a class that implements a Rectangle data type:
class Rectangle
{
// Fields
protected int width = 1;
protected int height = 1;
// Properties
public int Width
{
get { return width; }
set
{
if (value > 0)
width = value;
else
throw new ArgumentOutOfRangeException (
"Width must be 1 or higher");
}
}
get {
set
{
}
return height; }
// Methods (constructors)
public Rectangle () {}
public Rectangle (int cx, int cy)
{
Width = cx;
Height = cy;
}
if (value > 0)
height = value;
else
throw new ArgumentOutOfRangeException (
"Height must be 1 or higher");
Rectangle has seven class members: two fields, three properties, and two methods, which both happen to be c
that are called each time an instance of the class is created. The fields are protected, which means that only Re
derivatives can access them. To read or write a Rectangleobjects width and height, a client must use the W
Notice that these properties set accessors throw an exception if an illegal value is entered, a protection th
Rectangles width and height been exposed through publicly declared fields. Area is a read-only property
compiler will flag attempts to write to the Area property with compilation errors.
Many languages that target the .NET Framework feature a new operator for instantiating objects. The followin
Rectangle in C#:
Rectangle rect = new Rectangle (); // Use first constructor
Rectangle rect = new Rectangle (3, 4); // Use second constructor
Significantly, neither C# nor any other .NET programming language has a delete operator. You create objects
them.
In C#, classes define reference types, which are allocated on the garbage-collected heap (which is often called
its managed by the garbage collector) and accessed through references that abstract underlying pointers. T
type is the value type, which youll learn about in the next section. Most of the time you dont have to
differences between the two, but occasionally the differences become very important and can actually be debi
accounted for. See the section Boxing and Unboxing later in this chapter for details.
All classes inherit a virtual method named Finalize from System.Object, which is the ultimate root class for al
just before an object is destroyed by the garbage collector. The garbage collector frees the objects memor
handles, window handles, and other unmanaged resources (unmanaged because theyre not freed
overrideFinalize and use it to free those resources. This, too, has some important implications for developers.
chapter in the section entitled Nondeterministic Destruction.
Incidentally, classes can derive from at most one other class, but they can derive from one class and any numb
the documentation for FCL classes, dont be surprised if you occasionally see long lists of base classe
classes at all, but interfaces. Also be aware that if you dont specify a base class when declaring a class, yo
System.Object. Consequently, you can call ToString and other System.Object methods on any object.
Structs
Classes are intended to represent complex data types. Because class instances are allocated on the managed he
with creating and destroying them. Some types, however, are simple types that would benefit from be
lives outside the purview of the garbage collector and offers a high-performance alternative to the managed he
examples of simple data types.
Thats why the .NET Framework supports value types as well as reference types. In C#, value types are de
Value types impose less overhead than reference types because theyre allocated on the stack, not the heap
other primitive data types that the CLR supports are value types.
Heres an example of a simple value type:
struct
{
}
Point
public
public
public
{
}
int x;
int y;
Point (int x, int y)
this.x = x;
this.y = y;
Point stores x and y coordinates in fields exposed directly to clients. It also defines a constructor that can be u
Point in one operation. A Point can be instantiated in any of the following ways:
Point point = new Point (3, 4); // x==3, y==4
Point point = new Point (); // x==0, y==0
Point point; // x==0, y==0
Note that even though the first two statements appear to create a Point object on the heap, in reality the object
come from a C++ heritage, get over the notion that new always allocates memory on the heap. Also, despite t
creates a Point object whose fields hold zeros, C# considers the Point to be uninitialized and wont let you
values to x and y.
Value types are subject to some restrictions that reference types are not. Value types cant derive from oth
derive from System.ValueType and can (and often do) derive from interfaces. They also shouldnt wrap u
handles because value types have no way to release those resources when theyre destroyed. Even though
method from System.Object,Finalize is never called because the garbage collector ignores objects created on t
Interfaces
An interface is a group of zero or more abstract methodsmethods that have no default implementation but
class or struct. Interfaces can also include properties and events, although methods are far more common.
An interface defines a contract between a type and users of that type. For example, many of the classes in the
derive from an interface named IEnumerable.IEnumerable defines methods for iterating over the items in a co
FCLs collection classes implement IEnumerable that C#s foreach keyword can be used with them. A
A class or struct that wants to implement an interface simply derives from it and provides concrete implemen
class Message : ISecret
{
public void Encrypt (byte[] inbuf, out byte[] outbuf, Key key)
{
...
}
public void Unencrypt (byte[] inbuf, out byte[] outbuf, Key key)
{
...
}
In C#, the is keyword can be used to determine whether an object implements a given interface. If msg is an o
then in this example, is returns true; otherwise, it returns false:
if (msg is ISecret) {
ISecret secret = (ISecret) msg;
secret.Encrypt (...);
}
The related as operator can be used to test an object for an interface and cast it to the interface type with a sing
Enumerations
Enumerations in .NET Frameworkland are similar to enumerations in C++. Theyre types that consist
in C# theyre defined with the enum keyword. Heres a simple enumerated type named Color:
enum
{
}
Color
Red,
Green,
Blue
Many FCL classes use enumerated types as method parameters. For example, if you use the Regex class to pa
be case-insensitive, you dont pass a numeric value to Regexs constructor; you pass a member of an e
RegexOptions:
Regex regex = new Regex (exp, RegexOptions.IgnoreCase);
Using words rather than numbers makes your code more readable. Nevertheless, because an enumerated type
numeric values (by default, 0 for the first member, 1 for the second, and so on), you can always use a number
prefer.
Theenum keyword isnt simply a compiler keyword; it creates a bona fide type that implicitly derives from
defines methods that you can use to do some interesting things with enumerated types. For example, you can
type to enumerate the names of all its members. Try that in unmanaged C++!
Delegates
Newcomers to the .NET Framework often find delegates confusing. A delegate is a type-safe wrapper around
rather simple to write an unmanaged C++ application that crashes when it performs a callback. Its impos
application that does the same, thanks to delegates.
Delegates are most commonly used to define the signatures of callback methods that are used to respond to ev
Timer class (a member of the System.Timers namespace) defines an event named Elapsed that fires whenever
elapses. Applications that want to respond to Elapsed events pass a Timer object a reference to the method the
event fires. The reference that they pass isnt a raw memory address but rather an instance of a de
methods memory address. The System.Timers namespace defines a delegate named ElapsedEventHandle
If you could steal a look at the Timerclasss source code, youd see something like this:
public delegate void ElapsedEventHandler (Object sender, ElapsedEventArgs e);
public
{
}
class Timer
public event ElapsedEventHandler Elapsed;
.
.
.
And heres how a client might use a Timer object to call a method named UpdateData every 60 seconds:
Timer timer = new Timer (60000);
timer.Elapsed += new ElapsedEventHandler (UpdateData);
.
.
.
void UpdateData (Object sender, ElapsedEventArgs e)
{
// Callback received!
}
As you can see, UpdateData conforms to the signature specified by the delegate. To register to receive Elapse
instance of ElapsedEventHandler that wraps UpdateData (note the reference to UpdateData passed to Elapsed
and wires it to timersElapsed event using the += operator. This paradigm is used over and over in .NET F
and delegates are an important feature of the type system.
In practice, its instructive to know more about what happens under the hood when a compiler encounters
the C# compiler encounters code such as this:
It responds by generating a class that derives from System.MulticastDelegate. The delegate keyword is simply
this case looks like this:
public
{
...
The derived class inherits several important members from MulticastDelegate, including private fields that id
delegate wraps and the object instance that implements the method (assuming the method is an instance meth
The compiler adds an Invoke method that calls the method that the delegate wraps. C# hides the Invoke metho
method simply by using a delegates instance name as if it were a method name.
Boxing and Unboxing
The architects of the .NET Framework could have made every type a reference type, but they chose to suppor
imposing undue overhead on the use of integers and other primitive data types. But theres a downside to
personality. To pass a value type to a method that expects a reference type, you must convert the value type to
convert a value type to a reference type per se, but you can box the value type. Boxing creates a copy of a valu
opposite of boxing is unboxing, which, in C#, duplicates a reference type on the stack. Common intermediate
for performing boxing and unboxing.
Some compilers, the C# and Visual Basic .NET compilers among them, attempt to provide a unified view of t
and unboxing under the hood. The following code wouldnt work without boxing because it stores an int
Hashtable objects store references exclusively:
Hashtable table = new Hashtable (); // Create a Hashtable
table.Add ("First", 1); // Add 1 keyed by "First"
Notice the BOX instruction that converts the integer value 1 to a boxed value type. The compiler emitted this
wouldnt have to think about reference types and value types. The string used to key the Hashtable entry (
be boxed because its an instance of System.String, and System.String is a reference type.
Many compilers are happy to box values without being asked to. For example, the following C# code compile
int val = 1; // Declare an instance of a value type
object obj = val; // Box it
You lose a bit of performance when you box or unbox a value, but in the vast majority of applications, such lo
added efficiency of storing simple data types on the stack rather than in the garbage-collected heap.
Reference Types vs. Value Types
Thanks to boxing and unboxing, the dichotomy between value types and reference types is mostly transparent
however, you must know which type youre dealing with; otherwise, subtle differences between the two c
behavior in ways that you might not expect.
Heres an example. The following code defines a simple reference type (class) named Point. It also declar
p2. The reference p1 is initialized with a reference to a new Point object, and p2 is initialized by setting it equ
little more than pointers in disguise, setting one equal to the other does not make a copy of the Point object; it
Therefore, modifying one Point affects both:
class Point
{
public int x;
public int y;
}
.
.
.
Point p1 = new Point ();
p1.x = 1;
p1.y = 2;
Point p2 = p1; // Copies the underlying pointer
p2.x = 3;
p2.y = 4;
Console.WriteLine ("p1 = ({0}, {1})", p1.x, p1.y); // Writes "(3, 4)"
Console.WriteLine ("p2 = ({0}, {1})", p2.x, p2.y); // Writes "(3, 4)"
The next code fragment is identical to the first, save for the fact that Point is now a value type (struct). But be
to another creates a copy of the latter, the results are quite different. Changes made to one Point no longer affe
struct Point
{
public int x;
public int y;
}
.
.
.
Point p1 = new Point ();
p1.x = 1;
p1.y = 2;
Point p2 = p1; // Makes a new copy of the object on the stack
p2.x = 3;
p2.y = 4;
Console.WriteLine ("p1 = ({0}, {1})", p1.x, p1.y); // Writes "(1, 2)"
Console.WriteLine ("p2 = ({0}, {1})", p2.x, p2.y); // Writes "(3, 4)"
Sometimes differences between reference types and value types are even more insidious. For example, if Poin
code is perfectly legal:
Point p;
p.x = 3;
p.y = 4;
But if Point is a reference type, the very same instruction sequence wont even compile. Why? Because th
Point p;
declares an instance of a value type but only a reference to a reference type. A reference is like a pointerit
initialized, as in the following:
Point p = new Point ();
Programmers with C++ experience are especially vulnerable to this error because they see a statement that dec
automatically assume that an object is being created on the stack.
The FCL contains a mixture of value types and reference types. Clearly, its sometimes important to know
with. How do you know whether a particular FCL type is a value type or a reference type? Simple. If the docu
in String Class), its a reference type. If the documentation says its a structure (for example,
its a value type. Be aware of the difference, and youll avoid frustrating hours spent in the debugger t
looks perfectly good produces unpredictable results.
Nondeterministic Destruction
In traditional environments, objects are created and destroyed at precise, deterministic points in time. As an ex
class written in unmanaged C++:
class File
{
protected:
int Handle; // File handle
public:
};
If you create the object on the stack instead of the heap, destruction is still deterministic because the class des
object goes out of scope.
Destruction works differently in the .NET Framework. Remember, you create objects, but you never delete th
them for you. But therein lies a problem. Suppose you write a File class in C#:
class File
{
protected IntPtr Handle = IntPtr.Zero;
~File ()
{
// TODO: Close the file handle
}
Now ask yourself a question: when does the file handle get closed?
The short answer is that the handle gets closed when the object is destroyed. But when is the object destroyed
destroys it. When does the garbage collector destroy it? Ahtheres the key question. You dont kno
the garbage collector decides on its own when to run, and until the garbage collector runs, the object isnt
isnt called. Thats called nondeterministic destruction, or NDD. Technically, theres no such thin
code. When you write something that looks like a destructor in C#, the compiler actually overrides the Finaliz
fromSystem.Object. C# simplifies the syntax by letting you write something that looks like a destructor, but th
because it implies that it is a destructor, and to unknowing developers, destructors imply deterministic destruc
Deterministic destruction doesnt exist in framework applications unless your code does something really
GC.Collect ();
GC is a class in the System namespace that provides a programmatic interface to the garbage collector. Collec
collection. Garbage collecting impedes performance, so now that you know that this method exists, forget abo
do is write code that simulates deterministic destruction by calling the garbage collector periodically.
NDD is a big deal because failure to account for it can lead to all sorts of run-time errors in your applications.
class to open a file. Later on that person uses it to open the same file again. Depending on how the file was op
open again because the handle is still open if the garbage collector hasnt run.
File handles arent the only problem. Take bitmaps, for instance. The FCL features a handy little class nam
System.Drawing namespace) that encapsulates bitmapped images and understands a wide variety of image fil
Bitmap object on a Windows machine, the Bitmap object calls down to the Windows GDI, creates a GDI bitm
handle in a field. But guess what? Until the garbage collector runs and the BitmapobjectsFinalize method
remains open. Large GDI bitmaps consume lots of memory, so its entirely conceivable that after the appl
itll start throwing exceptions every time it tries to create a bitmap because of insufficient memory. End u
image viewer utility (like the one youll build in Chapter 4) that has to be restarted every few minutes.
So what do you do about NDD? Here are two rules for avoiding the NDD blues. The first rule is for programm
classes that encapsulate file handles and other unmanaged resources. Most such classes implement a method n
releases resources that require deterministic closure. If you use classes that wrap unmanaged resources, call C
moment youre finished using them. Assuming File implements a Close method that closes the encapsula
right way to use the File class:
File file = new File ("Readme.txt");
.
.
.
// Finished using the file, so close it
file.Close ();
The second rule, which is actually a set of rules, applies to developers who write classes that wrap unmanaged
summary:
Implement a protected Dispose method (hereafter referred to as the protected Dispose) that take
this method, free any unmanaged resources (such as file handles) that the class encapsulates. If the param
Dispose is true, also call Close or Dispose (the public Dispose inherited from IDisposable) on any class
unmanaged resources.
Implement the .NET Frameworks IDisposable interface, which contains a single method named Dis
Implement this version of Dispose (the public Dispose) by calling GC.SuppressFinalize to prev
callingFinalize, and then calling the protected Dispose and passing in true.
If it makes sense semantically (for example, if the resource that the class encapsulates can be closed in th
implement a Close method that calls the public Dispose.
Based on these principles, heres the right way to implement a File class:
class File : IDisposable
{
protected IntPtr Handle = IntPtr.Zero;
~File ()
{
Dispose (false);
}
//
if
}
Note that the destructoractually, the Finalize methodnow calls the protected Dispose with a fal
Dispose calls the protected Dispose and passes in true. The call to GC.SuppressFinalize is both a performance
prevent the handle from being closed twice. Because the object has already closed the file handle, theres
to call its Finalize method. Its still important to override the Finalize method to ensure proper disposal if
Dynamic Linking
When you ran the C# compiler in Chapter 1, you created an assembly named Hello.exe. Hello.exe is as simple
as an assembly can be: it contains but one file and it lacks a strong name, meaning that the common language
runtime performs no version checking when loading it.
Weakly named, single-file assemblies are fine for the majority of applications. But occasionally developers
need more. For example, you might want to write a library of routines that other applications can link to,
similar to a dynamic link library (DLL) in Windows. If you do, youll need to know more about
assemblies. Perhaps youd like to write a library for the private use of your application. Or maybe
youve heard that Microsoft .NET solves the infamous DLL Hell problem and youd like to know
how. The next several sections walk you, tutorial-style, through the process of creating, deploying, and
dynamically linking to a multifile assembly. During the journey, youll see firsthand how such assemblies
are produced and what they mean to the design and operation of managed applications. And just to prove that
the framework is language-agnostic, youll write half of the assembly in C# and half in Visual Basic
.NET.
Creating a Multifile Assembly
The assembly that youre about to create contains two classes: one named SimpleMath, written in Visual
Basic .NET, and another named ComplexMath, written in C#. SimpleMath has two methods: Add and
Subtract.ComplexMath has one method, Square, which takes an input value and returns the square of that
value.
Physically, the assembly consists of three files: Simple.netmodule, which holds the SimpleMath class;
Complex.netmodule, which holds ComplexMath; and Math.dll, which houses the assembly manifest. Because
the managed modules containing SimpleMath and ComplexMath belong to the same assembly, clients neither
know nor care about the assemblys physical makeup. They simply see one entitythe assemblythat
contains the types theyre interested in.
Heres how to create the assembly:
1. Create a new text file named Complex.cs and enter the source code shown in Figure 2-1.
2. Compile Complex.cs into a managed module with the command
csc /target:module complex.cs
The/target switch tells the C# compiler to generate a managed module that is neither an EXE nor a
DLL. Such a module cant be used by itself, but it can be used if its added to an assembly.
Because you didnt specify a file name with a /out switch, the compiler names the output file
Complex.netmodule.
3. In the same directory, create a new text file named Simple.vb. Type in the source code shown in Figure
2-2.
4. Compile Simple.vb with the following command:
vbc /target:module simple.vb
This command produces a managed module named Simple.netmodule, which makes up the Visual
Basic .NET half of the assemblys code.
5. Create an assembly that binds the two managed modules together by running the SDKs AL
(Assembly Linker) utility as follows:
al /target:library /out:Math.dll simple.netmodule
complex.netmodule
The resulting fileMath.dllcontains the assembly manifest. Inside the manifest is information
identifying Simple.netmodule and Complex.netmodule as members of the assembly. Also encoded in
the assembly manifest is the assemblys name: Math.
Complex.cs
using System;
public
{
}
class ComplexMath
public int Square (int a)
{
return a * a;
}
Figure 2-1
TheComplexMath class.
Simple.vb
Imports System
Public
Class SimpleMath
Function Add (a As Integer, b As Integer) As Integer
Return a + b
End Function
Figure 2-2
TheSimpleMath class.
You just created the .NET Framework equivalent of a DLL. Now lets write a client to test it with.
Dynamically Linking to an Assembly
Follow these simple steps to create a console client for the Math assembly:
1. In the same directory that Math.dll, Simple.netmodule, and Complex.netmodule reside in, create a new
text file named MathDemo.cs. Then enter the code shown in Figure 2-3.
2. Compile MathDemo.cs with the following command:
csc /target:exe /reference:math.dll mathdemo.cs
The compiler creates an EXE named MathDemo.exe. The /reference switch tells the compiler that
MathDemo.cs uses types defined in the assembly whose manifest is stored in Math.dll. Without this
switch, the compiler would complain that the types are undefined.
Notice that in step 2, you did not have to include a /reference switch pointing to Simple.netmodule or
Complex.netmodule, even though thats where SimpleMath and ComplexMath are defined. Why?
Because both modules are part of the assembly whose manifest is found in Math.dll.
MathDemo.cs
using System;
class MyApp
{
static
{
void Main ()
SimpleMath simple = new SimpleMath ();
int sum = simple.Add (2, 2);
Console.WriteLine ("2 + 2 = {0}", sum);
Figure 2-3
Client for the Math assembly.
Now that you have a client ready, its time to test CLR-style dynamic linking. Heres a script to serve
as a guide:
1. In a command prompt window, run MathDemo.exe. You should see the output shown in Figure 2-4,
which proves that MathDemo.exe successfully loaded and used SimpleMath and ComplexMath.
2. Temporarily rename Complex.netmodule to something like Complex.foo.
3. Run MathDemo again. A dialog box appears informing you that a FileNotFoundException occurred.
The exception was generated by the CLR when it was unable to find the module containing
ComplexMath. Click the No button to acknowledge the error and dismiss the dialog box.
4. Restore Complex.netmodules original name and run MathDemo again to verify that it works.
5. Modify MathDemo.cs by commenting out the final three statementsthe ones that use ComplexMath.
Then rebuild MathDemo.exe by repeating the command you used to build it the first time.
6. Run MathDemo. This time, the only output you should see is 2 + 2 = 4.
7. Temporarily rename Complex.netmodule again. Then run MathDemo.exe. No exception occurs this
time because MathDemo.exe doesnt attempt to instantiate ComplexMath. The CLR doesnt
load modules that it doesnt need to. Had this code been deployed on the Internet, the CLR
wouldnt have attempted to download Complex.netmodule, either.
8. Restore Complex.netmodules name, uncomment the statements that you commented out in step 5,
and rebuild MathDemo.exe one more time.
8.
Figure 2-4
MathDemo output.
Youve now seen firsthand how dynamic linking works in the .NET Framework and demonstrated that
the CLR loads only the parts of an assembly that it has to. But what if you wanted to install the assembly in a
subdirectory of the application directory? Heres how to deploy the assembly in a subdirectory named bin
1. Create a bin subdirectory in the application directory (the directory where MathDemo.exe is stored).
2. Move Math.dll, Simple.netmodule, and Complex.netmodule to the bin directory. Run MathDemo.exe
again. The CLR throws a FileNotFoundException because it cant find the assembly in the
application directory.
3. Create a new text file named MathDemo.exe.config in the application directory, and then enter the
statements shown in Figure 2-5. MathDemo.exe.config is an XML application configuration file
containing configuration data used by the CLR. The probing element tells the CLR to look in the bin
subdirectory for assemblies containing types referenced by MathDemo.exe. You can include multiple
subdirectory names by separating them with semicolons.
4. Run MathDemo again and verify that it works even though the assembly is now stored in the bin
directory.
These exercises demonstrate how assemblies containing types used by other applications are typically
deployed. Most assemblies are private to a particular application, so theyre deployed in the same
directory as the application that they serve or in a subdirectory. This model is consistent with the .NET
Frameworks goal of XCOPY installs, which is synonymous with simplified install and uninstall
procedures. Because MathDemo.exe doesnt rely on any resources outside its own directory tree,
removing it from the system is as simple as deleting the application directory and its contents.
MathDemo.exe.config
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
Figure 2-5
MathDemo.exes application configuration file.
Versioning an Assembly
If you were to modify Simple.vb or Complex.cs right now and inadvertently introduce an error, the CLR
would be happy to load the buggy assembly the next time you run MathDemo.exe. Why? Because the
assembly lacks a strong name. The CLRs versioning mechanism doesnt work with weakly named
assemblies. If you want to take advantage of CLR versioning, you must assign the assembly a strong name.
Strong naming is the key to avoiding DLL Hell.
Use the following procedure to create a strongly named assembly containing the SimpleMath and
ComplexMath classes:
1. Go to the bin subdirectory and run the SDKs SN (Strong Name) utility. The following command
generates a key file named Keyfile.snk containing public and private keys that can be used for
strong naming:
sn /k Keyfile.snk
2. Use AL to create a strongly named assembly that uses the keys found in Keyfile.snk:
al /keyfile:keyfile.snk /target:library/out:Math.dll
/version:1.0.0.0 simple.netmodule complex.netmodule
The/keyfile switch identifies the key file. The /version switch specifies the version number written to
the assemblys manifest. The four values in the version number, from left to right, are the major
version number, the minor version number, the build number, and the revision number.
3. Go to MathDemo.exes application directory and rebuild MathDemo.cs using the following
command:
csc /target:exe /reference:bin\math.dll mathdemo.cs
This time, MathDemo.exe is bound to the strongly named Math assembly. Moreover, the new build of
MathDemo.exe contains metadata noting what version of the assembly it was compiled against.
4. Verify that MathDemo.exe works as before by running it.
So far, so good. Youve created a version of MathDemo.exe that is strongly bound to version 1.0.0.0 of a
private assembly whose manifest is stored in Math.dll. Now use the following exercises to explore the
ramifications:
1. Execute the following command in the bin directory to increment the assemblys version number
from 1.0.0.0 to 1.1.0.0:
al /keyfile:keyfile.snk /target:library/out:Math.dll
/version:1.1.0.0 simple.netmodule complex.netmodule
2. Run MathDemo.exe. Because MathDemo.exe was compiled against version 1.0.0.0 of the assembly, the
2.
CLR throws a FileLoadException.
3. Restore the assemblys version number to 1.0.0.0 with the following command:
al /keyfile:keyfile.snk /target:library /out:Math.dll
/version:1.0.0.0 simple.netmodule complex.netmodule
to read
return a + a;
Clearly this is a buggy implementation because the Square method now doubles a rather than squaring it
But the version number has been reset to 1.0.0.0the one MathDemo.exe was compiled against. What
will the CLR do when you rebuild Complex.netmodule and run MathDemo again?
5. Rebuild Complex.netmodule with the command
csc /target:module /out:bin\Complex.netmodule complex.cs
Run MathDemo.exe. Once again, the CLR throws an exception. Even though the version number is
valid, the CLR knows that Complex.netmodule has changed because Math.dlls manifest contains a
cryptographic hash of each of the files in the assembly. When you modified Complex.netmodule, you
modified the value it hashes to as well. Before loading Complex.netmodule, the CLR rehashed the file
and compared the resulting hash to the hash stored in the assembly manifest. Upon seeing that the two
hashes didnt match, the CLR threw an exception.
Now suppose circumstances were reversed and that version 1.0.0.0 contained the buggy Square method. In
that case, youd wantMathDemo.exe to use version 1.1.0.0. You have two options. The first is to
recompile MathDemo.exe against version 1.1.0.0 of the assembly. The second is to use a binding redirect to
tell the CLR to load version 1.1.0.0 of the assembly when MathDemo asks for version 1.0.0.0. A binding
redirect is enacted by modifying MathDemo.exe.config as follows:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Math"
publicKeyToken="cd16a90001d313af" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="1.1.0.0" />
</dependentAssembly>
<probing privatePath="bin" />
</assemblyBinding>
</runtime>
</configuration>
The new dependentAssembly element and its subelements instruct the CLR to resolve requests for Math
version 1.0.0.0 by loading version 1.1.0.0 instead. The publicKeyToken attribute is a tokenized representation
(specifically, a 64-bit hash) of the public key encoded in Maths assembly manifest; it was obtained by
running SN with a /T switch against Math.dll:
sn /T math.dll
Your assemblys public key token will be different from mine, so if you try this out on your code, be sure
to plug your assemblys public key token into MathDemo.exe.configs publicKeyToken attribute.
Now you can have your cake and eat it too. The CLR enacts a strong versioning policy to prevent incorrect
versions of the assembly from being loaded, but if you want to load another version, a simple configuration
change makes it possible.
Sharing an Assembly: The Global Assembly Cache
Suppose that you build the Math assembly with the intention of letting any application, not just
MathDemo.exe, use it. If thats your goal, you need to install the assembly where any application can find
it. That location is the global assembly cache (GAC), which is a repository for shared assemblies. The FCL is
several shared assemblies. Only strongly named assemblies can be installed in the GAC. When the CLR
attempts to load an assembly, it looks in the GAC even before it looks in the local application directory.
The .NET Framework SDK includes a utility named GacUtil that makes it easy to install and uninstall shared
assemblies. To demonstrate, do this:
1. Create a directory named Shared somewhere on your hard disk. (Theres nothing magic about the
directory name; call it something else if you like.) Move the files in MathDemo.exes bin directory
to the Shared directory. Then delete the bin directory.
2. Go to the Shared directory and install the Math assembly in the GAC by executing the following
command:
gacutil /i math.dll
3. Run MathDemo.exe. It should run fine, even though the assembly that it relies on is no longer in a
subdirectory of the application directory.
4. Remove the assembly from the GAC by executing this command:
gacutil /u math
5. Run MathDemo.exe again. This time, the CLR throws an exception because it cant find the Math
assembly in the GAC or in a local directory.
Thats shared assemblies in a nutshell. They must be strongly named, and the act of installing them in the
GAC makes them shared assemblies. The downside to deploying shared assemblies is that doing so violates
the spirit of XCOPY installs. Installing a shared assembly on an end users machine requires version 2 or
later of the Windows Installer or a third-party installation program that is GAC-aware because GacUtil comes
with the .NET Framework SDK and is not likely to be present on a nondevelopers PC. Uninstalling a
shared assembly requires removing it from the GAC; simply deleting files wont do the trick.
Applying Strong Names Using Attributes
The SDKs AL utility is one way to create strongly named assemblies, but its not the only way, nor is
it the most convenient. An easier way to produce a strongly named assembly is to attribute your code.
Heres a modified version of Complex.cs that compiles to a strongly named single-file assembly:
using System;
using System.Reflection
[assembly:AssemblyKeyFile ("Keyfile.snk")]
[assembly:AssemblyVersion ("1.0.0.0")]
public
{
}
class ComplexMath
public int Square (int a)
{
return a * a;
}
And heres how Simple.vb would look if it, too, were modified to build a strongly named assembly:
Imports System
Imports System.Reflection
<Assembly:AssemblyKeyFile ("Keyfile.snk")>
<Assembly:AssemblyVersion ("1.0.0.0")>
Public
Class SimpleMath
Function Add (a As Integer, b As Integer) As Integer
Return a + b
End Function
AssemblyKeyFile and AssemblyVersion are attributes. Physically, they map to the AssemblyKeyFileAttribute
andAssemblyVersionAttribute classes defined in the FCLs System.Reflection namespace. Attributes are
mechanisms for declaratively adding information to a modules metadata. These particular attributes
create a strongly named assembly by signing the assembly and specifying a version number.
Delayed Signing
Unless an assembly is strongly named, it cant be installed in the GAC and its version number cant
be used to bind clients to a particular version of the assembly. Strongly naming an assembly is often referred
to as signing the assembly because the crux of strong naming is adding a digital signature generated
from the assembly manifest and the publishers private key. And therein lies a problem. In large
corporations, private keys are often locked away in vaults or hardware devices where only a privileged few
can access them. If youre a rank-and-file programmer developing a strongly named assembly and you
dont have access to your companys private key (which is exactly the situation that Microsoft
developers find themselves in), how can you fully test the assembly if you cant install it in the GAC or
use its version number to do strong versioning?
The answer is delayed signing. Delayed signing embeds the publishers public key (which is available to
everyone) in the assembly and reserves space for a digital signature to be added later. The presence of the
public key allows the assembly to be installed in the GAC. It also enables clients to build into their metadata
information denoting the specific version of the assembly that they were compiled against. The lack of a
digital signature means the assembly is no longer tamperproof, but you can fix that by signing the assembly
with the publishers private key before the assembly ships.
How does delayed signing work? If Public.snk holds the publishers public key, the following command
creates and delay-signs a Math assembly (note the /delaysign switch):
al /keyfile:public.snk /delaysign /target:library /out:Math.dll
/version:1.1.0.0 simple.netmodule complex.netmodule
In either event, the resultant assembly contains the publishers public key but lacks the signature
generated with the help of the private key. To sign the assembly before releasing it, have someone who has
access to the publishers private key do this:
sn /R Math.dll keyfile.snk
Using this statement assumes that Keyfile.snk holds the publishers public and private keys.
One trap to watch for regarding delayed signing is that neither the /delaysign switch nor the DelaySign
attribute in and of itself enables the assembly to be installed in the GAC or strongly versioned. To enable
both, run the SN utility against the assembly with a /Vr switch to enable verification skipping:
sn /Vr Math.dll
After signing the assembly with the publishers private key, disable verification skipping by running SN
with a /Vu switch:
sn /Vu Math.dll
Verification skipping enables an assembly to be loaded without verifying that it hasnt been tampered
with. After all, verification cant be performed if the assembly lacks the digital signature used for
verification. Verification skipping doesnt have to be enabled every time the assembly is built. Enabling it
once is sufficient to enable verification skipping until it is explicitly disabled again by running SN with a /Vu
switch.
Exception Handling
When something goes wrong during the execution of an application, the .NET Framework responds
by throwing an exception. Some exceptions are thrown by the CLR. For example, if an application
attempts to cast an object to a type that its not, the CLR throws an exception. Others are thrown
by the FCLfor example, when an application attempts to open a nonexistent file. The types of
exceptions that the .NET Framework throws are legion, so an application that targets the framework
better be prepared to handle them.
The beauty of exceptions in the world of managed code is that theyre an intrinsic part of the
.NET Framework. In the past, languages (and even individual language compilers) have used
proprietary means to throw and handle exceptions. You couldnt throw an exception in Visual
Basic and catch it in C++. You couldnt even throw an exception in a function compiled with one
C++ compiler and catch it in a function compiled with another. Not so in a managed application. The
CLR defines how exceptions are thrown and how theyre handled. You can throw an exception in
any language and catch it in any other. You can even throw exceptions across machines. And to top it
off, languages such as C# and Visual Basic .NET make exception handling extraordinarily easy.
Catching Exceptions
C# uses four keywords to expose the CLRs exception handling mechanism: try,catch,finally,
andthrow. The general idea is to enclose code that might throw an exception in a try block and to
include exception handlers in a catch block. Heres an example:
try {
Hashtable table = new Hashtable ();
table.Add ("First", 1);
string entry = (string) table["First"]; // Retrieve 1 and cast it
}
catch (InvalidCastException e) {
Console.WriteLine (e.Message);
}
An integer is not a string, so attempting to cast it to one will generate an InvalidCastException. That
will activate the InvalidCastException handler, which in this example writes the message
encapsulated in the exception object to the console. To write a more generic catch handler that
catches any exception thrown by the framework, specify Exception as the exception type:
catch (Exception e) {
...
}
And to respond differently to different types of exceptions, simply include a catch handler for each
type youre interested in:
try {
...
}
catch (InvalidCastException e) {
...
}
catch (FileNotFoundException e) {
...
}
catch (Exception e) {
...
}
The CLR calls the handler that most closely matches the type of exception thrown. In this example,
anInvalidCastException or FileNotFoundException vectors execution to one of the first two catch
handlers. Any other FCL exception type will activate the final handler. Notice that you dont have
to dispose of the Exception objects you catch because the garbage collector disposes of them for you.
All of the exception types defined in the FCL derive directly or indirectly from System.Exception,
which defines a base set of properties common to FCL exception types. Thanks to System.Exception,
for example, all FCL exception classes contain a Message property, which holds an error message
describing what went wrong, and a StackTrace property, which details the call chain leading up to the
exception. Derivative classes frequently add properties of their own. For instance,
FileNotFoundException includes a FileName property that reveals what file caused the exception.
The FCL defines dozens of different exception classes. Theyre not defined in any one namespace
but are spread throughout the FCLs roughly 100 namespaces. To help you get a handle on the
different types of exceptions youre liable to encounter, the following table lists some of the most
common exception types.
Common FCL Exception Classes
Class
Thrown When
ArgumentNullException
A null reference is illicitly passed as an argument
ArgumentOutOfRangeExceptionAn argument is invalid (out of range)
DivideByZeroException
An attempt is made to divide by 0
IndexOutOfRangeException
An invalid array index is used
InvalidCastException
A type is cast to a type its not
NullReferenceException
A null reference is dereferenced
OutOfMemoryException
A memory allocation fails because of a lack of memory
WebException
An error occurs during an HTTP request
As in C++, exception handlers can be nested. If method A calls method B and method B throws an
exception, the exception handler in method B is called provided a suitable handler exists. If method B
lacks a handler for the type of exception that was thrown, the CLR looks for one in method A. If A
too lacks a matching exception handler, the exception bubbles upward to the method that called A,
then to the method that called the method that called A, and so on.
What does the .NET Framework do with unhandled exceptions? It depends on the application type.
When a console application suffers an uncaught exception, the framework terminates the application
and writes an error message to the console window. If the application is a Windows Forms
application, the framework alerts the user with a message box. For a Web Forms application, the
framework displays an error page. Generally speaking, its far preferable to anticipate exceptions
and handle them gracefully than allow your users to witness an unhandled exception.
Guaranteeing Execution
Code in a finally block is guaranteed to execute, whether an exception is thrown or not. The finally
keyword really comes in handy when youre dealing with those pesky classes that wrap file
handles and other unmanaged resources. If you write code like
File file = new File ("Readme.txt");
.
.
.
file.Close ();
youve left a file open if an exception occurs after the file is opened but before Closeis called.
But if you structure your code this way, youre safe:
File file = null;
try {
file = new File ("Readme.txt");
.
.
.
}
catch (FileNotFoundException e) {
Console.WriteLine (e.Message);
}
finally {
if (file != null)
file.Close ();
}
NowClose is called regardless of whether an exception is thrown.Be aware that try blocks
accompanied by finally blocks do not have to have catch blocks. In the previous example, suppose
you want to make sure the file is closed, but you dont really care to handle the exception
yourself; youd rather leave that to a method higher up the call stack. Heres how to go about
it:
File file = null;
try {
file = new File ("Readme.txt");
.
.
.
}
finally {
if (file != null)
file.Close ();
}
This code is perfectly legitimate and in fact demonstrates the proper way to respond to an exception
that is best handled by the caller rather than the callee. Class library authors in particular should be
diligent about not eating exceptions that callers should be aware of.
Throwing Exceptions
Applications can throw exceptions as well as catch them. Look again at the Width and Height
properties in the Rectangle class presented earlier in this chapter. If a user of that class passes in an
invalidWidth or Height value, the set accessor throws an exception. You can also rethrow exceptions
thrown to you by using the throw keyword with no arguments.
You can use throw to throw exception types defined in the FCL, and you can use it to throw custom
exception types that you define. Although its perfectly legal to derive custom exception types
fromSystem.Exception (and even to declare exception classes that derive directly from
System.Object), developers are encouraged to derive from System.ApplicationException instead,
primarily because doing so enables applications to distinguish between exceptions thrown by the
Chapter 3
The .NET Framework Class Library
The .NET Framework class library (FCL) provides the API that managed applications write to.
Including more than 7,000 typesclasses, structs, interfaces, enumerations, and delegatesthe
FCL is a rich resource that includes everything from basic types such as Int32 and String to
exotic types such as Regex, which represents regular expressions, and Form, which is the base
class for windows in GUI applications. Ill often use the word classes to refer to FCL
members, but realize that Im taking literary license and that the FCL is not, as you are well
aware after reading Chapter 2, merely a class library.
The FCL is partitioned into approximately 100 hierarchically organized namespaces. System is
the root for most namespaces. It defines core data types such as Int32 and Byte, as well as utility
types such as Math and TimeSpan. A name such as System.Data refers to a namespace that is a
child of the System namespace. Its not unusual to find namespaces nested several levels
deep, as in System.Runtime.Remoting.Channels.Tcp.
Segregating FCL types into namespaces adds structure to the .NET Framework class library and
makes it easier to find the classes you need as you learn your way around the FCL. Learning is
made easier by the fact that namespace names reflect what the types in a namespace are used for.
For example, System.Web.UI.WebControls contains ASP.NET Web controls, while
System.Collections is home to the FCLs collection classesHashtable,ArrayList, and
others.
This chapter introduces some of the .NET Framework class librarys key classes and
namespaces. Its not meant to be exhaustive; no chapter can possibly cover the FCL in its
entirety. The classes youll read about here are ones that tend to be used by a broad crosssection of applications. They were chosen not only for their generality, but also because they
provide a fair and accurate representation of the breadth, depth, and wide-ranging capabilities of
the .NET Framework class library.
Classes in the System.IO namespace enable managed applications to perform file I/O and other forms of inpu
and output. The fundamental building block for managed I/O is the stream, which is an abstract representation
byte-oriented data. Streams are represented by the System.IO.Stream class. Because Stream is abstract,
System.IO as well as other namespaces include concrete classes derived from Stream that represent physical d
sources. For example, System.IO.FileStream permits files to be accessed as streams; System.IO.MemoryStrea
does the same for blocks of memory. The System.Net.Sockets namespace includes a Stream derivative named
NetworkStream that abstracts sockets as streams, and the System.Security.Cryptography namespace defines a
CryptoStream class used to read and write encrypted streams.
Stream classes have methods that you can call to perform input and output, but the .NET Framework offers an
additional level of abstraction in the form of readers and writers. The BinaryReader and BinaryWriter classes
provide an easy-to-use interface for performing binary reads and writes on stream objects. StreamReader and
StreamWriter, which derive from the abstract TextReader and TextWriter classes, support the reading and
writing of text.
One of the most common forms of I/O that managed and unmanaged applications alike are called upon to
perform is file I/O. The general procedure for reading and writing files in a managed application is as follows
1. Open the file using a FileStream object.
2. For binary reads and writes, wrap instances of BinaryReader and BinaryWriter around the FileStream
object and call BinaryReader and BinaryWriter methods such as Read and Write to perform input and
output.
3. For reads and writes involving text, wrap a StreamReader and StreamWriter around the FileStream obje
and use StreamReader and StreamWriter methods such as ReadLine and WriteLine to perform input and
output.
4. Close the FileStream object.
That this example deals specifically with file I/O is not to imply that readers and writers are only for files.
Theyre not. Later in this chapter, youll see a sample program that uses a StreamReader object to read
text fetched from a Web page. The fact that readers and writers work with any kind of Stream object makes
them powerful tools for performing I/O on any stream-oriented media.
System.IO also contains classes for manipulating files and directories. The File class provides static methods
opening, creating, copying, moving, and renaming files, as well as for reading and writing file attributes.
FileInfo boasts the same capabilities, but FileInfo exposes its features through instance methods rather than
static methods. The Directory and DirectoryInfo classes provide a programmatic interface to directories,
enabling them to be created, deleted, enumerated, and more via simple method calls. Chapter 4s
ControlDemo application demonstrates how to use File and Directory methods to enumerate the files in a
directory and obtain information about those files.
Text File I/O
The reading and writing of text files from managed applications is aided and abetted by the FileStream,
StreamReader, and StreamWriter classes. Suppose you wanted to write a simple app that dumps text files to th
console windowthe functional equivalent of the old DOS TYPE command. Heres how to go about it:
The first line creates a StreamReader object that wraps a FileStream created from filename. The for loop uses
StreamReader.ReadLine to iterate through the lines in the file and Console.WriteLine to output them to the
console window. The final statement closes the file by closing the StreamReader.
Thats the general approach, but in real life you have to anticipate the possibility that things might not go
strictly according to plan. For example, what if the file name passed to StreamReaders constructor is
invalid? Or what if the framework throws an exception before the final statement is executed, causing the file
be left open? Figure 3-1 contains the source code for a managed version of the TYPE command (called LIST
distinguish it from the real TYPE command) that responds gracefully to errors using C# exception handling. T
catch block traps exceptions thrown when StreamReaders constructor encounters an invalid file name or
when I/O errors occur as the file is being read. The finally block ensures that the file is closed even if an
exception is thrown.
List.cs
using System;
using System.IO;
class MyApp
{
static
{
try {
reader = new StreamReader (args[0]);
for (string line = reader.ReadLine (); line != null;
line = reader.ReadLine ())
Console.WriteLine (line);
}
catch (IOException e) {
Console.WriteLine (e.Message);
}
finally {
if (reader != null)
reader.Close ();
}
Figure 3-1
A managed application that mimics the TYPE command.
Because the FCL is such a comprehensive class library, passing a file name to StreamReaders constructo
isnt the only way to open a text file for reading. Here are some others:
// Use File.Open to create a FileStream, and then wrap a
// StreamReader around it
There are other ways, too, but you get the picture. None of these methods for wrapping a StreamReader aroun
file is intrinsically better than the others, but they do demonstrate the numerous ways in which ordinary,
everyday tasks can be accomplished using the .NET Framework class library.
StreamReaders read from text files; StreamWriters write to them. Suppose you wanted to write catch handlers
that log exceptions to a text file. Heres a LogException method that takes a file name and an Exception
object as input and uses StreamWriter to append the error message in the Exception object to the file:
void
{
}
Passing true in the second parameter to StreamWriters constructor tells the StreamWriter to append data
the file exists and to create a new file if it doesnt.
Binary File I/O
BinaryReader and BinaryWriter are to binary files as StreamReader and StreamWriter are to text files. Their k
methods are Read and Write, which do exactly what you would expect them to. To demonstrate, the sample
program in Figure 3-2 uses BinaryReader and BinaryWriter to encrypt and unencrypt files by XORing their
contents with passwords entered on the command line. Encrypting a file is as simple as running Scramble.exe
from the command line and including a file name and password, in that order, as in:
scramble readme.txt imbatman
XOR-encryption is hardly industrial-strength encryption, but its sufficient to hide file contents from casu
intruders. And its simple enough to not distract from the main point of the application, which is to get a
firsthand look at BinaryReader and BinaryWriter.
Scramble.cs contains two lines of code that merit further explanation:
ASCIIEncoding enc = new ASCIIEncoding ();
byte[] keybytes = enc.GetBytes (key);
These statements convert the second command-line parametera string representing the encryption keyin
an array of bytes. Strings in the .NET Framework are instances of System.String.ASCIIEncoding.GetBytes is
convenient way to convert a System.String into a byte array. Scramble XORs the bytes in the file with the byt
in the converted string. Had the program used UnicodeEncoding.GetBytes instead, encryption would be less
effective because calling UnicodeEncoding.GetBytes on strings containing characters from Western alphabets
produces a buffer in which every other byte is a 0. XORing a byte with 0 does absolutely nothing, and XOR
encryption is weak enough as is without worsening matters by using keys that contain lots of zeros.
ASCIIEncoding is a member of the System.Text namespace, which explains the using System.Text directive a
the top of the file.
Scramble.cs
using System;
using System.IO;
using System.Text;
class MyApp
{
const int bufsize = 1024;
static
{
Figure 3-2
A simple file encryption utility.
stream = File.Open (filename, FileMode.Open,
FileAccess.ReadWrite);
// Wrap a reader and writer around the FileStream
BinaryReader reader = new BinaryReader (stream);
BinaryWriter writer = new BinaryWriter (stream);
// Convert the key into a byte array
ASCIIEncoding enc = new ASCIIEncoding ();
byte[] keybytes = enc.GetBytes (key);
// Allocate an I/O buffer and a key buffer
byte[] buffer = new byte[bufsize];
byte[] keybuf = new byte[bufsize + keybytes.Length - 1];
Collections
The .NET Framework class librarys System.Collections namespace contains classes that serve as
containers for groups, or collections, of data. Hashtable is one example of a System.Collections type. It
implements hash tables, which feature lightning-fast lookups. Another example is ArrayList, which represents
resizable arrays. The presence of these and other types defined in System.Collectionsmeans you can spend
more time writing code that makes your application unique and less time writing tedious infrastructural code.
The following table lists the core collection classes defined in System.Collections. Succeeding sections
formally introduce the Hashtable and ArrayList classes. Other collection classes, including Stack and
SortedList, are used in sample programs presented in this chapter and others.
System.Collections Collection Classes
Class
Implements
ArrayList Resizable arrays
BitArray Bit arrays
Hashtable Tables of key/value pairs structured for fast lookups
Queue
First-in, first-out (FIFO) buffers
SortedListTables of sorted key/value pairs accessible by key or index
Stack
Last-in, first-out (LIFO) buffers
One characteristic of all the collection classes in System.Collections (with the exception of BitArray, which
stores Boolean values) is that theyre weakly typed. In other words, they store instances of System.Object
Weak typing enables collections to store virtually any kind of data because all .NET Framework data types
derive directly or indirectly from System.Object. Unfortunately, weak typing also means that you have to do a
lot of casting. For example, if you put a string in a Hashtable in C# and then retrieve it, you have to cast it bac
to a string to call String methods on it. If youre morally opposed to casting, you can use the
System.Collections classes CollectionBase and DictionaryBase as base classes for strongly typed collections o
your own. However, its very likely that a future version of the .NET Framework will support something
calledgenerics, which are analogous to C++ templates. If you can stomach a moderate amount of casting for
now, building type-safe collection classes should be a lot easier in the future.
Hash Tables
Since the dawn of computing, programmers have searched for ways to optimize data retrieval operations.
When it comes to fast lookups, nothing beats the hash table. Hash tables store key/value pairs. When an item
inserted, the key is hashed and the resulting value (modulo the table size) is used as an index into the table,
specifying where the item should be stored. When a value is retrieved, the key is hashed again. The resulting
index reveals the items precise location in the table. A well-designed hash table can retrieve items with
just one lookup, irrespective of the number of items in the table.
System.Collections.Hashtable is the FCLs hash table data type. The following code uses a Hashtable
object to build a simple French-English dictionary. The values stored in the table are the French words for the
days of the week. The keys are the English words for the same:
Hashtable
table.Add
table.Add
table.Add
table.Add
table.Add
table.Add
table.Add ("Saturday",
"Samedi");
With the hash table initialized in this manner, finding the French equivalent of Tuesday requires one simple
statement:
string word = (string) table["Tuesday"];
Semantically, theres a difference between adding items with Add and adding them with indexers. Add
throws an exception if the key you pass to it is already in the table. Indexers dont. They simply replace th
old value with the new one.
Physically, a Hashtable stores items added to it in System.Collections.DictionaryEntry objects. Each
DictionaryEntry object holds a key and a value that are exposed through properties named Key and Value.
BecauseHashtable implements the FCLs IDictionary interface, which in turn derives indirectly from
IEnumerable, you can use the C# foreach command (or the Visual Basic .NET For Each command) to
enumerate the items in a Hashtable. The following code writes all the keys and values stored in a Hashtable
namedtable to the console window:
foreach (DictionaryEntry entry in table)
Console.WriteLine ("Key={0}, Value={1}\n", entry.Key, entry.Value);
Hashtable also has methods for removing items (Remove), removing all items (Clear), checking for the
existence of items (ContainsKey and ContainsValue), and more. To find out how many items a Hashtable
contains, read its Count property. To enumerate only a Hashtables keys or values, use its Keys or Values
property.
Two factors control Hashtable lookup performance: the Hashtables size and the uniqueness of the hashes
produced from the input keys. A Hashtables size is dynamic; the table automatically grows as new items
are added to it to minimize the chance of collisions. A collision occurs when two different keys hash to
identical table indexes. Hashtable uses a double hashing algorithm to mitigate the negative effect of collisions
on performance, but the best performance comes when there are no collisions at all.
Grow operations are expensive because they force the Hashtable to allocate new memory, recompute the table
indexes, and copy each item to a new position in the table. By default, a Hashtable is sized for 0 items,
meaning that many grow operations are required to grow it to a respectable size. If you know in advance
approximately how many items youll be adding to a Hashtable, set the tables initial size by passing a
count to the class constructor. The following statement creates a Hashtable whose size is optimized for 1,000
items:
Hashtable table = new Hashtable (1000);
Initializing the Hashtable size in this manner doesnt affect lookup performance, but it can improve
insertion speed by a factor of 2 or more.
When a Hashtable grows, it always assumes a size thats a prime number to minimize the likelihood of
collisions. (Statistically, if n is a random number, n modulo m is more likely to produce a unique result if m is
prime number.) By default, a Hashtable expands its memory allocation when the item count exceeds a
predetermined percentage of the table size. You can control the percentage by varying the load factor. A load
factor of 1.0 corresponds to 72 percent, 0.9 corresponds to 65 percent (0.9 72), and so on. Valid load factors
range from 0.1 to 1.0. The following statement sizes a Hashtable for 1,000 items and sets its load factor to 0.8
meaning that the Hashtable will grow when the item count reaches approximately 58 percent of the table size:
Hashtable table = new Hashtable (1000, 0.8f);
The default load factor (1.0) is fine for most applications, so chances are youll never need to change it.
Maximizing the uniqueness of hash values generated from input keys is also critical to a Hashtables
performance. By default, Hashtable hashes an input key by calling the keys GetHashCode method, which
all objects inherit from System.Object. If you key values with instances of a class whose GetHashCode metho
does a poor job of generating unique hash values, do one of the following to optimize performance:
OverrideGetHashCode in a derived class and provide an implementation that produces unique hash
values.
Create a type that implements IHashCodeProvider and pass a reference to an instance of that type to
Hashtables constructor. Hashtable will respond by calling the objects
IHashCodeProvider.GetHashCode method to hash the input keys.
Many FCL data types, including strings, hash just fine and therefore work well as Hashtable keys right out of
the box.
Hashtable calls a keys Equals methodanother method inherited from System.Objectto compare
keys. If you use a custom data type as Hashtable keys and the Equals method your type inherits from
System.Object doesnt accurately gauge equality, either override Equals in the derived class or pass
Hashtables constructor an IComparer interface whose Compare method is capable of comparing keys.
Resizable Arrays
The FCLs System namespace contains a class named Array that models the behavior of static arrays.
System.Collections.ArrayList encapsulates dynamic arraysarrays that can be sized and resized as needed.
ArrayLists are useful when you want to store data in an array but dont know up front how many items
youll be storing.
Creating an ArrayList and adding items to it is simplicity itself:
ArrayList list = new ArrayList ();
list.Add ("John");
list.Add ("Paul");
list.Add ("George");
list.Add ("Ringo");
Add adds an item to the end of the array and grows the arrays memory allocation if necessary to
accommodate the new item. The related Insert method inserts an item into an ArrayList at a specified position
and moves higher-numbered items upward in the array. Insert also grows the array if thats necessary.
If you know approximately how many items youll be adding to an ArrayList, you should specify a count
creation time to minimize the number of resizing operations performed. The following code creates an
The next code sample does the same, but does it in half the time (10 milliseconds versus 20 milliseconds on th
machine I tested it on):
ArrayList list = new ArrayList (100000);
for (int i=0; i<100000; i++)
list.Add (i);
TheCount property reveals how many items an ArrayList contains. Consequently, one way to iterate through
the items in an ArrayList is as follows:
for (int i=0; i<list.Count; i++)
Console.WriteLine (list[i]);
To remove items from an ArrayList, call Remove,RemoveAt,RemoveRange, or Clear. When items are
removed, items with higher indexes are automatically shifted down to fill the void. If you delete the item at
index 5, for example, the item at index 6 becomes the item at index 5, the item at index 7 becomes the item at
index 6, and so on.
Instances of ArrayListautomatically allocate memory to accommodate new items. They dont
automatically release that memory when items are removed. To downsize an ArrayList to fit exactly the
number of items that it currently contains, call TrimToSize. The following example adds 1000 integers to an
ArrayList, deletes the first 500, and then resizes the array to fit the remaining 500:
// Add items
ArrayList list = new ArrayList (1000);
for (int i=0; i<1000; i++)
list.Add (i);
// Remove items
list.RemoveRange (0, 500);
// Resize the array
list.TrimToSize ();
The number of items that an ArrayList can hold without allocating additional memory is called its capacity.
You can find out the capacity of an ArrayList from its Capacity property. Capacity is a get/set property, so you
can use it to set the capacity as well as to read it. The ability to increase an ArrayLists capacity on the fly
comes in handy if you dont know how many items the array will store when you create it, but you do kno
approximately how many it will store when you start adding items.
The output consists of an alphabetically sorted list of words found in the file and the number of times that eac
word appears. WordCount uses a StreamReader, a Hashtable, and an ArrayList to do its work. For good
measure, it also throws in a SortedList. Its source code appears in Figure 3-3.
When executed, WordCount opens the input file and reads through it a line at a time with repeated calls to
StreamReader.ReadLine. It extracts the words from each line by calling a local method named GetWords, and
it uses each word returned by GetWords as a key in the Hashtable. If the key doesnt existmeaning the
word hasnt been encountered beforeWordCount adds a 1 to the Hashtable and keys it with the word. I
the key does existmeaning the word has been encountered beforeWordCount reads the associated
integer value from the Hashtable, increments it by one, and writes it back using the same key. By the time
WordCount reaches the end of the file, every word it encountered is represented as a key in the Hashtable, and
the value associated with each key is a count of the number of times the word appears. Thus, the
Hashtables purpose is twofold:
It provides a super-fast way to determine whether a word has been encountered before.
It provides a store for the word list and associated occurrence counts.
How do ArrayList and SortedList fit into the picture? When GetWords begins parsing a line of text, it has no
idea how many words it will encounter. Because it cant very well store the results in a static array, it uses
dynamic arrayan ArrayListinstead. After parsing is complete, GetWords allocates a static array just larg
enough to hold the items in the ArrayList and copies the ArrayList to the static array with ArrayList.CopyTo.
Then it returns the static array to the caller.
MethodMain uses a SortedList object to sort the word list before writing it to the console window. One simpl
statement copies the Hashtable to the SortedList; a foreach loop extracts items from the SortedList and output
them to the screen. Because the values used to key the items in the SortedList are strings, the simple act of
inserting them into the SortedList sorts them alphabetically.
WordCount.cs
using System;
using System.IO;
using System.Collections;
class MyApp
{
static
{
try {
// Iterate through the file a word at a time, creating
Figure 3-3
WordCount source code.
}
static
{
// Parse the words from the line and add them to the ArrayLis
int i = 0;
string word;
char[] characters = line.ToCharArray ();
if (i == characters.Length)
return null;
int start = i;
Regular Expressions
One of the lesser known but potentially most useful classes in all of the .NET Framework class library is Rege
which belongs to the System.Text.RegularExpressions namespace. Regex represents regular expressions. Reg
expressions are a language for parsing and manipulating text. (A full treatment of the language is beyond the
scope of this book, but wonderful tutorials are available both in print and on line.) Regex supports three basic
types of operations:
Splitting strings into substrings using regular expressions to identify separators
Searching strings for substrings that match patterns in regular expressions
Performing search-and-replace operations using regular expressions to identify the text you want to repla
One very practical use for regular expressions is to validate user input. Its trivial, for example, to use a
regular expression to verify that a string entered into a credit card field conforms to a pattern thats consis
with credit card numbersthat is, digits possibly separated by hyphens. Youll see an example of such u
in a moment.
Another common use for regular expressions is to do screen scraping. Say you want to write an app that displa
stock prices gathered from a real-time (or near real-time) data source. One approach is to send an HTTP reque
to a Web site such as Nasdaq.com and screen scrape the prices from the HTML returned in the respon
Regex simplifies the task of parsing HTML. The downside to screen scraping, of course, is that your app may
cease to work if the format of the data changes. (I know because I once wrote an app that used screen scraping
fetch stock prices, and the day after I published it, my data source changed the HTML format of its Web page
But unless you can find a data source that provides the information you want as XML, screen scraping might
your only choice.
When you create a Regex object, you pass to the class constructor the regular expression to encapsulate:
Regex regex = new Regex ("[a-z]");
In the language of regular expressions, [a-z] means any lowercase letter of the alphabet. You can also
pass a second parameter specifying Regex options. For example, the statement
Regex regex = new Regex ("[a-z]", RegexOptions.IgnoreCase);
creates a Regex object that matches any letter of the alphabet without regard to case. If the regular expression
passed to the Regex constructor is invalid, Regex throws an ArgumentException.
Once a Regex object is initialized, you call methods on it to apply the regular expression to strings of text. Th
following sections describe how to put Regex to work in managed applications and offer examples regarding
use.
Splitting Strings
Regex.Split splits strings into constituent parts by using a regular expression to identify separators. Heres
example that divides a path name into drive and directory names:
Regex regex = new Regex (@"\\");
string[] parts = regex.Split (@"c:\inetpub\wwwroot\wintellect");
foreach (string part in parts)
Console.WriteLine (part);
Notice the double backslash passed to Regexs constructor. The @ preceding the string literal prevents yo
from having to escape the backslash for the compilers sake, but because the backslash is also an escape
character in regular expressions, you have to escape a backslash with a backslash to form a valid regular
expression.
The fact that Split identifies separators using full-blown regular expressions makes for some interesting
possibilities. For example, suppose you wanted to extract the text from the following HTML by stripping out
everything in angle brackets:
<b>Every</b>good<h3>boy</h3>does<b>fine</b>
The regular expression <[^>]*> means anything that begins with an opening angle bracket (<
followed by zero or more characters that are not closing angle brackets ([^>]*), followed by a closing
angle bracket (>).
WithRegex.Split to lend a hand, you could simplify this chapters WordCount utility considerably. Rather
than having the GetWords method manually parse a line of text into words, you could rewrite GetWords to sp
the line using a regular expression that identifies sequences of one or more nonalphanumeric characters as
separators. Then you could delete the GetNextWord method altogether.
Searching Strings
Perhaps the most common use for Regex is to search strings for substrings matching a specified pattern. Rege
includes three methods for searching strings and identifying the matches: Match,Matches, and IsMatch.
The simplest of the three is IsMatch. It provides a simple yes or no answer revealing whether an input string
contains a match for the text represented by a regular expression. Heres a sample that checks an input str
for HTML anchor tags (<a>):
Regex regex = new Regex ("<a[^>]*>", RegexOptions.IgnoreCase);
if (regex.IsMatch (input)) {
// Input contains an anchor tag
}
else {
// Input does NOT contain an anchor tag
}
Another use for IsMatch is to validate user input. The following method returns true if the input string contain
digits grouped into fours separated by hyphens, and false if it does not:
bool IsValid (string input)
{
Regex regex = new Regex ("^[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}$");
return regex.IsMatch (input);
}
Strings such as 1234-5678-8765-4321 pass the test just fine; strings such as 1234567887654321
and 1234-ABCD-8765-4321 do not. The ^ and $ characters denote the beginning and end of the line,
respectively. Without these characters, strings such as 12345-5678-8765-4321 would pass, even thou
you didnt intend for them to. Regular expressions such as this are often used to perform cursory validatio
on credit card numbers. If youd like, you can replace [0-9] in a regular expression with /d
Thus, the expression
"^\d{4}-\d{4}-\d{4}-\d{4}$"
Figure 3-4 contains the source code for a grep-like utility named NetGrep that uses IsMatch to parse a file for
lines of text containing text matching a regular expression. Both the file name and the regular expression are
entered on the command line. The following command lists all the lines in Index.html that contain anchor tag
netgrep index.html "<a[^>]*>"
This command displays all lines in Readme.txt that contain numbers consisting of two or more digits:
netgrep readme.txt "\d{2,}"
In the source code listing, note the format specifier used in the WriteLine call. The D5 in {0:D5}
specifies that the line number should be formatted as a decimal value with a fixed field width of 5for exam
00001.
NetGrep.cs
using System;
using System.IO;
using System.Text.RegularExpressions;
class MyApp
{
static void Main (string[] args)
{
// Make sure a file name and regular expression were entered
if (args.Length < 2) {
Console.WriteLine ("Syntax: NETGREP filename expression");
return;
}
StreamReader reader = null;
int linenum = 1;
try {
// Initialize a Regex object with the regular expression
// entered on the command line
}
}
}
Figure 3-4
NetGrep source code.
IsMatch tells you whether a string contains text matching a regular expression, but it doesnt tell you whe
the string the match is located or how many matches there are. Thats what the Match method is for. The
following example displays all the Hrefs in Index.html that are followed by URLs enclosed in quotation mark
The metacharacter \s in a regular expression denotes whitespace; \s followed by an asterisk
(\s*) means any number of consecutive whitespace characters:
Regex regex = new Regex ("href\\s*=\\s*\"[^\"]*\"", RegexOptions.IgnoreCase);
StreamReader reader = new StreamReader ("Index.html");
for (string line = reader.ReadLine (); line != null;
line = reader.ReadLine ()) {
for (Match m = regex.Match (line); m.Success; m = m.NextMatch ())
Console.WriteLine (m.Value);
}
TheMatch method returns a Match object indicating either that a match was found (Match.Success== true) or
that no match was found (Match.Success == false). A Match object representing a successful match exposes t
text that produced the match through its Value property. If Match.Success is true and the input string contains
additional matches, you can iterate through the remaining matches with Match.NextMatch.
If the input string contains (or might contain) multiple matches and you want to enumerate them all, the Matc
method offers a slightly more elegant way of doing it. The following example is functionally equivalent to the
above:
Regex regex = new Regex ("href\\s*=\\s*\"[^\"]*\"", RegexOptions.IgnoreCase);
StreamReader reader = new StreamReader ("Index.html");
for (string line = reader.ReadLine (); line != null;
line = reader.ReadLine ()) {
MatchCollection matches = regex.Matches (line);
foreach (Match match in matches)
Console.WriteLine (match.Value);
}
Matches returns a collection of Match objects in a MatchCollection whose contents can be iterated over with
foreach. Each Match represents one match in the input string.
Match objects have a property named Groups that permits substrings within a match to be identified. Let
say you want to scan an HTML file for Hrefs, and for each Href that Regex finds, you want to extract the targ
thatHreffor example, the dotnet.html in href=dotnet.html. You can do that by using parentheses to
define a group in the regular expression and then use the Match objects Groups collection to access the
group. Heres an example:
Regex regex = new Regex ("href\\s*=\\s*\"([^\"]*)\"", RegexOptions.IgnoreCase);
StreamReader reader = new StreamReader ("Index.html");
for (string line = reader.ReadLine (); line != null;
line = reader.ReadLine ()) {
MatchCollection matches = regex.Matches (line);
foreach (Match match in matches)
Console.WriteLine (match.Groups[1]);
}
Notice the parentheses that now surround the part of the regular expression that corresponds to all characters
between the quotation signs. That defines those characters as a group. In the Match objects Groups
collection,Groups[0] identifies the full text of the match and Groups[1] identifies the subset of the match in
parentheses. Thus, if Index.html contains the following line:
<a href="help.html">Click here for help</a>
Groups can even be nested, meaning that virtually any subset of the text identified by a regular expression (or
subset of a subset) can be extracted following a successful match.
Replacing Strings
If you decide to embellish NetGrep with the capability to perform search-and-replace, youll love
Regex.Replace, which replaces text matching the regular expression in the Regex object with text you pass as
input parameter. The following example replaces all occurrences of Hello with Goodbye in th
string named input:
Regex regex = new Regex ("Hello");
string output = regex.Replace (input, "Goodbye");
The next example strips everything in angle brackets from the input string by replacing expressions in angle
brackets with null strings:
Regex regex = new Regex ("<[^>]*>");
string output = regex.Replace (input, "");
A basic knowledge of regular expressions (and a helping hand from Regex) can go a long way when it comes
parsing and manipulating text in .NET Framework applications.
Internet Classes
The FCLs System.Net namespace includes classes for performing Internet-related tasks such as
submitting HTTP requests to Web servers and resolving names using the Domain Name System (DNS).
The daughter namespace System.Net.Sockets contains classes for communicating with other machines
using TCP/IP sockets. Together, these namespaces provide the foundation for the FCLs Internet
programming support. Other namespaces, such as System.Web and System.Web.Mail, contribute classes
of their own to make the .NET Framework a first-rate tool for writing Internet-enabled applications.
Two of the most useful classes in the System.Net namespace are WebRequest and WebResponse, which
are abstract base classes that serve as templates for object-oriented wrappers placed around HTTP and
other Internet protocols. System.Net includes a pair of WebRequest/WebResponse derivatives named
HttpWebRequest and HttpWebResponse that make it easy for managed applications to fetch Web pages
and other resources available through HTTP. Learning about these classes is a great starting point for
exploring the Internet programming support featured in the FCL.
HttpWebRequest and HttpWebResponse
HttpWebRequest and HttpWebResponse reduce the otherwise complex task of submitting HTTP requests
to Web servers and capturing the responses to a few simple lines of code. To submit a request, use the
WebRequestclasss static Create method to create a request and then call GetResponse on the
resultingHttpWebRequest object:
WebRequest request = WebRequest.Create ("http://www.wintellect.com");
WebResponse response = request.GetResponse ();
Its that simple. HttpWebRequest also contains methods named BeginGetResponse and
EndGetResponse that you can use to submit asynchronous Web requests, which might be useful if you
dont want to wait around for large amounts of data to come back through a slow dial-up connection.
The LinkList application in Figure 3-5 uses the WebRequest,WebResponse, and Regex classses to list a
Web pages hyperlinks. Its input is the URL of a Web page; its output is a list of all the URLs
accompanyingHrefs in the Web page. Fetching the Web page is easy thanks to WebRequest.GetResponse.
Regex.Match simplifies the task of parsing the Hrefs out of the response. To see LinkList in action, as
shown in Figure 3-6, compile it and type linklist followed by a URL at the command prompt:
linklist http://www.wintellect.com
LinkList also demonstrates that StreamReader objects can read from any kind of stream, not just file
streams. Specifically, it uses a StreamReader to read the contents of the stream returned by
WebResponsesGetResponseStream method. This is abstraction at its finest and is the primary reason
that the FCLs architects decided to use readers and writers to abstract access to streams.
LinkList.cs
using
using
using
using
System;
System.IO;
System.Net;
System.Text.RegularExpressions;
class MyApp
{
static
{
try {
Figure 3-5
LinkList source code.
Console.WriteLine (match.Groups[1]);
}
catch (Exception e) {
Console.WriteLine (e.Message);
}
finally {
if (reader != null)
reader.Close ();
}
Figure 3-6
LinkList output.
TheSystem.Web.Mail Namespace
Want to send e-mail from a .NET Framework application? You could do it the hard way by using sockets
to establish a connection to a mail server and then transmit a mail message using Simple Mail Transfer
Protocol (SMTP). Or you could do it the easy way and rely on classes in the System.Web.Mail namespace.
System.Web.Mail provides a simple managed interface to SMTP. The core classes are MailMessage,
which represents e-mail messages; MailAttachment, which represents attachments; and SmtpMail, which
places a friendly wrapper around the host systems SMTP mail service.
Heres how easy it is to send e-mail from a managed application:
using System.Web.Mail;
.
.
.
MailMessage message = new MailMessage ();
message.From = "[email protected]";
message.To = "[email protected]";
message.Subject = "Scheduled Power Outage";
message.Body = "Our servers will be down tonight.";
SmtpMail.SmtpServer = "localhost";
SmtpMail.Send (message);
Having the capability to send e-mail programmatically can come in handy in countless ways. For example,
you might want to send e-mail confirmations to customers who purchase merchandise from your Web site,
or you might write your own software to transmit electronic newsletters to clients. Whatever your
motivation, it doesnt get much easier than this.
Figure 3-7 contains the source code for a simple e-mail client named SendMail, built around the
MailMessage and SmtpMail classes. To spice things up just a bit, and to serve as a preview of things to
come, SendMail isnt a console applicationits a Web application. Specifically, its an
ASP.NET Web form. (See Figure 3-8.) Clicking the Send Mail button activates the OnSend method,
which composes an e-mail message from the users input and sends it to the recipient. To run the
application, copy SendMail.aspx to the \Inetpub\wwwroot directory of a PC that has ASP.NET and
Internet Information Services installed on it. Then open a browser and type http://localhost/sendmail.aspx
in the address bar. Once the Web form appears, fill in the fields and click the Send Mail button to send an
e-mail message.
Some machines require a bit of reconfiguring to allow ASP.NET applications to send mail. If
SendMail.aspx throws an exception when you click the Send Mail button, heres what to do. First
make sure your machines SMTP service is running. You can do that in the IIS configuration manager
or in the Services Control Panel applet. Second, make sure the SMTP service is configured to allow
relaying from localhost. To do that, open the IIS configuration manager (youll find it in
Administrative Tools), right-click Default SMTP Virtual Server, select Properties, click the Access tab,
click the Relay button, select Only The List Below, and use the Add button to add 127.0.0.1 to the list of
computers allowed to relay.
SendMail.aspx
<%@ Import Namespace="System.Web.Mail" %>
<html>
<body>
<h1>Simple SMTP E-Mail Client</h1>
<form runat="server">
<hr>
<table cellspacing="8">
<tr>
<td align="right" valign="bottom">From:</td>
<td><asp:TextBox ID="Sender" RunAt="server" /></td>
</tr>
Figure 3-7
A Web-based e-mail client.
<tr>
<td align="right" valign="bottom">To:</td>
<td><asp:TextBox ID="Receiver" RunAt="server" /></td>
</tr>
<tr>
<td align="right" valign="bottom">Subject:</td>
<td><asp:TextBox ID="Subject" RunAt="server" /></td>
</tr>
<tr>
<td align="right" valign="top">Message:</td>
<td><asp:TextBox ID="Body" TextMode="MultiLine" Rows="5"
Columns="40" RunAt="server" /></td>
</tr>
</table>
<hr>
<asp:Button Text="Send Mail" OnClick="OnSend" RunAt="server" />
</form>
</body>
</html>
<script language="C#" runat="server">
void OnSend (Object sender, EventArgs e)
{
MailMessage message = new MailMessage ();
message.From = Sender.Text;
message.To = Receiver.Text;
message.Subject = Subject.Text;
message.Body = Body.Text;
SmtpMail.SmtpServer = "localhost";
SmtpMail.Send (message);
}
</script>
Figure 38
The SendMail application.
Data Access
In recent years, Microsoft has promoted an alphabet soup of database access technologies. First
was ODBC. Then came DAO, RDO, ADO, and OLE DB, to name just a few. The .NET
Framework has its own database API called ADO.NET. The bad news is that despite its name,
ADO.NET has little in common with ADO. The good news is that learning the basics of
ADO.NET requires all of about 15 minutes.
The classes that make up ADO.NET are found in the System.Data namespace and its
descendants. Some ADO.NET classes, such as DataSet, are generic and work with virtually any
kind of database. Others, such as DataReader, come in two distinctly different flavors: one for
Microsoft SQL Server databases (SqlDataReader) and one for all others (OleDbDataReader).Sql
classes belong to the System.Data.SqlClient namespace. They use a managed provider (that is, a
database access layer that consists solely of managed code) thats optimized to work with
Microsoft SQL Server databases. Significantly, Sql classes work only with SQL Server. OleDb
classes, on the other hand, can be used with any database for which an OLE DB provider that is
compatible with the .NET Framework is available. They tend to be somewhat slower than Sql
classes because theyre not optimized for any particular database and because they rely on a
combination of managed and unmanaged code, but theyre also more generic, enabling you
to switch databases without having to rewrite your application. OleDb classes are defined in the
System.Data.OleDb namespace.
ADO.NET is covered in detail in Chapter 12. The next several sections of this chapter offer an
introductory look at ADO.NET, which will help you understand some of the data-aware sample
programs presented in intervening chapters. For readers accustomed to working with traditional
database APIs, the sections that follow also provide an educational first look at data access in the
era of .NET.
DataReaders
One of the most common tasks that data-aware applications are asked to perform involves
executing a query and outputting the results. For managed applications, the DataReader class is
the perfect tool for the job. DataReader objects expose the results of database queries as fast,
forward-only, read-only streams of data. DataReaders come in two flavors: SqlDataReader for
SQL Server databases and OleDbDataReader for other types of databases. The following
example uses SqlDataReader to query the Pubs database that comes with Microsoft SQL Server
for all the records in the Titles table. It then writes the Title field of each record
to a console window:
SqlConnection connection =
new SqlConnection ("server=localhost;uid=sa;pwd=;database=pubs");
connection.Open ();
SqlCommand command =
new SqlCommand ("select * from titles", connection);
SqlDataReader reader = command.ExecuteReader ();
while (reader.Read ())
Console.WriteLine (reader.GetString (1));
connection.Close ();
TheSqlConnection object represents the database connection. Open opens a connection, and
Close closes it. SqlCommand encapsulates the query used to extract records from the database.
CallingExecuteReader on the SqlCommand object executes the command and returns a
SqlDataReader object. Reading the records returned by the query is as simple as calling
SqlDataReader.Read repeatedly until it returns null.
I purposely didnt include exception handling code in this sample to keep the code as simple
and uncluttered as possible. In the real world, youll want to use try/catch/finally to recover
gracefully from errors and to ensure that the database connection is closed even in the face of
inopportune exceptions:
SqlConnection connection =
new SqlConnection ("server=localhost;uid=sa;pwd=;database=pubs");
try {
connection.Open ();
SqlCommand command =
new SqlCommand ("select * from titles", connection);
SqlDataReader reader = command.ExecuteReader ();
while (reader.Read ())
Console.WriteLine (reader.GetString (1));
}
catch (SqlException e) {
Console.WriteLine (e.Message);
}
finally {
connection.Close ();
}
Tailoring this code to work with databases other than Microsoft SQL Server is a simple matter of
changing the Sql classes to OleDb classes and modifying the connection string accordingly.
Inserts, Updates, and Deletes
ACommand objects ExecuteReader method executes a query and returns a DataReader
encapsulating the results. The complementary ExecuteNonQuery method performs inserts,
updates, and deletes. The following code uses a SQL INSERT command to add a record to SQL
Servers Pubs database:
SqlConnection connection =
new SqlConnection ("server=localhost;uid=sa;pwd=;database=pubs");
try {
connection.Open ();
string sqlcmd =
"insert into titles (title_id, title, type, pub_id, " +
"price, advance, royalty, ytd_sales, notes, pubdate) " +
"values ('BU1001', 'Programming Microsoft.NET', " +
"'Business', '1389', NULL, NULL, NULL, NULL, " +
"'Learn to program Microsoft.NET', 'Jan 01 2002')";
SqlCommand command = new SqlCommand (sqlcmd, connection);
command.ExecuteNonQuery ();
}
catch (SqlException e) {
Console.WriteLine (e.Message);
}
finally {
connection.Close ();
}
To update or delete a record (or set of records), you simply replace the INSERT command with
an UPDATE or DELETE command. Of course, there are other ways to add, modify, and remove
records. The full range of options is discussed in Chapter 12.
DataSets and DataAdapters
DataSet, which belongs to the System.Data namespace, is the centerpiece of ADO.NET. A
DataSet is an in-memory database capable of holding multiple tables and even of modeling
constraints and relationships. Used in combination with SqlDataAdapter and OleDbDataAdapter,
DataSet can handle virtually all the needs of modern-day data access applications and is
frequently used in lieu of DataReader to facilitate random read/write access to back-end
databases.
The following code fragment uses SqlDataAdapter and DataSet to query a database and display
the results. Its functionally equivalent to the SqlData-Reader example presented earlier:
SqlDataAdapter adapter = new SqlDataAdapter (
"select * from titles",
"server=localhost;uid=sa;pwd=;database=pubs"
);
DataSet ds = new DataSet ();
adapter.Fill (ds);
foreach (DataRow row in ds.Tables[0].Rows)
Console.WriteLine (row[1]);
SqlDataAdapter serves as a liaison between DataSet objects and physical data sources. In this
example it executes a query, but its capable of performing inserts, updates, and deletes, too.
For details, seeyou guessed itChapter 12.
Reflection
You already know that managed applications are deployed as assemblies, that assemblies contain
files that are usually (but not always) managed modules, and that managed modules contain types.
You also know that every managed module has metadata inside it that fully describes the types
defined in the module, and that assemblies carry additional metadata in their manifests identifying the
files that make up the assembly and other pertinent information. And youve seen how ILDASM
can be used to inspect the contents of an assembly or managed module. Much of the information that
ILDASM displays comes straight from the metadata.
TheSystem.Reflection namespace contains types that you can use to access metadata without having
to understand the binary metadata format. The term reflection means inspecting metadata to
get information about an assembly, module, or type. The .NET Framework uses reflection to acquire
important information at run time about the assemblies that it loads. Visual Studio .NET uses
reflection to obtain IntelliSense data. The managed applications that you write can use reflection, too.
Reflection makes the following operations possible:
Retrieving information about assemblies and modules and the types they contain
Reading information added to a compiled executables metadata by custom attributes
Performing late binding by dynamically instantiating and invoking methods on types
Not every managed application uses reflection or has a need to use reflection, but reflection is
something every developer should know about, for two reasons. First, learning about reflection
deepens ones understanding of the .NET Framework. Second, reflection can be extraordinarily
useful to certain types of applications. While far from exhaustive, the next few sections provide a
working introduction to reflection and should at least enable you to hold your own when the
conversation turns to reflection at a .NET party.
Retrieving Information About Assemblies, Modules, and Types
One use for reflection is to gather information at run time about assemblies, managed modules, and
the types that assemblies and modules contain. The key classes that expose the functionality of the
frameworks reflection engine are
System.Reflection.Assembly, which represents assemblies
System.Reflection.Module, which represents managed modules
System.Type, which represents types
The first step in acquiring information from an assemblys metadata is to load the assembly. The
following statement uses the static Assembly.LoadFrom method to load the assembly whose manifest
is stored in Math.dll:
Assembly a = Assembly.LoadFrom ("Math.dll");
LoadFrom returns a reference to an Assembly object representing the loaded assembly. A related
method named Load takes an assembly name rather than a file name as input. Once an assembly is
loaded, you can use Assembly methods to retrieve all sorts of interesting information about it. For
example, the GetModules method returns an array of Module objects representing the modules in the
assembly.GetExportedTypes returns an array of Type objects representing the types exported from
the assembly (in other words, the assemblys public types). GetReferencedAssemblies returns an
array of AssemblyName objects identifying assemblies used by this assembly. And the GetName
method returns an AssemblyName object that serves as a gateway to still more information encoded
in the assembly manifest.
Figure 3-9 contains the source code listing for a console application named AsmInfo that, given the
name of a file containing an assembly manifest, uses reflection to display information about the
assembly. Included in the output is information indicating whether the assembly is strongly or weakly
named, the assemblys version number, the managed modules that it consists of, the types
exported from the assembly, and other assemblies containing types that this assembly references.
When run on the weakly named version of the Math assembly (Math.dll) presented in Chapter 2,
AsmInfo produces the following output:
Naming: Weak
Version: 0.0.0.0
Modules
math.dll
simple.netmodule
complex.netmodule
Exported Types
SimpleMath
ComplexMath
Referenced Assemblies
mscorlib
Microsoft.VisualBasic
You can plainly see the two types exported from the Math assembly (SimpleMath and ComplexMath)
and the modules that make up the assembly (Math.dll, Simple.netmodule, and Complex.netmodule).
Mscorlib appears in the list of referenced assemblies because it contains the core data types used by
virtually all managed applications. Microsoft.VisualBasic shows up also because one of the modules
in the assembly, Simple.netmodule, was written in Visual Basic .NET.
AsmInfo.cs
using System;
using System.Reflection;
class MyApp
{
static
{
if (args.Length == 0) {
Console.WriteLine ("Error: Missing file name");
return;
}
try {
// Load the assembly identified on the command line
Assembly a = Assembly.LoadFrom (args[0]);
AssemblyName an = a.GetName ();
if (bytes == null)
Console.WriteLine ("Naming: Weak");
else
Console.WriteLine ("Naming: Strong");
Figure 3-9
AsmInfo source code.
Version ver = an.Version;
Console.WriteLine ("Version: {0}.{1}.{2}.{3}",
ver.Major, ver.Minor, ver.Build, ver.Revision);
If youd like to know even more about an assemblyspecifically, about the modules that it
containsyou can use the Module objects returned by Assembly.GetModules. Calling GetTypes on
aModule object retrieves a list of types defined in the moduleall types, not just exported types.
The following code sample writes the names of all the types defined in module to a console window:
Type[] types = module.GetTypes ();
foreach (Type type in types)
Console.WriteLine (type.FullName);
To learn even more about a given type, you can call GetMembers on a Type object returned by
GetTypes.GetMembers returns an array of MemberInfo objects representing the types individual
members.MemberInfo.MemberType tells you what kind of member a MemberInfo object represents.
MemberTypes.Field, for example, identifies the member as a field, while MemberTypes.Method
identifies it as a method. A MemberInfo objects Name property exposes the members name.
Using these and other Type members, you can drill down as deeply as you want to into a type, even
identifying the parameters passed to individual methods (and the methods return types) if you
care to.
Using reflection to inspect the contents of managed executables is probably only interesting if you
plan to write diagnostic utilities. But the fact that reflection exists at all leads to some other
interesting possibilities, one of which is discussed in the next section.
Custom Attributes
One of the ground-breaking new language features supported by CLR-compliant compilers is the
attribute. Attributes are a declarative means for adding information to metadata. For example, if you
attribute a method this way and compile it without a DEBUG symbol defined, the compiler
emits a token into the modules metadata noting that DoValidityCheck cant be called:
[Conditional ("DEBUG")]
public DoValidityCheck ()
{
...
}
If you later compile a module that calls DoValidityCheck, the compiler reads the metadata, sees that
DoValidityCheck cant be called, and ignores statements that call it.
Attributes are instances of classes derived from System.Attribute. The FCL defines several attribute
classes, including System.Diagnostics.ConditionalAttribute, which defines the behavior of the
Conditional attribute. You can write attributes of your own by deriving from Attribute. The canonical
example of a custom attribute is a CodeRevision attribute for documenting source code revisions.
Revisions noted with source code commentsa traditional method for documenting code
revisionsappear only in the source code. Revisions noted with attributes, however, are written into
the compiled executables metadata and can be retrieved through reflection.
Heres the source code for a custom attribute named CodeRevisionAttribute:
[AttributeUsage (AttributeTargets.All, AllowMultiple=true)]
class CodeRevisionAttribute : Attribute
{
public string Author;
public string Date;
public string Comment;
The first statement, AttributeUsage, is itself an attribute. The first parameter passed to it,
AttributeTargets.All, indicates that CodeRevisionAttribute can be applied to any element of the
source codeto classes, methods, fields, and so on. The second parameter permits multiple
CodeRevisionAttributes to be applied to a single element. The remainder of the code is a rather
ordinary class declaration. The class constructor defines CodeRevisionAttributes required
parameters. Public fields and properties in an attribute class can be used as optional parameters.
BecauseCodeRevisionAttribute defines a public field named Comment, for example, you can include
a comment string in a code revision attribute simply by prefacing the string with Comment=.
Heres an example demonstrating how CodeRevisionAttribute might be used:
[CodeRevision ("billg", "07-19-2001")]
[CodeRevision ("steveb", "09-30-2001", Comment="Fixed Bill's bugs")]
struct Point
{
public int x;
public int y;
public int z;
}
Get the picture? You can attribute any element of your source code by simply declaring a
CodeRevisionAttribute in square brackets. You dont have to include the word Attribute in the
attribute name because the compiler is smart enough to do it for you.
Reflection is important to developers who write (or use) custom attributes because it is through
reflection that an application reads information added to its (or someone elses) metadata via
custom attributes. The following code sample enumerates the code revision attributes attached to type
Point and writes them to the console window. Enumeration is made possible by
MemberInfo.GetCustomAttributes, which reads the custom attributes associated with any element
that can be identified with a MemberInfo object:
MemberInfo info = typeof (Point);
object[] attributes = info.GetCustomAttributes (false);
if
}
(attributes.Length > 0) {
Console.WriteLine ("Code revisions for Point struct");
foreach (CodeRevisionAttribute attribute in attributes) {
Console.WriteLine ("\nAuthor: {0}", attribute.Author);
Console.WriteLine ("Date: {0}", attribute.Date);
if (attribute.Comment != null)
Console.WriteLine ("Comment: {0}", attribute.Comment);
}
And heres the output when this code is run against the Point struct shown above:
Code revisions for Point struct
Author: billg
Date: 07-19-2001
Author: steveb
Date: 09-30-2001
Comment: Fixed Bill's bugs
Writing a reporting utility that lists all the code revisions in a compiled executable wouldnt be
difficult because types and type members are easily enumerated using the reflection techniques
described in the previous section.
Dynamically Loading Types (Late Binding)
A final use for reflection is to dynamically load types and invoke methods on them. Dynamic
loading means binding to a type at run time rather than compile time. Lets say your source
code references a type in another assembly, like this:
Rectangle rect = new Rectangle ();
Here youre practicing early binding because your compiler inserts data into the resulting
executable, noting that a type named Rectangle is imported from another assembly. Late binding
gives you the ability to use a type without embedding references to it in your metadata. Late binding
is accomplished by using reflection.
One use for late binding is to facilitate plug-ins. Suppose you want to enable third-party developers to
extend your application by contributing images to the splash screen your app displays when it starts
up. Because you dont know in advance what plug-ins might be present at startup, you cant
early bind to classes in the plug-ins. But you can late bind to them. If you instruct third-party
developers to build classes named PlugIn, and if each Plug-In class contains a method named
GetImage that returns an image to the caller, the following code calls GetImage on each plug-in
represented in the list of assembly names in the names array:
ArrayList images = new ArrayList ();
foreach (string name in names) {
Assembly a = Assembly.Load (name);
Type type = a.GetType ("PlugIn");
MethodInfo method = type.GetMethod ("GetImage");
Object obj = Activator.CreateInstance (type);
Image image = (Image) method.Invoke (obj, null);
images.Add (image);
}
At the end, the ArrayList named images holds an array of Image objects representing the images
obtained from the plug-ins.
Visual Basic .NET uses late binding to interact with variables whose declared type is Object. Late
binding is an important part of the .NET Framework architecture and something you should be aware
of even if you dont use it.
Chapter 4
Windows Forms
The Microsoft .NET Framework is chiefly a platform for writing Web applications and Web
services, but it supports other programming models as well. Chapter 3 spotlighted console
applications and even threw in a Web application for good measure. This chapter is about
Windows Formsthe programming model used to write GUI applications for the .NET
Framework.
On the outside, Windows Forms applications look like ordinary Windows applications. They
have windows, and they frequently incorporate common GUI application elements such as
menus, controls, and dialog boxes. On the inside, theyre managed applications in every
sense of the word. They contain common intermediate language (CIL) and metadata, they use the
.NET Framework class library (FCL), and they run in the highly managed environment of the
common language runtime (CLR).
The chief benefit to writing Windows applications the Windows Forms way is that the
framework homogenizes the GUI programming model and eliminates many of the bugs, quirks,
and inconsistencies that plague the Windows API. For example, every experienced Windows
programmer knows that certain window styles can be applied to a window only when the
window is created. Windows Forms largely eliminates such inconsistencies. If you apply a style
thats only meaningful at creation time to an existing window, the framework quietly
destroys the window and re-creates it with the specified style. In addition, the FCL is much richer
than the Windows API, and when you write Windows Forms applications, you have the full
power of the FCL at your disposal. More often than not, Windows Forms applications require
less code than conventional Windows applications. For example, an application written to the
native Windows API requires hundreds of lines of code (or a third-party graphics library) to
extract an image from a JPEG file. A Windows Forms application needs just one.
In Windows Forms, the term form is a synonym for window. An applications main window is a
the application has other top-level windows, they too are forms. Dialog boxes are also forms. Despite their na
Windows Forms applications dont have to look like forms. They, like traditional Windows applications,
full control over what appears in their windows.
Windows Forms applications rely heavily upon classes found in the FCLs System.Windows.Forms name
That namespace includes classes such as Form, which models the behavior of windows, or forms; Me
which represents menus; and Clipboard, which provides a managed interface to the systems clipboard. Th
System.Windows.Forms namespace also contains numerous classes representing controls, with names like Bu
TextBox,ListView, and MonthCalendar.
At the heart of nearly every Windows Forms application is a class derived from System.Windows.Forms.Form
instance of the derived class represents the applications main window. It inherits from Form scores of pro
and methods that make up a rich programmatic interface to forms. Want to know the dimensions of a form
area? In Windows, youd call the GetClientRect API function. In Windows Forms, you read the form
ClientRectangle or ClientSize property. Many properties can be written to as well as read. For example, you c
change a forms border style by writing to its BorderStyle property, resize a form using its Size or ClientS
property, or change the text in a forms title bar with its Text property.
Another important building block of a Windows Forms application is a System.Windows.Forms class named
Application. That class contains a static method named Run that drives a Windows Forms application by prov
message pump. You dont see the message pump, of coursethe very existence of messages is abstracte
by the .NET Framework. But its there, and its one more detail you dont have to worry about be
framework sweats such details for you.
Many Windows Forms applications also rely on classes in the System.Drawing namespace. System.Drawing c
classes that wrap the Graphics Device Interface+ (GDI+) portion of Windows. Classes such as Brush and Pen
represent logical drawing objects. They define the look of lines, curves, and fills. The Bitmap and Image class
represent images and are capable of importing images from a variety of file types, including BMP files, GIFs,
JPEGs.
But the most important System.Drawing class of all is Graphics.Graphics is the Windows Forms equivalent o
Windows device context; its the conduit for graphical output. If you want to draw a line in a Windows fo
callDrawLine on a Graphics object. If you want to draw a string of text, you call DrawString.Graphics contain
assortment of methods and properties that you can use to write graphical output to a form or any other device
a printer) that you can associate with a Graphics object.
Your First Windows Form
Youre a programmer, so when it comes to learning a new platform, nothing speaks to you better than a
world application. (Entire companies have been built around Hello, world applications.) Figure 4
the Windows Forms version of Hello, world. Figure 4-2 shows the resulting application.
Hello.cs
using System;
using System.Windows.Forms;
using System.Drawing;
class MyForm : Form
{
MyForm ()
{
Text = "Windows Forms Demo";
}
protected override void OnPaint (PaintEventArgs e)
{
e.Graphics.DrawString ("Hello, world", Font,
new SolidBrush (Color.Black), ClientRectangle);
}
static void Main ()
{
Application.Run (new MyForm ());
}
}
Figure 4-1
Hello.cs source code.
Figure 4-2
The Hello, world Windows Forms sample.
In a Windows Forms application, each form is represented by an instance of a class derived from Form. In He
the derived class is named MyForm.MyForms constructor customizes the forms title bar text by assi
string to MyFormsText property. Text is one of more than 100 properties that MyForm inherits from For
Every Windows programmer knows that windows receive WM_PAINT messages, and that most screen rende
performed in response to these messages. In Windows Forms, the equivalent of a WM_PAINT message is a v
Form method named OnPaint. A derived class can override this method to paint itself in response to WM_PA
messages.
TheOnPaint override in MyForm (notice the keyword override, which tells the C# compiler that youre ov
rather than hiding a virtual method inherited from a base class) writes Hello, world to the forms
areathe portion of the form bounded by the window border and title bar. OnPaint is passed a PaintEventAr
(System.Windows.Forms.PaintEventArgs) object, which contains properties named Graphics and ClipRectan
Graphics property holds a reference to a Graphics object that permits OnPaint to draw in the forms client
ClipRectangle contains a reference to a Rectangle (System.Drawing.Rectangle) object that describes which pa
client area needs repainting.
MyForm.OnPaint uses Graphics.DrawString to render its output. The first parameter to DrawString is the strin
The second is a Font (System.Drawing.Font) object that describes the font in which the text should be rendere
MyForm.OnPaint uses the forms default font, a reference to which is stored in the Form property named
The third parameter is a Brush (System.Drawing.Brush) object specifying the text color. OnPaint uses a black
SolidBrush (System.Drawing.SolidBrush) object, resulting in black text. The fourth and final parameter is a fo
rectangle describing where the text should be positioned. MyForm.OnPaint uses the forms entire client a
description of which is found in the Form property named ClientRectangle, as the formatting rectangle. Becau
DrawString outputs text in the upper left corner of the formatting rectangle by default, Hello, world a
the forms upper left corner.
If you wanted Hello, world displayed in the center of the window, you could use an alternate form of
DrawString that accepts a StringFormat object as an input parameter and initialize the StringFormatsAlig
andLineAlignment properties to center the string horizontally and vertically. Heres the modified version
OnPaint:
protected override void OnPaint (PaintEventArgs e)
{
StringFormat format = new StringFormat ();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
e.Graphics.DrawString ("Hello, world", Font,
new SolidBrush (Color.Black), ClientRectangle, format);
}
The final member of MyForm is a static method named Main.Main is the applications entry point. Displa
form on the screen is a simple matter of instantiating MyForm and passing a reference to the resulting object t
Application.Run. In Figure 4-1, the statement
Application.Run (new MyForm ());
Once youve entered the code in Figure 4-1 and saved it to a file named Hello.cs, youll need to comp
Open a command prompt window, go to the folder where Hello.cs is stored, and type
csc /target:winexe hello.cs
The/target switch tells the compiler to produce a GUI Windows application (as opposed to a console applicati
say, a DLL). The resulting executable is named Hello.exe.
Drawing in a Form: The GDI+
Writing Windows Forms applications that incorporate rich graphics means getting to know Graphics and othe
that expose the Windows GDI+ to managed code. The GDI has existed in Windows since version 1. The GDI
enhanced version of the GDI thats available on any system with Windows XP or the .NET Framework in
One of the differences between the GDI and the GDI+ is the latters support for gradient brushes, cardinal
and other drawing aids. But the larger difference lies in their respective programming models. Unlike the GDI
uses a stateful model, the GDI+ is mostly stateless. A traditional Windows application programs values such a
and text colors into a device context. A Windows Forms application doesnt. It passes parameters detailin
characteristics to every Graphics method it calls. You saw an example of this in the previous sections sam
program, which passed a Font object and a SolidBrush object to the DrawString method to identify the output
text color.
Drawing Lines, Curves, and Figures
Font and SolidBrush are graphics primitives used to control the output rendered to a form. But theyre not
primitives the GDI+ places at your disposal. Here are some of the others:
GDI+ Graphics Primitives
Class
Description
Bitmap
Represents bitmapped images
Font
Defines font attributes: size, typeface, and so on
HatchBrush
Defines fills performed with hatch patterns
LinearGradientBrushDefines fills performed with linear gradients
Pen
Defines the appearance of lines and curves
SolidBrush
Defines fills performed with solid colors
TextureBrush
Defines fills performed with bitmaps
Some of these classes are defined in the System.Drawing namespace; others belong to System.Drawing.Draw
Pens, which are represented by instances of Pen, control the appearance of lines and curves. Font objects cont
appearance of text, while brushes, represented by HatchBrush,LinearGradientBrush,SolidBrush, and TextureB
control fills. The following OnPaint method draws three different styles of rectangles: one that has no fill, a se
thats filled with red, and a third thats filled with a gradient that fades from red to blue:
protected override void OnPaint (PaintEventArgs e)
{
Pen pen = new Pen (Color.Black);
// Draw an unfilled rectangle
e.Graphics.DrawRectangle (pen, 10, 10, 390, 90);
The output is shown in Figure 4-3. The Pen object passed to DrawRectangle borders each rectangle with a bla
thats 1 unit wide. (By default, 1 unit equals 1 pixel, so the lines in this example are 1 pixel wide.) The br
passed to FillRectangle govern the fills in the rectangles interiors. Observe that outlines and fills are draw
separately. Veteran Windows developers will find this especially interesting because Windows GDI functions
draw closed figures draw outlines and fills together.
Figure 4-3
Rectangles rendered in a variety of styles.
TheGraphics class includes a variety of public Draw and Fill methods that you can use to draw lines, curves,
rectangles, ellipses, and other figures. A partial list appears in the following table. The documentation that com
the .NET Framework SDK contains an excellent dissertation on the GDI+, and its packed with examples
demonstrating how to draw anything from the simplest line to the most complex filled figure.
Graphics Methods for Drawing Lines, Curves,
and Figures
Method
Description
DrawArc
Draws an arc
DrawBezier Draws a Bezier spline
DrawCurve Draws a cardinal spline
DrawEllipse Draws a circle or an ellipse
DrawIcon
Draws an icon
DrawImage Draws an image
DrawLine Draws a line
DrawPie
Draws a pie-shaped wedge
DrawPolygonDraws a polygon
DrawString Draws a string of text
FillEllipse Draws a filled circle or ellipse
FillPie
Draws a filled pie-shaped wedge
FillPolygon Draws a filled polygon
FillRectangle Draws a filled rectangle
Disposing of GDI+ Objects
As you learned in Chapter 2, classes that wrap unmanaged resources such as file handles and database connec
require special handling to ensure that their resources are properly released. Pen,Brush, and other GDI+ classe
represent graphics primitives fall into this category because they wrap GDI+ handles. Failure to close GDI+ h
can result in debilitating resource leaks, especially in applications that run for a very long time. To be safe, yo
callDispose on pens, brushes, and other primitives to deterministically dispose of the resources that they enca
EvenGraphics objects should be disposed of if theyre created programmatically rather than obtained from
PaintEventArgs.
One way to dispose of GDI+ objects is to call Dispose on them manually:
Pen pen = new Pen (Color.Black);
.
.
.
pen.Dispose ();
Some C# programmers prefer the special form of using that automatically generates calls to Dispose and encl
them in finally blocks. One advantage of this technique is that it ensures Dispose is called, even if an exceptio
thrown:
using (Pen pen = new Pen (Color.Black)) {
.
.
.
}
And some programmers do neither, banking on the hope that the garbage collector will kick in before the GD
out of resource space, or that the application wont run long enough for GDI+ resources to grow critically
Another approach is to create pens, brushes, and other graphics primitives when an application starts up and t
reuse them as needed. This dramatically reduces the number of GDI+ resources that an application consumes,
but eliminates the need to call Dispose on each and every GDI+ object. It also improves performance ever so s
Coordinates and Transformations
When you call DrawRectangle and FillRectangle, you furnish coordinates specifying the position of the
rectangles upper left corner and distances specifying the rectangles width and height. By default, dis
are measured in pixels. Coordinates specify locations in a two-dimensional coordinate system whose origin li
upper left corner of the form and whose x and y axes point to the right and down, respectively. If the default
coordinate system or unit of measure is ill-suited to your needs, you can customize them as needed by adding
simple statements to your program.
The coordinates passed to Graphics methods are world coordinates. World coordinates undergo two transform
on their way to the screen. Theyre first translated into page coordinates, which denote positions on a logi
drawing surface. Later, page coordinates are translated into device coordinates, which denote physical positio
screen.
The GDI+ uses a transformation matrix to convert world coordinates to page coordinates. Transformation ma
mathematical entities that are used to convert x-y coordinate pairs from one coordinate system to another. The
well established in the world of computer graphics and well documented in computer science literature.
The transformation matrices that the GDI+ uses to perform coordinate conversions are instances of
System.Drawing.Drawing2D.Matrix. Every Graphics object encapsulates a Matrix object and exposes it throu
property named Transform. The default matrix is an identity matrix, which is the mathematical term for a
transformation matrix that performs no transformation (just as multiplying a number by 1 yields the same num
You can customize the transformation matrixand hence the way world coordinates are converted to page
coordinatesin either of two ways. Option number one is to initialize an instance of Matrix with values that
the desired transformation and assign it to the Transform property. Thats no problem if youre an exp
linear algebra, but mere mortals will probably prefer option number two, which involves using Graphics meth
Confused? Then maybe an example will help. The following OnPaint method draws a rectangle thats 200
wide and 100 units tall in a forms upper left corner, as seen in Figure 4-4:
protected override void OnPaint (PaintEventArgs e)
{
SolidBrush brush = new SolidBrush (Color.Red);
e.Graphics.FillRectangle (brush, 0, 0, 200, 100);
brush.Dispose ();
}
Figure 4-4
Rectangle drawn with no translation or rotation.
The next OnPaint method draws the same rectangle, but only after calling TranslateTransform to move the ori
(100,100). Because the GDI+ now adds 100 to all x and y values you input, the rectangle moves to the right an
100 units, as shown in Figure 4-5:
protected override void OnPaint (PaintEventArgs e)
{
SolidBrush brush = new SolidBrush (Color.Red);
e.Graphics.TranslateTransform (100.0f, 100.0f);
e.Graphics.FillRectangle (brush, 0, 0, 200, 100);
brush.Dispose ();
}
Figure 4-5
Rectangle drawn with translation applied.
The final example uses RotateTransform to rotate the x and y axes 30 degrees counterclockwise after moving
origin, producing the output shown in Figure 4-6:
protected override void OnPaint (PaintEventArgs e)
{
SolidBrush brush = new SolidBrush (Color.Red);
e.Graphics.TranslateTransform (100.0f, 100.0f);
e.Graphics.RotateTransform (-30.0f);
e.Graphics.FillRectangle (brush, 0, 0, 200, 100);
brush.Dispose ();
}
Figure 4-6
Rectangle drawn with translation and rotation applied.
TranslateTransform and RotateTransform are powerful tools for positioning the coordinate system and orienti
axes. A related method named ScaleTransform lets you scale the coordinate system as well.
Something to keep in mind when you use transforms is that the order in which theyre applied affects the
In the preceding example, the coordinate system was first translated and then rotated. The origin was moved t
(100,100) and then rotated 30 degrees. If you rotate first and translate second, the rectangle appears in a differ
location because the translation occurs along axes that are already rotated. Think of it this way: if you stand in
walk a few steps forward and then turn, you end up in a different place than you would had you turned first an
started walking. The same principle applies to matrix transformations.
Figure 4-8 lists the source code for a sample program named Clock that draws an analog clock face (see Figur
that shows the current time of day. The drawing is done with FillRectangle and FillPolygon, but the transform
real story. TranslateTransform moves the origin to the center of the form by translating x and y coordinates by
amounts equal to half the forms width and height. RotateTransform rotates the coordinate system in prep
for drawing the hour and minute hands and the squares denoting positions on the clock face. ScaleTransform
the output so that the clock face fills the form regardless of the forms size. The coordinates passed to
FillRectangle and FillPolygon assume that the forms client area is exactly 200 units wide and 200 units t
ScaleTransform applies x and y scaling factors that scale the output by an amount that equals the ratio of the p
client area size to the logical client area size.
So much happens in MyFormsOnPaint method that its easy to miss an important statement in MyFo
constructor:
SetStyle (ControlStyles.ResizeRedraw, true);
This method call configures the form so that its entire client area is invalidated (and therefore repainted) when
forms size changes. This step is essential if you want the clock face to shrink and expand as the form shr
expands. If its not clear to you what effect this statement has on the output, temporarily comment it out a
recompile the program. Then resize the form and observe that the clock face doesnt resize until an extern
stimulus (such as the act of minimizing and restoring the form) forces a repaint to occur.
Figure 4-7
Analog clock drawn with the GDI+.
Clock.cs
using System;
using System.Windows.Forms;
using System.Drawing;
class MyForm : Form
{
MyForm ()
{
Text = "Clock";
SetStyle (ControlStyles.ResizeRedraw, true);
}
Figure 4-8
Clock source code.
DateTime now = DateTime.Now;
int minute = now.Minute;
int hour = now.Hour % 12;
// Reinitialize the transformation matrix
InitializeTransform (e.Graphics);
// Draw the hour hand
e.Graphics.RotateTransform ((hour * 30) + (minute / 2));
DrawHand (e.Graphics, blue, 40);
// Reinitialize the transformation matrix
InitializeTransform (e.Graphics);
// Draw the minute hand
e.Graphics.RotateTransform (minute * 6);
DrawHand (e.Graphics, red, 80);
void
{
points[3].Y = 0;
g.FillPolygon (brush, points);
}
void
{
}
InitializeTransform (Graphics g)
Units of Measure
Just as a Graphics objects Transform property governs the conversion of world coordinates to page coord
thePageUnit and PageScale properties play important roles in the conversion of page coordinates to device
coordinates.PageUnit identifies a system of units and can be set to any value defined in the
System.Drawing.GraphicsUnit enumeration: Pixel for pixels, Inch for inches, and so on. PageScale specifies t
scaling factor. It can be used in lieu of or in combination with ScaleTransform to scale most output. Some typ
output, including fonts, can be scaled only with PageScale.
PageUnits default value is GraphicsUnit.Display, which means that one unit in page coordinates equals o
on the screen. The following OnPaint method sets the unit of measurement to inches and draws the ruler show
Figure 4-9:
protected override void OnPaint (PaintEventArgs e)
{
Pen pen = new Pen (Color.Black, 0);
SolidBrush yellow = new SolidBrush (Color.Yellow);
SolidBrush black = new SolidBrush (Color.Black);
// Set the unit of measurement to inches
e.Graphics.PageUnit = GraphicsUnit.Inch;
// Add 0.5 to all x and y coordinates
e.Graphics.TranslateTransform (0.5f, 0.5f);
// Draw the body of the ruler
e.Graphics.FillRectangle (yellow, 0, 0, 6, 1);
e.Graphics.DrawRectangle (pen, 0, 0, 6, 1);
// Draw tick marks
for (float x=0.25f; x<6.0f; x+=0.25f)
e.Graphics.DrawLine (pen, x, 0.0f, x, 0.08f);
The same ruler could be drawn using the default PageUnit value by using the Graphics objects DpiX and
properties to manually convert between inches and pixels. But expressing coordinates and distances in inches
the code more readable. Note the call to TranslateTransform offsetting all x and y coordinates by a half-inch t
the ruler out of the upper left corner of the form. Also note the 0 passed to Pens constructor. The 0 sets th
width to 1 pixel, no matter what system of units is selected. Without the 0, a pen defaults to a width of 1 unit.
GraphicsUnit.Inch system of measurement, 1 unit equals 1 inch, so a 1-unit-wide pen would be a very wide pe
indeed.
One nuance you should be aware of is that values expressed in inches are logical values, meaning the ruler pro
wont measure exactly 6 inches long. Logical values differ slightly from physical values because Window
doesnt know precisely how many pixels per inch your screen can display. To compensate, it uses an assu
resolution of 96 dots per inch.
Fi
Ruler drawn using inches as the unit of measurement.
Menus
Menus are a staple of GUI applications. Nearly everyone who sits down in front of a computer understands th
clicking an item in an overhead menu displays a drop-down list of commands. Even novices quickly catch on
they see menus demonstrated a time or two.
Because menus are such an important part of a user interface, the .NET Framework provides a great deal of su
applications that use them. The System.Windows.Forms classes listed in the following table play a role in me
creation and operation. The next several sections discuss these and other menu-related classes and offer exam
demonstrating their use.
System.Windows.Forms Menu Classes
Class
Description
Menu
Abstract base class for other menu classes
MainMenu Represents main menus
ContextMenuRepresents context menus
MenuItem Represents the items in a menu
Main Menus
A main menu, sometimes called a top-level menu, is one that appears in a horizontal bar underneath a window
title bar. The following code creates a main menu containing two drop-down menus labeled File and
Edit and attaches it to the form:
// Create a MainMenu object
MainMenu menu = new MainMenu ();
// Add a File menu and populate it with items
MenuItem item = menu.MenuItems.Add ("&File");
item.MenuItems.Add ("&New", new EventHandler (OnFileNew));
item.MenuItems.Add ("&Open...", new EventHandler (OnFileOpen));
item.MenuItems.Add ("&Save", new EventHandler (OnFileSave));
item.MenuItems.Add ("Save &As...", new EventHandler (OnFileSaveAs));
item.MenuItems.Add ("-"); // Menu item separator (horizontal bar)
item.MenuItems.Add ("E&xit", new EventHandler (OnFileExit));
// Add an Edit menu and populate it, too
item = menu.MenuItems.Add ("&Edit");
item.MenuItems.Add ("Cu&t", new EventHandler (OnEditCut));
item.MenuItems.Add ("&Copy", new EventHandler (OnEditCopy));
item.MenuItems.Add ("&Paste", new EventHandler (OnEditPaste));
// Attach the menu to the form
Menu = menu;
The first statement creates a MainMenu object and captures the returned MainMenu reference in a variable na
menu. All menus, including instances of MainMenu, inherit a property named MenuItems from Menu that rep
the items in the menu. Calling Add on the MenuItemCollection represented by MenuItems adds an item to the
and optionally registers an event handler thats called when a user selects the item. The statements
MenuItem item = menu.MenuItems.Add ("&File");
item = menu.MenuItems.Add ("&Edit");
add top-level items named File and Edit to the main menu. The remaining calls to Add add i
the File and Edit menus. (The ampersand in the menu text identifies a keyboard shortcut. The ampersand in "&
designates Alt+F as the shortcut for File and automatically underlines the F.) The final statement attaches the
MainMenu to the form by assigning the MainMenu to the forms Menu property. All forms inherit the Me
property from System.Windows.Forms.Form.
Processing Menu Commands
Selecting an item from a menu fires a Click event and activates the Click event handler, if any, registered for
The code sample in the previous section registers handlers named OnFileNew,OnFileOpen, and so on for its m
Thus, the OnFileExit handler registered for the File/Exit command might be implemented like this:
void OnFileExit (Object sender, EventArgs e)
{
Close (); // Close the form
}
The first parameter passed to the event handler identifies the menu item that the user selected. The second par
a container for additional information about the event that precipitated the call. Menu item event handlers typi
ignore this parameter because EventArgs contains no information of interest.
An alternative method for connecting menu items to event handlers is to use C#s += syntax, as shown he
MenuItem exit = item.MenuItems.Add ("E&xit");
exit.Click += new EventHandler (OnFileExit);
I prefer to identify event handlers in Add simply because doing so makes my code more concise. You should
works best for you.
Context Menus
Many applications pop up context menus in response to clicks of the right mouse button. Inside a context men
context-sensitive list of commands that can be applied to the target of the click. In Windows Forms applicatio
ContextMenu objects represent context menus. ContextMenus are populated with items in the same way that
MainMenus are. One way to display a context menu is to call ContextMenu.Show. Heres an example tha
a context menu containing three items and displays it on the screen:
ContextMenu menu = new ContextMenu ();
menu.MenuItems.Add ("&Open", new EventHandler (OnOpen));
menu.MenuItems.Add ("&Rename", new EventHandler (OnRename));
menu.MenuItems.Add ("&Delete", new EventHandler (OnDelete));
menu.Show (this, new Point (x, y));
The first parameter to ContextMenu.Show identifies the form that the menu belongs to. (Its this form
handlers that are called when items are selected from the context menu.) The second parameter specifies wher
menu is to be displayed. The units are pixels, and the coordinates are relative to the upper left corner of the fo
identified in Shows first parameter.
If an application uses one context menu to serve an entire formthat is, if it displays the same context menu
matter where in the form a right-click occurstheres an easier way to display a context menu. Simply c
ContextMenu object and assign it to the forms ContextMenu property, as shown here:
ContextMenu menu = new ContextMenu ();
.
.
.
ContextMenu = menu;
The .NET Framework responds by displaying the context menu when the form is right-clicked.
As with items in a main menu, context menu items fire Click events when clicked. And like main menu items
identify event handlers when adding items to the menu or wire them up separately with the += operator.
Many applications change the states of their menu items on the fly to reflect changing states in the application
example, if nothing is selected in an application that features an Edit menu, protocol demands that the applica
disable its Cut and Copy commands.
Windows Forms applications control menu item states by manipulating the properties of MenuItem objects. A
MenuItemsChecked property determines whether the corresponding item is checked or unchecked. Enab
determines whether the item is enabled or disabled, and Text exposes the text of a menu item. If item is a Men
object, the following statement places a check mark next to it:
item.Checked = true;
MFC programmers are familiar with the concept of update handlers, which are functions whose sole purpose
update menu item states. MFC calls update handlers just before a menu appears on the screen, affording the h
the opportunity to update the items in the menu before the user sees them. Update handlers are useful because
decouple the logic that changes the state of an application from the logic that updates the state of the applicati
menu items.
You can write update handlers for Windows Forms menu items, too. The secret is to register a handler for the
event that fires when the menu containing the item you want to update is clicked and to do the updating in the
handler. The following example registers a Popup handler for the Edit menu and updates the items in the men
reflect the current state of the application:
MenuItem item = menu.MenuItems.Add ("&Edit");
item.Popup += new EventHandler (OnPopupEditMenu);
.
.
.
void OnPopupEditMenu (Object sender, EventArgs e)
{
MenuItem menu = (MenuItem) sender;
foreach (MenuItem item in menu.MenuItems) {
switch (item.Text) {
case "Cu&t":
case "&Copy":
item.Enabled = IsSomethingSelected ();
break;
case "&Paste":
item.Enabled = IsSomethingOnClipboard ();
break;
}
}
}
IsSomethingSelected and IsSomethingOnClipboard are fictitious methods, but hopefully their intent is clear
nonetheless. By processing MenuItem.Popup events, an application can delay updating its menu items until th
to be updated and eliminate the possibility of forgetting to update them at all.
Accelerators
A final note concerning menus regards accelerators. Accelerators are keys or key combinations that, when pre
invoke menu commands. Ctrl+O (for Open) is an example of a commonly used accelerator; Ctrl+S (for Save)
another. Windows programmers implement accelerators by loading accelerator resources. Windows Forms
programmers have it much easier. They new up a MenuItem and identify the accelerator in the MenuItem
constructors third parameter. Then they add the resulting item to the menu with MenuItemCollection.Add
item.MenuItems.Add (new MenuItem ("&Open...",
new EventHandler (OnFileOpen), Shortcut.CtrlO));
The application shown in Figures 4-10 and 4-11 is a Windows Forms image viewer thats capable of disp
wide variety of image files. The heart of the application is the statement
Bitmap bitmap = new Bitmap (FileName);
which creates a new System.Drawing.Bitmap object encapsulating the image in the file identified by FileNam
Having the ability to load an image with a single statement is powerful. The Bitmap class is capable of readin
GIFs, JPEGs, and other popular image file formats. To accomplish the same thing in a traditional (unmanaged
Windows application, youd have to write reams of code or purchase a third-party graphics library.
Equally interesting is the statement
g.DrawImage (MyBitmap, ClientRectangle);
which draws the bitmap on the form. (MyBitmap is a private field that stores a reference to the currently displ
bitmap.) Windows programmers who want to display bitmaps have to grapple with memory device contexts a
BitBlts. Windows Forms programmers simply call Graphics.DrawImage.
ImageView demonstrates other important principles of Windows Forms programming as well. Here are some
highlights:
How to use menus, menu handlers, and menu update handlers
How to size a form by writing to its ClientSize property
How to use Open File dialog boxes (instances of System.Windows.Forms.OpenFileDialog) to solicit file
from users
How to use MessageBox.Show to display message boxes
How to create a scrollable form by setting a forms AutoScroll property to true and its AutoScrollMi
property to the size of the virtual viewing area
How to properly dispose of Bitmap objects
The final item in this list is an important one because without it, ImageView would leak memory like a sieve.
than open an image file and assign the Bitmap reference to MyBitmap in one statement, like this:
MyBitmap = new Bitmap (FileName);
See whats happening? Bitmap objects encapsulate bitmaps, which are unmanaged resources that can con
lot of memory. By calling Dispose on the old Bitmap after opening a new one, ImageView disposes of the
encapsulated bitmap immediately rather than wait for the garbage collector to call the BitmapsFinalize m
Remember the dictum in Chapter 2 that admonished developers to always call Close or Dispose on objects tha
unmanaged resources? This code is the embodiment of that dictum and a fine example of why programmers m
constantly be on their guard to avoid the debilitating side effects of nondeterministic destruction.
Figur
ImageView showing a JPEG file.
ImageView.cs
using System;
using System.Windows.Forms;
using System.Drawing;
class MyForm : Form
{
MenuItem NativeSize;
MenuItem FitToWindow;
bool ShowNativeSize = true;
int FilterIndex = -1;
Bitmap MyBitmap = null;
MyForm ()
{
// Create a menu
MainMenu menu = new MainMenu ();
MenuItem item = menu.MenuItems.Add ("&Options");
item.Popup += new EventHandler (OnPopupOptionsMenu);
item.MenuItems.Add (new MenuItem ("&Open...",
new EventHandler (OnOpenImage), Shortcut.CtrlO));
item.MenuItems.Add ("-");
item.MenuItems.Add (FitToWindow =
new MenuItem ("Size Image to &Fit Window",
new EventHandler (OnFitToWindow))
);
item.MenuItems.Add (NativeSize =
new MenuItem ("Show Image in &Native Size",
new EventHandler (OnNativeSize))
);
item.MenuItems.Add ("-");
item.MenuItems.Add (new MenuItem ("E&xit",
new EventHandler (OnExit)));
if (FilterIndex != -1)
ofd.FilterIndex = FilterIndex;
if
(ofd.ShowDialog () == DialogResult.OK) {
String FileName = ofd.FileName;
if (FileName.Length != 0) {
FilterIndex = ofd.FilterIndex;
try {
Bitmap bitmap = new Bitmap (FileName);
if (MyBitmap != null)
MyBitmap.Dispose (); // Important!
MyBitmap = bitmap;
string[] parts = FileName.Split ('\\');
// OnPaint handler
protected override void OnPaint (PaintEventArgs e)
{
if (MyBitmap != null) {
Graphics g = e.Graphics;
if (ShowNativeSize)
g.DrawImage (MyBitmap,
AutoScrollPosition.X, AutoScrollPosition.Y,
MyBitmap.Width, MyBitmap.Height);
else
g.DrawImage (MyBitmap, ClientRectangle);
}
}
Figure 4-11
ImageView source code.
Mouse and Keyboard Input
ImageView takes all its input from menus, but forms can also process mouse and keyboard input. Windows n
forms about mouse and keyboard input events using messages. The WM_LBUTTONDOWN message, for ex
signifies a press of the left mouse button in a forms client area. Windows forms process mouse and keybo
input by overriding virtual methods inherited from Form. The following table lists the virtual methods that co
to mouse and keyboard events. The rightmost column identifies the type of event argument passed in the meth
parameter list.
Form Methods for Processing Mouse and Keyboard Input
Method
Called When
Argument Type
OnKeyDown A key is pressed
KeyEventArgs
OnKeyPress
A character is typed on the keyboard KeyPressEventArgs
OnKeyUp
A key is released
KeyEventArgs
OnMouseDown A mouse button is pressed
MouseEventArgs
OnMouseEnter The mouse cursor enters a form
EventArgs
OnMouseHover The mouse cursor pauses over a formEventArgs
OnMouseLeave The mouse cursor leaves a form
EventArgs
OnMouseMove The mouse cursor moves over a formMouseEventArgs
OnMouseUp A mouse button is released
MouseEventArgs
OnMouseWheelThe mouse wheel is rolled
MouseEventArgs
Processing Keyboard Input
KeyEventArgs also includes properties named Alt,Control,Shift, and Modifiers that you can use to determine
the Ctrl, Alt, or Shift key (or some combination thereof) was held down when the keyboard event occurred.
A related method named OnKeyPress corresponds to WM_CHAR messages in Windows. Its called when
character is input from the keyboard. Not all keys generate character input. Some, such as the A through Z key
but others, such as F1 and F2, do not. To process input from noncharacter keys, you override OnKeyDown. T
input from character keys, you generally override OnKeyPress instead. Why? Because OnKeyPress receives a
KeyPressEventArgs whose KeyChar property tells you what character was entered, taking into account the sta
other keys on the keyboard (such as Shift) that affect the outcome. If a user presses the C key, OnKeyDown te
that the C key was pressed. OnKeyPress tells you whether the C is uppercase or lowercase. Heres an OnK
handler that responds one way to an uppercase C and another way to a lowercase C:
protected override void OnKeyPress (KeyPressEventArgs e)
{
if (e.KeyChar == 'C') {
// Do something
}
else if (e.KeyChar == 'c') {
// Do something else
}
}
Pressing and releasing a mouse button with the cursor over a form calls the forms OnMouseDown and
OnMouseUp methods, in that order. Both methods receive a MouseEventArgs containing a Button property
identifying the button that was clicked (MouseButtons.Left,MouseButtons.Middle, or MouseButtons.Right), a
property indicating whether an OnMouseDown signifies a single click (Clicks == 1) or double-click (Clicks =
X and Y properties identifying the cursor position. Coordinates are always expressed in pixels and are always
to the upper left corner of the form in which the click occurred. The coordinate pair (100,200), for example, in
that the event occurred with the cursor 100 pixels to the right of and 200 pixels below the forms upper le
The following OnMouseDown method draws a small X at the current cursor position each time the left mouse
is pressed:
protected override void OnMouseDown (MouseEventArgs e)
{
if (e.Button == MouseButtons.Left) {
Graphics g = Graphics.FromHwnd (Handle);
Pen pen = new Pen (Color.Black);
g.DrawLine (pen, e.X - 4, e.Y - 4, e.X + 4, e.Y + 4);
g.DrawLine (pen, e.X - 4, e.Y + 4, e.X + 4, e.Y - 4);
pen.Dispose ();
g.Dispose ();
}
}
This code sample demonstrates another important Windows Forms programming principle: how to draw in a
outsideOnPaint. The secret? Pass the forms window handle, which is stored in Form.Handle, to the static
Graphics.FromHwnd method. You get back a Graphics object. Dont forget to call Dispose on the Graphi
when youre finished with it because a Graphics object acquired this way, unlike a Graphics object passed
PaintEventArgs, is not disposed of automatically.
An alternate way to sense mouse clicks is to override Form methods named OnClick and OnDoubleClick. Th
is called when a form is clicked with the left mouse button; the latter is called when the form is double-clicke
Neither method receives information about click coordinates. OnClick and OnDoubleClick are generally more
interesting to control developers than to form developers because forms that process mouse clicks typically w
know where the clicks occur.
To track mouse movement over a form, override OnMouseMove in the derived class. OnMouseMove corresp
Windows WM_MOUSEMOVE messages. The X and Y properties of the MouseEventArgs passed to OnMou
identify the latest cursor position. The Button property identifies the button or buttons that are held down. If b
left and right buttons are depressed, for example, Button will equal MouseButtons.Left and MouseButtons.Ri
logically ORed together.
TheOnMouseEnter,OnMouseHover, and OnMouseLeave methods enable a form to determine when the curso
it, hovers motionlessly over it, and leaves it. One use for these methods is to update a real-time cursor coordin
display. The code for the MouseTracker application shown in Figure 4-13 demonstrates how to go about it. A
mouse moves over the form, OnMouseMove updates a coordinate display in the center of the form. (See Figu
When the mouse leaves the form, OnMouseLeave blanks out the coordinate display. No OnMouseEnter hand
needed because an OnMouseEnter call is always closely followed by an OnMouseMove call identifying the c
position.
Figure 4-12
MouseTracker displaying real-time cursor coordinates.
MouseTracker.cs
using System;
using System.Windows.Forms;
using System.Drawing;
class MyForm : Form
{
int cx;
int cy;
MyForm
{
}
()
void
{
Figure 4-13
MouseTracker source code.
The NetDraw Application
Heres another sample application to chew on, one that processes both mouse and keyboard input. Its
NetDraw, and its a simple sketching application inspired by the Scribble tutorial that comes with Visual
(See Figure 4-14.) To draw, press and hold the left mouse button and begin moving the mouse. To clear the d
area, press the Del key.
NetDraws source code appears in Figure 4-15. The OnMouseDown,OnMouseMove, and OnMouseUp m
do most of the heavy lifting. OnMouseDown creates a new Stroke object representing the stroke the user has
begun drawing and records it in CurrentStroke.OnMouseMove adds the latest x-y coordinate pair to the Strok
thatOnMouseDown created. OnMouseUp adds the Stroke to Strokes, which is an ArrayList that records all th
that the user draws. When the form needs repainting, OnPaint iterates through the Strokes array calling Stroke
reproduce each and every stroke.
Stroke is a class defined in NetDraw.cs. It wraps an ArrayList that stores an array of Points. Thanks to the Arr
oneStroke object is capable of holding a virtually unlimited number of x-y coordinate pairs. StrokesDraw
method uses Graphics.DrawLine to draw lines connecting the pairs.
Figure 4-14
NetDraw in action.
NetDraw.cs
using
using
using
using
using
System;
System.Collections;
System.Windows.Forms;
System.Drawing;
System.Drawing.Drawing2D;
MyForm ()
{
Text = "NetDraw";
}
g.Dispose ();
}
}
class Stroke
{
ArrayList Points = new ArrayList ();
pen.Dispose ();
}
}
Figure 4-15
NetDraw source code.
Other Form-Level Events
A class that derives from Form inherits a long list of virtual methods whose names begin with On.On method
called in response to form-level events. They exist to simplify event processing. If there were no On methods,
event handler you write would have to be wrapped in a delegate and manually connected to an event.
OnKeyDown,OnMouseDown, and OnPaint are just a few of the On methods that you can override in a derive
to respond to the various events that take place over a forms lifetime. The following table lists some of th
If you want to know when your form is resized, simply override OnSizeChanged in the derived class. OnSize
is the Windows Forms equivalent of WM_SIZE messages and is called whenever a forms size changes. W
know when a form is about to close so you can warn the user if your application contains unsaved data? If so,
OnClosing. If you want, you can even prevent the form from closing by setting the Cancel property of the
CancelEventArgs parameter passed to OnClosing to true.
Virtual Methods That Correspond to Form-Level Events
Method
Called When
OnActivated
A form is activated
OnClosed
A form is closed
OnClosing
A form is about to close
OnDeactivate
A form is deactivated
OnGotFocus
A form receives the input focus
OnLoad
A form is created
OnLocationChangedA form is moved
OnLostFocus
A form loses the input focus
OnPaintBackground A forms background needs repainting
OnSizeChanged
A form is resized
The sample program shown in Figure 4-16 demonstrates one application for OnClosing. When the user clicks
Close button in the forms upper right corner, Windows begins closing the form and the framework calls
OnClosing. This implementation of OnClosing displays a message box asking the user to click Yes to close th
or No to cancel the operation. If the answer is yes, OnClosing sets CancelEventArgs.Cancel to false and the fo
closes. If, however, the answer is no, OnClosing sets CancelEventArgs.Cancel to true and the form remains o
screen.
CloseDemo.cs
using System;
using System.Windows.Forms;
using System.ComponentModel;
class MyForm : Form
{
MyForm ()
{
Text = "OnClosing Demo";
}
protected override void OnClosing (CancelEventArgs e)
{
DialogResult result =
Figure 4-16
CloseDemo source code.
Controls
Windows includes more than 20 built-in control types that programmers can use to conserve development tim
applications a look and feel that is consistent with other Windows applications. Push buttons and list boxes ar
examples of controls; so are tree views, toolbars, and status bars. Controls have been a staple of the Windows
system since version 1, and the roster has grown over the years to include a rich and varied assortment of cont
TheSystem.Windows.Forms namespace provides managed wrappers around the built-in Windows controls an
in a few controls of its own that have no direct analogue in the operating system. The following table lists the
Each class exposes properties that vastly simplify control programming. Want to create a stylized button with
bitmapped background? No problem. Just wrap an image in a Bitmap object and assign it to the buttons
BackgroundImage property. Another example involves control colors. Ever tried to customize the background
an edit control? Other developers have, because I get e-mail asking about doing this all the time. In Windows
its easy: you just write the color to the controls BackColor property and then sit back and let the .NE
Framework do the rest.
System.Windows.Forms Control Classes
Class
Description
Button
Push buttons
CheckBox
Check boxes
CheckedListBox
List boxes whose items include check boxes
ComboBox
Combo boxes
DataGrid
Controls that display tabular data
DataGridTextBox Edit controls hosted by DataGrid controls
DateTimePicker
Controls for selecting dates and times
GroupBox
Group boxes
HScrollBar
Horizontal scroll bars
Label
Label controls that display static text
LinkLabel
Label controls that display hyperlinks
ListBox
List boxes
ListView
List views (display flat lists of items in a variety of styles)
MonthCalendar
Month-calendar controls
NumericUpDown Spinner buttons (up-down controls)
PictureBox
Controls that display images
PrintPreviewControlControls that display print previews
ProgressBar
Progress bars
PropertyGrid
Controls that list the properties of other objects
RadioButton
Radio buttons
RichTextBox
Rich-edit controls
StatusBar
Status bars
TabControl
Tab controls
TextBox
Edit controls
ToolBar
Toolbars
ToolTip
Tooltips
TrackBar
Track bars (slider controls)
TreeView
Tree views (display hierarchical lists of items)
VScrollBar
Vertical scroll bars
Creating a control and making it appear in a form is a three-step process:
The following code creates a push button control and adds it to a form. The button measures 96 pixels by 24 p
its upper left corner is positioned 16 pixels to the right of and 16 pixels below the forms upper left corner
on the face of the button reads Click Me:
Button MyButton; // Field
.
.
.
MyButton = new Button ();
MyButton.Location = new Point (16, 16);
MyButton.Size = new Size (96, 24);
MyButton.Text = "Click Me";
Controls.Add (MyButton);
Most controls fire events that apprise their owner of actions taken by the user. For example, button controls fi
events when clicked. A form that wants to respond to clicks of MyButton can register an event handler this wa
MyButton.Click += new EventHandler (OnButtonClicked);
.
.
.
void OnButtonClicked (Object sender, EventArgs e)
{
// TODO: Respond to the button click
}
EventHandler is a delegate defined in the System namespace. Recall that a delegate is a type-safe wrapper aro
callback function and that delegates are especially useful for wiring events to event handlers. The event handl
first parameter identifies the control that fired the event. The second parameter provides additional informatio
the event. As with events involving menu items, EventArgs contains no useful information. However, some c
pass other argument types to their event handlers. For example, tree view controls fire AfterExpand events aft
branch of the tree expands. AfterExpand uses TreeViewEventHandler as its delegate. The second parameter p
TreeViewEventHandler is a TreeViewEventArgs that identifies the branch of the tree that was expanded.
How do you know what events a given control fires? You read the documentation. Look up ListBox, for exam
youll see that it defines an event named DoubleClick thats fired whenever the user double-clicks an
the list box. DoubleClick is prototyped this way:
public event EventHandler DoubleClick;
From this statement, you know that handlers for DoubleClick events must be wrapped in EventHandler deleg
becauseEventHandler is prototyped this way:
public delegate void EventHandler (Object sender, EventArgs e);
The application depicted in Figure 4-17 demonstrates the basics of Windows Forms control usage. Its one and
form contains four controls: a Label control, a TextBox control, a ListBox control, and a Button control. To p
ControlDemo through its paces, start it up, type a path name (for example, C:\Winnt) into the TextBox contro
click the Show File Names button. A list of all the files in that directory will appear in the ListBox. Next doub
one of the file names to pop up a message box revealing when the file was created and when it was last modif
ControlDemos source code appears in Figure 4-18. The applications main form is an instance of My
MyForms constructor instantiates the controls and stores references to them in private fields. Then it add
controls to the forms Controls collection so that they will physically appear in the form. Event handlers n
OnShowFileNames and OnShowFileInfo respond to the push buttons Click events and the list boxs
DoubleClick events. These handlers use static methods belonging to the System.IO namespaces Director
classes to enumerate files and retrieve file information.
Note the statements that assign numeric values to the TextBox,ListBox, and Button controls TabIndex pr
TabIndex specifies the order in which the input focus cycles between the controls when the user presses the T
The logic for moving the focus is provided by the framework; your only responsibility is to provide the order.
control isnt assigned a tab index because it never receives the input focus.
Figure 4-17
ControlDemo showing file details.
ControlDemo.cs
using
using
using
using
System;
System.Windows.Forms;
System.Drawing;
System.IO;
MyForm ()
{
// Set the form's title
Text = "Control Demo";
// Add the
Controls.Add
Controls.Add
Controls.Add
Controls.Add
if
(path.Length > 0) {
// Make sure the path name is valid
if (Directory.Exists (path)) {
// Empty the list box
FileNameBox.Items.Clear ();
}
// If
else
}
}
// Display the dates and times that the file was created
// and last modified
DateTime created = File.GetCreationTime (file);
DateTime modified = File.GetLastWriteTime (file);
string
Figure 4-18
ControlDemo source code.
Anchoring
When the Windows Forms team in Redmond drew up the blueprints for their part of the .NET Framework, th
to include a few bells and whistles, too. One of those bells and whistles is a feature called anchoring. Anchori
forms designers to create forms whose controls move and resize as the form is resized. Figure 4-19 shows how
ControlDemo looks if its form is expanded. Even though the form is larger, the controls retain their original s
4-20 shows the same form with anchoring enabled. The controls now flow with the size of the form.
Anchoring is applied on a control-by-control basis. Its enabled for a given control by initializing the Anc
property that the control inherits from Control.Anchor can be set to any combination of the following values t
control what to do when its container is resized:
Anchor Style
AnchorStyles.Left
AnchorStyles.Right
AnchorStyles.Top
Meaning
Maintain a constant distance between the left edge of the control and the left edge of the
Maintain a constant distance between the right edge of the control and the right edge of
Maintain a constant distance between the top edge of the control and the top edge of the
Maintain a constant distance between the bottom edge of the control and the bottom edg
AnchorStyles.Bottom
form
AnchorStyles.None Dont maintain a constant distance between the control and any edge of the form
If you want a control to move to the right when the container is widened, anchor the right edge of the control
edge of the form, like this:
MyControl.Anchor = AnchorStyles.Right;
If you want the control to widen rather than move when the form is widened, anchor both the left and right ed
this:
MyControl.Anchor = AnchorStyles.Left AnchorStyles.Right;
By applying various combinations of anchor styles, you can configure a control to flow how you want it to wh
container is resized.
To see anchoring in action first-hand, add the following statements to MyForms constructor in ControlDe
PathNameBox.Anchor = AnchorStyles.Left AnchorStyles.Right
AnchorStyles.Top;
FileNameBox.Anchor = AnchorStyles.Left AnchorStyles.Right
AnchorStyles.Bottom AnchorStyles.Top;
ShowFileNamesButton.Anchor = AnchorStyles.Right AnchorStyles.Bottom;
These statements produce the result that you see in Figure 4-20. Specifically, they configure the TextBox cont
expand horizontally as the form expands but to remain fixed with respect to the top of the form; they configur
ListBox control to stretch horizontally and vertically as the form expands; and they configure the Button cont
remain fixed in size but move with the lower right corner of the form.
Figure 4-19
ControlDemo without anchoring.
Figure 4-20
ControlDemo with anchoring.
Dialog Boxes
A dialog box is a form used to solicit input from a user. Dialog boxes, or simply dialogs, as
they are often called, come in two styles: modal and modeless. When a modal dialog is displayed, its
owner, which is typically the applications main form, is temporarily disabled. The Open File
dialog featured in countless Windows applications is an example of a modal dialog. Modeless dialogs
dont disable their owners; users are free to click back to the main form at any time. The
Find and Replace dialog in Microsoft Word is an example of a modeless dialog.
To a developer versed in programming Windows using the Windows API, dialog boxes are special
creatures that are just different enough from ordinary windows to be a bit of a nuisance. In Windows
Forms, dialog boxes are nothing special; theyre just forms. Literally. In fact, just three
characteristics differentiate a dialog box from an ordinary form:
Dialogs have slightly different styles than ordinary forms. For example, dialogs rarely have
minimize and maximize buttons, so most dialog constructors set the forms MinimizeBox
andMaximizeBox properties to false.
Dialogs generally have OK and Cancel buttons. A couple of lines of code in a Windows Forms
dialog identifies these buttons to the framework and enables the framework to automatically
dismiss the dialog when either button is clicked.
To show an ordinary form, you call its Show method. To display a modal dialog, you call
ShowDialog.
Experienced Windows developers are often shocked to find that Windows Forms dialogs create their
controls programmatically. Traditional Windows applications use dialog resources that enable a
dialog to be created, controls and all, with a simple function call. Windows Forms doesnt use
dialog resources, so every dialog class contains code to create each and every control that it hosts.
Windows features an assortment of so-called system dialogs or common dialogs that
developers can use to display Open File dialogs, Print dialogs, and other types of dialogs that are
commonly found in Windows applications. The FCL wraps the common dialogs with easy-to-use
classes. The following table lists the common dialog classes. All are defined in
System.Windows.Forms. Youve seen one of these dialogsOpenFileDialogalready in this
chapters ImageView application. Use these classes whenever appropriate to save on
development time and give your Windows Forms applications a look and feel that is consistent with
other Windows applications.
Common Dialog Classes Defined in System.Windows.Forms
Class
Dialog Type
ColorDialog
Color dialog boxes for choosing colors
FontDialog
Font dialog boxes for choosing fonts
OpenFileDialog Open File dialog boxes for choosing files
PageSetupDialogPage Setup dialog boxes for entering page setup parameters
PrintDialog
Print dialog boxes for entering print parameters
SaveFileDialog Save File dialog boxes for entering file names
The DialogDemo Application
Figure 4-22 contains the source code for a simple Windows Forms application that displays a dialog
box in response to a menu command. To display the dialog, select the Edit Ellipse command from the
Options menu. In the dialog are controls that you can use to change the width and height of the ellipse
in the applications main form. (See Figure 4-21.) You can also select the system of
unitsinches, centimeters, or pixelsthat the width and height are measured in.
DialogDemos dialog class is named MyDialog. Code in the class constructor initializes the
dialogs properties. The first statement sizes the dialog:
ClientSize = new Size (296, 196);
The next statement gives the dialog a fixed dialog border, which prevents the dialog from
being resized:
FormBorderStyle = FormBorderStyle.FixedDialog;
The next two statements remove the minimize and maximize buttons that appear in the dialogs
title bar by default, and the statement after that sets the text in the dialogs title bar:
MaximizeBox = false;
MinimizeBox = false;
Text = "Edit Ellipse";
The final statement prevents the dialog from causing a button to appear in the systems task bar:
ShowInTaskbar = false;
Top-level forms should appear in the task bar, but dialog boxes should not.
Most of the remaining code in MyDialogs constructor creates the dialogs controls and adds
them to the Controls collection. But notice these two statements:
AcceptButton = OKButton;
CancelButton = NotOKButton;
AcceptButton and CancelButton are properties that MyDialog inherits from Form.AcceptButton
identifies the dialogs OK button; CancelButton identifies the Cancel button. OK and Cancel
buttons are special because they dismiss the dialog boxthat is, cause it to disappear from the
screen. The statements above identify the OK and Cancel buttons to the framework so that it can
dismiss the dialog when either button is clicked.
The following statements instantiate and display the dialog:
MyDialog dlg = new MyDialog ();
.
.
.
if (dlg.ShowDialog (this) == DialogResult.OK) {
The dialog appears on the screen when ShowDialog is called. ShowDialog is a blocking call; it
doesnt return until the dialog is dismissed. Its return value tells you how the dialog was
dismissed.DialogResult.OK means the user clicked the OK button. DialogResult.Cancel means the
user clicked Cancel. Most applications ignore what was entered into the dialog if the Cancel button is
clicked. DialogDemo discards the input if ShowDialogs return value is anything other than
DialogResult.OK, which means the user can cancel out of the dialog without affecting the ellipse in
any way.
MyDialog exposes the input that the user enters through public properties named UserWidth,
UserHeight, and UserUnits. When read, these properties extract data from the dialogs controls.
When written to, they transfer data to the controls. Before calling ShowDialog to display the dialog,
DialogDemo initializes the dialogs controls by initializing the corresponding properties:
dlg.UserWidth = MyWidth;
dlg.UserHeight = MyHeight;
dlg.UserUnits = (int) MyUnits;
And when the dialog is dismissed (provided its dismissed with the OK button), DialogDemo
extracts the user input by reading MyDialog property values:
MyWidth = dlg.UserWidth;
MyHeight = dlg.UserHeight;
MyUnits = (MyForm.Units) dlg.UserUnits;
Thus, public properties in the dialog class serve as a mechanism for transferring data to and from the
dialogs controls. This mechanism is typically how Windows Forms applications get data in and
out of dialogs.
Figure 4-21
DialogDemo in action.
DialogDemo.cs
using System;
using System.Windows.Forms;
using System.Drawing;
// Create a menu
MainMenu menu = new MainMenu ();
MenuItem item = menu.MenuItems.Add ("&Options");
item.MenuItems.Add ("Edit Ellipse...",
new EventHandler (OnEditEllipse));
item.MenuItems.Add ("-");
item.MenuItems.Add ("E&xit", new EventHandler (OnExit));
//
if
}
// OnPaint handler
protected override void OnPaint (PaintEventArgs e)
{
int multiplier = 1;
switch (MyUnits) {
case Units.Inches:
e.Graphics.PageUnit = GraphicsUnit.Inch;
break;
case
Units.Centimeters:
e.Graphics.PageUnit = GraphicsUnit.Millimeter;
multiplier = 10;
break;
case Units.Pixels:
e.Graphics.PageUnit = GraphicsUnit.Pixel;
break;
}
public
{
int UserUnits
get
{
}
set
{
}
return i;
}
return -1;
public
{
RadioButton button =
(RadioButton) UnitsGroup.Controls[value];
button.Checked = true;
MyDialog ()
AcceptButton = OKButton;
CancelButton = NotOKButton;
Figure 4-22
The DialogDemo source code.
Building applications by hand is a great way to learn the mechanics of Windows Forms, but in practice, most
Forms developers use Visual Studio .NET to craft their wares. Visual Studio .NET is the version of Visual Stu
.NET Framework developers. Its many features include an integrated editor, compiler, and debugger, contexthelp in the form of IntelliSense, and a built-in forms designer that simplifies the task of laying out and implem
Windows forms.
To acquaint you with Windows Forms development Visual Studio .NETstyle, the remaining sections of th
provide step-by-step instructions for building a very practical Windows Forms applicationa reverse Polish
(RPN) calculator capable of performing addition, subtraction, multiplication, and division. Figure 4-23 shows
product.
Figure 4-23
The NetCalc application.
In case youre not familiar with RPN calculators, heres a brief tutorial. To add 2 and 2 on a conventio
calculator, you press the following keys:
2
+
2
=
The first two key presses2 and Enterpush a 2 onto the calculators internal evaluation stack. Pressin
pushes another 2 onto the stack and moves the other one up one slot. Pressing the plus key (+) pops both 2s o
adds them together, and pushes the result onto the stack. A 4 appears in the calculators output window be
value at the top of the stack is always shown there.
One of the beauties of RPN calculators is that you dont need parentheses, and even complex expressions
evaluated with a minimum of effort. Suppose, for example, you were given the following expression:
((2 + 2) * 6) / 3
Evaluating this expression would be a chore on a conventional calculator (especially one that lacks parenthese
on an RPN calculator. Heres the sequence of keystrokes:
2
Enter
2
+
6
*
3
/
RPN was popularized on Hewlett-Packard scientific calculators in the 1970s. Once you get used to crunching
RPN way, youll never want to use a conventional calculator again. But enough about the virtues of RPN.
write some code.
Step 1: Create a Project
The first step in creating a Windows Forms application in Visual Studio .NET is to create a project. To begin,
New/Project command from Visual Studio .NETs File menu. When the New Project dialog appears, mak
Visual C# Projects is selected in the Project Types box, as shown in Figure 4-24. Then choose Windows App
identify the kind of application you intend to create. Type NetCalc into the Name box to give the proj
and then click the OK button.
Figure 4-24
Creating a C# Windows Forms project.
Step 2: Design the Form
After creating the project, Visual Studio .NET will drop you into its forms designer, which is a visual tool for
controls to forms, setting properties on those controls, writing event handlers, and more. The forms designer i
displays a blank form.
The next order of business is to customize the title at the top of the form. The default is Form1. Chan
locating the forms Text property in the Properties window (by default, the Properties window appears in
right corner of Visual Studio .NETs main window) and changing Form1 to NetCalc. Th
should appear in the forms title bar in the forms designer. While youre at it, make the form nonresiz
setting its Form-BorderStyle property to FixedDialog, and disable its maximize box by setting MaximizeBo
The following table summarizes the properties that need to be changed:
Property
Value
Text
NetCalc
FormBorderStyleFixedDialog
MaximizeBox False
On the left side of the Visual Studio .NET window is a vertical button labeled Toolbox. Moving the c
the button and pausing momentarily causes a palette to appear, listing the various controls that you can add to
form. Create a form that resembles the one shown in Figure 4-25 by selecting Button and TextBox controls fr
palette and positioning them on the form. Then use the Properties window to apply the following properties to
TextBox:
Property Value
TextAlign Right
TabStop False
Text
0.00
ReadOnly True
BackColorWindow
Cursor
Default
Name
Display
Font
10-point Microsoft Sans Serif
Next use the Properties window to assign the following values to the buttons Text and Name properties:
Text
Name
Text
Name
EnterEnterButton 1OneButton
Fix FixButton
2TwoButton
ClearClearButton 3ThreeButton
Del DeleteButton 4FourButton
.
DecimalButton5FiveButton
-
SubtractButton 6SixButton
+
AddButton
7SevenButton
x
MultiplyButton8EightButton
DivideButton 9NineButton
0
ZeroButton
Change the font size on the plus, minus, multiply, and divide buttons to 12 points, and then finish up by settin
controlsTabStop property to false. Setting TabStop to false in all the controls (TextBox included) prevent
key from moving the input focus around the form. Sometimes it makes sense to support tabbing between cont
step 7, youll endow NetCalc with a separate keyboard interface thats vastly superior to one that mere
around the input focus.
You just finished designing NetCalcs user interface. Now its time to add the logic that makes NetCa
like a calculator.
Figure 4-25
The NetCalc form in Visual Studio .NETs forms designer.
Step 3: Add Fields
Add the following private fields to the Form1 class. Form1 represents the form you designed in step 2. It was
automatically when the project was created, and it derives from System.Windows.Forms.Form:
Type Name
Initial Value
Stack RegStack
new Stack ()
string FormatString f2
bool
FixPending
False
bool
DecimalInStringFalse
bool
EntryInProgress False
const intMaxChars
21
You can add fields to Form1 by hand, or you can let Visual Studio .NET add them for you. Simply click the C
tab that appears (by default) near the right edge of the Visual Studio .NET window to display a list of the clas
project. Then right-click Form1 and select the Add/Add Field command from the ensuing context menu. Whe
Field wizard appears, fill it in with information about the field you want to add. Heres what the statemen
declare and initialize the fields should look like when youve finished:
private
private
private
private
private
private
If you use the Add Field wizard to create these statements, youll have to edit them by hand to add the me
initializersfor example, "= false". The Add Field wizard wont add member initializers to fields that ar
const.
Open Form1.cs and scroll down until you find Form1s class constructor. Inside that constructor is the fol
comment:
//
// TODO: Add any constructor code after InitializeComponent call
//
ThePush statement initializes the calculators internal stack by pushing a 0 onto it. The second statement i
the calculators display by copying the value in the X register (the uppermost item on the stack) to the Tex
control.
Step 5: Add Helper Methods
Add the following helper methods to the Form1 class:
Method Name
Return TypeParameter List
ProcessDigit
void
intValue
Reset
void
None
ConditionalResetDisplay
void
None
InitializeXRegisterFromDisplayvoid
None
DisplayXRegister
void
None
ReportError
void
stringMessage
You can add these methods by hand (in Form1.cs), or you can add them by right-clicking Form1 in the Class
window and selecting the Add/Add Method command. Make all of the methods private. Fill in the method bo
code shown for the finished methods in Figure 4-26. If you use the Add Method command to add these metho
Studio .NET adds empty method bodies for you. Its still up to you to enter code between the curly braces
Step 6: Add Click Handlers
Go back to the forms designer and, one by one, double-click (with the left mouse button) the forms 19 pu
Each double-click adds a Click event handler to Form1 and wires the handler to a button. The handlers are na
OneButton_Click,TwoButton_Click, and so on. Fill in the empty method bodies with the code in Figure 4-26
Step 7: Add Keyboard Handlers
NetCalc is now a functional application, but it lacks a keyboard interface. A usable calculator applet must sup
keystroke entry. To that end, go back to the forms designer and change the forms KeyPreview property fr
true. Turning KeyPreview on enables the form to see keystrokes before its controls do, even if one of its contr
input focus.
Next override the following methods in Form1 to trap keyboard events:
As usual, you can add these methods by hand, or you can add them with the Add Method command. Mark all
methodsprotected. If you use Add Method, be sure to check the Override box to include the override keyword
method declaration. Finish up by implementing these methods as you did the ones shown in Figure 4-26.
Step 8: Override ProcessDialogKey
If you were to build and run NetCalc right now, youd find that the keyboard interface works only if you u
keyboard exclusively. For example, if you click the 2 button with the mouse and then press Enter on the keybo
2 appears in the calculator window. Why? Because the click sets the input focus to the 2 button, and when a b
input focus, the system interprets the Enter key to mean that the button should be clicked.
This problem is solved by overriding an obscure method that Form1 inherits from Form. The method is name
ProcessDialogKey. Its called by the framework when certain keys (such as Enter) are pressed to give the
chance to process the keystroke. Your job is to override the method like this:
protected override bool ProcessDialogKey(Keys keyData)
{
return keyData == Keys.Enter ?
false : base.ProcessDialogKey (keyData);
}
This implementation of ProcessDialogKey performs default processing on keys other than Enter by calling the
classs implementation of ProcessDialogKey. When the Enter key is pressed, however, ProcessDialogKey
from calling the base class to prevent the system from grabbing the keystroke. The Enter key becomes just ano
the keyboard, and pressing it activates your OnKeyDown handler even if a control currently has the input focu
Step 9: Build and Run the Application
Build the application by selecting the Build command from Visual Studio .NETs Build menu. Run it by s
Start Without Debugging command from the Debug menu (or invoking its keyboard equivalent, Ctrl+F5). Ve
code is working properly by adding, subtracting, multiplying, and dividing a few numbers. Also click the Fix
followed by the 4 button to change the calculators display precision from two places to four. If four numb
to the right of the decimal point, youre set.
The NetCalc Source Code
The finished version of Form1.cs is shown in Figure 4-26. The lines that I typed in are shown in boldface type
were generated by Visual Studio .NET. I wont take the time to analyze each and every line, but I will poi
highlights:
Near the top of the file youll find the statements that declare instances of Button and TextBox as pr
These statements were added when you added the controls to the form in the forms designer.
The wizard-generated portion of the forms class constructor contains a call to a local method named
InitializeComponent.InitializeComponent instantiates the Button and TextBox controls and initializes th
values. Much of this code was added when you added the controls to the form. The remainder was added
used the Properties window to edit the controls properties.
InitializeComponent also includes statements that wire the Button controls to Click handlers. Here is on
statement:
this.MultiplyButton.Click +=
new System.EventHandler(this.MultiplyButton_Click);
These statements were added when you double-clicked the buttons in the forms designer window.
Near the end of InitializeComponent is a single statement that adds all the controls to the form. Rather th
repeatedly, the forms designer adds the controls to the form in one fell swoop by calling AddRange on th
Controls collection.
TheClick handlers for the calculators numeric buttons call a helper method named ProcessDigit. Th
either adds a digit to the number shown in the calculator window or changes the display precision. Click
button toggles an internal flag named FixPending indicating whether the next number entered represents
precision or a numeric input.
Clicking the Enter button activates EnterButton_Click, which pushes the value currently displayed in the
window onto the calculators internal stack.
Clicking any of the arithmetic buttons (plus, minus, multiply, or divide) applies the specified operation t
two values popped off the stack. If one of these buttons is pressed and the value in the calculator window
currently on the stack, its pushed onto the stack so that it can serve as an operand in the forthcoming
Visual Studio .NET is a big help in building form-based applications such as NetCalc because it reduces the t
sizing and positioning controls and assigning values to control properties. Less tedium means shorter develop
and faster times to market. Thats a winning proposition no matter how you look at it.
Form1.cs
using
using
using
using
using
using
System;
System.Drawing;
System.Collections;
System.ComponentModel;
System.Windows.Forms;
System.Data;
namespace NetCalc
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Button FixButton;
private System.Windows.Forms.Button EnterButton;
private System.Windows.Forms.TextBox Display;
private System.Windows.Forms.Button ClearButton;
private System.Windows.Forms.Button SubtractButton;
private System.Windows.Forms.Button SevenButton;
private System.Windows.Forms.Button EightButton;
private System.Windows.Forms.Button NineButton;
private System.Windows.Forms.Button FiveButton;
private System.Windows.Forms.Button FourButton;
private System.Windows.Forms.Button AddButton;
private System.Windows.Forms.Button SixButton;
private System.Windows.Forms.Button ThreeButton;
private System.Windows.Forms.Button MultiplyButton;
public
{
Form1()
//
// TODO: Add any constructor code after InitializeComponent
//
RegStack.Push (0.0);
DisplayXRegister ();
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
//
// Required for Windows Form Designer support
//
InitializeComponent();
this.SubtractButton.Font =
new System.Drawing.Font("Microsoft Sans Serif", 12F,
System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.SubtractButton.Location = new System.Drawing.Point(16, 97
this.SubtractButton.Name = "SubtractButton";
this.SubtractButton.Size = new System.Drawing.Size(40, 32);
this.SubtractButton.TabIndex = 12;
this.SubtractButton.TabStop = false;
this.SubtractButton.Text = "-";
this.SubtractButton.Click +=
new System.EventHandler(this.SubtractButton_Click);
//
// SevenButton
//
this.SevenButton.Location = new System.Drawing.Point(64, 97);
this.SevenButton.Name = "SevenButton";
this.SevenButton.Size = new System.Drawing.Size(40, 32);
this.SevenButton.TabIndex = 19;
this.SevenButton.TabStop = false;
this.SevenButton.Text = "7";
this.SevenButton.Click +=
new System.EventHandler(this.SevenButton_Click);
//
// EightButton
//
this.EightButton.Location = new System.Drawing.Point(112, 97)
this.EightButton.Name = "EightButton";
this.EightButton.Size = new System.Drawing.Size(40, 32);
this.EightButton.TabIndex = 20;
this.EightButton.TabStop = false;
this.EightButton.Text = "8";
this.EightButton.Click +=
new System.EventHandler(this.EightButton_Click);
//
// NineButton
//
this.NineButton.Location = new System.Drawing.Point(160, 97);
this.NineButton.Name = "NineButton";
this.NineButton.Size = new System.Drawing.Size(40, 32);
this.NineButton.TabIndex = 18;
this.NineButton.TabStop = false;
this.NineButton.Text = "9";
this.NineButton.Click +=
new System.EventHandler(this.NineButton_Click);
//
// FiveButton
//
this.FiveButton.Location = new System.Drawing.Point(112, 137)
this.FiveButton.Name = "FiveButton";
this.FiveButton.Size = new System.Drawing.Size(40, 32);
this.FiveButton.TabIndex = 16;
this.FiveButton.TabStop = false;
this.FiveButton.Text = "5";
this.FiveButton.Click +=
new System.EventHandler(this.FiveButton_Click);
//
// FourButton
//
this.FourButton.Location = new System.Drawing.Point(64, 137);
this.FourButton.Name = "FourButton";
this.FourButton.Size = new System.Drawing.Size(40, 32);
this.FourButton.TabIndex = 17;
this.FourButton.TabStop = false;
this.FourButton.Text = "4";
this.FourButton.Click +=
new System.EventHandler(this.FourButton_Click);
//
// AddButton
//
this.AddButton.Font =
new System.Drawing.Font("Microsoft Sans Serif", 12F,
System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.AddButton.Location = new System.Drawing.Point(16, 137);
this.AddButton.Name = "AddButton";
this.AddButton.Size = new System.Drawing.Size(40, 32);
this.AddButton.TabIndex = 4;
this.AddButton.TabStop = false;
this.AddButton.Text = "+";
this.AddButton.Click +=
new System.EventHandler(this.AddButton_Click);
//
// SixButton
//
this.SixButton.Location = new System.Drawing.Point(160, 137);
this.SixButton.Name = "SixButton";
this.SixButton.Size = new System.Drawing.Size(40, 32);
this.SixButton.TabIndex = 5;
this.SixButton.TabStop = false;
this.SixButton.Text = "6";
this.SixButton.Click +=
new System.EventHandler(this.SixButton_Click);
//
// ThreeButton
//
this.ThreeButton.Location = new System.Drawing.Point(160, 177)
this.ThreeButton.Name = "ThreeButton";
this.ThreeButton.Size = new System.Drawing.Size(40, 32);
this.ThreeButton.TabIndex = 3;
this.ThreeButton.TabStop = false;
this.ThreeButton.Text = "3";
this.ThreeButton.Click +=
new System.EventHandler(this.ThreeButton_Click);
//
// MultiplyButton
//
this.MultiplyButton.Font =
new System.Drawing.Font("Microsoft Sans Serif", 12F,
System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.MultiplyButton.Location = new System.Drawing.Point(16, 17
this.MultiplyButton.Name = "MultiplyButton";
this.MultiplyButton.Size = new System.Drawing.Size(40, 32);
this.MultiplyButton.TabIndex = 1;
this.MultiplyButton.TabStop = false;
this.MultiplyButton.Text = "x";
this.MultiplyButton.Click +=
new System.EventHandler(this.MultiplyButton_Click);
//
// OneButton
//
this.OneButton.Location = new System.Drawing.Point(64, 177);
this.OneButton.Name = "OneButton";
this.OneButton.Size = new System.Drawing.Size(40, 32);
this.OneButton.TabIndex = 2;
this.OneButton.TabStop = false;
this.OneButton.Text = "1";
this.OneButton.Click +=
new System.EventHandler(this.OneButton_Click);
//
// TwoButton
//
this.TwoButton.Location = new System.Drawing.Point(112, 177);
this.TwoButton.Name = "TwoButton";
this.TwoButton.Size = new System.Drawing.Size(40, 32);
this.TwoButton.TabIndex = 9;
this.TwoButton.TabStop = false;
this.TwoButton.Text = "2";
this.TwoButton.Click +=
new System.EventHandler(this.TwoButton_Click);
//
// ZeroButton
//
this.ZeroButton.Location = new System.Drawing.Point(64, 217);
this.ZeroButton.Name = "ZeroButton";
this.ZeroButton.Size = new System.Drawing.Size(40, 32);
this.ZeroButton.TabIndex = 10;
this.ZeroButton.TabStop = false;
this.ZeroButton.Text = "0";
this.ZeroButton.Click +=
new System.EventHandler(this.ZeroButton_Click);
//
// DivideButton
//
this.DivideButton.Font =
new System.Drawing.Font("Microsoft Sans Serif", 12F,
System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.DivideButton.Location = new System.Drawing.Point(16, 217)
this.DivideButton.Name = "DivideButton";
this.DivideButton.Size = new System.Drawing.Size(40, 32);
this.DivideButton.TabIndex = 8;
this.DivideButton.TabStop = false;
this.DivideButton.Text = "";
this.DivideButton.Click +=
new System.EventHandler(this.DivideButton_Click);
//
// DeleteButton
//
this.DeleteButton.Location = new System.Drawing.Point(160, 217
this.DeleteButton.Name = "DeleteButton";
this.DeleteButton.Size = new System.Drawing.Size(40, 32);
this.DeleteButton.TabIndex = 6;
this.DeleteButton.TabStop = false;
this.DeleteButton.Text = "Del";
this.DeleteButton.Click +=
new System.EventHandler(this.DeleteButton_Click);
//
// DecimalButton
//
this.DecimalButton.Location = new System.Drawing.Point(112, 21
this.DecimalButton.Name = "DecimalButton";
this.DecimalButton.Size = new System.Drawing.Size(40, 32);
this.DecimalButton.TabIndex = 7;
this.DecimalButton.TabStop = false;
this.DecimalButton.Text = ".";
this.DecimalButton.Click +=
new System.EventHandler(this.DecimalButton_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(218, 266);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this.FixButton,
this.EnterButton,
this.Display,
this.ClearButton,
this.SubtractButton,
this.SevenButton,
this.EightButton,
this.NineButton,
this.FiveButton,
this.FourButton,
this.AddButton,
this.SixButton,
this.ThreeButton,
this.MultiplyButton,
this.OneButton,
this.TwoButton,
this.ZeroButton,
this.DivideButton,
this.DeleteButton,
this.DecimalButton});
this.FormBorderStyle =
System.Windows.Forms.FormBorderStyle.FixedDialog;
this.KeyPreview = true;
this.MaximizeBox = false;
this.Name = "Form1";
this.Text = "NetCalc";
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private
private
private
private
private
private
//
// Process a press of one of the numeric buttons. If Fix was
// the last button pressed, change the calculator's output
// precision by modifying FormatString. Otherwise, reset the
// display if necessary and append the number to any numbers
// previously entered.
//
private void ProcessDigit(int Value)
{
if (FixPending) {
FormatString = "f" + Value.ToString ();
if (EntryInProgress)
InitializeXRegisterFromDisplay ();
DisplayXRegister ();
FixPending = false;
}
else {
ConditionalResetDisplay ();
if (Display.Text.Length < MaxChars)
Display.Text += Value.ToString ();
}
}
//
// Reset the calculator's internal flags.
//
private void Reset()
{
DecimalInString = false;
FixPending = false;
EntryInProgress = false;
}
//
// Blank out the value displayed in the calculator window
// if an entry isn't already in progress.
//
private void ConditionalResetDisplay()
{
if (!EntryInProgress) {
EntryInProgress = true;
Display.Text = "";
}
}
//
// Convert the text in the calculator display to a numeric
// value and push it onto the stack.
//
private void InitializeXRegisterFromDisplay()
{
double x = (Display.Text.Length == 0 Display.Text =
0.0 : Convert.ToDouble (Display.Text);
RegStack.Push (x);
}
//
// Show the value at the top of the stack in the calculator
// window. Check for overflow and underflow and display an
// error message if either has occurred.
//
private void DisplayXRegister()
//
// Report an error to the user by displaying an error
// message in the calculator window. Also reset the
// calculator's internal state so the next button press
// will start things over again.
//
private void ReportError(string Message)
{
Display.Text = Message;
RegStack.Clear ();
RegStack.Push (0.0);
Reset ();
}
//
// Handlers for the calculator's numeric buttons (0-9).
//
private void ZeroButton_Click(object sender, System.EventArgs e)
{
ProcessDigit (0);
}
double
if (x
else {
}
//
// Handlers for the Add, Subtract, Multiply, and
// Divide buttons.
//
// General strategy employed by all four handlers:
// 1) Push the value in the calculator display onto the
// stack if it hasn't been pushed already.
// 2) Pop two values from the stack to use as operands.
// 3) Compute the sum, difference, product, or quotient.
// 4) Push the result onto the stack.
// 5) Display the result.
//
private void SubtractButton_Click(object sender, System.EventArgs e)
{
if (EntryInProgress)
InitializeXRegisterFromDisplay ();
if
}
if
}
(RegStack.Count >= 2) {
double op1 = (double) RegStack.Pop ();
double op2 = (double) RegStack.Pop ();
RegStack.Push (op2 - op1);
DisplayXRegister ();
Reset ();
(RegStack.Count >= 2) {
double op1 = (double) RegStack.Pop ();
double op2 = (double) RegStack.Pop ();
RegStack.Push (op2 + op1);
DisplayXRegister ();
Reset ();
if (RegStack.Count >= 2) {
double op1 = (double) RegStack.Pop ();
//
// Handler for the Enter button.
//
private void EnterButton_Click(object sender, System.EventArgs e)
{
InitializeXRegisterFromDisplay ();
DisplayXRegister ();
Reset ();
}
//
// Handler for the Fix button.
//
private void FixButton_Click(object sender, System.EventArgs e)
{
FixPending = !FixPending;
}
//
// Handler for the Clear button.
//
private void ClearButton_Click(object sender, System.EventArgs e)
{
RegStack.Clear ();
RegStack.Push (0.0);
DisplayXRegister ();
Reset ();
}
//
// Handler for the Delete button.
//
private void DeleteButton_Click(object sender, System.EventArgs e)
{
int len = Display.Text.Length;
if (len > 0 && EntryInProgress) {
if (Display.Text[len - 1] == '.')
DecimalInString = false;
if
}
(RegStack.Count >= 2) {
if ((double) RegStack.Peek () == 0.0)
ReportError ("Divide by zero");
else {
double op1 = (double) RegStack.Pop ();
double op2 = (double) RegStack.Pop ();
RegStack.Push (op2 / op1);
DisplayXRegister ();
Reset ();
}
//
// Handler for the '.' button.
//
private void DecimalButton_Click(object sender, System.EventArgs e)
{
ConditionalResetDisplay ();
if (Display.Text.Length < MaxChars && !DecimalInString) {
Display.Text += ".";
DecimalInString = true;
}
}
//
// Handler for KeyPress events. Comprises half of NetCalc's
// keyboard interface.
//
protected override void OnKeyPress(KeyPressEventArgs e)
{
switch (e.KeyChar) {
case '0':
ZeroButton_Click (ZeroButton, new EventArgs ());
break;
case '1':
OneButton_Click (OneButton, new EventArgs ());
break;
case '2':
TwoButton_Click (TwoButton, new EventArgs ());
break;
case '3':
ThreeButton_Click (ThreeButton, new EventArgs ());
break;
case '4':
FourButton_Click (FourButton, new EventArgs ());
break;
case '5':
FiveButton_Click (FiveButton, new EventArgs ());
break;
case '6':
SixButton_Click (SixButton, new EventArgs ());
break;
case '7':
SevenButton_Click (SevenButton, new EventArgs ());
break;
case '8':
EightButton_Click (EightButton, new EventArgs ());
break;
case '9':
NineButton_Click (NineButton, new EventArgs ());
break;
case '.':
case '-':
SubtractButton_Click (SubtractButton, new EventArgs ()
break;
case '+':
AddButton_Click (AddButton, new EventArgs ());
break;
case '*':
MultiplyButton_Click (MultiplyButton, new EventArgs ()
break;
case '/':
DivideButton_Click (DivideButton, new EventArgs ());
break;
}
//
// Handler for KeyDown events. The other half of NetCalc's
// keyboard interface.
//
protected override void OnKeyDown(KeyEventArgs e)
{
switch (e.KeyCode) {
case Keys.C:
ClearButton_Click (ClearButton, new EventArgs ());
break;
case Keys.F:
FixButton_Click (FixButton, new EventArgs ());
break;
case Keys.Enter:
EnterButton_Click (EnterButton, new EventArgs ());
break;
//
// The following override prevents the form from "stealing"
// the Enter key and using it to simulate button clicks.
//
protected override bool ProcessDialogKey(Keys keyData)
{
return keyData == Keys.Enter ?
false : base.ProcessDialogKey (keyData);
}
Figure 4-26
The NetCalc source code.
case Keys.Delete:
DeleteButton_Click (DeleteButton, new EventArgs ());
break;
}
Part 2
ASP.NET
Chapter 5
Web Forms
In recent years, the cutting edge of software development has shifted from traditional fat
client apps to Web apps. The integration of back-end systems and seamless data sharing,
once the holy grail of corporate IT departments, have given way to concerns over lower total cost
of ownership (TCO), zero-footprint installs, and the ability to run applications from anywhere an
Internet connection is available. The number one challenge that confronts developers today?
Make this software run on the Web. Unfortunately, Web programming isnt easy.
Writing applications like Microsoft Word and Microsoft Excel is a well understood art form.
Writing applications like eBay and Amazon.com is not. To make matters worse, Web
development today is practiced with first-generation tools and technologies that have more in
common with 1960s-era Dartmouth BASIC than the modern platforms and development
environments that developers have become accustomed to.
Microsofts answer to the sordid state of Web programming today is a second-generation
programming model called Web Forms. The Web Forms model is part of ASP.NET, which in
turn is part of the Microsoft .NET Framework. Just as Active Server Pages (ASP) revolutionized
Web programming in the 1990s with an easy-to-use model for dynamically generating HTML on
Web servers, ASP.NET advances the state of the art in Web programming by introducing
reusable server controls that render HTML to browser clients and fire events that can be
processed by server-side scripts. Thats Web Forms in a nutshell: Web pages built around
controls and event handlers. If the concept is alluring, the implementation is downright brilliant.
Once you learn to build Web apps the Web Forms way, youll never want to build them any
other way again.
This chapter introduces the Web Forms programming model by describing how to build Web
forms both with and without Visual Studio .NET. First youll nail down the basics by
building Web forms by hand. Then youll switch to Visual Studio .NET and experience rapid
application development (RAD), Internet-style. Along the way, youll be introduced to
important Web Forms programming techniques such as code-behind and dynamic control
initialization.
Before you begin, its worth noting what software you need to run this chapters sample
programs. First and foremost, you need the .NET Framework. Second, you need Microsoft
Internet Information Services (IIS), which is Microsofts Web server software. Finally, you
need ASP.NET. ASP.NET is automatically installed when you install the .NET Framework SDK
on a platform that supports ASP.NET. Currently those platforms include Windows 2000 and
Windows XP. Be sure to install IIS before you install the Framework SDK, or youll have to
go back and install ASP.NET separately.
The most proficient developers are those who possess an intimate understanding of the platforms they program
them. Because its difficult to understand how Web forms work if you lack a more general understanding
protocols that drive them, the next several sections provide a working introduction to the operation of Web ap
have little or no Web programming experience. If youre already familiar with HTTP, HTML forms, and
free to skip ahead to the section entitled Your First Web Form. If, however, Web apps are a new fron
following discussion helpful in building an in-depth understanding of the Web Forms programming model.
Hypertext Transfer Protocol
The Hypertext Transfer Protocol, better known as HTTP, is the protocol that drives the World Wide Web. Inv
the Web") and documented in RFC 2068, which is available online at www.w3.org/Protocols/rfc2068/rfc2068
important network protocol ever invented, with the notable exception of TCP/IP.
HTTP defines how Web browsers and Web servers communicate with each other. Its entirely text based,
over TCP connections linking Web browsers to Web servers. Suppose the following HTML file is deployed o
Simple.html, and that its URL is www.wintellect.com/simple.html:
<html>
<body>
Hello, world
</body>
</html>
If a user types http://www.wintellect.com/simple.html into Internet Explorers address bar, Internet Explo
Domain Name System (DNS) to convert www.wintellect.com into an IP address (for example, 66.45.26.25). T
the server at that address using a well-known port number (port 80) and transmits an HTTP request similar to
GET /simple.html HTTP/1.1
Accept: */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
If-Modified-Since: Wed, 24 Oct 2001 14:12:36 GMT
If-None-Match: "50b0d3ee955cc11:a78"
User-Agent: Mozilla/4.0.(compatible; MSIE.6.0; Windows NT 5.1)
Host: www.wintellect.com
Connection: Keep-Alive
[blank line]
The first line of the request is called the start line. It consists of a method name (GET), the name of the resour
and an HTTP version number (1.1). GET is one of seven methods defined in HTTP 1.1; it requests a resource
lines make up the message header. Each line, or header, contains additional information about the request, inc
that originated the request (User-Agent). A blank line (a simple carriage return/line feed pair) marks the end o
end of the request.
How does the Web server respond to the GET command? Assuming /simple.html is a valid resource identifie
prevent the file from being returned, the server transmits an HTTP response like this one:
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Wed, 24 Oct 2001 14:12:37 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Wed, 24 Oct 2001 14:00:53 GMT
ETag: "d02acf81975cc11:a78"
Content-Length: 46
[blank line]
<html>
<body>
Hello, world
</body>
</html>
Upon receiving the response, the browser parses the HTML returned by the Web server and displays the resul
header identifies the returned data as HTML, while Content-Length tells the browser how much HTML was r
line of the response is an HTTP status code signifying that the server fulfilled the browsers request. The H
different status codes, including the infamous 401 (Unauthorized) code indicating that the user isn
Conversations such as these form the backbone for communications over the Web. As you surf the Web by ty
your browser issues one GET command after another. Tools such as NetMonthe network packet-sniffing u
of Windowslet you spy on the HTTP traffic flying back and forth. You dont have to be an HTTP guru
a knowledge of basic HTTP semantics and a familiarity with commonly used request and response headers ar
ASP.NET object model.
HTML Forms
Simple.html is a far cry from a full-blown Web application. Its a static HTML file that solicits no user in
ones located at www.amazon.com and www.ebay.com accept input from their users, and they vary the HTML
based on the contents of that input.
At the heart of almost every genuine Web application is an HTML form. An HTML form is the portion of an
between <form> and </form> tags. The HTML in Figure 5-1 describes a simple form representing a Web-bas
contains two text input fields for the user to type numbers into and an equals button that submits the form bac
how the form appears in Internet Explorer. As you can see, the browser renders an <input type=text>
type=submit> tag as a push button. Similar tags can be used to create check boxes, radio buttons, list
Calc.html
<html>
<body>
<form>
<input type="text" name="op1" />
+
<input type="text" name="op2" />
<input type="submit" value=" =
</form>
</body>
</html>
Figure 5-1
A simple HTML form.
" />
A submit button (<input type=submit>) plays a special role in an HTML form. When clicked, it subm
more precise, the browser submits the form along with any input in the forms controls. How the form is s
<form> tag includes a Method attribute and the value of that attribute, if present. If the <form> tag lacks a Me
method=get attribute, the browser sends an HTTP GET command to the server with the users inp
of a query string:
GET /calc.html?op1=2&op2=2 HTTP/1.1
.
.
.
Connection: Keep-Alive
[blank line]
If, on the other hand, the <form> tag includes a method=post attribute, the form is submitted to the se
command. Rather than transmit user input in the URL, with a POST command the browser passes it in the bo
POST /calc.html HTTP/1.1
.
.
.
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
[blank line]
op1=2&op2=2
Regardless of whether a GET or a POST command is used, when input from an HTML form is submitted bac
postback has occurred. Remember that term because youll encounter it repeatedly in this and the
For a first-hand look at HTML forms in action, copy Calc.html to your PCs \Inetpub\wwwroot directory
typing the following URL:
http://localhost/calc.html
Now type 2 into each of the forms text boxes and click the = button. As evidence that a postback occurre
browsers address bar (shown in Figure 5-3). If you change the <form> tag to
<form method="post">
and repeat the experiment, you wont see any change in the URL. But the postback occurs just the same, a
users input by examining the body of the request.
So far, so good. As Calc.html demonstrates, building the client half of a Web application is easy. After all, it
building the code that runs on the Web server. Something has to be running there to extract the user input from
HTTP request if the postback was performed with POST instead of GET) and generate a new Web page that d
the = button. In other words, if the user enters 2 and 2 and clicks the = button, wed like the Web server to
HTML:
<html>
<body>
<form>
<input type="text" name="op1" value="2" />
+
<input type="text" name="op2" value="2" />
<input type="submit" value=" = " />
4
</form>
</body>
</html>
Note the Value attributes added to the <input type=text> tags. Including the inputs in the page returne
postback perpetuates the illusion that the user is seeing one Web page when in fact he or she is seeing two in s
There are many ways to write applications that process input from HTML forms. One solution is an applicatio
Interface (CGI). CGI defines a low-level programmatic interface between Web servers and applications that r
use it are typically written in a programming language called Perl, but they can be written in other languages a
input accompanying postbacks through server environment variables and standard input (stdin), and they writ
(stdout). CGI has a reputation for being slow because many implementations of it launch a new process to han
this, CGI enjoys widespread use on UNIX-based Web servers. Its rarely used on the Windows platform.
Another solutionone thats more likely to find favor among Windows developersis an ISAPI extens
Server Application Programming Interface. ISAPI extensions are Windows DLLs that are hosted by Internet I
referenced by URL just like HTML files (for example, http://www.wintellect.com/calc.dll). IIS forwards HTT
calling a special function exported from the DLL. The DLL, in turn, generates HTTP responses. ISAPI DLLs
because they (typically) run in the same process as IIS. And once loaded, they remain in memory awaiting sub
ISAPI DLLs is that theyre difficult to write. An ISAPI developer must be comfortable with the architectu
willing to deal with HTTP messages at a very low level.
Curious to know what an ISAPI DLL looks like? Figure 5-4 shows the C++ source code for an ISAPI DLL th
identical to the one shown in Figure 5-2. The heart of the DLL is the HttpExtensionProc function, which IIS c
pECB parameter points to a structure containing information about the request, including a pointer to the quer
request. If the query string is empty, this implementation of HttpExtensionProc returns an HTML page depict
postback, however, it parses the op1 and op2 parameters from the query string and returns an HTML page tha
other words, it returns precisely the HTML we set as our goal a moment ago.
Calc.cpp
#include <windows.h>
#include <httpext.h>
#include <string.h>
#include <stdlib.h>
int GetParameter (LPSTR pszQueryString, LPSTR pszParameterName);
BOOL WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason,
LPVOID lpvReserved)
{
return (TRUE);
}
BOOL
{
}
pVer->dwExtensionVersion =
MAKELONG (HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
lstrcpy (pVer->lpszExtensionDesc, "Calc ISAPI Extension");
return (TRUE);
//
// Build the response message body.
//
char szMessage[512];
if (lstrlen (pECB->lpszQueryString) == 0) {
// If the query string is empty, return a page that shows
// an empty calculator.
lstrcpy (szMessage, szPrePostbackMessage);
}
else {
// If the query string is not empty, extract the user input,
// process it, and return a page that shows inputs and outputs
int a = GetParameter (pECB->lpszQueryString, "op1");
int b = GetParameter (pECB->lpszQueryString, "op2");
wsprintf (szMessage, szPostPostbackMessage, a, b, a + b);
}
//
// Build the response message header.
//
char szHeader[128];
DWORD dwCount = lstrlen (szMessage);
//
// Output the response message header.
//
HSE_SEND_HEADER_EX_INFO shei;
shei.pszStatus = "200 OK";
shei.pszHeader = szHeader;
shei.cchStatus = lstrlen (shei.pszStatus);
shei.cchHeader = lstrlen (shei.pszHeader);
shei.fKeepConn = FALSE;
pECB->ServerSupportFunction (pECB->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER_EX, &shei, NULL, NULL);
//
// Output the response message body.
//
pECB->WriteClient (pECB->ConnID, szMessage, &dwCount, 0);
//
// Indicate that the request was processed successfully.
//
return HSE_STATUS_SUCCESS;
if (p
}
return
!= NULL) {
p += strlen (pszParameterName) + 1;
for (char* tmp = p; *tmp != '&' && *tmp != 0; tmp++)
;
int len = tmp - p;
char* buffer = new char[len + 1];
strncpy (buffer, p, len);
int val = atoi (buffer);
delete buffer;
return val;
0;
Figure 5-4
Source code for an ISAPI DLL.
The Active Server Pages Solution
A third solution to the problem of processing input from HTML forms on Web servers, and the one that made
Web applications in the second half of the 1990s, is Active Server Pages (ASP). Active Server Pages lower th
by allowing HTML and server-side script to be freely mixed in ASP files. Scripts are typically written in JScr
JavaScript) or VBScript, but they can be written in other languages as well. Intrinsic objects available to those
of HTTP and make it exceedingly easy to write code that generates HTML content dynamically. Just how eas
Figures 5-4 and 5-5 and judge for yourself.
When an Active Server Page is requested, ASP parses the page and executes any scripts contained inside it. S
the request by using the ASP Request object, and they write HTML to the HTTP response using the ASP Res
ASP version of Calc.html. The VBScript between <% and %> tags checks the incoming request for inputs nam
arent present, an empty calculator is rendered back to the client. If the inputs are presentthat is, if Req
(op2) evaluate to non-null stringsthe server-side script converts the inputs to integers, adds them t
string, and writes the string to the HTTP response using Response.Write.
To prevent the numbers typed into the text boxes from disappearing following a postback, Calc.asp uses ASP
%>) to initialize the Value attributes returned in the <input type=text> tags. When the page is first req
(op1) and Request (op2) return empty strings, so the tags output to the client produce empty
<input type="text" name="op1" value=""/>
<input type="text" name="op2" value=""/>
But when the form is rendered again following a postback, Request (op1) and Request (op2)
and are echoed to the client in the tags Value attributes:
<input type="text" name="op1" value="2"/>
<input type="text" name="op2" value="2"/>
To verify that this is the case, drop Calc.asp into \Inetpub\wwwroot and bring it up by typing http://localhost/c
numbers, click the = button, and use the View/Source command in Internet Explorer to view the HTML return
The appeal of ASPand the reason it caught on so quickly after its introduction in 1996is that it provides
dynamically generating HTML on Web servers. ASP provides a higher level of abstraction than either CGI or
learning curve and faster time to market. And ASP integrates seamlessly with ActiveX Data Objects (ADO), w
writing Web apps that interact with back-end databases.
Calc.asp
<%@ Language="VBScript" %>
<html>
<body>
<form>
<input type="text" name="op1" value="<%= Request ("op1") %>"/>
+
<input type="text" name="op2" value="<%= Request ("op2") %>" />
<input type="submit" value=" = " />
<%
If Request ("op1") <> "" And Request ("op2") <> "" Then
a = CInt (Request ("op1"))
b = CInt (Request ("op2"))
Response.Write (CStr (a + b))
End If
%>
</form>
</body>
</html>
Figure 5-5
ASP calculator applet.
Your First Web Form
ASP is a fine solution for performing server-side processing of HTML form input and dynamically generating
has already grown long in the tooth. Whats wrong with ASP? For starters, its slow. ASP scripts are
you incur the cost of recompiling your scripts on each and every page access. Another problem is that ASP lac
Its not possible, for example, to build reusable ASP controls that encapsulate complex rendering and beh
COM.
Enter ASP.NET Web forms. Web forms bring object-oriented programming to the Web. They also combine A
of compiled code. Figure 5-6 holds the source code for the Web Forms version of Calc.asp. The .aspx file nam
ASP.NET resource. Figure 5-7 shows how Calc.aspx appears in Internet Explorer. Heres how to run it on
1. Copy Calc.aspx to your PCs \Inetpub\wwwroot directory.
2. Start Internet Explorer or the browser of your choice and type http://localhost/calc.aspx in the browser
appear in the browser window.
3. Type 2 and 2 into the input fields and click the = button. The number 4 should appear to the right of the
The \Inetpub\wwwroot directory is an IIS virtual directory; its created automatically when you install IIS
\Inetpub\wwwroot, you can set up virtual directories of your own using the Internet Services Manager applet f
You could, for example, put Calc.aspx in a directory named Samples and make Samples a virtual directory. If
logical name Samples (virtual directory names dont have to equal physical directory names, alth
Calc by typing http://localhost/samples/calc.aspx in the browsers address bar. The same goes for other A
and throughout the remainder of the book.
Calc.aspx
<html>
<body>
<form runat="server">
<asp:TextBox ID="op1" RunAt="server" />
+
Figure 5-6
ASP.NET Web form calculator.
Calc.aspx in action.
Web forms are built from a combination of HTML and server controls. Calc.aspx contains four server control
control, and a Label control. TextBox,Button, and Label are classes defined in the System.Web.UI.WebContr
Framework class library (FCL). Each time Calc.aspx is requested, ASP.NET instantiates TextBox,Button, and
to render itself into HTML. The HTML returned by the controls is included in the HTTP response. Execute a
Calc.aspx is displayed in Internet Explorer and youll see the following HTML:
<html>
<body>
<form name="_ctl0" method="post" action="calc.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxOTE0NDY4ODE2Ozs+" />
" />
</form>
</body>
</html>
TheTextBox controls turned into <input type=text> tags, the Button control turned into an <input typ
Label control turned into a <span> tag. In effect, these controls project a user interface to the browser
HTML.
What about the <input> tag named __VIEWSTATE in the HTML returned by Calc.aspx? Thats the mech
data from the server to the client and back to the server again. Youll learn all about it in Chapter 8.
Control Properties
Server controls do more than render HTML. They also implement methods, properties, and events that make t
example,TextBox,Button, and Label controls each expose text through a read/write property named Text. If y
theTextBox controls by default, you could modify the control tags as follows:
<asp:TextBox Text="2" ID="op1" RunAt="server" />
<asp:TextBox Text="2" ID="op2" RunAt="server" />
Any public property that a control implements and that can be represented as a name/value pair can be initiali
attribute in the tag that declares the control.
Properties can also be accessed from server-side scripts. In Calc.aspx, the server-side script is the code that ap
</script> tags. The statements
int a = Convert.ToInt32 (op1.Text);
int b = Convert.ToInt32 (op2.Text);
extract user input from the TextBox controls by reading their Text properties, while the statement
Sum.Text = (a + b).ToString ();
displays the sum of the inputs by writing to the Label controls Text property. The names op1,op2, and Su
programmatic IDs. Control IDs are defined by including ID attributes in control tags. In Calc.aspx, the Label c
Web forms output. Because the default value of a Label controls Text property is an empty string, no
Label control is positioned until the server-side script assigns a string to the controlsText property.
Control Events
The ability to encapsulate complex rendering and behavioral logic in reusable control classes is one of the fun
programming model. Another is the use of events and event handling. Most server controls fire events in resp
for example, fire Click events when theyre clicked. Wiring an event to an event handler is accomplished
On and using the resulting text as an attribute in the tag that declares the control. In Calc.aspx, the sta
<asp:Button Text=" =
serves the dual purpose of declaring a Button control and designating OnAdd as the handler for the Button co
why the code in OnAdd executed when you clicked the = button. Knowing this, its a simple matter to con
events a control is capable of firing and connecting handlers to the events that interest you.
What happens under the hood to support the Web Forms event model is a little more complex. Look again at
Notice that it contains an HTML form and a submit button. Clicking the button posts the form back to the ser
Recognizing that the POST command represents a postback that occurred because the user clicked the = butto
object and the Button responds by firing a Click event on the server. ASP.NET subsequently calls OnAdd and
HTML. Because the Label controls Text property now has a non-null string assigned to it, this time the H
includes a text string between the <span> and </span> tags.
Implementation Notes
Calc.aspx contains no code to prevent the numbers typed into the TextBoxcontrols from disappearing followi
tags in Figure 5-6 lack Value attributes such as the ones in Figure 5-5s <input type= text> tags. Y
when you click the = button. Why? Because TextBox controls automatically persist their contents across post
the browser following the postback and youll find that <input type=text> tags rendered by the Te
that equal the text typed by the user.
To make Calc.aspx as simple as possible, I purposely omitted error checking code. To see what I mean, type s
value (say, hello) into one of the text boxes and click the = button. The page you see is ASP.NET
exceptions. To prevent this error, rewrite Calc.aspxs OnAdd method as follows:
void
{
}
try {
int a = Convert.ToInt32 (op1.Text);
int b = Convert.ToInt32 (op2.Text);
Sum.Text = (a + b).ToString ();
}
catch (FormatException) {
Sum.Text = "Error";
}
This version of OnAdd catches the exception thrown when Convert.ToInt32 is unable to convert the input to a
the word Error to the right of the push button.
Description
Displays rotating banners in Web forms
Generates submit buttons
Displays calendars with selectable dates
Displays a check box in a Web form
Displays a group of check boxes
Validates user input by comparing it to another value
Validates user input using the algorithm of your choice
Displays data in tabular format
Displays items in single-column or multicolumn lists using HTML templates
Generates HTML drop-down lists
Generates hyperlinks
Displays images in Web forms
Displays graphical push buttons
Generates programmable text fields
Generates hyperlinks that post back to the server
ListBox
Generates HTML list boxes
Literal
Generates literal text in a Web form
Panel
Groups other controls
RadioButton
Displays a radio button in a Web form
RadioButtonList
Displays a group of check boxes
RangeValidator
Verifies that user input falls within a specified range
RegularExpressionValidatorValidates user input using regular expressions
Repeater
Displays items using HTML templates
RequiredFieldValidator
Verifies that an input field isnt empty
Table
Generates HTML tables
TextBox
Generates text input fields
ValidationSummary
Displays a summary of validation errors
Xml
Displays XML documents and optionally formats them using XSLT
Some Web controls are simple devices that produce equally simple HTML. Others produce more
complex HTML, and some even return client-side script. Calendar controls, for example, emit a rich
mixture of HTML and JavaScript. Its not easy to add a calendar to a Web page by hand (especially if
you want dates in the calendar to be clickable), but calendars are no big deal in Web forms: you simply
include an <asp:Calendar> tag in an ASPX file. DataGrid is another example of a sophisticated control
type. One DataGrid control can replace reams of old ASP code that queries a database and returns the
results in a richly formatted HTML table. Youll learn all about the DataGrid and other Web controls
in the next chapter.
HTML Controls
Most Web forms are built from Web controls, but ASP.NET supports a second type of server control
calledHTML controls. HTML controls are instances of classes defined in the FCLs
System.Web.UI.HtmlControls namespace. Theyre declared by adding RunAt=server (or, if
youd prefer, runat=server; capitalization doesnt matter in HTML) attributes to ordinary
HTML tags. For example, the statement
<input type="text" />
HtmlInputText and HtmlForm are but two of many controls defined in the System.Web.UI.HtmlControls
namespace. The following table lists all the HTML controls that the FCL supports and the tags that
produce them.
HTML Controls
Tag
Corresponding HTML Control
<a runat=server>
HtmlAnchor
<button runat=server>
HtmlButton
<form runat=server>
HtmlForm
<img runat=server>
HtmlImage
<input type=button runat=server> HtmlInputButton
<input type=reset runat=server>
HtmlInputButton
<input type=submit runat=server> HtmlInputButton
<input type=checkbox runat=server>HtmlInputCheckBox
<input type=file runat=server>
HtmlInputFile
<input type=hidden runat=server> HtmlInputHidden
<input type=image runat=server> HtmlInputImage
<input type=radio runat=server>
HtmlInputRadioButton
<input type=password runat=server>HtmlInputText
<input type=text runat=server>
HtmlInputText
<select runat=server>
HtmlSelect
<table runat=server>
HtmlTable
<td runat=server>
HtmlTableCell
<th runat=server>
HtmlTableCell
<tr runat=server>
HtmlTableRow
<textarea runat=server>
HtmlTextArea
Any other tag with runat=server
HtmlGenericControl
Its important to know which HtmlControls class corresponds to a given HTML tag because only by
knowing the class name can you consult the documentation to determine which properties you can use
with that tag and which events the resulting control fires. For example, heres the HTML controls
version of Calc.aspx:
<html>
<body>
<form runat="server">
<input type="text" id="op1" runat="server" />
+
<input type="text" id="op2" runat="server" />
<input type="submit" value=" = " OnServerClick="OnAdd"
runat="server" />
<span id="Sum" runat="server" />
</form>
</body>
</html>
<script language="C#" runat="server">
void OnAdd (Object sender, EventArgs e)
{
int a = Convert.ToInt32 (op1.Value);
int b = Convert.ToInt32 (op2.Value);
Sum.InnerText = (a + b).ToString ();
}
</script>
Besides the different way in which the forms controls are declared, the HTML controls version of
this Web form differs from the Web controls version in three important respects:
The attribute that wires the button control to the event handler is named OnServerClick rather than
OnClick. Why? Because an <input type=button runat=server /> tag translates into
an instance of HtmlInputButton, and HtmlInputButton controls, unlike Button controls, dont
fireClick events. They fire ServerClick events.
OnAdd reads input from the text boxes using the property name Value rather than Text.
HtmlInputText controls dont have Text properties as Labels and TextBoxes do; instead, they
expose their contents using Value properties.
OnAdd writes its output by initializing SumsInnerText property instead of its Text property.
The <span runat=server> tag creates an instance of HtmlGenericControl.
HtmlGenericControl doesnt have a Text property, but it does have an InnerText property.
Once you know which class ASP.NET instantiates as a result of applying a runat=server tag to
an otherwise ordinary HTML tag, you can figure out from the documentation what the tags
programmatic interface looks like.
Why does ASP.NET support HTML controls when Web controls do everything HTML controls do and
then some? HTML controls simplify the task of turning existing HTML forms into Web forms. It takes a
while to convert a couple of hundred <input> tags and <select> tags and other HTML tags into Web
controls. It doesnt take long to add runat=server to each of them.
Page-Level Events
Server controls that render HTML and fire events are a cornerstone of the Web Forms programming
model, but controls arent the only entities that fire events. Pages do, too. To understand page-level
events, it helps to understand what goes on behind the scenes when ASP.NET processes the first HTTP
request for an ASPX file:
1. It creates a temporary file containing a class derived from System.Web.UI.Page. The Page class is
one of the most important classes in ASP.NET; it represents ASP.NET Web pages.
2. ASP.NET copies the code in the ASPX file, as well as some code of its own, to the Page-derived
class. A method named OnAdd in a <script> block in an ASPX file becomes a member method of
the derived class.
3. ASP.NET compiles the derived class and places the resulting DLL in a system folder. The DLL is
cached so that steps 1 and 2 wont have to be repeated unless the contents of the ASPX file
change.
4. ASP.NET instantiates the derived class and executes it by calling a series of methods on it.
It is during this execution phase that the Page object instantiates any controls declared inside it and
solicits their output.
As a Page object executes, it fires a series of events that can be processed by server-side scripts. The
most important are Init, which is fired when the page is first instantiated, and Load, which is fired after
the pages controls are initialized but before the page renders any output. The Load event is
particularly important to ASP.NET developers because a Load handler is the perfect place to initialize
any controls that require dynamic (that is, run-time) initialization. The next section offers an example.
If you want to see the DLLs that ASP.NET generates from your ASPX files, youll find them in
subdirectories under the Windows (or Winnt) directorys
Microsoft.NET\Framework\vn.n.nnnn\Temporary ASP.NET Files subdirectory, where n.n.nnnn is the
version number of the .NET Framework installed on your PC. Drill down in the directory tree under
Temporary ASP.NET Files\root, for example, and youll find a DLL containing the class that
ASP.NET derived from Page to serve Calc.aspx (assuming you ran Calc.aspx from \Inetpub\wwwroot).
If the subdirectory contains several DLLs, open them with ILDASM, and youll find one containing a
Page-derived class named Calc_aspx. (See Figure 5-8.) Thats the class ASP.NET instantiates each
time a request arrives for Calc.aspx. If Calc.aspx changes, ASP.NET recompiles the DLL on the next
request. Otherwise, the DLL remains on your hard disk so that ASP.NET can reuse it as needed.
Figure 5-8
DLL generated from Calc.aspx.
ThePage.Load Event and the Page.IsPostBack Property
Suppose you want to build a Web form that displays todays date and the four days following it in a
drop-down list. If today is January 1, 2002, one solution is to statically initialize a DropDownList
control:
<asp:DropDownList ID="MyList" RunAt="server">
<asp:ListItem Text="January 1, 2002" RunAt="server"
<asp:ListItem Text="January 2, 2002" RunAt="server"
<asp:ListItem Text="January 3, 2002" RunAt="server"
<asp:ListItem Text="January 4, 2002" RunAt="server"
<asp:ListItem Text="January 5, 2002" RunAt="server"
</asp:DropDownList>
/>
/>
/>
/>
/>
The problem with this approach is obvious: every day youll have to modify the form to update the
dates. A smarter approach is to write a handler for the pages Load event that initializes the
DropDownList at run time:
<asp:DropDownList ID="MyList" RunAt="server" />
.
.
.
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
if (!IsPostBack) {
for (int i=0; i<5; i++) {
DateTime date =
DateTime.Today + new TimeSpan (i, 0, 0, 0);
MyList.Items.Add (date.ToString ("MMMM dd, yyyy"));
}
}
}
</script>
APage_Load method prototyped this way is automatically called by ASP.NET when the page fires a
Load event. You dont have to manually wire the event to the handler as you do for controls. The
same is true for all page-level events. You can respond to any event fired by Page by writing a method
namedPage_EventName, where EventName is the name of the event you want to handle.
ThePage_Load handler in the previous example adds items to the DropDownList by calling Add on the
controlsItems collection. Items represents the items in the DropDownList. Significantly, this
implementation of Page_Load initializes the control only if a value named IsPostBack is false.
IsPostBack is one of several properties defined in the Page class. Because all code in an ASPX file
executes in the context of a class derived from Page, your code enjoys intrinsic access to Page properties
and methods. IsPostBack is a particularly important property because it reveals whether your code is
executing because the page was requested from the Web server with an HTTP GET (IsPostBack==false)
or because the page was posted back to the server (IsPostBack==true). In general, you dont want to
initialize a Web control during a postback because ASP.NET maintains the controls state for you. If
you call Add on the controls Items collection the first time the page is fetched and then call it again
when the page is posted back, the control will have twice as many items in it following the first postback.
ThePage.Init Event
Page_Load methods are handy for performing run-time control initializations. You can also write
Page_Init methods that fire in response to Init events. One use for Init events is to create controls and add
them to the page at run time. Another is to programmatically wire events to event handlers. For example,
instead of connecting Click events to an event handler with an OnClick attribute, like this:
<asp:Button Text=" = " OnClick="OnAdd" RunAt="server" />
.
.
.
<script language="C#" runat="server">
void OnAdd (Object sender, EventArgs e)
{
int a = Convert.ToInt32 (op1.Text);
int b = Convert.ToInt32 (op2.Text);
Sum.Text = (a + b).ToString ();
}
</script>
This is the technique that Visual Studio .NET uses to wire events to event handlers. Youll see an
example at the end of this chapter when you build a Web Forms application with Visual Studio .NET.
Page-Level Directives
ASP.NET supports a number of commands called page-level directives that you can put in ASPX files.
Theyre sometimes called @ directives because all directive names begin with an @ sign: @ Page,
@ Import, and so on. Page-level directives appear between <% and %> symbols and must be positioned
at the top of an ASPX file. In practice, @ directives appear in all but the simplest of ASPX files. The
following table lists the directives that ASP.NET supports. Succeeding sections document the most
commonly used directives. Other directives are discussed as circumstances warrant elsewhere in the
book.
ASP.NET @ Directives
Directive
Description
@ Page
Defines general attributes and compilation settings for ASPX files
@ Control
Defines general attributes and compilation settings for ASCX files
@ Import
Imports a namespace
@ Assembly Enables linkage to assemblies not linked to by default
@ Register
Registers user controls and custom controls for use in a Web form
@ OutputCacheExerts declarative control over page caching and fragment caching
@ Reference Adds a reference to an external ASPX or ASCX file
@ Implements Identifies an interface implemented by a Web page
The@ Page Directive
Of the various page-level directives that ASP.NET supports, @ Page is the one used most often. The
following@ Page directive changes the default language for all scripts that dont specify otherwise
from Visual Basic .NET to C#. Its especially useful when you inline code in an ASPX file
by placing it between <% and %> tags:
<%@ Page Language="C#" %>
As this example demonstrates, ASP.NET pages can use Response and other intrinsic objects in the same
way ASP pages can. Because you cant include Language=C# attributes in <% %> blocks,
you either need an @ Page directive telling ASP.NET which compiler to pass your code to or a
Web.config file that changes the default language on a directory-wide basis. (If youre not familiar
with Web.config files just yet, dont worry about it for now. Youll learn all about them in
Chapter 9.)
Another common use for @ Page directives is to enable debugging support. By default, ASP.NET builds
release-build DLLs from your ASPX files. If you encounter a run-time error and need to debug it, you
need DLLs with debugging symbols. The statement
<%@ Page Debug="true" %>
commands ASP.NET to create debug DLLs rather than release DLLs and enriches the information
available to you when you debug a malfunctioning Web form.
As you can see, @ Page is overloaded to support a variety of uses. In all, it supports some 28 different
attributes such as Language and Debug. A page can have only one @ Page directive, but that directive
can contain any number of attributes. For example, the statement
<%@ Page Language="C#" Debug="true" %>
makes all the data types defined in System.Data available to a Web form.
What namespaces does ASP.NET import by default? Heres a complete list:
System
System.Collections
System.Collections.Specialized
System.Configuration
System.IO
System.Text
System.Text.RegularExpressions
System.Web
System.Web.Caching
System.Web.Security
System.Web.SessionState
System.Web.UI
System.Web.UI.HtmlControls
System.Web.UI.WebControls
BecauseSystem.Data isnt imported automatically, you must import it yourself if you want to use
System.Data types (for example, DataSet) in a Web form. Otherwise, youll receive an error message
the first time ASP.NET attempts to compile the page. System.Web.Mail is another example of a
commonly used namespace that isnt imported automatically. Look back at Chapter 3s SendMail
program (Figure 3-7), and youll see an @ Import statement importing System.Web.Mail on the very
first line of the ASPX file.
Unlike@ Page,@ Import can appear multiple times in a Web page. The following statements import
three namespaces and are often used together in ASPX files that access SQL Server databases:
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Data.SqlTypes" %>
System.Web.dll
System.Web.Services.dll
System.Xml.dll
These assemblies include the data types that Web forms are most likely to use. But suppose you want to
use the FCLs System.DirectoryServices.DirectorySearcher class in a <script> block to perform a
query against Active Directory. Because DirectorySearcher lives in an assembly
(System.DirectoryServices.dll) that ASP.NET doesnt reference by default, its use requires an @
Assembly directive. In the following example, @ Import is required also because DirectorySearcher is
defined in a nondefault namespace:
<%@ Import Namespace="System.DirectoryServices" %>
<%@ Assembly Name="System.DirectoryServices" %>
Its coincidental that the namespace name and assembly name are one and the same; thats not
always the case. Note that an assembly name passed to @ Assembly must not include the filename
extension (.dll). In addition, the list of default assemblies can be changed by editing a machinewide configuration file named Machine.config or augmented by dropping a Web.config file containing
an <assemblies> section into an application root. Like @ Import,@ Assembly can appear multiple times
in a Web page.
The@ OutputCache Directive
One of the best ways to optimize the performance of ASP.NET applications is to cache Web pages
generated from ASPX files so that they can be delivered straight from the cache if theyre requested
again. ASP.NET supports two forms of caching: page caching, which caches entire pages, and fragment
(orsubpage)caching, which caches portions of pages. The @ OutputCache directive enables an
application to exert declarative control over page and fragment caching.
Because examples have a way of lending clarity to a subject (funny how that works, isnt it?),
heres a simple one that demonstrates @ OutputCache:
<%@ Page Language="C#" %>
<%@ OutputCache Duration="60" VaryByParam="None" %>
<html>
<body>
Today is <%= DateTime.Now.ToLongDateString () %>
</body>
</html>
This ASPX file displays todays date in a Web page. (The <%= ... %> syntax is an alternative to
usingResponse.Write. Its an easy way to inject text into the pages output.) Since todays
date changes only every 24 hours, its wasteful to reexecute this page every time its requested.
Therefore, the page includes an @ OutputCache directive that caches the output for 60 seconds at a time.
Subsequent requests for the page come straight from the cache. When the cache expires and ASP.NET
receives another request for the page, ASP.NET reexecutes (and recaches) the page. The Duration
attribute controls the length of time that the cached page output is valid.
In real life, Web pages are rarely this simple. The output from an ASPX file often varies based on input
provided by users. The designers of ASP.NET anticipated this and gave the page cache the ability to hold
multiple versions of a page, qualified by the user input that produced each version. Imagine, for example,
that you wrote a Web form that takes a city name and state name as input and returns a satellite image of
that city from a database. (Sound far-fetched? Its not. Chapter 11 includes an application that does
just that.) If the city and state names accompanying each request are transmitted in variables called city
andstate, the following directive caches a different version of the page for each city and state requested
for up to 1 hour:
<%@ OutputCache Duration="3600" VaryByParam="city;state" %>
Its that simple. You can even use a shortened form of the VaryByParam attribute to cache a separate
version of the page for every different input:
<%@ OutputCache Duration="3600" VaryByParam="*" %>
Now if two users request a satellite image of Knoxville, Tennessee, 30 minutes apart, the second of the
two requests will be fulfilled very quickly.
A Web Forms Currency Converter
Figure 5-9 shows a Web form that performs currency conversions using exchange rates stored in an
XML file. To see it in action, copy Converter.aspx and Rates.xml, which are listed in Figures 5-10 and 511, to \Inetpub\wwwroot and type http://localhost/converter.aspx in your browsers address bar. Then
pick a currency, enter an amount in U.S. dollars, and click the Convert button to convert dollars to the
currency of your choice.
Here are some points of interest regarding the source code:
Because it uses the DataSet class defined in the System.Data namespace, Converter.aspx begins
with an @ Import directive importing System.Data.
Rather than show a hard-coded list of currency types in the list box, Converter.aspx reads them
from Rates.xml. Page_Load reads the XML file and initializes the list box. To add new currency
types to the application, simply add new Rate elements to Rates.xml. Theyll automatically
appear in the list box the next time the page is fetched.
For good measure, Converter.aspx wires the Convert button to the Click handler named OnConvert
programmatically rather than declaratively. The wiring is done in Page_Init.
Notice how easily Converter.aspx reads XML from Rates.xml. It doesnt parse any XML; it simply
callsReadXml on a DataSet and provides an XML file name. ReadXml parses the file and initializes the
DataSet with the files contents. Each Rate element in the XML file becomes a row in the DataSet,
and each row, in turn, contains fields named Currency and Exchange. Enumerating all
the currency types is a simple matter of enumerating the DataSets rows and reading each rows
Currency field. Retrieving the exchange rate for a given currency is almost as easy. OnConvert
usesDataTable.Select to query the DataSet for all rows matching the currency type. Then it reads the
Exchange field from the row returned and converts it to a decimal value with Convert.ToDecimal.
One reason I decided to use a DataSet to read the XML file is that a simple change would enable the
Web form to read currencies and exchange rates from a database. Were Converter.aspx to open the XML
file and parse it using the FCLs XML classes, more substantial changes would be required to
incorporate database input.
A word of caution regarding this Web form: Dont use it to perform real currency conversions! The
exchange rates in Rates.xml were accurate when I wrote them, but theyll be outdated by the time
you read this. Unless you devise an external mechanism for updating Rates.xml in real time, consider the
output from Converter.aspx to be for educational purposes only.
Figure
5-9
Web form currency converter.
Converter.aspx
<%@ Import Namespace=System.Data %>
<html>
<body>
<h1>Currency Converter</h1>
<hr>
<form runat="server">
Target Currency<br>
<asp:ListBox ID="Currencies" Width="256" RunAt="server" /><br>
<br>
Amount in U.S. Dollars<br>
<asp:TextBox ID="USD" Width="256" RunAt="server" /><br>
<br>
<asp:Button Text="Convert" ID="ConvertButton" Width="256"
RunAt="server" /><br>
<br>
<asp:Label ID="Output" RunAt="server" />
</form>
</body>
</html>
void
{
}
void
{
}
</script>
//
if
}
Figure 5-10
Currency converter source code.
Rates.xml
<?xml version="1.0"?>
<Rates>
<Rate>
<Currency>British Pound</Currency>
<Exchange>0.698544</Exchange>
</Rate>
<Rate>
<Currency>Canadian Dollar</Currency>
<Exchange>1.57315</Exchange>
</Rate>
<Rate>
<Currency>French Franc</Currency>
<Exchange>7.32593</Exchange>
</Rate>
<Rate>
<Currency>German Mark</Currency>
<Exchange>2.18433</Exchange>
</Rate>
<Rate>
<Currency>Italian Lira</Currency>
<Exchange>2162.67</Exchange>
</Rate>
<Rate>
<Currency>Japanese Yen</Currency>
<Exchange>122.742</Exchange>
</Rate>
<Rate>
<Currency>Mexican Peso</Currency>
<Exchange>9.22841</Exchange>
</Rate>
<Rate>
<Currency>Swiss Franc</Currency>
<Exchange>1.64716</Exchange>
</Rate>
</Rates>
Figure 5-11
XML file used by Converter.aspx.
Code-Behind Programming
While its perfectly legal to put HTML and code in the same ASPX file, in the real world you should
segregate the two by placing them in separate files. Proper separation of code and data is achieved in
ASP.NET by using a technique called code-behind programming. A Web form that uses code-behind is
divided into two parts: an ASPX file containing HTML, and a source code file containing code. Here are
two reasons why all commercial Web forms should employ code-behind:
Robustness. If a programming error prevents the code in an ASPX file from compiling, the error
wont come to light until the first time the page is accessed. Careful testing will take care of this,
but how often do unit tests achieve 100 percent code coverage?
Maintainability. ASP files containing thousands of lines of spaghetti-like mixtures of HTML and script
are not uncommon. Clean separation of code and data makes applications easier to write and to
maintain.
Code-behind is exceptionally easy to use. Heres a recipe for using code-behind in Web forms coded in
C#:
1. Create a CS file containing event handlers, helper methods, and other codeeverything that would
normally appear between <script> and </script> tags in an ASPX file. Make each of these source code
elements members of a class derived from System.Web.UI.Page.
2. In your Page-derived class, declare protected fields whose names mirror the IDs of the controls
declared in the ASPX file. For example, if the Web form includes a pair of TextBox controls whose
IDs are UserName and Password, include the following statements in your class declaration:
protected TextBox UserName;
protected TextBox Password;
Without these fields, the CS file wont compile because references to UserName and Password are
unresolvable. At run time, ASP.NET maps these fields to the controls of the same name so that reading
UserName.Text, for example, reads the text typed into the TextBox control named UserName.
3. Compile the CS file into a DLL and place the DLL in a subdirectory named bin in the virtual directory
that holds the ASPX file.
4. Place the HTML portion of the Web formeverything between the <html> and </html> tagsin an
ASPX file. Include in the ASPX file an @ Page directive containing an Inherits attribute that identifies
thePage-derived class in the DLL.
Thats it; thats all there is to it. You get all the benefits of embedding code in an ASPX file but
none of the drawbacks. The application in the next section demonstrates code-behind at work.
The Lander Application
In 1969, Neil Armstrong landed the Apollo 11 Lunar Excursion Module (LEM) on the moon with just 12
seconds of fuel to spare. You can duplicate Armstrongs feat with a Web-based lunar lander simulation
patterned after the lunar lander game popularized on mainframe computers in the 1970s. This version of the
game is built from an ASP.NET Web form, and it uses code-behind to separate code and HTML. Its
called Lander, and its shown in Internet Explorer in Figure 5-12. Its source code appears in Figure 5-13.
To run the program, copy Lander.aspx to your PCs \Inetpub\wwwroot directory. If \Inetpub\wwwroot
lacks a subdirectory named bin, create one. Then compile Lander.cs into a DLL and place the DLL in the bin
subdirectory. Heres the command to compile the DLL:
csc /t:library Lander.cs
into the address bar. Begin your descent by entering a throttle value (any percentage from 0 to 100, where 0
means no thrust and 100 means full thrust) and a burn time in seconds. Click the Calculate button to input
the values and update the altitude, velocity, acceleration, fuel, and elapsed time read-outs. Repeat until you
reach an altitude of 0, meaning youve arrived at the moons surface. A successful landing is one
that occurs with a downward velocity of 4 meters per second or less. Anything greater and you dig your very
own crater in the moon.
Figure 5-12
The Lander application.
Lander.aspx is a Web form built from a combination of ordinary HTML and ASP.NET server controls.
Clicking the Calculate button submits the form to the server and activates the DLLs OnCalculate
method, which extracts the throttle and burn time values from the input fields and updates the onscreen
flight parameters using equations that model the actual flight physics of the Apollo 11 LEM. Like many
Web pages, Lander.aspx uses an HTML table with invisible borders to visually align the pages controls.
Lander.aspx differs from the ASPX files presented thus far in this chapter in that it contains no source code.
Lander.cs contains the forms C# source code. Inside is a Page-derived class named LanderPage
containing the OnCalculate method that handles Click events fired by the Calculate button. Protected fields
namedAltitude,Velocity,Acceleration,Fuel,ElapsedTime,Output,Throttle, and Seconds serve as proxies
for the controls of the same names in Lander.aspx. LanderPage and OnCalculate are declared public, which
is essential if ASP.NET is to use them to serve the Web form defined in Lander.aspx.
Lander.aspx
<%@ Page Inherits="LanderPage" %>
<html>
<body>
<h1>Lunar Lander</h1>
<form runat="server">
<hr>
<table cellpadding="8">
<tr>
<td>Altitude (m):</td>
<td><asp:Label ID="Altitude" Text="15200.0"
RunAt="Server" /></td>
</tr>
<tr>
<td>Velocity (m/sec):</td>
<td><asp:Label ID="Velocity" Text="0.0"
RunAt="Server" /></td>
</tr>
<tr>
<td>Acceleration (m/sec2):</td>
<td><asp:Label ID="Acceleration" Text="-1.6"
RunAt="Server" /></td>
</tr>
<tr>
<td>Fuel (kg):</td>
<td><asp:Label ID="Fuel" Text="8165.0" RunAt="Server" /></td>
</tr>
<tr>
<td>Elapsed Time (sec):</td>
<td><asp:Label ID="ElapsedTime" Text="0.0"
RunAt="Server" /></td>
</tr>
<tr>
<td>Throttle (%):</td>
<td><asp:TextBox ID="Throttle" RunAt="Server" /></td>
</tr>
<tr>
<td>Burn Time (sec):</td>
<td><asp:TextBox ID="Seconds" RunAt="Server" /></td>
</tr>
</table>
<br>
<asp:Button Text="Calculate" OnClick="OnCalculate"
RunAt="Server" />
<br><br>
<hr>
<h3><asp:Label ID="Output" RunAt="Server" /></h3>
</form>
</body>
</html>
Figure 5-13
The Lander source code.
Lander.cs
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
public class LanderPage : Page
{
const double gravity = 1.625; // Lunar gravity
const double landermass = 17198.0; // Lander mass
protected
protected
protected
protected
protected
protected
protected
protected
Label Altitude;
Label Velocity;
Label Acceleration;
Label Fuel;
Label ElapsedTime;
Label Output;
TextBox Throttle;
TextBox Seconds;
if
if (Seconds.Text.Length == 0) {
Output.Text = "Error: Required field missing";
return;
}
//
if
}
double
double
double
double
//
if
Figure 5-14 shows (in ILDASM) the class that ASP.NET derived from LanderPage the first time
Lander.aspx was requested. You can clearly see the name of the ASP.NET-generated
classLander_aspxas well as the name of its base class: LanderPage.
Figure 5-14
DLL generated from a page that uses code-behind.
Using Code-Behind Without Precompiling: The Src Attribute
If you like the idea of separating code and data into different files but for some reason would prefer not to
compile the source code files yourself, you can use code-behind and still allow ASP.NET to compile the
code for you. The secret? Place the CS file in the same directory as the ASPX file and add a Src attribute to
the ASPX files @ Page directive. Heres how Lander.aspxs Page directive would look if it
were modified to let ASP.NET compile Lander.cs:
<%@ Page Inherits="LanderPage" Src="Lander.cs" %>
Why anyone would want to exercise code-behind this way is a question looking for an answer. But it works,
and the very fact that the Src attribute exists means someone will probably find a legitimate use for it.
Using Non-ASP.NET Languages in ASP.NET Web Forms
Code embedded in ASPX files has to be written in one of three languages: C#, Visual Basic .NET, or
JScript. Why? Because even though compilers are available for numerous other languages, ASP.NET uses
parsers to strip code from ASPX files and generate real source code files that it can pass to language
compilers. The parsers are language-aware, and ASP.NET includes parsers only for the aforementioned
three languages. To write a Web form in C++, you have to either write a C++ parser for ASP.NET or figure
out how to bypass the parsers altogether. Code-behind is a convenient mechanism for doing the latter.
Code-behind makes it possible to code Web forms in C++, COBOL, and any other language thats
supported by a .NET compiler. Figure 5-15 contains the C++ version of Lander.cs. Lander.cpp is an example
of C++ with Managed Extensions, better known as managed C++. Thats Microsofts term for C++
code that targets the .NET Framework. When you see language extensions such as __gc, which declares a
managed type, being used, you know youre looking at managed C++.
The following command compiles Lander.cpp into a managed DLL and places it in the current
directorys bin subdirectory:
cl /clr lander.cpp /link /dll /out:bin\Lander.dll
You can replace the DLL created from the CS file with the DLL created from the CPP file and Lander.aspx
is none the wiser; it still works the same as it did before. All it sees is a managed DLL containing the
LanderPage type identified by the Inherits attribute in the ASPX file. It neither knows nor cares how the
DLL was created or what language it was written in.
Lander.cpp
#using <system.dll>
#using <mscorlib.dll>
#using <system.web.dll>
using namespace System;
using namespace System::Web::UI;
using namespace System::Web::UI::WebControls;
public __gc class LanderPage : public Page
{
protected:
static const double gravity = 1.625; // Lunar gravity
static const double landermass = 17198.0; // Lander mass
Label* Altitude;
Label* Velocity;
Label* Acceleration;
Label* Fuel;
Label* ElapsedTime;
Label* Output;
TextBox* Throttle;
TextBox* Seconds;
public:
void OnCalculate (Object* sender, EventArgs* e)
{
double alt1 = Convert::ToDouble (Altitude->Text);
if
if (Seconds->Text->Length == 0) {
Output->Text = "Error: Required field missing";
return;
}
//
if
}
double
double
double
double
//
if
Figure 5-15
Managed C++ version of Lander.cs.
Now that you know what makes Web forms tick, its time to learn to build Web forms the Visual Studio .
.NET brings rapid application development to the Web. You design forms by choosing controls from a palette
forms. You write event handlers by double-clicking controls and filling in empty method bodies. And you com
by executing simple menu commands. Its no accident that building Web forms with Visual Studio .NET
Windows applications with Visual Basic. Thats exactly the feel Microsoft intended to convey.
This chapter closes with a step-by-step tutorial describing how to build a Web-based mortgage payment calcu
.NET. Figure 5-16 shows the finished product. Enter a loan amount, interest rate, and term (length of the loan
Compute Payment button. The corresponding monthly payment appears at the bottom of the page.
When you create a Web application project with Visual Studio .NET, you dont tell Visual Studio .NET w
entering a path name; you enter a URL. Assuming you want to store the files on your PC but dont want to
with project subdirectories, your first step is to create a project directory and turn it into a virtual directory so
Here are the steps:
1. Create a folder named Projects somewhere on your hard disk to hold your Web application projects. The
subdirectory named LoanCalc.
2.
1.
2. Start the Internet Information Services applet in Windows. Youll find it under Administrative Tools
3. In the left pane of the Internet Information Services window, expand the Local Computer\Web Sites fold
Site.
4. Select the New/Virtual Directory command from the Action menu to start the Virtual Directory Creation
5. When the wizard asks for an alias, type LoanCalc. When it asks for a path name, enter the path t
created in step 1. Click the Next and Finish buttons until the wizard closes.
You just created a physical directory named LoanCalc and converted it into a virtual directory. Its URL is http
proceeding, verify that LoanCalc appears with the other virtual directories listed under Default Web Site in th
Services window, as shown in Figure 5-17.
Start Visual Studio .NET, and then select the File/New/Project command. Fill in the New Project dialog exac
Verify that the statement Project will be created at http://localhost/LoanCalc appears near the bottom
Projects is selected in the Project Types box, and that ASP.NET Web Application is selected in the Templates
a new project named LoanCalc in the LoanCalc directory you created a moment ago.
Figu
Creating the LoanCalc project.
Step 3: Change to Flow Layout Mode
The next screen you see is the Visual Studio .NET Web forms designer. Here you design forms by dragging a
you begin, however, you have a decision to make.
The forms designer supports two layout modes: grid layout and flow layout. Grid layout mode lets you place c
relies on CSS-P (Cascading Style Sheets-Position) to achieve precise positioning of controls and other HTML
eschews CSS-P and relies on the normal rules of HTML layout. Flow layout mode is more restrictive, but it
contemporary browsers.
So that LoanCalc will be compatible with as wide a range of browsers as possible, go to Visual Studio .NET
change to flow layout mode by changing the documents pageLayout property from GridLayout, which is
Note that DOCUMENT must be selected in the combo box at the top of the Properties window for th
appear. If DOCUMENT doesnt appear in the drop-down list, click the empty form in the forms designer.
Before proceeding, click the form and select the Snap To Grid command from Visual Studio .NETs Form
make it easier to size and position the forms controls consistently with respect to one another.
Step 4: Add a Table
Since youre working in flow layout mode, tables are your best friend when it comes to positioning and al
Click the Web form design window to set the focus to the designer. Then use Visual Studio .NETs Table
an HTML table to the Web form. When the Insert Table dialog appears, fill it in as shown in Figure 5-19. In p
Columns to 2, Width to 100 percent, Border Size to 0, and Cell Padding to 8. When you click OK, the table ap
window.
Figure 5-19
Adding a table to a Web form.
Step 5: Insert Text
Click the cell in the tables upper left corner. A caret appears signaling that any text you type will appear i
Principal. Then go to the Properties window and change the cells align property to right
the process to add Rate (percent) to the cell in the next row, and Term (months) to the cell be
dragging the vertical divider between table cells until the tables leftmost column is just wide enough to fi
how the table should look when youve finished.
If the Toolbox window isnt displayed somewhere in the Visual Studio .NET window (it appears at the fa
Toolbox command from the View menu to display it. Click the Toolboxs Web Forms button to display a
use drag-and-drop to add TextBox controls to the right-hand cells in the tables first three rows. (See Figu
the Properties window to change the TextBox controls IDs to Principal, Rate, and T
Add a Button control to the rightmost cell in the tables bottom row, as shown in Figure 5-22. Size the bu
that of the text box above it. Change the button text to Compute Payment and the button ID to Pa
Next scroll to the bottom of the file and add these statements between the </table> tag and the <asp:Label> ta
<br>
<hr>
<br>
<h3>
As a last step, move the </h3> tag that Visual Studio .NET inserted so that it comes after the <asp:Label> tag
at the bottom of the forms designer to switch out of HTML view and back to design view. Figure 5-24 shows
should look.
Double-click the forms Compute Payment button. Visual Studio .NET responds by adding a method nam
WebForm1.aspx.cs and showing the method in the program editor. Add the following code to the empty meth
try {
double principal = Convert.ToDouble (Principal.Text);
double rate = Convert.ToDouble (Rate.Text) / 100;
double term = Convert.ToDouble (Term.Text);
double tmp = System.Math.Pow (1 + (rate / 12), term);
double payment = principal * (((rate / 12) * tmp) / (tmp - 1));
Output.Text = "Monthly Payment = " + payment.ToString ("c");
}
catch (Exception) {
Output.Text = "Error";
}
PaymentButton_Click isnt an ordinary method; its an event handler. Check out the InitializeCompon
Studio .NET wrote into WebForm1.aspx.cs and youll find a statement that registers PaymentButton_Clic
the Compute Payment buttons Click events. InitializeComponent is called by OnInit, which is called whe
The handler that you just implemented responds to Click events by extracting user input from the forms T
the corresponding monthly payment, and displaying the result in the Label control.
Step 11: Build and Test
Youre now ready to try out your handiwork. Select BuildLoanCalc from the Build menu to compile your
errors, choose Start (or Start Without Debugging) from the Debug menu to run it. When the Web form pops u
that it works properly by entering the following three inputs:
Principal: 100000
Rate: 10
Term: 240
Now click the Compute Payment button. If Monthly Payment = $965.02 appears at the bottom of the
the back. You just built your first Web form with Visual Studio .NET.
The LoanCalc Source Code
Of the many files in the LoanCalc directory, WebForm1.aspx and WebForm1.aspx.cs are the two that interest
LoanCalcs source code. (If youre curious to know what all those other files are for, be patient. You
themparticularly Global.asax and Web.configin Chapter 9. Most of the extra files are superfluous in th
.NET insists on creating them anyway.) WebForm1.aspx contains no code; only HTML. Visual Studio .NET
Web forms, so all the C# code is located in WebForm1.aspx.cs. Figure 5-25 shows the finished versions of bo
that you see was generated by Visual Studio .NET. The statements that you added are shown in boldface type
Given what you already know about Web forms, there isnt much in LoanCalcs source code to write
defines the user interface using a mixture of HTML and Web controls, and the CS file contains the Compute P
handler as well as the code that connects the button to the handler. Neither file contains anything you couldn
it should be apparent that building Web forms visually is faster and less error prone than building them manua
WebForm1.aspx
Figure 5-25
The LoanCalc source code.
WebForm1.aspx.cs
using
using
using
using
using
using
using
using
using
using
System;
System.Collections;
System.ComponentModel;
System.Data;
System.Drawing;
System.Web;
System.Web.SessionState;
System.Web.UI;
System.Web.UI.WebControls;
System.Web.UI.HtmlControls;
namespace LoanCalc
{
/// <summary>
/// Summary description for WebForm1.
/// </summary>
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.TextBox Rate;
protected System.Web.UI.WebControls.TextBox Term;
protected System.Web.UI.WebControls.Button PaymentButton;
protected System.Web.UI.WebControls.TextBox Principal;
protected System.Web.UI.WebControls.Label Output;
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.PaymentButton.Click +=
new System.EventHandler(this.PaymentButton_Click);
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
Chapter 6
Web Controls
Now that youre acquainted with the Web Forms programming model, the next step on the
road to becoming an ASP.NET programmer is getting to know the various types of server
controls that the Microsoft .NET Framework places at your disposal. Youve seen three of
them already: TextBox,Button, and Label. Now its time to learn about the others.
Recall from Chapter 5 that ASP.NET supports two distinctly different types of server controls:
HTML controls and Web controls. HTML controls are instances of classes defined in the
System.Web.UI.HtmlControls namespace. When you add runat=server to a conventional
HTML tag (as in <input type=text runat=server>), the .NET Framework
responds by instantiating an HTML control. Web controls come from classes defined in
System.Web.UI.WebControls. Theyre declared explicitly by prefixing class names with
asp:, and, of course, including runat=server attributes (for example, <asp:TextBox
RunAt=server>). HTML controls exist primarily to ease the chore of migrating existing
HTML forms to ASP.NET. Web controls are richer and more diverse in scope than HTML
controls, so the focus of this chapter is Web controls.
To lend order to the otherwise dizzying array of Web controls defined in the WebControls
namespace, Ive divided the Web controls in the .NET Framework class library (FCL) into
the following categories:
Simple controls, so called because (in general) they wrap simple HTML control
tags
Button controls, which create various types of buttons in Web forms
List controls, which display simple lists of items
Data-bound controls, which use data binding to display information obtained from
databases and other data sources
Calendar controls, whose sole member, Calendar, adds interactive calendars to Web forms
Validation controls, which validate user input before and after forms are submitted to the
server
The more you know about Web controls, the better equipped youll be to build sophisticated
Web forms that take advantage of the best features ASP.NET has to offer.
Simple Controls
The simple controls are so named because most emit only a few lines of HTML. Some return clientside script too, but only under special circumstances. Theyre exceedingly easy to use, and thus
are a great starting point for an exploration of Web controls.
TextBox Controls
TextBox controls are the ASP.NET equivalent of <input type=text/password> and
<textarea> tags in HTML. Their purpose? To create text input fields in Web forms. The statement
<asp:TextBox ID="UserName" RunAt="server" />
creates a text input field in a Web form and assigns it the programmatic ID UserName. You
can use a TextBoxsText property to declaratively insert text into a TextBox and also to read and
writeTextBox text from a server-side script. The following statement creates a TextBox that initially
contains the string Elmo:
<asp:TextBox ID="UserName" Text="Elmo" RunAt="server" />
And the following server-side script reads the contents of the TextBox:
string name = UserName.Text;
Text is one of several public properties that the TextBox class exposes. Others include Rows and
Columns, which size a TextBox by setting the number of rows and columns that it displays;
MaxLength, which limits the number of characters a TextBox will accept; ReadOnly, which, when
true, prevents the TextBox from accepting input; Wrap, which determines whether text wraps in a
multilineTextBox; and TextMode, which can be set to SingleLine (the default) to create single-line
input fields, MultiLine to create multiline input fields, or Password to create password input
fieldsfields that display asterisks or other characters in place of the actual characters that the user
types. The following statement creates a password input field named Password:
<asp:TextBox ID="Password" TextMode="Password" RunAt="server" />
To create a multiline input field, set TextMode to MultiLine and Rows to the number of rows you
want the TextBox to display:
<asp:TextBox ID="Comments" TextMode="MultiLine" Rows="10"
RunAt="server" />
Examining the HTML that Web controls return is a great way to get acquainted with Web controls
and learn more about how they work.
TextChanged Events and the AutoPostBack Property
TextBox controls fire TextChanged events following a postback if the text inside them has changed.
AnOnTextChanged attribute in an <asp:TextBox> tag designates a handler for TextChanged events:
<asp:TextBox ID="UserName" OnTextChanged="OnNameChanged"
RunAt="server" />
.
.
.
<script language="C#" runat="server">
void OnNameChanged (Object sender, EventArgs e)
{
// Name changed; read it from the TextBox
string name = UserName.Text;
}
</script>
TextChanged events fire only when the page posts back to the server. By default, TextBox controls
dont generate postbacks themselves and therefore fire TextChanged events only when another
control on the page forces a postback. However, you can set a TextBox controls AutoPostBack
property to true to force postbacks to occur (and TextChanged events to fire) the moment the text
inside the control changes:
<asp:TextBox ID="UserName" OnTextChanged="OnNameChanged"
AutoPostBack="true" RunAt="server" />
Unlike Windows edit controls, which fire EN_CHANGE notifications in response to each and every
character that the user enters, TextBox controls with AutoPostBack enabled fire TextChanged events
only when they lose the input focus (that is, when the user moves to another control in the Web page)
following a text change. Thats good, because a page that posts back to the server every time a
character is entered into a TextBox would be a slow page indeed.
How does setting AutoPostBack to true cause postbacks to occur when a TextBox loses the input
focus? With a sprinkle of JavaScript and a dash of Dynamic HTML (DHTML). Enter this statement
into a Web form:
<asp:TextBox ID="UserName" AutoPostBack="true" RunAt="server" />
.
theform.submit();
}
// -->
</script>
The <input> tag includes an onchange attribute that activates a JavaScript function named
__doPostBack on the client when the control loses the input focus following a text change. The
__doPostBack function programmatically posts the page back to the server by calling the Submit
method of the DHTML object that represents the form.
TextBox isnt the only Web control that features an AutoPostBack property; CheckBox,
RadioButton, and several other controls support it as well. Whenever AutoPostBack appears in a
controls property list, setting it to true causes postbacks to occur (and events to fire) the moment
a change occurs in the state of the control. Otherwise, the controls events wont fire until an
external stimulus forces a postback.
Label Controls
Label controls are among the simplestif not the simplestof all Web controls. They add
programmable textual labels to Web forms. A LabelcontrolsText property exposes the control
text. The following statement adds Hello to a Web page:
<asp:Label Text="Hello" RunAt="server" />
ALabel control declared this way renders itself to the Web page as a <span> tag:
<span>Hello</span>
Spans are benign HTML tags that are used to group other HTML elements.
Label controls frequently serve as placeholders for output written by server-side scripts. The
following statement declares an empty Label control and assigns it the programmatic ID
Output:
<asp:Label ID="Output" RunAt="server" />
And this statement in a server-side script writes Hello to the Web page where the Label
control is positioned:
Output.Text = "Hello";
Use a Label control whenever you want to add text to a Web page and change that text from a serverside script. For static labels, use ordinary HTML text instead. Static HTML text improves
performance by preventing ASP.NET from having to instantiate and execute a control each time the
page is requested from the server.
HyperLink Controls
HyperLink controls add hyperlinks to Web forms. HyperLink controls come in two forms: text
hyperlinks and image hyperlinks. The following statement creates a hyperlink that renders itself as a
text string and points to www.wintellect.com:
<asp:HyperLink Text="Click here"
NavigateUrl="http://www.wintellect.com" RunAt="server" />
A slight modification transforms the hyperlink into an image that targets the same URL:
<asp:HyperLink ImageUrl="logo.jpg"
NavigateUrl="http://www.wintellect.com" RunAt="server" />
Text hyperlinks render as <a href> tags; image hyperlinks render as <img> tags enclosed in <a href>
tags. You normally include either a Text or an ImageUrl attribute in an <asp:HyperLink> tag, but not
both. However, if you do specify both, the control uses the text you specify as a tool tip in supportive
browsers.
TheHyperLink class exposes a Target property that can be used to control how the targeted Web
page is displayed. For example, the statement
<asp:HyperLink Text="Click here" Target="_new"
NavigateUrl="http://www.wintellect.com" RunAt="server" />
opens the page in a new browser window. Any value thats valid for a Target attribute in an <a>
tag is also valid in a HyperLink. Another use for Target attributes is to open pages in specific
windows or frames.
LikeLabel controls, HyperLink controls should be used only when you want to change the properties
of the control dynamicallythat is, when ordinary <a href> tags wont do. The following code
initializes the target of a hyperlink when the page loads:
<asp:HyperLink ID="MyLink" Text="Web page du jour" RunAt="server" />
.
.
.
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
MyLink.NavigateUrl = "www.wintellect.com";
}
</script>
One motivation for initializing a HyperLink control in this way is to retrieve the targeted URL from a
database or an XML file.
Image Controls
Image controls add images to Web forms by emitting <img> tags. Images most important
properties are ImageUrl, which specifies the URL of the image that the control renders; ImageAlign,
which controls the alignment of the image; and AlternateText, which specifies the images
alternate text. Alternate text is displayed in place of the image in text-only browsers. The following
statement declares an Image control in a Web form:
<asp:Image ImageUrl="logo.jpg" AlternateText="Company Logo"
RunAt="server" />
Image controls are perfect for displaying images whose URLs are assigned at run time, possibly in
response to user input. For static images, you can reduce overhead by using conventional <img> tags
instead.
CheckBox Controls
And this server-side script determines whether the check box is checked when the form is submitted
to the server:
if (Confirm.Checked) {
// The box is checked
}
else {
// The box is not checked
}
On the off chance that youd like to reverse the positions of a check box and the text that
normally appears to its right, include a TextAlign=Left attribute in the control tag.
CheckBox controls fire CheckedChanged events when theyre checked and unchecked. By
default, a CheckedChanged event doesnt fire the moment the check box is clicked; it waits until
the page posts back to the server. To respond immediately to changes in a check boxs state, set
the controls AutoPostBack property to true to force postbacks:
<asp:CheckBox ID="Confirm" Text="E-mail my confirmation"
AutoPostBack="true" OnCheckedChanged="DoItNow" RunAt="server" />
.
.
.
<script language="C#" runat="server">
void DoItNow (Object sender, EventArgs e)
{
// The check box was just checked or unchecked; do something!
}
</script>
Dont set AutoPostBack to true unless you really need CheckedChanged events to fire
immediately. One justification for setting AutoPostBack to true is to dynamically change the contents
of the page each time the check box is clicked.
RadioButton Controls
RadioButton controls create radio buttons in Web forms. Radio buttons present users with mutually
exclusive lists of choices. Clicking a radio button checks that radio button and unchecks other radio
buttons in the group.
RadioButton derives from CheckBox and therefore supports the same properties and events that
CheckBox supports. It also adds a GroupName property for designating the group that a radio button
belongs to. The following code declares five RadioButton controls and divides them into two groups:
one group of three and another group of two. It also uses the RadioButton.Checked property to check
the first radio button in each group:
<asp:RadioButton Text="Red" ID="Button1" Checked="true"
GroupName="Colors" RunAt="server" /><br>
Grouping these controls by using the GroupName attribute is important because it tells the browser
which radio buttons to uncheck when a radio button is checked.
Figuring out which radio button in a group of radio buttons is checked from a server-side script
requires checking each buttons Checked property one by one. A better way to add radio buttons
to a Web page is to use a RadioButtonList.ItsSelectedIndex property identifies the button thats
checked.RadioButtonList also simplifies the task of arranging radio buttons on a page. Youll
learn all about RadioButtonList later in this chapter.
Table Controls
Table controls add HTML tables to Web forms. They render a combination of <table>, <tr>, and
<td> tags to browsers. Heres one way to add a table to a Web form:
<table>
<tr>
<td>Row
<td>Row
</tr>
<tr>
<td>Row
<td>Row
</tr>
</table>
1, Column 1</td>
1, Column 2</td>
2, Column 1</td>
2, Column 2</td>
Table controls add value to a Web form when you want to change a tables contents dynamically.
For example, the following server-side script modifies the text in each of the table cells:
MyTable.Rows[0].Cells[0].Text
MyTable.Rows[0].Cells[1].Text
MyTable.Rows[1].Cells[0].Text
MyTable.Rows[1].Cells[1].Text
=
=
=
=
"Cell
"Cell
"Cell
"Cell
1";
2";
3";
4";
These scripts work because a Table object exposes the rows that it contains through a property named
Rows. Each row in the Rows collection is an instance of TableRow. Within a row, each cell is
represented as a TableCell object thats accessible through the rows Cells collection. Calling
Add on a Rows or Cells collection programmatically adds a row to a table or a cell to a row.
By default, a Table controls borders are invisible. You can change that by setting the
controlsGridLines property to Horizontal, Vertical, or Both. Other useful Table properties
includeCellPadding and CellSpacing, which, like the HTML attributes of the same name, control the
spacing within and between cells, and BackImageUrl, which identifies a background image. Tables
are often used in Web pages to paint colored backgrounds. To change a Table objects
background color, use the BackColor property that Table inherits from WebControl.
Panel Controls
Panel controls serve as containers for other controls. One use for Panel controls is to control the
visibility of a group of controls. The following Web form toggles four Label controls on and off by
setting a Panel controls Visible property to true or false each time a check box is clicked. Note
theAutoPostBack=true attribute in the <asp:CheckBox> tag:
<html>
<body>
<form runat="server"><br>
<asp:CheckBox ID="Toggle" Text="Show Labels" Checked="true"
AutoPostBack="true" OnCheckedChanged="OnToggle"
RunAt="server" />
<asp:Panel ID="MyPanel" RunAt="server">
<asp:Label Text="John" RunAt="server" /><br>
<asp:Label Text="Paul" RunAt="server" /><br>
<asp:Label Text="George" RunAt="server" /><br>
<asp:Label Text="Ringo" RunAt="server" /><br>
</asp:Panel>
</form>
</body>
</html>
<script language="C#" runat="server">
void OnToggle (Object sender, EventArgs e)
{
MyPanel.Visible = Toggle.Checked;
}
</script>
Another use for Panel controls is to specify horizontal alignment for a group of controls:
<asp:Panel HorizontalAlign="Center" ID="MyPanel" RunAt="server">
<asp:Label Text="John" RunAt="server" /><br>
<asp:Label Text="Paul" RunAt="server" /><br>
<asp:Label Text="George" RunAt="server" /><br>
<asp:Label Text="Ringo" RunAt="server" /><br>
</asp:Panel>
Panel controls render as HTML <div> tags. Therefore, its appropriate to use them any time you
would ordinarily use a <div> tag but want to change the attributes of that tag dynamically.
Button Controls
The Web controls family includes three types of button controls: Button,LinkButton, and
ImageButton. Functionally, all three do exactly the same thing: they submit the form that hosts
them to the server. The difference lies in their physical appearance. A Button control looks like a
push button, a LinkButton looks like a hyperlink, and an ImageButton renders itself using an
image you supply. Nearly every Web form uses one or more buttons to enable the user to submit
the form to the server.
The following statements declare an instance of each control type in a Web form:
<asp:Button Text="Sort" RunAt="server" />
<asp:LinkButton Text="Sort" RunAt="server" />
<asp:ImageButton ImageUrl="sort.jpg" RunAt="server" />
TheText property specifies the text that appears on the face of a Button or LinkButton.ImageUrl
identifies the image displayed by an ImageButton.
All three button controls fire two kinds of events when clicked: a Click event and a Command
event. An OnClick attribute in the control tag wires a button to a Click handler. Click handlers
forButton and LinkButton controls are prototyped this way:
void OnClick (Object sender, EventArgs e)
{
// Event handling code goes here
}
}
</script>
}
else
}
Command events are useful for overloading button controls and having them perform
different actions based on the value of CommandArgument. They can also be used to connect
multiple buttons to a single handler and have the handler respond differently depending on which
button was clicked.
List Controls
The list controls family has four members: ListBox,DropDownList,CheckBoxList, and
RadioButtonList. All four have two important characteristics in common: they all derive from
System.Web.UI.WebControls.ListControl, and theyre all designed to present lists of items to
the user. ListBox and DropDownList controls display textual items that the user can select. Both
render back to the browser as HTML <select> tags. CheckBoxList and RadioButtonList display
arrays of check boxes and radio buttons and render as <input type=checkbox> and
<input type=radio> tags, respectively. The <input> tags are optionally contained in an
HTML table for alignment purposes.
Items in a list control are represented by instances of ListItem. Instances of ListItem are declared
with <asp:ListItem> tags. Inside a ListItem are string properties named Text and Value.Text
exposes the text that represents the item in a list control; Value allows an arbitrary string to be
associated with the item. ListItem also exposes a Boolean property named Selected that
determines whether the item is selected. The following statements declare a ListBox control
containing four items and select the second item:
<asp:ListBox ID="MyListBox" RunAt="server">
<asp:ListItem Text="John" RunAt="server" />
<asp:ListItem Text="Paul" Selected="true" RunAt="server" />
<asp:ListItem Text="George" RunAt="server" />
<asp:ListItem Text="Ringo" RunAt="server" />
</asp:ListBox>
SelectedIndex and SelectedItem arent that interesting for CheckBoxList controls because
multiple check boxes in the list might be checked, but theyre extremely useful for other
types of list controls.
List controls fire SelectedIndexChanged events when the selection changesthat is, when a
new item is selected in a ListBox or DropDownList or a button is clicked in a CheckBoxList or
RadioButtonList. By default, the event doesnt fire until something else on the page causes a
postback. However, all list controls inherit an AutoPostBack property from ListControl that you
Figure 6-21 later in the chapter contains a complete listing for a DropDownList control that
displays the abbreviations of all 50 U.S. states plus the District of Columbia.
ListBox Controls
ListBox controls are similar to DropDownList controls, but they display their items in a static list
rather than in a drop-down list. The following example creates a ListBox control that displays the
names of the U.S. states and writes the users selection to the Web page:
<html>
<body>
<form runat="server">
<asp:ListBox ID="StateList" Rows="10" RunAt="server">
<asp:ListItem Text="Alabama" RunAt="server" />
<asp:ListItem Text="Alaska" RunAt="server" />
<asp:ListItem Text="Arkansas" RunAt="server" />
.
.
.
<asp:ListItem Text="Wisconsin" RunAt="server" />
<asp:ListItem Text="West Virginia" RunAt="server" />
<asp:ListItem Text="Wyoming" RunAt="server" />
</asp:ListBox>
<asp:Button Text="Submit" OnClick="OnSubmit" RunAt="server" />
<br>
<asp:Label ID="Output" RunAt="server" />
</form>
</body>
</html>
<script language="C#" runat="server">
void OnSubmit (Object sender, EventArgs e)
{
Output.Text = StateList.SelectedItem.Text;
}
</script>
By default, a ListBox control is sized to display only four items at a time. The Rows attribute in
the <asp:ListBox> tag above increases the ListBox height to 10 items.
The only functional difference between a ListBox control and a DropDownList is that the former
can be programmed to support multiple selections. A SelectionMode=Multiple attribute
in the control tag creates a multiple-selection ListBox:
<asp:ListBox ID="StateList" SelectionMode="Multiple"
Rows="10" RunAt="server">
Unfortunately, the ListBox class lacks a public method or property for retrieving the indices of
the items selected in a multiple-selection list box. To figure out which items the user selected,
you have to iterate through all the list boxs items, checking their Selected properties one by
one. The following method takes a ListBox reference as an input parameter and returns an array
of integers containing the 0-based indices of all selected items:
int[] GetSelectedIndices (ListBox lb)
{
ArrayList a = new ArrayList ();
for (int i=0; i<lb.Items.Count; i++) {
if (lb.Items[i].Selected)
a.Add (i);
}
int [] indices = new int[a.Count];
a.CopyTo (indices);
return indices;
}
identifies all the items selected in the multiple-selection ListBox named StateList.
CheckBoxList Controls
TheCheckBoxList control creates an array of check boxes. The following statements display
four vertically stacked check boxes:
<asp:CheckBoxList
<asp:ListItem
<asp:ListItem
<asp:ListItem
ID="MyCheckBoxList" RunAt="server">
Text="John" RunAt="server" />
Text="Paul" RunAt="server" />
Text="George" RunAt="server" />
To determine whether a given check box is checked, read its Selected property from a server-side
script:
// Is the third check box checked?
if (MyCheckBoxList.Items[2].Selected) {
// The check box is checked
else {
// The check box is not checked
}
Creating an array of check boxes with CheckBoxList is generally preferable to using an array of
<asp:CheckBox> tags because CheckBoxList makes it easy to align check boxes in rows and
columns and to control the spacing between check boxes. Two properties control the check
boxes layout: RepeatColumns and RepeatDirection. The following statements create an
array of check boxes divided into four rows and three columns. The first row contains the check
boxes whose indices are 02, the second row check boxes 35, and so on:
<asp:CheckBoxList ID="MyCheckBoxList" RepeatColumns="3"
RepeatDirection="Horizontal" RunAt="server">
<asp:ListItem Text="Item 0" RunAt="server" />
<asp:ListItem Text="Item 1" RunAt="server" />
<asp:ListItem Text="Item 2" RunAt="server" />
<asp:ListItem Text="Item 3" RunAt="server" />
<asp:ListItem Text="Item 4" RunAt="server" />
<asp:ListItem Text="Item 5" RunAt="server" />
<asp:ListItem Text="Item 6" RunAt="server" />
<asp:ListItem Text="Item 7" RunAt="server" />
<asp:ListItem Text="Item 8" RunAt="server" />
<asp:ListItem Text="Item 9" RunAt="server" />
<asp:ListItem Text="Item 10" RunAt="server" />
<asp:ListItem Text="Item 11" RunAt="server" />
</asp:CheckBoxList>
ChangingRepeatDirection to Vertical modifies the array so that the first column contains check
boxes 03, the second column contains check boxes 47, and the third column contains
check boxes 811:
<asp:CheckBoxList ID="MyCheckBoxList" RepeatColumns="3"
RepeatDirection="Vertical" RunAt="server">
</asp:RadioButtonList>
create a column of radio buttons and check the first one. A server-side script can use
RadioButtonList.SelectedIndex to determine which button the user selected:
int index = MyRadioButtonList.SelectedIndex;
Because initializing list controls at run time using the results of database queries or data extracted
from XML files is so common, ASP.NETs list controls support a feature called data
binding. Rather than initialize the ListBox control by calling ListItemCollection.Add repeatedly,
Converter.aspx could have done this:
DataSet ds = new DataSet ();
ds.ReadXml (Server.MapPath ("Rates.xml"));
Currencies.DataSource = ds;
Currencies.DataTextField = "Currency";
Currencies.DataBind ();
This method is simpler and more intuitive. It also makes the code more generic by eliminating
direct interactions with the DataSet.
How does data binding work? All list controls inherit from ListControl properties named
DataSource,DataTextField, and DataValueField.DataSource identifies a data source. It can be
initialized with a reference to any object that implements the FCLs IEnumerable or
IListSource interface. IEnumerable is an enumeration interface that allows a control to interact
with a data source using a well-defined protocol. IListSource permits objects that dont
implementIEnumerable themselves but that have subobjects that implement IList (which derives
fromIEnumerable) to expose their subobjects IList interfaces. Because DataSet implements
IListSource, a list control can enumerate the rows in the DataTable that a DataSet contains.
DataTextField connects the Text property of the list controls items to a field in the data
source.DataValueField specifies which field, if any, provides the items Value properties.
List controls also inherit a method named DataBind from the ListControl base class. DataBind
commands the control to initialize itself from the data source.
Literally dozens of FCL classes implement IEnumerable, meaning a list control can bind to a
wide variety of data sources. The following example initializes a ListBox named
MyListBox by binding to an array of strings. The example works because an array is an
instance of System.Array, and System.Array implements IList:
string[] names = { "John", "Paul", "George", "Ringo" };
MyListBox.DataSource = names;
MyListBox.DataBind ();
Theres no need to initialize DataTextField when binding to an array because an array holds a
single column of data. That column is automatically bound to the list items Text property.
You can write custom data types that support binding to list controls by implementing
IEnumerable in those types. The following example defines a class named Beatles that serves up
the names of the Fab Four. It implements IEnumerables one and only method,
GetEnumerator, by returning an IEnumerator interface implemented by a nested class named
Enumerator:
class Beatles : IEnumerable
{
protected Enumerator enumerator = new Enumerator ();
public
{
public
{
}
public
{
}
object Current
get
{
if (index == -1)
index = 0; // Just in case
return names[index];
}
bool MoveNext ()
if (index < (names.Length - 1)) {
index++;
return true;
}
return false;
Two simple statements initialize a ListBox control with the names encapsulated in Beatles:
Figure 6-1 contains a modified version of Converter.aspx that populates its list box by binding to
aDataSet containing the currency and exchange values read from Rates.xml. It also stores
exchange rates in the list box items Value properties, eliminating the need to access the
XML file again when OnConvert is called. Changes are highlighted in bold.
Converter2.aspx
<%@ Import Namespace=System.Data %>
<html>
<body>
<h1>Currency Converter</h1>
<hr>
<form runat="server">
Target Currency<br>
<asp:ListBox ID="Currencies" Width="256" RunAt="server" /><br>
<br>
Amount in U.S. Dollars<br>
<asp:TextBox ID="USD" Width="256" RunAt="server" /><br>
<br>
<asp:Button Text="Convert" ID="ConvertButton" Width="256"
RunAt="server" /><br>
<br>
<asp:Label ID="Output" RunAt="server" />
</form>
</body>
</html>
<script language="C#" runat="server">
void Page_Init (Object sender, EventArgs e)
{
// Wire the Convert button to OnConvert
ConvertButton.Click += new EventHandler (OnConvert);
}
void
{
}
void
{
//
if
}
}
</script>
}
catch (FormatException) {
Output.Text = "Error";
}
Figure 6-1
A currency converter that takes advantage of data binding.
Data-Bound Controls
Speaking of data binding: the WebControls namespace includes three controls whose primary mission in life
results as HTML. The controls are Repeater,DataList, and DataGrid.Repeater controls use UI templatessn
of the controls outputto render items obtained from a data source. DataList controls also use UI templ
formatting, item selection, and item editing. DataGrid controls display tabular data in highly customizable HT
editing, and other features. Repeater,DataList, and DataGrid are arguably the three most powerful members of
real-world developers consider them to be an indispensable part of their toolbox.
Repeater Controls
Repeater controls provide a flexible and easy-to-use mechanism for displaying repetitive lists of items. A repe
Repeater what to display and how to display it. Heres a simple example that uses a Repeater to display a
<html>
<body>
<form runat="server">
<asp:Repeater ID="MyRepeater" RunAt="server">
<ItemTemplate>
<%# Container.DataItem %><br>
</ItemTemplate>
</asp:Repeater>
</form>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
if (!IsPostBack) {
string[] beatles = { "John", "Paul", "George", "Ringo" };
MyRepeater.DataSource = beatles;
MyRepeater.DataBind ();
}
}
</script>
ItemTemplate is a Repeater property that defines the appearance of individual items. Content bracketed by <It
constitutes an item template thats invoked repeatedly to render all the items in the data source. Statement
binding expressions. Inside a data-binding expression, Container.DataItem represents the item that the control
current row in a DataTable or the current string in a string array.
An item template forms the core of a Repeater control, but Repeatercontrols support other template types as w
items differently by using alternating item templates. The following example displays alternating lines in diffe
<asp:Repeater ID="MyRepeater" RunAt="server">
<ItemTemplate>
<%# Container.DataItem %><br>
</ItemTemplate>
<AlternatingItemTemplate>
<span style="background-color:gainsboro;width:128;">
<%# Container.DataItem %>
</span><br>
</AlternatingItemTemplate>
</asp:Repeater>
Header templates and footer templates enable a Repeater to render HTML elements that require start and end
In practice, Repeater controls rarely bind to simple arrays. Typically, they bind to more complex data sources
The following example uses a Repeater to display the books listed in the Pubs database that comes with Micro
consists of a book title obtained from the title field of the current record and a price obtained from the
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<body>
<form runat="server">
<asp:Repeater ID="MyRepeater" RunAt="server">
<ItemTemplate>
<%# DataBinder.Eval (Container.DataItem, "title") +
" (" +
DataBinder.Eval (Container.DataItem, "price", "{0:c}") +
")" %><br>
</ItemTemplate>
</asp:Repeater>
</form>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
if (!IsPostBack) {
SqlConnection connection = new SqlConnection
("server=localhost;database=pubs;uid=sa;pwd=");
try {
connection.Open ();
SqlCommand command = new SqlCommand
("select * from titles where price != 0",
connection);
SqlDataReader reader = command.ExecuteReader ();
MyRepeater.DataSource = reader;
MyRepeater.DataBind ();
}
finally {
connection.Close ();
}
}
}
</script>
Note the use of DataBinder.Eval, which is common in data-binding expressions. DataBinder is a class defined
static method that uses reflection to evaluate a data-binding expression. The second parameter passed to Data
record; the optional third parameter is a formatting string specifying how that field is converted into a string. D
binding syntax. Without it, the Repeater control in the previous example would have to have been defined this
<asp:Repeater ID="MyRepeater" RunAt="server">
<ItemTemplate>
<%# ((System.Data.Common.DbDataRecord)
Container.DataItem)["title"] + " (" +
String.Format ("{0:c}",
((System.Data.Common.DbDataRecord)
Container.DataItem)["price"]) + ")" %><br>
</ItemTemplate>
</asp:Repeater>
Besides simplifying data-binding syntax, DataBinder.Eval provides an added level of indirection between dat
WithoutDataBinder.Eval, changing the data source from a DataReader to a DataSet would also require chang
DataBinder.Eval, no such change is necessary.
TheRepeater class defines three events: ItemCreated,ItemDataBound, and ItemCommand.ItemCreated and Ite
is created and each time an item binds to a data source, respectively. They let the developer further customize
events fire when a button declared within a Repeater control is clicked. The CommandSource property of the
event handler identifies the button that prompted the event. The CommandName and CommandArgument pro
names assigned to the button control. The following example wraps the output from the Repeater control in a
button to each row (Figure 6-3). Clicking a button displays the selected title at the bottom of the page:
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<body>
<form runat="server">
<asp:Repeater ID="MyRepeater" OnItemCommand="OnItemCommand"
RunAt="server">
<HeaderTemplate>
<table border="1">
<tr>
<td align="center">Title</td>
<td align="center">Price</td>
<td align="center">Action</td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td>
<%# DataBinder.Eval (Container.DataItem, "title") %>
</td>
<td align="center">
<%# DataBinder.Eval (Container.DataItem, "price",
"{0:c}") %>
</td>
<td align="center">
<asp:Button Text="Add to Cart" RunAt="server"
CommandArgument='<%# DataBinder.Eval
(Container.DataItem, "title") %>' />
</td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
<asp:Label ID="Output" RunAt="server" />
</form>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
if (!IsPostBack) {
SqlConnection connection = new SqlConnection
("server=localhost;database=pubs;uid=sa;pwd=");
try {
connection.Open ();
SqlCommand command;
command = new SqlCommand
("select * from titles where price != 0",
connection);
SqlDataReader reader = command.ExecuteReader ();
MyRepeater.DataSource = reader;
MyRepeater.DataBind ();
}
finally {
connection.Close ();
}
}
To simplify the process of figuring out which item in the control corresponds to the button that was clicked, th
CommandArgument property of each Button control with the book title stored in the current records
expression in the <asp:Button> tag.
For a first-hand look at a Repeater control in action, check out the Web form in Figure 6-4. Named MyComic
display the contents of a SQL Server database named MyComics that stores information about a collection of
control includes a thumbnail-size cover scan, a title and issue number, and other relevant information about th
wondered, the CGC column specifies whether the numerical grade in the column to the left is an offic
CGC stands for Comics Guaranty Corporation, one of the worlds most respected authorities on comic bo
full-size scan of the cover.
Before you can run MyComicsRepeater.aspx, you must do the following:
1. Create the MyComics database. The CD that comes with this book contains a SQL script file named My
database. To execute the script, open a command prompt window and type
1.
osql -U sa -P -i mycomics.sql
in the directory where MyComics.sql is stored. This assumes, of course, that Microsoft SQL Server is in
2. Either copy the MyComics folder that was created when you installed the CD to \Inetpub\wwwroot, or u
MyComics a virtual directory.
If you copy MyComics to wwwroot, you can open the Web form by typing
http://localhost/mycomicsrepeater.aspx
into your browsers address bar. If you make MyComics a virtual directory and name it MyComics,
http://localhost/mycomics/mycomicsrepeater.aspx
MyComicsRepeater.aspx is listed in Figure 6-5. At its heart is a Repeater control that binds to the records retu
Each item rendered by the Repeater includes a table for visual formatting purposes. Most of the data-binding
This code embeds an <img> tag in each item output by the Repeater control. The Src attribute references one
subdirectory. Furthermore, the <img> tag is enclosed in an <a> tag whose Href attribute references one of the
Images\Large subdirectory. Thats why clicking a thumbnail displays a close-up image of the comic book
cover scans come from the databases SmallCover and LargeCover fields and are append
MyComicsRepeater.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<body>
<h1>My Comics (Repeater)</h1>
<hr>
<form runat="server">
<table width="100%">
<tr>
<td width="104" />
<td>
<table cellpadding="4" width="100%">
<tr height="48" bgcolor="yellow">
<td width="40%" align="center">
Title
</td>
<td width="15%" align="center">
Number
</td>
<td width="15%" align="center">
Year
</td>
<td width="15%" align="center">
Rating
</td>
<td width="15%" align="center">
CGC Rated?
</td>
</tr>
</table>
</td>
</tr>
</table>
<asp:Repeater ID="MyRepeater" RunAt="server">
<ItemTemplate>
<table width="100%">
<tr>
<td width="104">
<a href='Images/Large/<%# DataBinder.Eval
(Container.DataItem, "LargeCover") %>'>
<img src='Images/Small/<%# DataBinder.Eval
(Container.DataItem, "SmallCover") %>'
</a>
</td>
<td>
try {
connection.Open ();
SqlCommand command;
command = new SqlCommand
("select * from books order by title, number",
connection);
SqlDataReader reader = command.ExecuteReader ();
MyRepeater.DataSource = reader;
MyRepeater.DataBind ();
}
finally {
connection.Close ();
}
}
</script>
Figure 6-5
MyComicsRepeater source code.
DataList Controls
DataList controls are similar to Repeater controls, but they include features that Repeaters dont. Specific
formatting, item selection, and item editing. Multicolumn layouts are controlled with the RepeatColumns and
controlled with the SelectedIndex property, which holds the 0-based index of the item thats currently sele
SelectedItemTemplate properties, which govern the appearance of items that are in the selected state. To enab
the controls EditItemStyle and EditItemTemplate properties to define the appearance of the item that
property specifies which item is currently being edited.
Developers accustomed to working with Windows controls are often surprised to learn that the selection in a
doesnt automatically highlight an item when the item is clicked. Rather, its up to a server-side event
controlsSelectedIndex property equal to the index of the item. Each item rendered by a DataList that supp
activates a server-side event handler. In the following example, the DataLists item template includes a Li
clicked, the DataList fires an ItemCommand event, and the OnItemCommand handler selects the item that wa
also takes advantage of multicolumn formatting by arranging its items in two columns:
<html>
<body>
<form runat="server">
<asp:DataList ID="MyDataList" RunAt="server"
RepeatColumns="2" RepeatDirection="Horizontal"
OnItemCommand="OnItemCommand">
<ItemTemplate>
<asp:LinkButton Text="<%# Container.DataItem %>"
RunAt="server" /><br>
</ItemTemplate>
<SelectedItemStyle BackColor="gainsboro" />
</asp:DataList>
</form>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
if (!IsPostBack) {
string[] beatles = { "John", "Paul", "George", "Ringo" };
MyDataList.DataSource = beatles;
MyDataList.DataBind ();
}
}
void OnItemCommand (Object sender, DataListCommandEventArgs e)
{
MyDataList.SelectedIndex = e.Item.ItemIndex;
}
</script>
The <SelectedItemStyle> tag in this example highlights the currently selected item by setting its background c
of the items appearance, however, are defined by the item template. If you want to, you can use a <Select
selected items independent of the item template.
Incidentally, the statement
initializes the subproperty named BackColor of the property named SelectedItemStyle. If youd prefer, yo
declares a control by separating property and subproperty names with hyphens, as in
<asp:DataList ID="MyDataList" RunAt="server"
RepeatColumns="2" RepeatDirection="Horizontal"
OnItemCommand="OnItemCommand"
SelectedItemStyle-BackColor="gainsboro">
This syntax isnt limited to DataList controls. Its applicable to all controls that implement properties
DataGrid, and Calendar controls.
The MyComicsDataList Page
The Web form in Figure 6-6 uses a DataList rather than a Repeater to put a face on the MyComics database. T
MyComicsDataList.aspx, appears in Figure 6-7. The DataLists item template renders a one-row, two-col
thumbnail cover image and whose right cell contains the comic book title, issue number, and other informatio
LinkButton. Clicking the LinkButton invokes OnItemCommand, which selects the item that was clicked and d
the top of the page. That text comes from the databases Comment field, which is passed to the ev
CommandArgument property. As with the Repeater control version of this page, clicking one of the cover thu
cover, thanks to an <a> tag surrounding the <img> tag emitted by the DataList control.
<ItemTemplate>
<table width="100%" cellpadding="4">
<tr>
<td width="100">
<a href='Images/Large/<%# DataBinder.Eval
(Container.DataItem, "LargeCover") %>'>
<img src='Images/Small/<%# DataBinder.Eval
(Container.DataItem, "SmallCover") %>'>
</a>
</td>
<td valign="top">
<asp:LinkButton CommandName="Select" RunAt="server"
CommandArgument='<%# DataBinder.Eval
(Container.DataItem, "Comment") %>'
Text='<%# DataBinder.Eval (Container.DataItem,
"Title") + " " +
DataBinder.Eval (Container.DataItem,
"Number") %>' /><br>
<%# DataBinder.Eval (Container.DataItem,
<%# DataBinder.Eval (Container.DataItem, "Rating",
"{0:f1}") %><br>
</td>
</tr>
</table>
</ItemTemplate>
<SelectedItemStyle BackColor="gainsboro" />
</asp:DataList>
</form>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
if (!IsPostBack) {
SqlConnection connection = new SqlConnection
("server=localhost;database=mycomics;uid=sa;pwd=");
void
{
}
try {
connection.Open ();
SqlCommand command;
command = new SqlCommand
("select * from Books order by title, number",
connection);
SqlDataReader reader = command.ExecuteReader ();
MyDataList.DataSource = reader;
MyDataList.DataBind ();
}
finally {
connection.Close ();
}
if (e.CommandName == "Select") {
MyDataList.SelectedIndex = e.Item.ItemIndex;
Output.Text = e.CommandArgument.ToString ();
}
</script>
Figure 6-7
MyComicsDataList source code.
DataGrid Controls
DataGrid controls are the most complex of the data-bound Web controls for the simple reason that they offer
DataGrids purpose is to display tabular data. A single DataGrid control can replace reams of old ASP cod
outputs a table using repeated calls to Response.Write. Heres an example that uses a DataGrid to display
databases Titles table. The output is shown in Figure 6-8.
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<body>
<form runat="server">
<asp:DataGrid ID="MyDataGrid" RunAt="server" />
</form>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
if (!IsPostBack) {
SqlConnection connection = new SqlConnection
("server=localhost;database=pubs;uid=sa;pwd=");
}
</script>
try {
connection.Open ();
SqlCommand command = new SqlCommand
("select * from titles where price != 0",
connection);
SqlDataReader reader = command.ExecuteReader ();
MyDataGrid.DataSource = reader;
MyDataGrid.DataBind ();
}
finally {
connection.Close ();
}
Bare-bonesDataGrid control.
Thats a lot of output for a relatively small amount of code, but aesthetically, the output leaves something
support a wide range of formatting options. Some of those options are controlled by using properties such as B
control tags. Others are exercised by using the Columns property to specify what columns should appear in th
rendered. A DataGrid supports the following column types:
Column Type
Description
BoundColumn
Creates a column whose content comes from a field in the data source.
ButtonColumn
Creates a column of buttons (push buttons or link buttons).
EditColumn
Creates a column of buttons enabling DataGrid items to be edited and chang
HyperLinkColumnCreates a column of hyperlinks. Hyperlink text can be static or drawn from
TemplateColumn Creates a column of items whose appearance is defined by a UI template.
By default, a DataGrid control contains one BoundColumn for every field in the data source. You can change
AutoGenerateColumns property to false and manually creating columns. The following example uses a DataG
databases Titles tablethe same table depicted in Figure 6-8. But this time, the results are more
6-9.
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<body>
<form runat="server">
<asp:DataGrid ID="MyDataGrid"
AutoGenerateColumns="false" CellPadding="2"
BorderWidth="1" BorderColor="lightgray"
Font-Name="Verdana" Font-Size="8pt"
GridLines="vertical" Width="90%"
OnItemCommand="OnItemCommand" RunAt="server">
<Columns>
<asp:BoundColumn HeaderText="Item ID"
DataField="title_id" />
<asp:BoundColumn HeaderText="Title"
DataField="title" />
<asp:BoundColumn HeaderText="Price"
DataField="price" DataFormatString="{0:c}"
HeaderStyle-HorizontalAlign="center"
ItemStyle-HorizontalAlign="right" />
<asp:ButtonColumn HeaderText="Action" Text="Add to Cart"
HeaderStyle-HorizontalAlign="center"
ItemStyle-HorizontalAlign="center"
CommandName="AddToCart" />
</Columns>
<HeaderStyle BackColor="teal" ForeColor="white"
Font-Bold="true" />
<ItemStyle BackColor="white" ForeColor="darkblue" />
<AlternatingItemStyle BackColor="beige"
ForeColor="darkblue" />
</asp:DataGrid>
<br>
<asp:Label ID="Output" RunAt="server" />
</form>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
if (!IsPostBack) {
SqlConnection connection = new SqlConnection
("server=localhost;database=pubs;uid=sa;pwd=");
try {
connection.Open ();
SqlCommand command = new SqlCommand
("select * from titles where price != 0",
connection);
SqlDataReader reader = command.ExecuteReader ();
MyDataGrid.DataSource = reader;
MyDataGrid.DataBind ();
}
finally {
connection.Close ();
}
ThisDataGrid contains three BoundColumns and one ButtonColumn. The BoundColumns bind to the title
fields of the data source, as indicated by the DataField attributes. The ButtonColumn renders a column of Link
Clicking a LinkButton activates OnItemCommand, which uses the Label control at the bottom of the page to d
to Cart button was clicked. Just like Repeater controls and DataList controls, a DataGrid control that rend
whenever one of those buttons is clicked.
When an ItemCommand event fires, the handler receives a DataGridCommandEventArgs whose Item propert
clicked. As the example in the previous paragraph demonstrates, the handler can read the contents of the row
collection. Each TableCellsText property exposes the text contained in one of the rows cells.
CustomizedDataGrid control.
SortableDataGrid Controls
By default, items appear in a DataGrid in the same order in which theyre enumerated from the data sourc
by binding to a DataView instead of a DataReader or DataSet.DataView is an ADO.NET class used to create
source. A DataView can be sorted by assigning a sort expression to its Sort property. For example, the sort ex
DataView in ascending order based on the contents of its Title column. The sort expression Price
descending order based on the contents of its Price column. The following code fragment demonstrat
method in the previous example to sort the DataGrid output by book title:
void
{
In this example, the DataView wraps a DataTable. The DataTable comes from a DataSet initialized with a Da
property to Title ASC sorts items enumerated from the DataView by title.
DataGrids also support interactive sorting. Heres a recipe for creating a sortable DataGridone whose c
header:
1. Set the DataGridsAllowSorting property to true.
2. Include a SortExpression attribute in each BoundColumn that supports sorting. The sort expression
on the Title field.
3. Write a handler for the SortCommand event that a DataGrid fires when the header atop a sortable colum
DataGrid to a DataView. Initialize the DataViewsSort property with the sort expression in the SortE
DataGridSortCommandEventArgs passed to the event handler.
4. Use an OnSortCommand attribute in the <asp:DataGrid> tag to connect the DataGrid to the SortComma
To demonstrate, the following example shows how to modify the DataGrid control shown in the previous sec
price. Changes are highlighted in bold:
<asp:DataGrid ID="MyDataGrid"
AutoGenerateColumns="false" CellPadding="2"
BorderWidth="1" BorderColor="lightgray"
Font-Name="Verdana" Font-Size="8pt"
GridLines="vertical" Width="90%"
OnItemCommand="OnItemCommand" RunAt="server"
AllowSorting="true" OnSortCommand="OnSort">
<Columns>
<asp:BoundColumn HeaderText="Item ID"
DataField="title_id" />
<asp:BoundColumn HeaderText="Title"
DataField="title"SortExpression="title ASC"/>
<asp:BoundColumn HeaderText="Price"
DataField="price" DataFormatString="{0:c}"
HeaderStyle-HorizontalAlign="center"
ItemStyle-HorizontalAlign="right"
SortExpression="price DESC"/>
.
.
.
</asp:DataGrid>
.
.
.
// In the <script> block
void OnSort (Object sender, DataGridSortCommandEventArgs e)
{
SqlDataAdapter adapter = new SqlDataAdapter
("select * from titles where price != 0",
"server=localhost;database=pubs;uid=sa;pwd=");
DataSet ds = new DataSet ();
adapter.Fill (ds);
DataView view = new DataView (ds.Tables[0]);
view.Sort = e.SortExpression.ToString (); // e.g., "Title ASC"
MyDataGrid.DataSource = view;
MyDataGrid.DataBind ();
}
The value passed in the SortExpression property of the DataGridSortCommandEventArgs is the same SortEx
whose header was clicked. Figure 6-10 shows how the DataGrid appears after its sorted by title. Note the
the Title and Price columns indicating that these columns support sorting.
SortableDataGrid control.
PageableDataGrid Controls
DataGrid controls also support paging. Paging enables you to paginate the output when you bind to a data sou
following example displays records from the Microsoft SQL Server Northwind databases Products
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<body>
<form runat="server">
<asp:DataGrid ID="MyDataGrid"
AutoGenerateColumns="false" CellPadding="2"
BorderWidth="1" BorderColor="lightgray"
Font-Name="Verdana" Font-Size="8pt"
GridLines="vertical" Width="90%" RunAt="server"
AllowPaging="true" PageSize="10"
OnPageIndexChanged="OnNewPage">
<Columns>
<asp:BoundColumn HeaderText="Product ID"
DataField="ProductID"
HeaderStyle-HorizontalAlign="center"
ItemStyle-HorizontalAlign="center" />
<asp:BoundColumn HeaderText="Product Name"
DataField="ProductName"
HeaderStyle-HorizontalAlign="center" />
<asp:BoundColumn HeaderText="Unit Price"
DataField="UnitPrice"
HeaderStyle-HorizontalAlign="center"
ItemStyle-HorizontalAlign="right"
DataFormatString="{0:c}" />
<asp:BoundColumn HeaderText="Unit"
DataField="QuantityPerUnit"
HeaderStyle-HorizontalAlign="center" />
</Columns>
<HeaderStyle BackColor="teal" ForeColor="white"
Font-Bold="true" />
<ItemStyle BackColor="white" ForeColor="darkblue" />
<AlternatingItemStyle BackColor="beige"
ForeColor="darkblue" />
</asp:DataGrid>
</form>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
if (!IsPostBack) {
SqlDataAdapter adapter =
new SqlDataAdapter ("select * from products",
"server=localhost;database=northwind;uid=sa;pwd=");
DataSet ds = new DataSet ();
adapter.Fill (ds);
MyDataGrid.DataSource = ds;
MyDataGrid.DataBind ();
}
}
void
{
}
</script>
MyDataGrid.CurrentPageIndex = e.NewPageIndex;
SqlDataAdapter adapter =
new SqlDataAdapter ("select * from products",
"server=localhost;database=northwind;uid=sa;pwd=");
DataSet ds = new DataSet ();
adapter.Fill (ds);
MyDataGrid.DataSource = ds;
MyDataGrid.DataBind ();
The output from this code is shown in Figure 6-11. Clicking one of the arrows at the bottom of the page displa
AllowPaging attribute in the <asp:DataGrid> tag enables paging. The PageSize attribute sizes each page to di
OnPageIndexChanged attribute identifies the event handlerOnNewPagethats called when an arrow
previous page by reinitializing the DataSet and setting the DataGridsCurrentPageIndex property equal to
DataGridPageChangedEventArgs. The DataGrid does the hard part by extracting the data for the current page
PageableDataGrid control.
You can further customize a pageable DataGrid by using its PagerStyle property. Adding the following attribu
arrows displayed at the bottom of the control to the strings Previous Page and Next Page:
PagerStyle-PrevPageText="Previous Page"
PagerStyle-NextPageText="Next Page"
The attribute shown in the next statement replaces the arrows with page numbers, providing random access to
PagerStyle-Mode="NumericPages"
For a list of other changes you can effect with PagerStyle, consult the list of DataGridPagerStyle members in
One drawback to paging a DataGrid using the technique shown in the previous example is that its ineffic
Products table each time its called, even though it needs only 10 records. Thats why DataGr
paging called custom paging. Setting a DataGridsAllowCustomPaging property to true enables custom pa
handler to fetch just the records shown on the current page. Custom paging can deliver dramatic performance
hundreds or thousands of records rather than just a few.
The MyComicsDataGrid Page
The Web page shown in Figure 6-12 rounds out this chapters treatment of data-bound Web controls by u
MyComics database used in earlier examples. In addition to showing how to use BoundColumns to expose se
MyComicsDataGrid.aspx, shown in Figure 6-13, demonstrates how to use TemplateColumns to create user-d
<asp:TemplateColumn HeaderText="CGC"
HeaderStyle-HorizontalAlign="center">
<ItemTemplate>
<center>
<%# ((bool) DataBinder.Eval (Container.DataItem,
"CGC")) == false ? "N" : "Y" %>
</center>
</ItemTemplate>
</asp:TemplateColumn>
create a column that programmatically binds to the databases CGC field and displays either
if it contains a 0. DataGrid controls dont support UI templates per se, but TemplateColumns do. One com
columns containing TextBox controls that the user can type information into.
TheDataGrid in this example also uses a ButtonColumn to put View Cover buttons in the grids l
ItemCommand event that activates the pages OnItemCommand method, which displays a close-up of the
Response.Redirect with the URL of the corresponding image in the Images/Large directory. Response.Redirec
of ASP.NET; it transfers control to another Web page. Youll learn more about it later in this chapter.