C# - Net
C# - Net
AND .NET
PRENTICE HALL
CORE SERIES
Stephen C. Perry
Part I
FUNDAMENTALS OF C# PROGRAMMING AND
INTRODUCTION TO .NET 2
C ha p t er 1
INTRODUCTION TO .NET AND C# 4
1.1 Overview of the .NET Framework 6
Microsoft .NET and the CLI Standards 7
1.2 Common Language Runtime 9
Compiling .NET Code 10
Common Type System 11
Assemblies 13
1.3 Framework Class Library 18
v
vi Contents
Chapter 2
C# LANGUAGE FUNDAMENTALS 38
2.1 The Layout of a C# Program 40
General C# Programming Notes 42
2.2 Primitives 45
decimal 47
bool 47
char 48
byte, sbyte 48
short, int, long 48
single, double 49
Using Parse and TryParse to Convert a Numeric String 49
2.3 Operators: Arithmetic, Logical, and Conditional 50
Arithmetic Operators 50
Conditional and Relational Operators 51
Control Flow Statements 52
if-else 53
switch 54
2.4 Loops 55
while loop 55
Contents vii
do loop 56
for loop 56
foreach loop 57
Transferring Control Within a Loop 58
2.5 C# Preprocessing Directives 59
Conditional Compilation 60
Diagnostic Directives 60
Code Regions 61
2.6 Strings 61
String Literals 61
String Manipulation 63
2.7 Enumerated Types 66
Working with Enumerations 66
System.Enum Methods 68
Enums and Bit Flags 69
2.8 Arrays 69
Declaring and Creating an Array 70
Using System.Array Methods and Properties 71
2.9 Reference and Value Types 73
System.Object and System.ValueType 73
Memory Allocation for Reference and Value Types 74
Boxing 75
Summary of Value and Reference Type Differences 77
2.10 Summary 78
2.11 Test Your Understanding 78
Chapter 3
CLASS DESIGN IN C# 80
3.1 Introduction to a C# Class 82
3.2 Defining a Class 82
Attributes 83
Access Modifiers 85
viii Contents
Chapter 4
WORKING WITH OBJECTS IN C# 144
4.1 Object Creation 145
Example: Creating Objects with Multiple Factories 148
4.2 Exception Handling 149
System.Exception Class 150
Writing Code to Handle Exceptions 151
Example: Handling Common SystemException Exceptions 153
How to Create a Custom Exception Class 155
Unhandled Exceptions 157
Exception Handling Guidelines 159
4.3 Implementing System.Object Methods in a Custom Class 160
ToString() to Describe an Object 161
Equals() to Compare Objects 163
Cloning to Create a Copy of an Object 165
4.4 Working with .NET Collection Classes and Interfaces 167
Collection Interfaces 168
System.Collections Namespace 177
Stack and Queue 177
ArrayList 179
Hashtable 181
System.Collections.Generic Namespace 184
4.5 Object Serialization 187
Binary Serialization 188
4.6 Object Life Cycle Management 192
.NET Garbage Collection 192
4.7 Summary 198
4.8 Test Your Understanding 198
x Contents
Par t II
CREATING APPLICATIONS USING THE
.NET FRAMEWORK CLASS LIBRARY 200
Chapter 5
C# TEXT MANIPULATION AND FILE I/O 202
5.1 Characters and Unicode 204
Unicode 204
Working with Characters 205
5.2 The String Class 209
Creating Strings 209
Overview of String Operations 211
5.3 Comparing Strings 212
Using String.Compare 213
Using String.CompareOrdinal 215
5.4 Searching, Modifying, and Encoding a String’s Content 216
Searching the Contents of a String 216
Searching a String That Contains Surrogates 217
String Transformations 217
String Encoding 219
5.5 StringBuilder 220
StringBuilder Class Overview 221
StringBuilder Versus String Concatenation 222
5.6 Formatting Numeric and DateTime Values 223
Constructing a Format Item 224
Formatting Numeric Values 225
Formatting Dates and Time 227
5.7 Regular Expressions 232
The Regex Class 232
Creating Regular Expressions 237
A Pattern Matching Example 239
Contents xi
Chapter 6
BUILDING WINDOWS FORMS APPLICATIONS 266
6.1 Programming a Windows Form 268
Building a Windows Forms Application by Hand 268
6.2 Windows.Forms Control Classes 271
The Control Class 272
Working with Controls 274
Control Events 279
6.3 The Form Class 285
Setting a Form’s Appearance 286
Setting Form Location and Size 290
Displaying Forms 292
The Life Cycle of a Modeless Form 292
xii Contents
Chapter 7
WINDOWS FORMS CONTROLS 318
7.1 A Survey of .NET Windows Forms Controls 319
7.2 Button Classes, Group Box, Panel, and Label 323
The Button Class 323
The CheckBox Class 324
The RadioButton Class 325
The GroupBox Class 327
The Panel Class 328
The Label Class 330
7.3 PictureBox and TextBox Controls 331
The PictureBox Class 331
The TextBox Class 333
7.4 ListBox, CheckedListBox, and ComboBox Classes 335
The ListBox Class 335
Contents xiii
Chapter 8
.NET GRAPHICS USING GDI+ 378
8.1 GDI+ Overview 380
The Graphics Class 380
The Paint Event 384
8.2 Using the Graphics Object 388
Basic 2-D Graphics 388
Pens 393
Brushes 395
Colors 400
A Sample Project: Building a Color Viewer 402
xiv Contents
Chapter 9
FONTS, TEXT, AND PRINTING 426
9.1 Fonts 428
Font Families 428
The Font Class 430
9.2 Drawing Text Strings 433
Drawing Multi-Line Text 434
Formatting Strings with the StringFormat Class 435
Using Tab Stops 436
String Trimming, Alignment, and Wrapping 438
9.3 Printing 439
Overview 439
PrintDocument Class 441
Printer Settings 442
Page Settings 445
PrintDocument Events 446
PrintPage Event 448
Previewing a Printed Report 449
A Report Example 450
Creating a Custom PrintDocument Class 454
9.4 Summary 457
9.5 Test Your Understanding 458
Contents xv
Chapter 10
WORKING WITH XML IN .NET 460
10.1 Working with XML 462
Using XML Serialization to Create XML Data 462
XML Schema Definition (XSD) 466
Using an XML Style Sheet 468
10.2 Techniques for Reading XML Data 472
XmlReader Class 472
XmlNodeReader Class 477
The XmlReaderSettings Class 479
Using an XML Schema to Validate XML Data 480
Options for Reading XML Data 481
10.3 Techniques for Writing XML Data 482
10.4 Using XPath to Search XML 485
Constructing XPath Queries 486
XmlDocument and XPath 489
XPathDocument and XPath 490
XmlDataDocument and XPath 491
10.5 Summary 493
10.6 Test Your Understanding 494
Chapter 11
ADO.NET 496
11.1 Overview of the ADO.NET Architecture 498
OLE DB Data Provider in .NET 498
.NET Data Provider 499
11.2 Data Access Models: Connected and Disconnected 502
Connected Model 502
Disconnected Model 504
11.3 ADO.NET Connected Model 506
Connection Classes 506
xvi Contents
ChapteR 12
DATA BINDING WITH WINDOWS FORMS CONTROLS 544
12.1 Overview of Data Binding 546
Simple Data Binding 546
Complex Data Binding with List Controls 549
One-Way and Two-Way Data Binding 550
Using Binding Managers 552
12.2 Using Simple and Complex Data Binding in an Application 555
Binding to a DataTable 555
Binding Controls to an ArrayList 558
Adding an Item to the Data Source 560
Identifying Updates 561
Update Original Database with Changes 562
12.3 The DataGridView Class 563
Properties 564
Contents xvii
Events 571
Setting Up Master-Detail DataGridViews 576
Virtual Mode 579
12.4 Summary 585
12.5 Test Your Understanding 585
Part III
ADVANCED USE OF C# AND THE .NET FRAMEWORK 588
Chapter 13
ASYNCHRONOUS PROGRAMMING
AND MULTITHREADING 590
13.1 What Is a Thread? 592
Multithreading 592
13.2 Asynchronous Programming 595
Asynchronous Delegates 596
Examples of Implementing Asynchronous Calls 599
13.3 Working Directly with Threads 609
Creating and Working with Threads 609
Multithreading in Action 613
Using the Thread Pool 617
Timers 618
13.4 Thread Synchronization 620
The Synchronization Attribute 622
The Monitor Class 623
The Mutex 625
The Semaphore 627
Avoiding Deadlock 628
Summary of Synchronization Techniques 630
13.5 Summary 631
13.6 Test Your Understanding 631
xviii Contents
Chapter 14
CREATING DISTRIBUTED
APPLICATIONS WITH REMOTING 636
14.1 Application Domains 638
Advantages of AppDomains 638
Application Domains and Assemblies 639
Working with the AppDomain Class 640
14.2 Remoting 643
Remoting Architecture 644
Types of Remoting 648
Client-Activated Objects 650
Server-Activated Objects 650
Type Registration 652
Remoting with a Server-Activated Object 654
Remoting with a Client-Activated Object (CAO) 664
Design Considerations in Creating a Distributed Application 670
14.3 Leasing and Sponsorship 671
Leasing 672
Sponsorship 675
14.4 Summary 678
14.5 Test Your Understanding 678
Chapter 15
CODE REFINEMENT, SECURITY, AND DEPLOYMENT 680
15.1 Following .NET Code Design Guidelines 682
Using FxCop 683
15.2 Strongly Named Assemblies 686
Creating a Strongly Named Assembly 687
Delayed Signing 688
Global Assembly Cache (GAC) 689
Versioning 690
Contents xix
Part IV
PROGRAMMING FOR THE INTERNET 730
Chapter 16
ASP.NET WEB FORMS AND CONTROLS 732
16.1 Client-Server Interaction over the Internet 734
Web Application Example: Implementing a BMI Calculator 735
Using ASP.NET to Implement a BMI Calculator 740
Inline Code Model 741
xx Contents
Chapter 17
THE ASP.NET APPLICATION ENVIRONMENT 806
17.1 HTTP Request and Response Classes 808
HttpRequest Object 808
Contents xxi
Chapter 18
XML WEB SERVICES 868
18.1 Introduction to Web Services 870
Discovering and Using a Web Service 871
18.2 Building an XML Web Service 875
Creating a Web Service by Hand 875
Creating a Web Service Using VS.NET 878
xxii Contents
Appendix A
FEATURES SPECIFIC TO .NET 2.0 AND C# 2.0 920
Appendix B
DATAGRIDVIEW EVENTS AND DELEGATES 924
INDEX 952
Chapter
xxiii
This page intentionally left blank
Chapter
Learning a new programming language, or even a new version of one, can be a lot
like traveling to a foreign country. Some things will look familiar. Some things will
look very odd. You can usually get by if you stick to the familiar; but, who wants to
just “get by”? Often times, it’s the odd things in a country, culture, or language where
you can realize many interesting and valuable benefits.
To do it right, however, you’ll need a proper guide or guide book. This is essential.
Just as a good guide book will tell you what to see, when to see it, what to eat, what to
avoid, and a few tips, so will a good programming book tell you what frameworks and
classes to use, how to call their methods, what bad practices to avoid, and a few pro-
ductivity tips and best practices.
This is exactly what Steve has provided you.
If you were to approach C# 2.0 from a 1.0 perspective, or from some other lan-
guage background, you’d be missing out on all its new offerings. For example, I’m a
seasoned developer and set in my ways. If you’re like me, then you probably still
write methods, loops, and other design patterns the same way you have for many
years. You know it’s not producing the most efficient code, but it’s quick. It’s easy to
read and understand; and it works.
Steve has literally written the “Core” C# book here. He begins by introducing you
to the important C# concepts and their interaction with the .NET Framework; but,
then he deviates from the other C# reference books on the market and jumps right
into the application of C#. These include most of the common, daily tasks that you
could be asked to perform, such as working with text, files, databases, XML, Win-
dows forms and controls, printing, ASP.NET Web applications, Web services, and
xxv
xxvi Foreword
remoting. Steve even provides you with asynchronous, multithreaded, security, and
deployment topics as a bonus. You won’t need another book on your shelf.
So, what are you waiting for? I think it’s time to break a few of your familiar habits
and experience some new culture!
Richard Hundhausen
Author, Introducing Microsoft Visual Studio 2005 Team System
Chapter
Thirty-seven years later, programmers still experience the same creative satisfaction
from developing a well-crafted program. It can be 10 lines of recursive code that
pops into one’s head at midnight, or it can be an entire production management sys-
tem whose design requires a year of midnights. Then, as now, good programs still
convey an impression of logic and naturalness—particularly to their users.
But the challenges have evolved. Software is required to be more malleable—it
may be run from a LAN, the Internet, or a cellular phone. Security is also a much
bigger issue, because the code may be accessible all over the world. This, in turn,
raises issues of scalability and how to synchronize code for hundreds of concurrent
users. More users bring more cultures, and the concomitant need to customize pro-
grams to meet the language and culture characteristics of a worldwide client base.
.NET—and the languages written for it—addresses these challenges as well as any
unified development environment. This book is written for developers, software
architects, and students who choose to work with the .NET Framework. All code in
the book is written in C#, although only one chapter is specifically devoted to the
syntactical structure of the C# language.
This book is not an introduction to programming—it assumes you are experienced
in a computer language. This book is not an introduction to object-oriented program-
xxvii
xxviii Preface
Although some will disagree, if you really want to learn C# and .NET, shut down
your IDE, pull out your favorite text editor, and learn how to use the C# compiler
from the command line. After you have mastered the fundamentals, you can switch
to VS.NET and any other IDE for production programming.
Finally, a word about .NET and Microsoft: This book was developed using
Microsoft .NET 1.x and Whidbey betas. It includes topics such as ADO.NET and
ASP.NET that are very much Microsoft proprietary implementations. In fact,
Microsoft has applied to patent these methodologies. However, all of C# and many of
the .NET basic class libraries are based on a standard that enables them to be ported
to other platforms. Now, and increasingly in the future, many of the techniques
described in this book will be applicable to .NET like implementations (such as the
Mono project, http://www.mono-project.com/Main_Page) on non-Windows
platforms.
Chapter
I have received assistance from a great number of people over the 21 months that
went into the research and development of this book. I wish to thank first my wife,
Rebecca, who tirelessly read through pages of unedited manuscripts, and used her
systems programming background to add valuable recommendations. Next, I wish to
thank the reviewers whose recommendations led to better chapter organization,
fewer content and code errors, and a perspective on which topics to emphasize.
Reviewers included Greg Beamer, James Edelen, Doug Holland, Curtiss Howard,
Anand Narayanaswamy, and Gordon Weakliem. Special thanks go to Richard
Hundhausen whose recommendations went well beyond the call of duty; and Cay
Horstmann, who read every preliminary chapter and whose Java allegiance made
him a wonderful and influential “Devil’s Advocate.” I also wish to thank Dr. Alan
Tharp who encouraged the idea of writing a book on .NET and remains my most
respected advisor in the computer profession.
Finally, it has been a pleasure working with the editors and staff at Prentice Hall
PTR. I’m particularly grateful for the efforts of Stephane Nakib, Joan Murray, Ebony
Haight, Jessica D’Amico, Kelli Brooks, and Vanessa Moore. This book would not
exist without the efforts of my original editor Stephane Nakib. The idea for the book
was hers, and her support for the project kept it moving in the early stages. My other
editor, Joan Murray, took over midway through the project and provided the over-
sight, advice, and encouragement to complete the project. Production editor Vanessa
Moore and copy editor Kelli Brooks performed the “dirty work” of turning the final
manuscript—with its myriad inconsistencies and word misuse—into a presentable
book. To them, I am especially grateful. Working with professionals such as these was
of inestimable value on those days when writing was more Sisyphean than satisfying.
xxx
This page intentionally left blank
FUNDAMENTALS OF
C# PROGRAMMING
AND
INTRODUCTION TO
.NET
I
■ Chapter 1
Introduction to .NET and C# 4
■ Chapter 2
C# Language Fundamentals 38
■ Chapter 3
Class Design in C# 80
■ Chapter 4
Working with Objects in C# 144
INTRODUCTION TO
.NET AND C#
The effective use of a language requires a good deal more than learning the syntax
and features of the language. In fact, the greater part of the learning curve for new
technology is now concentrated in the programming environment. It is not enough to
be proficient with the C# language; the successful developer and software architect
must also be cognizant of the underlying class libraries and the tools available to
probe these libraries, debug code, and check the efficiency of underlying code.
The purpose of this chapter is to provide an awareness of the .NET environment
before you proceed to the syntax and semantics of the C# language. The emphasis is
on how the environment, not the language, will affect the way you develop software.
If you are new to .NET, it is necessary to embrace some new ideas. .NET changes
the way you think of dealing with legacy code and version control; it changes the way
program resources are disposed of; it permits code developed in one language to be
used by another; it simplifies code deployment by eliminating a reliance on the sys-
tem registry; and it creates a self-describing metalanguage that can be used to deter-
mine program logic at runtime. You will bump into all of these at some stage of the
software development process, and they will influence how you design and deploy
your applications.
To the programmer’s eye, the .NET platform consists of a runtime environment
coupled with a base class library. The layout of this chapter reflects that viewpoint. It
contains separate sections on the Common Language Runtime (CLR) and the
Framework Class Library (FCL). It then presents the basic tools that a developer
may use to gain insight into the inner workings of the .NET Framework, as well as
manage and distribute applications. As a prelude to Chapter 2, the final section intro-
duces the C# compiler with examples of its use.
5
6 Chapter 1 ■ Introduction to .NET and C#
Data Classes
ADO.NET, XML, SQL
Base Classes
System.IO, System.Drawing, System.Threading
Operating System
1. ECMA International was formerly known as the European Computer Manufacturers Association
and is referred to herein simply as the ECMA.
1.1 Overview of the .NET Framework 7
Runtime Infrastructure
Library
Base Class
Library
Kernel Profile
Compact Profile
The good news for developers—and readers of this book—is that the additional
Microsoft features are being adopted by CLI open source initiatives. Mono2, one of
the leading CLI projects, already includes major features such as ADO.NET, Win-
dows Forms, full XML classes, and a rich set of Collections classes. This is particularly
significant because it means the knowledge and skills obtained working with Microsoft
.NET can be applied to implementations on Linux, BSD, and Solaris platforms. With
that in mind, let’s take an overview of the Microsoft CLI implementation.
Base Class
IL + Metadata Libraries
CPU
2. http://www.mono-project.com/Main_Page
10 Chapter 1 ■ Introduction to .NET and C#
• The most important use is by the JIT compiler, which gathers all the
type information it needs for compiling directly from the metacode. It
also uses this information for code verification to ensure the program
performs correct operations. For example, the JIT ensures that a
method is called correctly by comparing the calling parameters with
those defined in the method’s metadata.
• Metadata is used in the Garbage Collection process (memory
management). The garbage collector (GC) uses metadata to know
when fields within an object refer to other objects so that the GC can
determine what objects can and can’t have their memory reclaimed.
• .NET provides a set of classes that provide the functionality to read
metadata from within a program. This functionality is known
collectively as reflection. It is a powerful feature that permits a
program to query the code at runtime and make decisions based on its
discovery. As we will see later in the book, it is the key to working with
custom attributes, which are a C#-supported construct for adding
custom metadata to a program.
1.2 Common Language Runtime 11
Object
Class Primitives
Interface Structures
Array Enums
Two things stand out in this figure. The most obvious is that types are categorized
as reference or value types. This taxonomy is based on how the types are stored and
accessed in memory: reference types are accessed in a special memory area (called a
heap) via pointers, whereas value types are referenced directly in a program stack.
The other thing to note is that all types, both custom and .NET defined, must inherit
from the predefined System.Object type. This ensures that all types support a
basic set of inherited methods and properties.
12 Chapter 1 ■ Introduction to .NET and C#
Core Note
A compiler that is compliant with the CTS specifications is guaranteed that its
types can be hosted by the Common Language Runtime. This alone does not guaran-
tee that the language can communicate with other languages. There is a more restric-
tive set of specifications, appropriately called the Common Language Specification
(CLS), that provides the ultimate rules for language interoperability. These specifica-
tions define the minimal features that a compiler must include in order to target the
CLR.
Table 1-1 contains some of the CLS rules to give you a flavor of the types of fea-
tures that must be considered when creating CLS-compliant types (a complete list is
included with the .NET SDK documentation).
Feature Rule
Visibility (Scope) The rules apply only to those members of a type that are avail-
able outside the defining assembly.
Characters and casing For two variables to be considered distinct, they must differ by
more than just their case.
Primitive types The following primitive data types are CLS compliant:
Byte, Int16, Int32, Int64, Single, Double, Boolean, Char,
Decimal, IntPtr, and String.
Constructor invocation A constructor must call the base class’s constructor before it can
access any of its instance data.
Array bounds All dimensions of arrays must have a lower bound of zero (0).
Method signature All return and parameter types used in a type or member
signature must be CLS compliant.
These rules are both straightforward and specific. Let’s look at a segment of C#
code to see how they are applied:
1.2 Common Language Runtime 13
Even if you are unfamiliar with C# code, you should still be able to detect where
the code fails to comply with the CLS rules. The second rule in the table dictates that
different names must differ by more than case. Obviously, Metric fails to meet this
rule. This code runs fine in C#, but a program written in Visual Basic.NET—which
ignores case sensitivity—would be unable to distinguish between the upper and low-
ercase references.
Assemblies
All of the managed code that runs in .NET must be contained in an assembly. Logi-
cally, the assembly is referenced as one EXE or DLL file. Physically, it may consist of
a collection of one or more files that contain code or resources such as images or
XML data.
Assembly
Name: FabricLib
Manifest Other files:
Public types:
Type: Private
Version Number: 1.1.3.04
Metadata
Strong Name:
IL
FabricLib.dll
Manifest. Each assembly must have one file that contains a manifest. The man-
ifest is a set of tables containing metadata that lists the names of all files in the
assembly, references to external assemblies, and information such as name and
version that identify the assembly. Strongly named assemblies (discussed later)
also include a unique digital signature. When an assembly is loaded, the CLR’s
first order of business is to open the file containing the manifest so it can identify
the members of the assembly.
IL. The role of Intermediate Language has already been discussed. Before the
CLR can use IL, it must be packaged in an EXE or DLL assembly. The two are
not identical: an EXE assembly must have an entry point that makes it execut-
able; a DLL, on the other hand, is designed to function as a code library holding
type definitions.
The assembly is more than just a logical way to package executable code. It forms
the very heart of the .NET model for code deployment, version control, and security:
As mentioned, an assembly may contain multiple files. These files are not
restricted to code modules, but may be resource files such as graphic images and text
files. A common use of these files is to permit resources that enable an application to
provide a screen interface tailored to the country or language of the user. There is no
limit to the number of files in the assembly. Figure 1-6 illustrates the layout of a
multi-file assembly.
Assembly
Manifest Metadata
Metadata IL (MSIL)
ApparelLib.dll
IL
FabricLib.dll Schematic.jpg
In the multi-file assembly diagram, notice that the assembly’s manifest contains
the information that identifies all files in the assembly.
Although most assemblies consist of a single file, there are several cases where
multi-file assemblies are advantageous:
Multi-file assemblies can be created by executing the C# compiler from the com-
mand line or using the Assembly Linker utility, Al.exe. An example using the C#
compiler is provided in the last section of this chapter. Notably, Visual Studio.NET
2005 does not support the creation of multi-file assemblies.
Public assemblies are usually located in the assembly directory located beneath
the system directory of the operating system (WINNT\ on a Microsoft Windows 2000
operating system). As shown in Figure 1-7, the assemblies are listed in a special for-
mat that displays their four attributes (.NET Framework includes a DLL file that
extends Windows Explorer to enable it to display the GAC contents). Let’s take a
quick look at these four attributes:
Assembly Name. Also referred to as the friendly name, this is the file name of
the assembly minus the extension.
Version. Every assembly has a version number that applies to all files in the
assembly. It consists of four numbers in the format
<major number>.<minor number>.<build>.<revision>
1.2 Common Language Runtime 17
Typically, the major and minor version numbers are updated for changes that
break backward compatibility. A version number can be assigned to an assembly
by including an AssemblyVersion attribute in the assembly’s source code.
Public Key Token. To ensure that a shared assembly is unique and authentic,
.NET requires that the creator mark the assembly with a strong name. This pro-
cess, known as signing, requires the use of a public/private key pair. When the
compiler builds the assembly, it uses the private key to generate a strong name.
The public key is so large that a token is created by hashing the public key and
taking its last eight bytes. This token is placed in the manifest of any client
assembly that references a shared assembly and is used to identify the assembly
during execution.
Core Note
Precompiling an Assembly
After an assembly is loaded, the IL must be compiled to the machine’s native code. If
you are used to working with executables already in a machine code format, this
should raise questions about performance and whether it’s possible to create equiva-
lent “executables” in .NET. The answer to the second part of the statement is yes;
.NET does provide a way to precompile an assembly.
The .NET Framework includes a Native Image Generator (Ngen) tool that is used
to compile an assembly into a “native image” that is stored in a native image cache—
a reserved area of the GAC. Any time the CLR loads an assembly, it checks the cache
to see if it has an associated native image available; if it does, it loads the precompiled
code. On the surface, this seems a good idea to improve performance. However, in
reality, there are several drawbacks.
Ngen creates an image for a hypothetical machine architecture, so that it will run,
for example, on any machine with an x86 processor. In contrast, when the JIT in
.NET runs, it is aware of the specific machine it is compiling for and can accordingly
18 Chapter 1 ■ Introduction to .NET and C#
make optimizations. The result is that its output often outperforms that of the pre-
compiled assembly. Another drawback to using a native image is that changes to a
system’s hardware configuration or operating system—such as a service pack
update—often invalidate the precompiled assembly.
Core Recommendation
Code Verification
As part of the JIT compile process, the Common Language Runtime performs two
types of verification: IL verification and metadata validation. The purpose is to
ensure that the code is verifiably type-safe. In practical terms, this means that param-
eters in a calling and called method are checked to ensure they are the same type, or
that a method returns only the type specified in its return type declaration. In short,
the CLR searches through the IL and metadata to make sure that any value assigned
to a variable is of a compatible type; if not, an exception occurs.
Core Note
A benefit of verified code is that the CLR can be certain that the code cannot
affect another application by accessing memory outside of its allowable range. Con-
sequently, the CLR is free to safely run multiple applications in a single process or
address space, improving performance and reducing the use of OS resources.
any language that targets the CLR. This is significant, because it means that libraries
are no longer tied to specific compilers. As a developer, you can familiarize yourself
with the types in a library and be assured that you can use this knowledge with what-
ever .NET language you choose.
The resources within the FCL are organized into logical groupings called
namespaces. For the most part, these groupings are by broad functionality. For exam-
ple, types used for graphical operations are grouped into the System.Drawing and
System.Drawing.Drawing2D namespaces; types required for file I/O are members
of the System.IO namespace. Namespaces represent a logical concept, not a physi-
cal one.
The FCL comprises hundreds of assemblies (DLLs), and each assembly may con-
tain multiple namespaces. In addition, a namespace may span multiple assemblies.
To demonstrate, let’s look inside an FCL assembly.
Table 1-2 lists some of the most important namespaces in .NET. For reference,
the last column in each row includes a chapter number in this book where you’ll find
the namespace(s) used.
Namespaces provide a roadmap for navigating the FCL. For example, if your
applications are Web based, you’ll spend most of your time exploring the types in the
System.Web.* namespaces. After you have learned the basics of .NET and gained
proficiency with C#, you’ll find that much of your time is spent familiarizing yourself
with the built-in types contained in the Framework Class Library.
3. http://msdn.microsoft.com/netframework/downloads/updates/
default.aspx
1.4 Working with the .NET Framework and SDK 23
\winnt\Microsoft.NET\Framework\v1.0.3705
\winnt\Microsoft.NET\Framework\v1.1.4322
\winnt\Microsoft.NET\Framework\v2.0.40607
The installation of any new software version raises the question of compatibility
with applications developed using an older version. .NET makes it easy to run exist-
ing applications against any framework version. The key to this is the application con-
figuration file (discussed in much greater detail in Chapter 15). This text file contains
XML tags and elements that give the CLR instructions for executing an application.
It can specify where external assemblies are located, which version to use, and, in this
case, which versions of the .NET Framework an application or component supports.
The configuration file can be created with any text editor, although it’s preferable to
rely on tools (such as the Framework Configuration tool) designed for the task. Your
main use of the configuration file will be to test current applications against new
framework releases. Although it can be done, it usually makes no sense to run an
application against an earlier version than it was originally compiled against.
Many of these are better discussed within the context of later chapters. However,
it is useful to be aware of which tools are available for performing these tasks; and a
24 Chapter 1 ■ Introduction to .NET and C#
few, such as those for exploring classes and assemblies, should be mastered early in
the .NET learning curve.
Table 1-3 lists some of the useful tools available to develop and distribute your
applications. Three of these, Ildasm.exe, wincv.exe, and the .NET Framework
Configuration tool, are the subject of further discussion.
Tool Description
c:\Program Files\Microsoft.NET\SDK\v2.0\Bin
To execute the tools at the command line (on a Windows operating system) while
in any directory, it is first necessary to place the path to the utilities in the system
Path variable. To do this, follow these steps:
If you have Visual Studio installed, a simpler approach is to use the preconfigured
Visual Studio command prompt. It automatically initializes the path information that
enables you to access the command-line tools.
Ildasm.exe
The Intermediate Language Disassembler utility is supplied with the .NET Frame-
work SDK and is usually located in the Bin subdirectory along the path where the
SDK is installed. It is invaluable for investigating the .NET assembly environment
and is one of the first tools you should become familiar with as you begin to work
with .NET assemblies and code.
The easiest way to use the utility is to type in
C:\>Ildasm /adv
Double clicking on the Metric method brings up a screen that displays its IL
(Figure 1-10).
Ildasm can be used as a learning tool to solidify the concepts of IL and assemblies.
It also has some practical uses. Suppose you have a third-party component (assem-
bly) to work with for which there is no documentation. Ildasm provides a useful start-
ing point in trying to uncover the interface details of the assembly.
Core Suggestion
Ildasm has a File – Dump menu option that makes it useful for saving
program documentation in a text file. Select Dump Metainfo to create a
lengthy human-readable form of the assembly’s metadata; select Dump
Statistics to view a profile of the assembly that details how many bytes
each part uses.
Source
Compile MSIL CLR
Code
The obfuscated code is functionally equivalent to the assembly’s IL code and pro-
duces identical results when run by the CLR. How does it do this? The most com-
mon trick is to rename meaningful types and members with names that have no
intrinsic meaning. If you look at obfuscated code, you’ll see a lot of types named “a”
28 Chapter 1 ■ Introduction to .NET and C#
or “b,” for example. Of course, the obfuscation algorithm must be smart enough not
to rename types that are used by outside assemblies that depend on the original
name. Another common trick is to alter the control flow of the code without chang-
ing the logic. For example, a while statement may be replaced with a combination
of goto and if statements.
An obfuscator is not included in the .NET SDK. Dotfuscator Community Edition,
a limited-feature version of a commercial product, is available with Visual Stu-
dio.NET. Despite being a relatively unsophisticated product—and only available for
the Microsoft environment—it is a good way to become familiar with the process.
Several vendors now offer more advanced obfuscator products.
wincv.exe
WinCV is a class viewer analogous to the Visual Studio Object Viewer, for those not
using Visual Studio. It is located in the Program Files\Microsoft.Net\
SDK\V1.x\Bin directory and can be run from the command prompt. When the win-
dow appears, type the name of the class you want to view into the Searching For box
(see Figure 1-12).
Figure 1-12 Using WinCV to view type definition of the Array class
WinCV provides a wealth of information about any type in the base class libraries.
The four highlighted areas provide a sampling of what is available:
1.4 Working with the .NET Framework and SDK 29
To illustrate a practical use of the configuration tool, let’s look at how it can be
used to address one of the most common problems that plagues the software devel-
opment process: the need to drop back to a previous working version when a current
application breaks. This can be a difficult task when server DLLs or assemblies are
involved. .NET offers a rather clever solution to this problem: Each time an applica-
tion runs, it logs the set of assemblies that are used by the program. If they are
unchanged from the previous run, the CLR ignores them; if there are changes, how-
ever, a snapshot of the new set of assemblies is stored.
30 Chapter 1 ■ Introduction to .NET and C#
When an application fails, one option for the programmer is to revert to a previous
version that ran successfully. The configuration tool can be used to redirect the applica-
tion to an earlier assembly. However, there may be multiple assemblies involved. This
is where the configuration tool comes in handy. It allows you to view previous assembly
configurations and select the assemblies en masse to be used with the application.
To view and select previous configurations, select Applications – Fix an Applica-
tion from the Configuration tool menu. Figure 1-13 combines the two dialog boxes
that subsequently appear. The main window lists applications that have run and been
recorded. The smaller window (a portion of a larger dialog) is displayed when you
click on an application. This window lists the most recent (up to five) configurations
associated with the application. You simply select the assembly configuration that you
want the application to use.
Visual Studio
IL + Metacode
SRC1
Third-Party IDE
SharpDevelop CSC.EXE APP.EXE
SRC2
Text Editor .cs
External
LIB.DLL Assembly
Figure 1-14 shows the basic steps that occur in converting source code to the final
compiled output. The purpose of this section is to demonstrate how a text editor and
the C# compiler can be used to build an application. Along the way, it will provide a
detailed look at the many compiler options that are hidden by the IDE.
C:\winnt\Microsoft.NET\Framework\v2.0.40607
Of course, this may vary depending on your operating system and the version of
Framework installed. To make the compiler available from the command line in any
32 Chapter 1 ■ Introduction to .NET and C#
current directory, you must add this path to the system Path variable. Follow the
steps described in the previous section for setting the path for the SDK utilities.
Type in the following statement at the command line to verify that the compiler
can be accessed:
C:\>csc /help
Both statements compile the source into an executable (.exe) file—the default
output from the compiler. As shown in Table 1-4, the output type is specified using
the /t: flag. To create a DLL file, set the target value to library. For a WinForms
application, specify /t:winexe. Note that you can use /t:exe to create a Win-
Forms application, but the console will be visible as background window.
Option Description
/delaysign Builds an assembly using delayed signing of the strong name. This
is discussed in Chapter 15.
/keyfile Specifies the path to the .snk file containing the key pair used for
strong signing (see Chapter 15).
/out Name of the file containing compiled output. The default is the
name of the input file with .exe suffix.
1.5 Understanding the C# Compiler 33
Option Description
/resource Used to embed resource files into the assembly that is created.
The real value of working with the raw compiler is the ability to work with multi-
ple files and assemblies. For demonstration purposes, create two simple C# source
files: client.cs and clientlib.cs.
client.cs
using System;
public class MyApp
{
static void Main(string[] args)
{
ShowName.ShowMe("Core C#");
}
}
clientlib.cs
using System;
public class ShowName
{
public static void ShowMe(string MyName)
{
Console.WriteLine(MyName);
}
}
It’s not important to understand the code details, only that the client routine
calls a function in clientlib that writes a message to the console. Using the C#
compiler, we can implement this relationship in a number of ways that not only dem-
onstrate compiler options but also shed light on the use of assemblies.
34 Chapter 1 ■ Introduction to .NET and C#
The output is an assembly named clientlib.dll. Now, compile the client code
and reference this external assembly:
The output is an assembly named client.exe. If you examine this with Ildasm,
you see that the manifest contains a reference to the clientlib assembly.
These examples, shown in Figure 1-15, illustrate the fact that even a simple appli-
cation presents the developer with multiple architectural choices for implementing
an application.
4. The PE format defines the layout for executable files that run on 32- or 64-bit Windows systems.
1.6 Summary 35
Example 1: Example 2:
Multiple Source Reference External Example 3:
Files Assembly Multi-File Assembly
IL
IL IL IL IL
clientlib.
client.exe client.exe clientlib.dll client.exe netmodule
1.6 Summary
The .NET Framework consists of the Common Language Runtime (CLR) and the
Framework Class Library (FCL). The CLR manages all the tasks associated with
code execution. It first ensures that code is CLR compliant based on the Common
Language Specification (CLS) standard. It then loads an application and locates all
dependent assemblies. Its Just-in-Time (JIT) compiler converts the IL contained in
an application’s assembly, the smallest deployable code unit in .NET, into native
machine code. During the actual program execution, the CLR handles security, man-
ages threads, allocates memory, and performs garbage collection for releasing
unused memory.
All code must be packaged in an assembly in order for the CLR to use it. An
assembly is either a single file or grouping of multiple physical files treated as a single
unit. It may contain code modules as well as resource files.
The FCL provides a reusable set of classes and other types that are available to all
CLR-compliant code. This eliminates the need for compiler-specific libraries.
Although the FCL consists of several physical DLLs containing over a thousand
types, it’s made manageable by the use of namespaces that impose a logical hierarchy
over all the types.
To assist the developer in debugging and deploying software, .NET includes a set
of utilities that enables an administrator to perform such tasks as managing assem-
blies, precompiling assemblies, adding files to an assembly, and viewing class details.
In addition, a wealth of open source .NET tools is becoming available to aid the
development process.
36 Chapter 1 ■ Introduction to .NET and C#
3. What is the difference between the Common Type System and the
Common Language Specification?
4. How does the CLR allow code from different compilers to interact?
8. Describe what these commonly used acronyms stand for: CLR, GAC,
FCL, IL.
This page intentionally left blank
C# LANGUAGE
FUNDAMENTALS
39
40 Chapter 2 ■ C# Language Fundamentals
The code in Figure 2-1 consists of a class MyApp that contains the program logic
and a class Apparel that contains the data. The program creates an instance of
Apparel and assigns it to myApparel. This object is then used to print the values of
the class members FabType and Price to the console. The important features to
note include the following:
C:\> MyApparel 5 6
Core Note
The contents of the command line are passed as an argument to the
Main() method. The System.Environment.CommandLine property
also exposes the command line’s contents.
42 Chapter 2 ■ C# Language Fundamentals
Naming Conventions
The ECMA standard provides naming convention guidelines to be followed in your
C# code. In addition to promoting consistency, following a strict naming policy can
minimize errors related to case sensitivity that often result from undisciplined nam-
ing schemes. Table 2-1 summarizes some of the more important recommendations.
Note that the case of a name may be based on two capitalization schemes:
Enum Type Pascal • Use Pascal case for the enum value names.
• Use singular name for enums.
public enum WarmColor { Orange, Yellow, Brown}
Event Pascal • The method that handles events should have the suffix
EventHandler.
• Event argument classes should have the suffix EventArgs.
Local Variable Camel • Variables with public access modifier use Pascal.
int myIndex
2.1 The Layout of a C# Program 43
Namespace Pascal • Do not have a namespace and class with the same name.
• Use prefixes to avoid namespaces having the same name.
For example, use a company name to categorize
namespaces developed by that company.
Acme.GraphicsLib
The rule of thumb is to use Pascal capitalization everywhere except with parame-
ters and local variables.
Commenting a C# Program
The C# compiler supports three types of embedded comments: an XML version and
the two single-line (//) and multi-line (/* */) comments familiar to most program-
mers:
An XML comment begins with three slashes (///) and usually contains XML tags
that document a particular aspect of the code such as a structure, a class, or class
member. The C# parser can expand the XML tags to provide additional information
and export them to an external file for further processing.
The <remarks> tag—shown in Figure 2-1—is used to describe a type (class). The
C# compiler recognizes eight other primary tags that are associated with a particular
program element (see Table 2-2). These tags are placed directly above the lines of
code they refer to.
44 Chapter 2 ■ C# Language Fundamentals
Tag Description
<include file="myXML"> file attribute is set to name of another XML file that is
to be included in the XML documentation produced by
this source code.
<permission cref= ""> Most of the time this is set to the following:
///<permission cref="System.Security.Permis-
sionSet"> </permission>
The value of the XML comments lies in the fact that they can be exported to a
separate XML file and then processed using standard XML parsing techniques. You
must instruct the compiler to generate this file because it is not done by default.
The following line compiles the source code consoleapp.cs and creates an
XML file consoleXML:
If you compile the code in Figure 2-1, you’ll find that the compiler generates
warnings for all public members in your code:
Warning CS1591: Missing XML comment for publicly visible type ...
2.2 Primitives 45
Core Note
2.2 Primitives
The next three sections of this chapter describe features that you’ll find in most pro-
gramming languages: variables and data types, operators, expressions, and statements
that control the flow of operations. The discussion begins with primitives. As the
name implies, these are the core C# data types used as building blocks for more com-
plex class and structure types. Variables of this type contain a single value and always
have the same predefined size. Table 2-3 provides a formal list of primitives, their
corresponding core data types, and their sizes.
As the table shows, primitives map directly to types in the base class library and
can be used interchangeably. Consider these statements:
They all generate exactly the same Intermediate Language (IL) code. The shorter
version relies on C# providing the keyword int as an alias for the System.Int32
type. C# performs aliasing for all primitives.
Here are a few points to keep in mind when working with primitives:
• The keywords that identify the value type primitives (such as int) are
actually aliases for an underlying structure (struct type in C#).
Special members of these structures can be used to manipulate the
primitives. For example, the Int32 structure has a field that returns
the largest 32-bit integer and a method that converts a numeric string
to an integer value:
The remainder of this section offers an overview of the most useful primitives with
the exception of string, which is discussed later in the chapter.
decimal
The decimal type is a 128-bit high-precision floating-point number. It provides 28
decimal digits of precision and is used in financial calculations where rounding can-
not be tolerated. This example illustrates three of the many methods available to
decimal type. Also observe that when assigning a literal value to a decimal type,
the M suffix must be used.
bool
The only possible values of a bool type are true and false. It is not possible to cast
a bool value to an integer—for example, convert true to a 1, or to cast a 1 or 0 to a
bool.
bool bt = true;
string bStr = bt.ToString(); // returns "true"
bt = (bool) 1; // fails
48 Chapter 2 ■ C# Language Fundamentals
char
The char type represents a 16-bit Unicode character and is implemented as an
unsigned integer. A char type accepts a variety of assignments: a character value
placed between individual quote marks (' '); a casted numeric value; or an escape
sequence. As the example illustrates, char also has a number of useful methods pro-
vided by the System.Char structure:
byte, sbyte
A byte is an 8-bit unsigned integer with a value from 0 to 255. An sbyte is an 8-bit
signed integer with a value from –128 to 127.
single, double
These are represented in 32-bit single-precision and 64-bit double-precision formats.
In .NET 1.x, single is referred to as float.
• The single type has a value range of 1.5 × 10 –45 to 3.4 × 1038 with
7-decimal digit precision.
• The double type has a value range of 5 × 10–324 to 1.7 × 10308 with
15- to 16-decimal digit precision.
• Floating-point operations return NaN (Not a Number) to signal that
the result of the operation is undefined. For example, dividing 0.0 by
0.0 results in NaN.
• Use the System.Convert method when converting floating-point
numbers to another type.
Note that the F suffix is used when assigning a literal value to a single type, and
D is optional for a double type.
avoid formal exception handling code. The following example uses an Int32 type to
demonstrate the two forms of TryParse:
int result;
// parse string and place result in result parameter
bool ok = Int32.TryParse("100", out result);
bool ok = Int32.TryParse("100", NumberStyles.Integer, null,
out result);
In the second form of this method, the first parameter is the text string being
parsed, and the second parameter is a NumberStyles enumeration that describes
what the input string may contain. The value is returned in the fourth parameter.
Arithmetic Operators
Table 2-4 summarizes the basic numerical operators. The precedence in which these
operators are applied during the evaluation of an expression is shown in parentheses,
with 1 being the highest precedence.
++ (1) Prefix/postfix x = 5;
-- Increment/decrement Console.WriteLine(x++) // x = 5
Console.WriteLine(++x) // x = 6
Core Note
== Equality if (x == y) {...}
!= Inequality
Note the two forms of the logical AND/OR operations. The && and || operators
do not evaluate the second expression if the first is false—a technique known as short
circuit evaluation. The & and | operators always evaluate both expressions. They are
used primarily when the expression values are returned from a method and you want
to ensure that the methods are called.
In addition to the operators in Table 2-5, C# supports a ?: operator for condition-
ally assigning a value to a variable. As this example shows, it is basically shorthand for
using an if-else statement:
string pass;
int grade=74;
If(grade >= 70) pass="pass"; else pass="fail";
// expression ? op1 : op2
pass = (grade >= 70) ? "pass" : "fail";
If the expression is true, the ?: operator returns the first value; if it’s false, the
second is returned.
if-else
Syntax:
C# if statements behave as they do in other languages. The only issue you may
encounter is how to format the statements when nesting multiple if-else clauses.
// Nested if statements
if (age > 16) if (age > 16)
{ if (sex == "M")
if (sex == "M") type = "Man";
{ else
type = "Man"; type = "Woman" ;
} else { else
type = "Woman" ; type = "child";
}
} else {
type = "child";
}
54 Chapter 2 ■ C# Language Fundamentals
Both code segments are equivalent. The right-hand form takes advantage of the
fact that curly braces are not required to surround single statements; and the subor-
dinate if clause is regarded as a single statement, despite the fact that it takes several
lines. The actual coding style selected is not as important as agreeing on a single style
to be used.
switch
Syntax:
The expression is one of the int types, a character, or a string. The switch block
consists of case labels—and an optional default label—associated with a constant
expression that must implicitly convert to the same type as the expression. Here is an
example using a string expression:
• C# does not permit execution to fall through one case block to the
next. Each case block must end with a statement that transfers
control. This will be a break, goto. or return statement.
• Multiple case labels may be associated with a single block of code.
• The switch statement is case sensitive; in the example, "Cotton"
and "COTTON" represent two different values.
2.4 Loops
C# provides four iteration statements: while, do, for, and foreach. The first three
are the same constructs you find in C, C++, and Java; the foreach statement is
designed to loop through collections of data such as arrays.
while loop
Syntax:
The statement(s) in the loop body are executed until the boolean expression is
false. The loop does not execute if the expression is initially false.
Example:
do loop
Syntax:
This is similar to the while statement except that the evaluation is performed at
the end of the iteration. Consequently, this loop executes at least once.
Example:
for loop
Syntax:
The for construct contains initialization, a termination condition, and the itera-
tion statement to be used in the loop. All are optional. The initialization is executed
once, and then the condition is checked; as long as it is true, the iteration update
occurs after the body is executed. The iteration statement is usually a simple incre-
ment to the control variable, but may be any operation.
Example:
If any of the clauses in the for statement are left out, they must be accounted for
elsewhere in the code. This example illustrates how omission of the for-iteration
clause is handled:
foreach loop
Syntax:
The type and identifier declare the iteration variable. This construct loops once
for each element in the collection and sets the iteration variable to the value of the
current collection element. The iteration variable is read-only, and a compile error
occurs if the program attempts to set its value.
For demonstration purposes, we will use an array as the collection. Keep in mind,
however, that it is not restricted to an array. There is a useful set of collection classes
defined in .NET that work equally well with foreach. We look at those in Chapter 4,
“Working with Objects in C#.”
Example:
int totVal = 0;
foreach (int arrayVal in r)
{
totVal += arrayVal;
}
There are few occasions where the use of a goto statement improves program
logic. The goto default version may be useful in eliminating redundant code inside
a switch block, but aside from that, avoid its use.
C# Preprocessing
Symbol Description
#line Changes the line number sequence and can identify which file is
the source for the line.
#region Used to specify a block of code that you can expand or collapse
#endregion when using the outlining feature of Visual Studio.NET.
The three most common uses for preprocessing directives are to perform condi-
tional compilation, add diagnostics to report errors and warnings, and define code
regions.
60 Chapter 2 ■ C# Language Fundamentals
Conditional Compilation
The #if related directives are used to selectively determine which code is included
during compilation. Any code placed between the #if statement and #endif state-
ment is included or excluded based on whether the #if condition is true or false.
This is a powerful feature that is used most often for debug purposes. Here is an
example that illustrates the concept:
#define DEBUG
using System;
public class MyApp
{
public static void Main()
{
#if (DEBUG)
Console.WriteLine("Debug Mode");
#else
Console.WriteLine("Release Mode");
#endif
}
}
Any #define directives must be placed at the beginning of the .cs file. A condi-
tional compilation symbol has two states: defined or undefined. In this example, the
DEBUG symbol is defined and the subsequent #if (DEBUG) statement evaluates to
true. The explicit use of the #define directive permits you to control the debug
state of each source file. Note that if you are using Visual Studio, you can specify a
Debug build that results in the DEBUG symbol being automatically defined for each
file in the project. No explicit #define directive is required.
You can also define a symbol on the C# compile command line using the /Define
switch:
Diagnostic Directives
Diagnostic directives issue warning and error messages that are treated just like any
other compile-time errors and warnings. The #warning directive allows compilation
to continue, whereas the #error terminates it.
2.6 Strings 61
#define CLIENT
#define DEBUG
using System;
public class MyApp
{
public static void Main()
{
#if DEBUG && INHOUSE
#warning Debug is on.
#elif DEBUG && CLIENT
#error Debug not allowed in Client Code.
#endif
// Rest of program follows here
In this example, compilation will terminate with an error message since DEBUG
and CLIENT are defined.
Code Regions
The region directives are used to mark sections of code as regions. The region direc-
tive has no semantic meaning to the C# compiler, but is recognized by Visual Stu-
dio.NET, which uses it to hide or collapse code regions. Expect other third-party
source management tools to take advantage of these directives.
#region
// any C# statements
#endregion
2.6 Strings
The System.String, or string class, is a reference type that is represented inter-
nally by a sequence of 16-bit Unicode characters. Unlike other reference types, C#
treats a string as a primitive type: It can be declared as a constant, and it can be
assigned a literal string value.
String Literals
Literal values assigned to string variables take two forms: literals enclosed in quota-
tion marks, and verbatim strings that begin with @" and end with a closing double
quote ("). The difference between the two is how they handle escape characters.
Regular literals respond to the meaning of escape characters, whereas verbatim
62 Chapter 2 ■ C# Language Fundamentals
strings treat them as regular text. Table 2-9 provides a summary of the escape charac-
ters that can be placed in strings.
\a System alert
\b Backspace
\f Form feed
\r Carriage return
\t Horizontal tab
\u Unicode character
\v Vertical tab
\0 Null character
A verbatim string serves the purpose its name implies: to include any character
placed between the beginning and ending double quote. The following segment pro-
vides several examples of using literals:
The regular literal string is normally your best choice because it supports the
escape sequences. The verbatim is to be favored when the text contains backslashes.
Its most common use is with file path values and Regular Expression matching pat-
terns (discussed in Chapter 5, “C# Text Manipulation and File I/O”).
String Manipulation
The System.String class contains a variety of string manipulation members. These
include ways to determine a string’s length, extract a substring, compare strings, and
convert a string to upper- or lowercase. The following examples illustrate some of the
more common operations.
String Concatenation
The + operator is used for concatenating two strings: s1 + s2 . Only one of these has
to be a string type; the other can be any type, and its ToString method is called
automatically to convert it.
Each loop results in the creation of a new string consisting of the previous string
plus the new appended name and tag. A better approach is to use the String-
Builder class as a replacement for the concatenation operator. This class sets aside
memory to operate on strings and thus avoids the copying and memory allocation
drawbacks of the concatenation (+) operator. It includes methods to append, insert,
delete, remove, and replace characters. StringBuilder is discussed in Chapter 5.
The IndexOf method locates the next occurrence of a character pattern within a
string. It searches for the occurrence from the beginning of the string or a specified
location. Listing 2-1 illustrates this.
IndexOf() performs a case-sensitive search. To ensure both upper- and lower-
case instances are counted, you could convert the original string to lowercase
(ToLower()) before searching it. Note that there is also a LastIndexOf method
that locates the last instance of a character pattern within a string.
2.6 Strings 65
Comparing Strings
This topic is more complex than one would expect. The first hint of this is when you
look at the System.String members and discover that there are four comparison
methods: Compare, CompareOrdinal, CompareTo, and Equals. The choice of a
comparison method is based on factors such as whether the comparison should be
case sensitive and whether it should take culture into account.
The .NET environment is designed to handle international character sets, cur-
rencies, and dates. To support this, the handling and representation of strings can be
tailored to different countries and cultures. Consider, for example, how to compare
the same date in U.S. and European format. The dates “12/19/04” and “19/12/04” are
logically equal, but do not have the same code value. Only a comparison method that
takes culture into consideration would consider them equal. Chapter 5 explains how
the various comparison methods work and the factors to be considered in selecting
one.
For the majority of applications, nothing more than the standard equality (==)
operator is required. This code segment illustrates its use:
bool isMatch;
string title = "Ancient Mariner";
isMatch = (title == "ANCIENT MARINER"); // false
66 Chapter 2 ■ C# Language Fundamentals
Note that the == operator is just a syntactical shortcut for calling the Equals
method; it is actually faster to call Equals()directly.
Syntax:
Example:
Note: If the enum symbols are not set to a value, they are set automatically to the
sequence 0, 1, 2, 3, and so on.
The access modifiers define the scope of the enum. The default is internal,
which permits it to be accessed by any class in its assembly. Use public to make it
available to any class in any assembly.
The optional enum-base defines the underlying type of the constants that corre-
spond to the symbolic names. This must be an integral value of the type byte,
sbyte, short, ushort, int, uint, long, or ulong. The default is int.
remain valid. Another advantage is that enumerated types are strongly typed. This
means, for example, that when an enum type is passed as a parameter, the receiving
method must have a matching parameter of the same type; otherwise, a compiler
error occurs.
The code segment in Listing 2-2 illustrates these ideas using the Fabric enum
from the preceding example.
Things to note:
This example shows how easy it is to obtain the symbol name or constant value
when the instance of an enum is known—that is, Cotton. But suppose there is a
need to determine whether an enum contains a member with a specific symbol or
constant value. You could use foreach to loop through the enum members, but
there is a better solution. Enumerations implicitly inherit from System.Enum, and
this class contains a set of methods that can be used to query an enumeration about
its contents.
System.Enum Methods
Three of the more useful System.Enum methods are Enum.IsDefined,
Enum.Parse, and Enum.GetName. The first two methods are often used together to
determine if a value or symbol is a member of an enum, and then to create an
instance of it. The easiest way to understand them is to see them in use. In this exam-
ple, the enum Fabric is queried to determine if it contains a symbol matching a
given string value. If so, an instance of the enum is created and the GetName method
is used to print one of its values.
The IsDefined method takes two parameters: an enumeration type that the
typeof operator returns and a string representing the symbol to be tested for.
Another form of this method tests for a specified constant value if a numeric value is
passed as the second parameter.
The Parse method takes the same arguments and creates an instance of an enu-
merated type. The variable fab created here is equivalent to the one created in List-
ing 2-2. It is important to ensure that the enum member exists before using the
Parse method. If it does not, an exception is thrown.
The GetName method returns a string value of the enum whose value is passed as
the second argument to the method. In this example, "Silk" is returned because its
constant value is 2.
2.8 Arrays 69
[Flags]
enum Fabric :short {
2.8 Arrays
C#, like most programming languages, provides the array data structure as a way to
collect and manipulate values of the same type. Unlike other languages, C# provides
three types of arrays. One is implemented as an ArrayList object; another as a
generic List object; and a third is derived from the System.Array class. The latter,
which is discussed here, has the traditional characteristics most programmers associ-
ate with an array. The ArrayList is a more flexible object that includes special
methods to insert and delete elements as well as dynamically resize itself. The List
is a type-safe version of the ArrayList that was introduced with .NET 2.0 and may
eventually replace the ArrayList. The List and ArrayList are discussed in
Chapter 4, along with other Collection classes.
Before looking at the details of creating and using an array, you should be familiar
with its general features:
Example:
// Set to an enum
Fabric[] enumArray = new Fabric[2];
enumArray[0] = Fabric.Cotton;
The size of the array is determined from the explicit dimensions or the number of
elements in the optional initializer list. If a dimension and an initializer list are both
included, the number of elements in the list must match the dimension(s). If no ini-
tialization values are specified, the array elements are initialized to 0 for numeric
types or null for all others. The CLR enforces bounds checking—any attempt to
reference an array index outside its dimensions results in an exception.
2.8 Arrays 71
IndexOf, Static Returns the index of the first or last occurrence of a value in a
LastIndexOf method one-dimensional array.
Clone Instance Copies the contents of an array into a new array. The new array
method is a shallow copy of the original—that is, reference pointers, not
values, are copied.
The members are classified as static or instance. A static member is not associated
with any particular array. It operates as a built-in function that takes any array as a
parameter. The instance members, on the other hand, are associated with a specific
instance of an array. The example shown in Listing 2-3 demonstrates how to use
many of these class members.
Things to note:
• The Sort method has many overloaded forms. The simplest takes a
single-dimensional array as a parameter and sorts it in place. Other
forms permit arrays to be sorted using an interface defined by the
programmer. This topic is examined in Chapter 4.
• The Clone method creates a copy of the artists array and assigns it
to artClone. The cast (string[]) is required, because Clone
returns an Object type. The Object.ReferenceEquals method is
used to determine if the cloned array points to the same address as the
original. Because string is a reference type, the clone merely copies
pointers to the original array contents, rather than copying the
contents. If the arrays had been value types, the actual contents would
have been copied, and ReferenceEquals would have returned false.
• The Copy method copies a range of elements from one array to
another and performs any casting as required. In this example, it takes
the following parameters:
System.Object
System.ValueType
System.Array
Struct
System.String
System.Enum
System.Exception
Primitives
Class, Interface Boolean Int16
Byte Int32
Char Int64
User-defined
System-defined Decimal Single
Double
appStatus 0
myApparel •
myApparel2 •
fabType • "Synthetic"
price 250
Overhead
1. The CLR allocates memory for the object on the top of the managed
heap.
2. Overhead information for the object is added to the heap. This infor-
mation consists of a pointer to the object’s method table and a
SyncBlockIndex that is used to synchronize access to the object
among multiple threads.
3. The myApparel object is created as an instance of the Apparel class,
and its Price and FabType fields are placed on the heap.
4. The reference to myApparel is placed on the stack.
5. When a new reference variable myApparel2 is created, it is placed on
the stack and given a pointer to the existing object. Both reference
variables—myApparel and myApparel2—now point to the same
object.
Creating a reference object can be expensive in time and resources because of the
multiple steps and required overhead. However, setting additional references to an
existing object is quite efficient, because there is no need to make a physical copy of
the object. The reverse is true for value types.
Boxing
.NET contains a special object type that accepts values of any data type. It provides
a generic way to pass parameters and assign values when the type of the value being
passed or assigned is not tied to a specific data type. Anything assigned to object
must be treated as a reference type and stored on the heap. Consider the following
statements:
The first statement creates the variable age and places its value on the stack; the
second assigns the value of age to a reference type. It places the value 17 on the
heap, adds the overhead pointers described earlier, and adds a stack reference to it.
This process of wrapping a value type so that it is treated as a reference type is known
as boxing. Conversely, converting a reference type to a value type is known as unbox-
ing and is performed by casting an object to its original type. Here, we unbox the
object created in the preceding example:
Note that the value being unboxed must be of the same type as the variable to
which it is being cast.
In general, boxing can be ignored because the CLR handles the details transpar-
ently. However, it should be considered when designing code that stores large
amounts of numeric data in memory. To illustrate, consider the System.Array and
ArrayList classes mentioned earlier. Both are reference types, but they perform
quite differently when used to store simple data values.
The ArrayList methods are designed to work on the generic object type. Con-
sequently, the ArrayList stores all its items as reference types. If the data to be
stored is a value type, it must be boxed before it can be stored. The array, on the
other hand, can hold both value and reference types. It treats the reference types as
the ArrayList does, but does not box value types.
The following code creates an array and an ArrayList of integer values. As
shown in Figure 2-4, the values are stored quite differently in memory.
The array stores the values as unboxed int values; the ArrayList boxes each
value. It then adds overhead required by reference types. If your application stores
large amounts of data in memory and does not require the special features of the
ArrayList, the array is a more efficient implementation. If using .NET 2.0 or later,
the List class is the best choice because it eliminates boxing and includes the more
flexible ArrayList features.
2.9 Reference and Value Types 77
int = 4
int = 3
int = 2
int = 1
int = 4 •
int = 3 •
int = 2 •
int = 1 •
ages Overhead Overhead ages
Array ArrayList
Figure 2-4 Memory layout comparison of Array and ArrayList
Releasing Memory
Memory on the stack is freed when a variable goes out of scope. A garbage collection
process that occurs when a system memory threshold is reached releases memory on
the heap. Garbage collection is controlled by .NET and occurs automatically at
unpredictable intervals. Chapter 4 discusses it in detail.
Variable Assignments
When a variable is set to a reference type, it receives a pointer to the original
object—rather than the object value itself. When a variable is set to a value type, a
field-by-field copy of the original variable is made and assigned to the new variable.
78 Chapter 2 ■ C# Language Fundamentals
2.10 Summary
This chapter offers an overview of the C# language, providing the syntax and exam-
ples for using the list of features that form the core of this and just about any pro-
gramming language. These features include basic data types, numerical and
relational operators, loop constructs, strings, enums, and arrays. The final section
stresses how all .NET types can be classified as a value or reference type. It explains
the different memory allocation schemes used for the two. In addition, it looks at the
concepts of boxing and unboxing: converting a value type to a reference type and
converting a reference type back to a value type.
2. What are the three types of inline comments available in C#? Which
can be exported?
3. What is a primitive?
a. c = c+ i;
b. s += i;
c. c += s;
d. d += i;
9. Name the two base classes from which all value types inherit.
10. What prime value is printed by the final statement in this code?
int[] primes = new int[6] {1,3,5,7,11,13};
int[] primesClone = new int[6];
Array.Copy(primes,1,primesClone,1,5);
Console.WriteLine("Prime: "+primesClone[3]);
CLASS DESIGN
IN C#
This chapter provides an advanced introduction to using classes within the .NET
environment. It is not a primer on object-oriented programming (OOP) and
assumes you have some familiarity with the principles of encapsulation, inheritance,
and polymorphism. C# is rich in object-oriented features, and the first challenge in
working with classes is to understand the variety of syntactical contstructs. At the
same time, it is necessary to appreciate the interaction between C# and .NET. Not
only does the Framework Class Library (FCL) provide thousands of predefined
classes, but it also provides a hierarchy of base classes from which all C# classes are
derived.
The chapter presents topics in a progressive fashion—each section building on the
previous. If you are new to C#, you should read from beginning to end; if you’re
familiar with the concepts and syntax, read sections selectively. The chapter begins
by presenting the syntactical construct of a class and then breaks it down in detail.
Attributes, modifiers, and members of the class body (constructors, properties,
fields, and methods) are all explained. Sprinkled throughout are recommended
.NET guidelines and best practices for designing and using custom classes. The
objective is to show not only how to use classes, but also encourage good design prac-
tices that result in efficient code.
81
82 Chapter 3 ■ Class Design in C#
Attribute
[assembly:CLSCompliant(true)]
Class public class Furniture
Declaration {
Constant const double salesTax = .065;
private double purchPrice;
Fields private string vendor, inventoryID;
public Furniture (string vendor, string invenID,
double purchPrice)
{
Constructor this.vendor = vendor;
this.inventoryID = invenID;
this.purchPrice = purchPrice;
}
public string MyVendor
Property { get {return vedor;} }
public double CalcSalesTax(double salePrice)
Method
{ return salePrice * salesTax; }
}
see in the discussion of inheritance, this is important because a class can explicitly
inherit from only one class.
Attributes
The optional attribute section consists of a pair of square brackets surrounding a
comma-separated list of one or more attributes. An attribute consists of the attribute
name followed by an optional list of positional or named arguments. The attribute
may also contain an attribute target—that is, the entity to which the attribute applies.
Examples
The attribute section contains an attribute name only:
[ClassDesc]
[ClassDesc(Author="Knuth", 0)]
[ClassDesc(Author="Knuth"), ClassDesc(Author="James")]
Description
Attributes provide a way to associate additional information with a target entity. In
our discussion, the target is a newly created class; but attributes may also be associ-
ated with methods, fields, properties, parameters, structures, assemblies, and mod-
ules. Their simple definition belies a truly innovative and powerful programming
tool. Consider the following:
Core Note
.NET supports two types of attributes: custom attributes and standard attributes.
Custom attributes are defined by the programmer. The compiler adds them to the
metadata, but it’s up to the programmer to write the reflection code that incorporates
this metadata into the program. Standard attributes are part of the .NET Framework
and recognized by the runtime and .NET compilers. The Flags attribute that was
discussed in conjunction with enums in Chapter 2, “C# Language Fundamentals,” is
an example of this; another is the conditional attribute, described next.
Conditional Attribute
The conditional attribute is attached to methods only. Its purpose is to indicate
whether the compiler should generate Intermediate Language (IL) code to call the
method. The compiler makes this determination by evaluating the symbol that is part
of the attribute. If the symbol is defined (using the define preprocessor directive),
code that contains calls to the method is included in the IL. Here is an example to
demonstrate this:
#define DEBUG
using System;
using System.Diagnostics; // Required for conditional attrib.
public class AttributeTest
{
[Conditional("TRACE")]
public static void ListTrace()
{ Console.WriteLine("Trace is On"); }
[Conditional("DEBUG")]
public static void ListDebug()
{ Console.WriteLine("Debug is On"); }
}
3.2 Defining a Class 85
#define TRACE
using System;
public class MyApp {
static void Main()
{
Console.WriteLine("Testing Method Calls");
AttributeTest.ListTrace();
AttributeTest.ListDebug();
}
}
Access Modifiers
The primary role of modifiers is to designate the accessibility (also called scope or
visibility) of types and type members. Specifically, a class access modifier indicates
whether a class is accessible from other assemblies, the same assembly, a containing
class, or classes derived from a containing class.
Core Note
Class Identifier
This is the name assigned to the class. The ECMA standard recommends the follow-
ing guidelines for naming the identifier:
Example
// .. FCL Interface and user-defined base class
public interface System.Icomparable
{Int32 CompareTo(Object object); }
class Furniture { }
// .. Derived Classes
class Sofa: Furniture { ... } // Inherits from one base class
// Following inherits from one base class and one interface.
class Recliner: Furniture, IComparable {...}
The C# language does not permit multiple class inheritance, thus the base list can
contain only one class. Because there is no limit on the number of inherited inter-
faces, this serves to increase the role of interfaces in the .NET world.
Core Note
Method Class, Structure, A function associated with the class that defines
Interface an action or computation.
Events Class, Structure, A way for a class or object to notify other classes
Interface or objects that its state has changed.
Access Modifiers
* Not applicable
Constants
C# uses the const keyword to declare variables that have a fixed, unalterable value.
Listing 3-1 provides an example of using constants. Although simple, the code illus-
trates several basic rules for defining and accessing constants.
90 Chapter 3 ■ Class Design in C#
The most important thing to recognize about a constant is that its value is deter-
mined at compile time. This can have important ramifications. For example, suppose
the Furniture class in Figure 3-1 is contained in a DLL that is used by other assem-
blies as a source for the sales tax rate. If the rate changes, it would seem logical that
assigning the new value to SalesTax and recompiling the DLL would then make
the new value to external assemblies. However, the way .NET handles constants
requires that all assemblies accessing the DLL must also be recompiled. The prob-
lem is that const types are evaluated at compile time.
3.4 Constants, Fields, and Properties 91
When any calling routine is compiled against the DLL, the compiler locates all
constant values in the DLL’s metadata and hardcodes them in the executable code of
the calling routine. This value is not changed until the calling routine is recompiled
and reloads the value from the DLL. In cases such as tax, where a value is subject to
change, it’s preferable to define the value as a readonly field—sometimes referred
to as a runtime constant.
Core Approach
Fields
A field is also used to store data within a class. It differs from a const in two sig-
nificant ways: Its value is determined at runtime, and its type is not restricted to
primitives.
Field Modifiers
In addition to the access modifiers, fields have two additional modifiers: static and
readonly (see Table 3-3).
Modifier Definition
static The field is part of the class’s state rather than any instances of the class.
This means that it can be referenced directly (like a constant) by specifying
classname.fieldname without creating an instance of the class.
readonly The field can only be assigned a value in the declaration statement or class
constructor. The net effect is to turn the field into a constant. An error
results if code later attempts to change the value of the field.
As a rule of thumb, fields should be defined with the private attribute to ensure
that the state of an object is safe from outside manipulation. Methods and properties
should then be used to retrieve and set the private data if outside access is required.
92 Chapter 3 ■ Class Design in C#
Core Note
If a field is not initialized, it is set to the default value for its type: 0 for
numbers, null for a reference type, single quotation marks ('') for a
string, and false for boolean.
There is one case where setting a field to public makes sense: when your pro-
gram requires a global constant value. By declaring a field to be public static
readonly, you can create a runtime constant. For example, this declaration in Fig-
ure 3-1:
Properties
A property is used to control read and write access to values within a class. Java and
C++ programmers create properties by writing an accessor method to retrieve field
data and a mutator method to set it. Unlike these languages, the C# compiler actually
recognizes a special property construct and provides a simplified syntax for creating
and accessing data. In truth, the syntax is not a whole lot different than a comparable
C++ implementation, but it does allow the compiler to generate more efficient code.
Syntax:
[attributes] <modifier> <data type> <property name>
94 Chapter 3 ■ Class Design in C#
{
[access modifier] get
{ ...
return(propertyvalue)
}
[access modifier] set
{ ... Code to set a field to the keyword value }
}
Note:
The syntax for accessing the property of a class instance is the same as for a field:
The get block of code serves as a traditional accessor method and the set block
as a mutator method. Only one is required. Leave out the get block to make the
property write-only or the set block to make it read-only.
All return statements in the body of a get block must specify an expression that
is implicitly convertible to the property type.
In this example, the code in the set block checks to ensure that the property is set
to a value greater than 0. This capability to check for invalid data is a major argument
in favor of encapsulating data in a property.
If you were to examine the underlying code generated for this example, you would
find that C# actually creates a method for each get or set block. These names are
created by adding the prefix get or set to the property name—for example,
get_PricePerYard. In the unlikely case you attempt to create a method with the
same name as the internal one, you will receive a compile-time error.
The use of properties is not necessarily any less efficient than exposing fields
directly. For a non-virtual property that contains only a small amount of code, the JIT
(Just-in-Time) compiler may replace calls to the accessor methods with the actual
code contained in the get or set block. This process, known as inlining, reduces the
overhead of making calls at runtime. The result is code that is as efficient as that for
fields, but much more flexible.
Indexers
An indexer is often referred to as a parameterized property. Like a property, it is
declared within a class, and its body may contain get and set accessors that share
the same syntax as property accessors. However, an indexer differs from a property in
two significant ways: It accepts one or more parameters, and the keyword this is
used as its name. Here is the formal syntax:
Syntax:
Example:
Note: The static modifier is not supported because indexers work only with
instances.
In a nutshell, the indexer provides a way to access a collection of values main-
tained within a single class instance. The parameters passed to the indexer are used
as a single- or multi-dimensional index to the collection. The example in Listing 3-4
should clarify the concept.
96 Chapter 3 ■ Class Design in C#
The Fabrics class contains an indexer that uses the get and set accessors to
control access to an internal array of Upholstery objects. A single instance of the
Fabrics class is created and assigned to sofaFabric. The indexer allows the inter-
nal array to be directly accessed by an index parameter passed to the object:
The advantage of using an indexer is that it hides the array handling details from
the client, and it can also perform any validation checking or data modification
before returning a value. Indexers are best used with objects whose properties can be
represented as a collection, rather than a scalar value. In this example, the various
fabrics available for sofas form a collection. We could also use the indexer to create a
collection of fabrics used for curtains:
3.5 Methods
Methods are to classes as verbs are to sentences. They perform the actions that
define the behavior of the class. A method is identified by its signature, which con-
sists of the method name and the number and data type of each parameter. A signa-
98 Chapter 3 ■ Class Design in C#
ture is considered unique as long as no other method has the same name and
matching parameter list. In addition to parameters, a method has a return type—
void if nothing is returned—and a modifier list that determines its accessibility and
polymorphic behavior.
For those who haven’t recently boned up on Greek or object-oriented principles,
polymorphism comes from the Greek poly (many) and morphos (shape). In program-
ming terms, it refers to classes that share the same methods but implement them dif-
ferently. Consider the ToString method implemented by all types. When used with
an int type, it displays a numeric value as text; yet on a class instance, it displays the
name of the underlying class—although this default should be overridden by a more
meaningful implementation.
One of the challenges in using .NET methods is to understand the role that
method modifiers play in defining the polymorphic behavior of an application. A
base class uses them to signal that a method may be overridden or that an inheriting
class must implement it. The inheriting class, in turn, uses modifiers to indicate
whether it is overriding or hiding an inherited method. Let’s look at how all this fits
together.
Method Modifiers
In addition to the access modifiers, methods have seven additional modifiers shown
in Table 3-4. Five of these—new, virtual, override, sealed, and abstract—
provide a means for supporting polymorphism.
Modifier Description
static The method is part of the class’s state rather than any instances of the class.
This means that it can be referenced directly by specifying class-
name.method (parameters) without creating an instance of the class.
virtual Designates that the method can be overridden in a subclass. This cannot be
used with static or private access modifiers.
override Specifies that the method overrides a method of the same name in a base
class. This enables the method to define behavior unique to the subclass.
The overriden method in the base class must be virtual.
Modifier Description
Static Modifier
As with other class members, the static modifier defines a member whose behav-
ior is global to the class and not specific to an instance of a class. The modifier is most
commonly used with constructors (described in the next section) and methods in
helper classes that can be used without instantiation.
In this example, the Conversions class contains methods that convert units from
the English to metric system. There is no real reason to create an instance of the
class, because the methods are invariant (the formulas never change) and can be con-
veniently accessed using the syntax classname.method(parameter).
Class Fiber
Class Natural
Class Cotton
Figure 3-2 Relationship between base class and subclasses for Listing 3-6
A subclass can inherit a virtual method without overriding it. If the Cotton class
does not override ShowMe(), it uses the method defined in its base class Natural. In
that case, the call to fib2.ShowMe() would return “Natural”.
class A {
public virtual void PrintID{….}
}
class B: A {
sealed override public void PrintID{…}
}
class C:B {
// This is illegal because it is sealed in B.
override public void PrintID{…}
}
3.5 Methods 103
Passing Parameters
By default, method parameters are passed by value, which means that a copy of the
parameter’s data—rather than the actual data—is passed to the method. Conse-
quently, any change the target method makes to these copies does not affect the orig-
inal parameters in the calling routine. If the parameter is a reference type, such as an
instance of a class, a reference to the object is passed. This enables a called method
to change or set a parameter value.
C# provides two modifiers that signify a parameter is being passed by reference:
out and ref. Both of these keywords cause the address of the parameter to be
passed to the target method. The one you use depends on whether the parameter is
initialized by the calling or called method. Use ref when the calling method initial-
izes the parameter value, and out when the called method assigns the initial value.
By requiring these keywords, C# improves code readability by forcing the program-
mer to explicitly identify whether the called method is to modify or initialize the
parameter.
The code in Listing 3-8 demonstrates the use of these modifiers.
104 Chapter 3 ■ Class Design in C#
In this example, the class MyApp is used to invoke three methods in the
TestParms class:
C# includes one other parameter modifier, params, which is used to pass a vari-
able number of arguments to a method. Basically, the compiler maps the variable
number of arguments in the method invocation into a single parameter in the target
method. To illustrate, let’s consider a method that calculates the average for a list of
numbers passed to it:
Except for the params modifier, the code for this method is no different than that
used to receive an array. Rather than sending an array, however, the invoking code
passes an actual list of arguments:
double avg;
avg = TestParms.GetAvg(12,15, 22, 5, 7 ,19);
avg = TestParms.GetAvg(100.50, 200, 300, 55,88,99,45);
When the compiler sees these method calls, it emits code that creates an array,
populates it with the arguments, and passes it to the method. The params modifier is
essentially a syntactical shortcut to avoid the explicit process of setting up and passing
an array.
106 Chapter 3 ■ Class Design in C#
Core Note
The params keyword can only be used with the last parameter to a
method.
3.6 Constructors
The Common Language Runtime (CLR) requires that every class have a construc-
tor—a special-purpose method that initializes a class or class instance the first time it
is referenced. There are three basic types of constructors: instance, private (a special
case of instance), and static.
Instance Constructor
Syntax:
The syntax is the same as that of the method except that it does not have a return
data type and adds an initializer option. There are a number of implementation
and behavioral differences:
.NET constructs an object by allocating memory, zeroing out the memory, and
calling the instance constructor. The constructor sets the state of the object. Any
fields in the class that are not explicitly initialized are set to zero or null, depending
on the associated member type.
If you try to compile this, you’ll get an error stating that “no overload for Apparel
takes 0 arguments”. Two factors conspire to cause this: first, because Apparel has an
explicit constructor, the compiler does not add a default parameterless constructor to
its class definition; and second, as part of compiling the constructor in the derived
class, the compiler includes a call to the base class’s default constructor—which in
this case does not exist. The solution is either to add a parameterless constructor to
Apparel or to include a call in the derived class to the explicit constructor. Let’s look
at how a constructor can explicitly call a constructor in a base class.
108 Chapter 3 ■ Class Design in C#
Using Initializers
The C# compiler provides the base initializer as a way for a constructor in an inher-
ited class to invoke a constructor in its base class. This can be useful when several
classes share common properties that are set by the base class constructor. Listing
3-9 demonstrates how a derived class, Shirt, uses a base initializer to call a construc-
tor in Apparel.
The compiler matches the signature of this initializer with the instance construc-
tor in the base class that has a matching signature. Thus, when an instance of Shirt
is created, it automatically calls the constructor in Apparel that has one parameter.
This call is made before any code in Shirt is executed.
A second version of the initializer, one that uses the keyword this rather than
base, also indicates which constructor is to be called when the class is instantiated.
However, this refers to a constructor within the class instance, rather than the base
class. This form of the initializer is useful for reducing the amount of compiler code
generated in a class having multiple constructors and several fields to be initialized.
For example, if you examine the generated IL code for the following class, you
would find that the fields fiberType and color are defined separately for each
constructor.
For more efficient code, perform the field initialization in a single constructor and
have the other constructors invoke it using the this initializer.
Private Constructor
Recall that the private modifier makes a class member inaccessible outside its
class. When applied to a class constructor, it prevents outside classes from creating
instances of that class. Although somewhat non-intuitive (what good is a class that
cannot be instantiated?), this turns out to be a surprisingly powerful feature.
Its most obvious use is with classes that provide functionality solely through static
methods and fields. A classic example is the System.Math class found in the Frame-
work Class Library.
It has two static fields, pi and the e (natural logarithmic base), as well as several
methods that return trigonometric values. The methods behave as built-in functions,
and there is no reason for a program to create an instance of the math class in order
to use them.
In the earlier discussion of static methods, we presented a class (refer to Listing
3-5) that performs metric conversions. Listing 3-10 shows this class with the pri-
vate constructor added.
Although a simple example, this illustrates a class that does not require instantia-
tion: The methods are static, and there is no state information that would be associ-
ated with an instance of the class.
A natural question that arises is whether it is better to use the private construc-
tor or an abstract class to prevent instantiation. The answer lies in understanding
the differences between the two. First, consider inheritance. Although an abstract
class cannot be instantiated, its true purpose is to serve as a base for derived classes
(that can be instantiated) to create their own implementation. A class employing a
private constructor is not meant to be inherited, nor can it be. Secondly, recall that
a private constructor only prevents outside classes from instantiating; it does not
prevent an instance of the class from being created within the class itself.
The traits of the private constructor can also be applied to managing object cre-
ation. Although the private constructor prevents an outside method from instanti-
ating its class, it does allow a public method in the class (sometimes called a factory
method) to create an object. This means that a class can create instances of itself,
control how the outside world accesses them, and control the number of instances
created. This topic is discussed in Chapter 4, “Working with Objects in C#.”
Static Constructor
Also known as a class or type constructor, the static constructor is executed after
the type is loaded and before any one of the type members is accessed. Its primary
purpose is to initialize static class members. This limited role results from the many
restrictions that distinguish it from the instance constructor:
Although it does not have a parameter, do not confuse it with a default base con-
structor, which must be an instance constructor. The following code illustrates the
interplay between the static and instance constructors.
class BaseClass
{
private static int callCounter;
// Static constructor
static BaseClass(){
Console.WriteLine("Static Constructor: "+callCounter);
}
// Instance constructors
public BaseClass()
112 Chapter 3 ■ Class Design in C#
{
callCounter+= 1;
Console.WriteLine("Instance Constructor: "+callCounter);
}
// ... Other class operations
}
Output:
Static Constructor: 0
Instance Constructor: 1
Instance Constructor: 2
Instance Constructor: 3
The compiler first emits code to initialize the static field to 0; it then executes the
static constructor code that displays the initial value of callCounter. Next, the base
constructor is executed. It increments the counter and displays its current value,
which is now 1. Each time a new instance of BaseClass is created, the counter is
incremented. Note that the static constructor is executed only once, no matter how
many instances of the class are created.
object1
OnClick
Click
object2
OnClick
delegate object
Figure 3-3 illustrates the fundamental relationship between events and event han-
dlers that is described in this section. You’ll often see this relationship referred to in
terms of publisher/subscriber, where the object setting off the event is the publisher
and the method handling it is the subscriber.
Delegates
Connecting an event to the handling method(s) is a delegate object. This object
maintains a list of methods that it calls when an event occurs. Its role is similar to that
of the callback functions that Windows API programmers are used to, but it repre-
sents a considerable improvement in safeguarding code.
In Microsoft Windows programming, a callback occurs when a function calls
another function using a function pointer it receives. The calling function has no way
of knowing whether the address actually refers to a valid function. As a result, pro-
gram errors and crashes often occur due to bad memory references. The .NET del-
egate eliminates this problem. The C# compiler performs type checking to ensure
that a delegate only calls methods that have a signature and return type matching
that specified in the delegate declaration. As an example, consider this delegate
declaration:
When the delegate is declared, the C# compiler creates a sealed class having the
name of the delegate identifier (MyString). This class defines a constructor that
accepts the name of a method—static or instance—as one of its parameters. It also
contains methods that enable the delegate to maintain a list of target methods. This
means that—unlike the callback approach—a single delegate can call multiple event
handling methods.
A method must be registered with a delegate for it to be called by that delegate.
Only methods that return no value and accept a single string parameter can be reg-
istered with this delegate; otherwise, a compilation error occurs. Listing 3-11 shows
how to declare the MyString delegate and register multiple methods with it. When
114 Chapter 3 ■ Class Design in C#
the delegate is called, it loops through its internal invocation list and calls all the reg-
istered methods in the order they were registered. The process of calling multiple
methods is referred to as multicasting.
Note that the += operator is used to add a method to the invocation list. Con-
versely, a method can be removed using the -= operator:
In the preceding example, the delegate calls each method synchronously, which
means that each succeeding method is called only after the preceding method has
completed operation. There are two potential problems with this: a method could
3.7 Delegates and Events 115
“hang up” and never return control, or a method could simply take a long time to
process—blocking the entire application. To remedy this, .NET allows delegates to
make asynchronous calls to methods. When this occurs, the called method runs on a
separate thread than the calling method. The calling method can then determine
when the invoked method has completed its task by polling it, or having it call back a
method when it is completed. Asynchronous calls are discussed in Chapter 13,
“Asynchronous Programming and Multithreading.”
1. Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides;
Addison-Wesley, 1995.
116 Chapter 3 ■ Class Design in C#
To implement an event handler you must provide the signature defined by the
delegate. You can find this in documentation that describes the declaration of the
MouseEventHandler delegate:
with this code that creates a delegate and includes the code to be executed when the
delegate is invoked:
The code block, which replaces OnMouseDown, requires no method name and is
thus referred to as an anonymous method. Let’s look at its formal syntax:
To further clarify the use of anonymous methods, let’s use them to simplify the
example shown earlier in Listing 3-11. In the original version, a custom delegate is
declared, and two callback methods are implemented and registered with the dele-
gate. In the new version, the two callback methods are replaced with anonymous
code blocks:
// delegate declaration
public delegate void MyString(string s);
When the delegate is called, it executes the code provided in the two anonymous
methods, which results in the input string being printed in all lower- and uppercase
letters, respectively.
Let’s look at a simple example that illustrates the interaction of an event and
delegate:
This code declares the event DataReceived and uses it in the FireReceived-
Event method to fire the event. For demonstration purposes, FireReceivedEvent
is assigned a public access modifier; in most cases, it would be private to ensure
that the event could only be fired within the IOMonitor class. Note that it is good
3.7 Delegates and Events 119
practice to always check the event delegate for null before publishing the event.
Otherwise, an exception is thrown if the delegate’s invocation list is empty (no client
has subscribed to the event).
Only a few lines of code are required to register a method with the delegate and
then invoke the event:
The delegate signature should define a void return type, and have an object and
EventArgs type parameter. The sender parameter identifies the publisher of the
event; this enables a client to use a single method to handle and identify an event that
may originate from multiple sources.
The second parameter contains the data associated with the event. .NET provides
the EventArgs class as a generic container to hold a list of arguments. This offers
several advantages, the most important being that it decouples the event handler
method from the event publisher. For example, new arguments can be added later to
the EventArgs container without affecting existing subscribers.
Creating an EventArgs type to be used as a parameter requires defining a new
class that inherits from EventArgs. Here is an example that contains a single
string property. The value of this property is set prior to firing the event in which it
is included as a parameter.
If an event does not generate data, there is no need to create a class to serve as the
EventArgs parameter. Instead, simply pass EventArgs.Empty.
Core Note
class MyApp
{
public static void Main()
{
EventHandlerClass eClass= new EventHandlerClass();
Seller sell = new Seller();
// Register two event handlers for stocksold event
sell.StockSold += new TraderDelegate(
eClass.HandleStockSale);
sell.StockSold += new TraderDelegate(
eClass.LogTransaction);
// Invoke method to sell stock(symbol, curr price,
sell price)
sell.StartUp("HPQ",100, 26);
}
}
3.8 Operator Overloading 123
The class Seller is at the heart of the application. It performs the stock transac-
tion and signals it by publishing a StockSold event. The client requesting the trans-
action registers two event handlers, HandleStockSale and LogTransaction, to be
notified when the event occurs. Note also how the TraderEvents class exposes the
transaction details to the event handlers.
Operator overloading with classes does not have to be limited to geometric or spa-
tial objects. The example shown in Listing 3-14 demonstrates how to use the concept
to maintain stocks in a portfolio. It contains two classes: one that represents a stock
(defined by its price, number of shares, and risk factor) and one that represents the
portfolio of stocks. Two overloaded operators (+ and -) add and remove stocks from
the portfolio.
124 Chapter 3 ■ Class Design in C#
The addition or deletion of a stock causes the portfolio total value and weighted
risk factor to be adjusted. For example, when both stocks are added to the portfolio,
the risk is 1.07. Remove ibm and the risk is 1.10, the risk of hpq alone.
When choosing to implement operator overloading, be aware that .NET lan-
guages are not required to support it. The easiest way to provide interoperability for
those languages (such as Visual Basic.NET) lacking this feature is to include an addi-
tional class member that performs the same function:
In this case, the code exposes a public method whose implementation calls the over-
loaded method.
Another approach is to take advantage of the fact that language interaction occurs
at the Intermediate Language level and that each operator is represented in the IL
by a hidden method. Thus, if a language knows how to invoke this method, it can
access the operator.
The ECMA standard provides a list of method names that correspond to each
operator. For example, the + and & used in the preceding code are represented by
op_Addition and op_BitwiseAnd, respectively. A language would access the over-
loaded + operator with its own syntactic variation of the following code:
newPortfolio = P.op_Addition(P, s)
126 Chapter 3 ■ Class Design in C#
Either approach works, but relying on assembly language is probably less appeal-
ing than providing a custom public method.
Although the discussion has been on classes, operator overloading also can be
applied to simple data types. For example, you could define your own exponentiation
operator for integers.
3.9 Interfaces
Syntax:
The syntax of the interface declaration is identical to that of a class except that the
keyword interface replaces class. This should not be surprising, because an
interface is basically a class that declares, but does not implement, its members. An
instance of it cannot be created, and classes that inherit from it must implement all of
its methods.
This sounds similar to an abstract class, which also cannot be instantiated and
requires derived classes to implement its abstract methods. The difference is that an
abstract class has many more capabilities: It may be inherited by subclasses, and it
may contain state data and concrete methods.
One rule of thumb when deciding whether to use a class or interface type is the
relationship between the type and its inheriting classes. An interface defines a behav-
ior for a class—something it “can do.” The built-in .NET ICloneable interface,
which permits an object to create a copy of itself, is an example of this. Classes, on
the other hand, should be used when the inheriting class is a “type of” the base class.
For example, you could create a shape as a base class, a circle as a subclass of shape,
and the capability to change the size of the shape as an interface method.
Aside from their differing roles, there are numerous implementation and usage
differences between a class and an interface:
The first statement creates an instance of the Circle class. The second statement
creates a variable that refers to this circle object. However, because it is specified as
an IShapeFunction type, it can only access members of that interface. This is why
an attempt to reference the ShowMe method fails. By using the interface type, you
effectively create a filter that restricts access to members of the interface only.
One of the most valuable aspects of working with interfaces is that a programmer
can treat disparate classes in a similar manner, as long at they implement the same
interface.
3.9 Interfaces 129
This code creates an array that can contain any class that implements the IShape-
Function interface. Its only interest is in using the GetArea method, and it neither
has nor requires any knowledge of other class members. We can easily extend this
example to create a class to work with this array that has no knowledge of the Circle
or Rectangle class.
The code in this class can be used with any concrete class that implements the
IShapeFunction interface.
Core Approach
This not only permits a class to implement multiple methods, but has the added
effect of limiting access to this method to interface references only. For example, the
following would result in an error:
3.10 Generics
To understand and appreciate the concept of generics, consider the need to create a
class that will manage a collection of objects. The objects may be of any type and are
specified at compile time by a parameter passed to the class. Moreover, the collection
class must be type-safe—meaning that it will accept only objects of the specified
type.
In the 1.x versions of .NET, there is no way to create such a class. Your best option
is to create a class that contains an array (or other container type) that treats every-
thing as an object. As shown here, casting, or an as operator, is then required to
access the actual object type. It is also necessary to include code that verifies the
stored object is the correct type.
Generics, introduced with .NET 2.0, offer an elegant solution that eliminates the
casting, explicit type checking, and boxing that occurs for value type objects. The pri-
mary challenge of working with generics is getting used to the syntax, which can be
used with a class, interface, or structure.
The best way to approach the syntax is to think of it as a way to pass the data type
you’ll be working with as a parameter to the generic class. Here is the declaration for
a generic class:
The type parameter T is placed in brackets and serves as a placeholder for the
actual type. The compiler recognizes any reference to T within the body of the class
and replaces it with the actual type requested. As this statement shows, creating an
instance of a generic class is straightforward:
In this case, string is a type argument and specifies that the class is to work with
string types only. Note that more than one type parameter may be used, and that
132 Chapter 3 ■ Class Design in C#
the type parameter can be any name, although Microsoft uses (and recommends)
single characters in its generic classes.
Although a class may be generic, it can restrict the types that it will accept. This is
done by including an optional list of constraints for each type parameter. To
declare a constraint, add the where keyword followed by a list of parameter/require-
ment pairs. The following declaration requires that the type parameter implement
the ISerializable and IComparable interfaces:
A parameter may have multiple interface constraints and a single class restraint.
In addition, there are three special constraints to be aware of:
The following code demonstrates how a client could access the generic class
described in Listing 3-16:
Generics and the .NET generic collection classes are discussed further in
Chapter 4.
134 Chapter 3 ■ Class Design in C#
3.11 Structures
A .NET structure—struct in C# syntax—is often described as a lightweight class. It
is similar to a class in that its members include fields, methods, properties, events,
and constructors; and it can inherit from interfaces. But there are also implementa-
tion differences and restrictions that limit its capabilities vis-à-vis a class:
Because a struct is a value type, it is stored on the stack where a program works
directly with its contents. This generally provides quicker access than indirectly
accessing data through a pointer to the heap. On the downside, structs can slow
things down when passed back and forth as parameters. Rather than passing a refer-
ence, the CLR copies a struct and sends the copy to the receiving method. Also, a
struct faces the boxing and unboxing issue of value types.
When deciding whether to use a struct to represent your data, consider the fact
that types that naturally have value semantics (objects that directly contain their
value as opposed to a reference to a value) are often implemented underneath as a
struct. Examples include the primitives discussed in Chapter 2, the color struc-
ture whose properties (red, aqua, and so on) define colors in .NET, the DateTime
structure used for date-related operations, and various graphics structures used to
represent objects such as points and rectangles.
Defining Structures
Syntax:
Example:
// constructor
public DressShirt(float collar, int sleeve)
{
this.CollarSz = collar;
this.SleeveLn = sleeve;
}
}
The syntax clearly resembles a class. In fact, replace struct with class and the
code compiles. It has a public modifier that permits access from any assembly. The
default modifier is internal, which restricts access to the containing assembly. No
interfaces are specified—although a struct can inherit from an interface—so the
struct is not required to implement any specific methods.
Core Note
The first statement creates an instance that relies on the user-defined constructor
to initialize the field values. When designing such a constructor, be aware that you
must initialize all fields within the constructor; otherwise, the compiler will issue an
error.
The second statement also creates an instance of the struct by calling the
default constructor, which initializes the fields to default values of zero (0). Note the
difference here between a struct and a class: A struct always has a default param-
eterless constructor; a class has the default parameterless constructor only if there
are no explicitly defined constructors.
136 Chapter 3 ■ Class Design in C#
The most important thing to note about this code is that it could be cut and pasted
into a class and run with no changes. Although a struct doesn’t support all of the
features of a class, the ones it does are implemented using identical syntax.
Structure Class
It is clear from the table that structures possess many of the features and capabili-
ties of classes. Consequently, a developer may have difficulty deciding which is the
better choice. The answer lies in understanding the few—but significant—differ-
ences between the two.
3.13 Summary
The goal of the C# language architects was to create a “component-oriented” lan-
guage based on the traditional object-oriented principles of encapsulation, inherit-
ance, and polymorphism. Toward this end, they included language features that
make properties, events, and attributes first-class language constructs. Properties
now have their own get and set syntax; events can be created with the event key-
word and linked to delegates that call registered methods when the event occurs;
custom or built-in attributes can be attached to a class or selected class members
to add descriptive information to an assembly’s metacode.
C# provides several forms of method declarations: a virtual method permits a
derived class to implement its own version of the method; sealed prevents a derived
class from overriding it; and abstract requires a derived class to implement its own
version of the method.
In some cases, a structure or interface provides a better programming solution
than a class. The C# struct is an efficient, simple way to represent self-contained
data types that don’t require the overhead of classes. An interface is a practical way to
define a behavior that can be passed on to inheriting classes. In .NET, its value is
enhanced because a class (or struct) can inherit from any number of interfaces—
but from only one class.
140 Chapter 3 ■ Class Design in C#
2. How many classes can a class directly inherit? How many interfaces?
a. x=60 y=40
b. x=20 y=60
c. x=20 y=40
d. x=60 y=60
3.14 Test Your Understanding 141
7. What is the best way to ensure that languages that do not recognize
operator overloading can access C# code containing this feature?
8. Name two ways that you can prevent a class from being instantiated.
10. What happens if you attempt to compile and run the following code?
using System;
public class Base
{
public void aMethod(int i, String s)
{
Console.WriteLine("Base Method");
}
public Base()
{
Console.WriteLine("Base Constructor");
}
}
The purpose of this chapter is to consider what happens to a class when it becomes
an object. This metamorphosis raises some interesting questions: What is the best
way to create an object? How do you ensure that an object handles errors gracefully?
How do you prevent an object from wasting resources (the dreaded memory leak)?
What is the best way to work with groups of objects? How do you dispose of an
object? Although these questions are unlikely to keep a developer awake at night,
their consideration should lead to a keener insight into class design.
In an attempt to answer these questions, a variety of topics are presented. These
include how to create objects using established design patterns; how to implement the
System.Object methods on custom classes; how to implement exception handling;
how to persist objects using serialization; and how to use collection classes and inter-
faces to manage groups of objects. The chapter concludes with a look at how to design
an object so that it shuts down properly when subject to .NET Garbage Collection.
145
146 Chapter 4 ■ Working with Objects in C#
sole purpose is to create other objects—much like a real-world factory. Its advantage
is that it handles all the details of object creation; a client instructs the factory which
object to create and is generally unaffected by any implementation changes that may
occur.
There are a number of ways to implement the factory pattern. This section pre-
sents two logical approaches—illustrated in Figures 4-1 and 4-2.
X Factory X X
X
Client 1 Client 1
Factory Y Factory Y Y
Client 2 Client 2
Z Z Z
Factory Z
Product Product
Figure 4-1 Factory with one factory class Figure 4-2 Factory with multiple factory
classes
Figure 4-1 represents the case where one factory is used to produce all of the
related products (objects). In Figure 4-2, each product has its own factory, and the
client sends the request to the factory that produces the desired object. We’ll look at
examples of both, beginning with code for the single factory implementation (see
Listing 4-1).
The same effect could be achieved by replacing the interface with an abstract
class. This could yield better code reuse in situations where objects share common
behavior, but should be weighed against other factors that were discussed in Chapter
3, “Class Design in C#.”
With the factory and product classes defined, all the hard work has been done. It’s
a simple matter for clients to create objects:
148 Chapter 4 ■ Working with Objects in C#
If the application needs to add any more products, the factory is supplied with the
new code, but no changes are required on the client side. It only needs to be aware of
how to request all available products.
// abstract
public abstract class AppFactory
{
public abstract IApparel CreateApparel();
}
// Concrete factory classes
public class DressShirtFactory:AppFactory
{
public override IApparel CreateApparel( )
{ return new DressShirt(); }
}
public class SportShirtFactory : AppFactory
{
public override IApparel CreateApparel( )
{ return new SportsShirt(); }
}
We have created the abstract class so that its subclasses can be passed to a new
ApparelCollector class that serves as an intermediary between the clients and the
factories. Specifically, the client passes the factory to this class, and it is responsible
for calling the appropriate factory.
The code to use the new class is analogous to that in the first example:
For a simple example like this, the first approach using one factory is easier to
implement. However, there are cases where it’s preferable to have multiple factories.
The objects may be grouped into families of products (a shirt factory and a dress fac-
tory, for example), or you may have a distributed application where it makes sense for
different developers to provide their own factory classes.
System.Exception Class
As shown in Figure 4-3, System.Exception is the base class for two generic sub-
classes—SystemException and ApplicationException—from which all excep-
tion objects directly inherit. .NET Framework exceptions (such as IOException
and ArithmeticException) derive directly from IOException, whereas custom
application exceptions should inherit from ApplicationException. The sole pur-
pose of these classes is to categorize exceptions, because they do not add any proper-
ties or methods to the base System.Exception class.
System.Object
System.Exception
System.ApplicationException
System.SystemException
The System.Exception class contains relatively few members. Table 4-1 sum-
marizes the members discussed in this section.
InnerException Exception Is set to null unless the exception occurs while a pre-
vious exception is being handled. A GetBaseExcep-
tion method can be used to list a chain of previous
inner exceptions.
TargetSite MethodBase Provides details about the method that threw the
exception. The property is an object of type Method-
Base. It returns the name of the method in which the
exception occurred. It also has a DeclaringType
property that returns the name of the class containing
the method.
try {
// Code that may cause an exception.
// It may consist of multiple lines of code.
}
// May contain any number of catch blocks.
catch(exception name) {
// Place code here that handles the exception.
// Catch block may contain a throw statement.
}
catch(exception name) {
// Place code here that handles the exception.
}
finally {
// This code is always executed whether or not an
// exception occurs.
}
The exception filter identifies the exception it handles and also serves as a param-
eter when an exception is thrown to it. Consider the following statement:
This codes first looks for specific exceptions such as a division by zero or an index
out of range. The final exception filter, Exception, catches any exception derived
from System.Exception. When an exception is caught, the code in the block is
executed and all other catch blocks are skipped. Control then flows to the finally
block—if one exists.
Note that the catch block may include a throw statement to pass the exception
further up the call stack to the previous caller. The throw statement has an optional
4.2 Exception Handling 153
parameter placed in parentheses that can be used to identify the type of exception
being thrown. If throw is used without a parameter, it throws the exception caught
by the current block. You typically throw an exception when the calling method is
better suited to handle it.
Core Recommendation
StackTrace displays only those methods on the call stack to the level
where the exception is first handled—not where it occurs. Although you
may be tempted to catch exceptions at the point where they occur in
order to view the full call stack, this is discouraged. It may improve
diagnostics; however, it takes time and space to throw an exception and
its entire call stack. Usually, the lower on a call stack that an exception
occurs, the more likely it is that the conditions causing it can be avoided
by improved coding logic.
4.2 Exception Handling 155
To view the custom exception in action, let’s create two shape objects and pass
them via calls to the static ObjAreas.ShowAreas method.
{
ObjAreas.ShowAreas(myRect);
ObjAreas.ShowAreas(myCircle);
}
catch (NoDescException ex)
{
Console.WriteLine(ex.Message);
}
The ShowAreas method checks to ensure the object it has received implements
the two interfaces. If not, it throws an instance of NoDescException and control
passes to the calling code. In this example, the Circle object implements only one
interface, resulting in an exception.
Pay particular attention to the design of NoDescException. It is a useful model
that illustrates the rules to be followed in implementing a custom exception type:
Unhandled Exceptions
Unhandled exceptions occur when the CLR is unable to find a catch filter to handle
the exception. The default result is that the CLR will handle it with its own methods.
Although this provides a warning to a user or developer, it is not a recommended way
158 Chapter 4 ■ Working with Objects in C#
to deal with it. The solution is in the problem: Take advantage of .NET’s unhandled
exception event handlers to funnel all of the exceptions to your own custom excep-
tion handling class.
The custom class provides a convenient way to establish a policy for dealing with
unhandled exceptions. The code can be implemented to recognize whether it is
dealing with a debug or release version, and respond accordingly. For example, in
debug version, your main concern is to start the debugger; in a release version, you
should log the error and provide a meaningful screen that allows the user to end the
program.
Unfortunately, there is no single approach that applies to all C# programming
needs. Your actual solution depends on whether you are working with a Console,
Windows Form, Web Forms, or Web Services application. In this section, we will
look at how to implement a Windows Forms solution, which is conceptually the same
as for a Console application. Web Forms and Web Services are addressed in the Web
Applications chapters, Chapters 16–18.
Unhandled Exceptions in a
Windows Forms Application
Event handling was discussed in the previous chapter along with the important role
of delegates. We can now use those techniques to register our own callback method
that processes any unhandled exceptions thrown in the Windows application.
When an exception occurs in Windows, the application’s OnThreadException
method is ultimately called. It displays a dialog box describing the unhandled excep-
tion. You can override this by creating and registering your own method that matches
the signature of the System.Threading.ThreadExceptionEventHandler dele-
gate. Listing 4-4 shows one way this can be implemented.
MyUnhandledMethod is defined to handle the exception and must be registered
to receive the callback. The following code registers the method for the Thread-
Exception event using the ThreadExceptionEventHandler delegate and runs
the application:
For a Console application, the same approach is used except that the delegate and
event names are different. You would register the method with the following:
Thread.GetDomain().UnhandledException += new
UnhandledExceptionEventHandler(
UnForgiven.MyUnhandledMethodAp);
Only catch exceptions when you have a specific need to do the following:
• Perform a recovery
• Perform a cleanup, particularly to release resources
• Log information
• Provide extra debug information
ex.ToString() // Output:
// Attempted to divide by zero
// at TestExcep.Calc(Int32 j)
// at MyApp.Main()
The following statements create an instance of the object and set desc to the
more meaningful value returned by ToString():
This method compares an instance of the current object with the one passed to it.
The first step is to ensure that the received object is not null. Next, following the steps
in Figure 4-5, the types of the two objects are compared to make sure they match.
164 Chapter 4 ■ Working with Objects in C#
1 2 3 4 5 6
The heart of the method consists of comparing the field values of the two objects.
To compare reference fields, it uses the static Object.Equals method, which takes
two objects as arguments. It returns true if the two objects reference the same
instance, if both objects are null, or if the object’s Equals comparison returns true.
Value types are compared using the field’s Equals method:
if (!myID.Equals(otherObj.myID))
return false;
Here is an example that demonstrates the new Equals method. It creates two
Chair objects, sets their fields to the same value, and performs a comparison:
Although the two objects have identical field values, the comparison fails—which
is probably not what you want. The reason is that the objects point to two different
myUpholstery instances, causing their reference comparison to fail. The solution is
to override the Equals method in the Upholstery class, so that it performs a value
comparison of the Fabric fields. To do so, place this code inside its Equals method,
in addition to the other overhead code shown in Listing 4-6:
Overriding GetHashCode
The GetHashCode method generates an Int32 hash code value for any object. This
value serves as an identifier that can be used to place any object in a hash table col-
lection. The Framework designers decreed that any two objects that are equal must
have the same hash code. As a result, any new Equals method must be paired with a
GetHashCode method to ensure that identical objects generate the same hash code
value.
4.3 Implementing System.Object Methods in a Custom Class 165
Ideally, the hash code values generated over the range of objects represent a wide
distribution. This example used a simple algorithm that calls the base type’s Get-
HashCode method to return a value based on the item’s ID. This is a good choice
because the IDs are unique for each item, the ID is an instance field, and the ID
field value is immutable—being set only in the constructor.
The method returns true because chair1 and chair3 reference the same
instance.
Figure 4-6 depicts a shallow and deep copy for this instance of the Chair class:
In both cases, the clone of the myChair object contains its own copy of the value
type fields. However, the shallow copy points to the same instance of myUpholstery
as the original; in the deep copy, it references a duplicate object.
Let’s now look at how to implement shallow cloning on a custom object. Deep
cloning (not discussed) is specific to each class, and it essentially requires creating an
instance of the object to be cloned and copying all field values to the clone. Any ref-
erence objects must also be created and assigned values from the original referenced
objects.
The only requirements are that the class inherit the ICloneable interface and
implement the Clone method using MemberwiseClone. To demonstrate, let’s use
this code segment to create a shallow copy clone of myChair by calling its Clone
method:
The results confirm this is a shallow copy: The reference comparison of myChair
and its clone fails because chairClone is created as a copy of the original object; on
the other hand, the comparison of the reference field myUpholstery succeeds
because the original and clone objects point to the same instance of the myUphol-
stery class.
To best work with container classes such as the ArrayList and Hashtable, a
developer should be familiar with the interfaces they implement. Interfaces not only
provide a uniform way of managing and accessing the contents of a collection, but
they are the key to creating a custom collection. We’ll begin the section by looking at
the most useful interfaces and the behavior they impart to the collection classes.
Then, we’ll examine selected collection classes.
Collection Interfaces
Interfaces play a major role in the implementation of the concrete collection classes.
All collections inherit from the ICollection interface, and most inherit from the
IEnumerable interface. This means that a developer can use common code seman-
tics to work with the different collections. For example, the foreach statement is
used to traverse the elements of a collection whether it is a Hashtable,a Sorted-
List, or a custom collection. The only requirement is that the class implements the
IEnumerable interface.
Table 4-2 summarizes the most important interfaces inherited by the collection
classes. IComparer provides a uniform way of comparing elements for the purpose of
sorting; IDictionary defines the special members required by the Hashtable and
Dictionary objects; similarly, IList is the base interface for the ArrayList collection.
Interface Description
ICollection The base interface for the collection classes. It contains prop-
erties to provide the number of elements in the collection and
whether it is thread-safe. It also contains a method that copies
elements of the collection into an array.
IList The base interface of all lists. It controls whether the elements
in the list can be modified, added, or deleted.
4.4 Working with .NET Collection Classes and Interfaces 169
The UML-like diagram in Figure 4-7 shows how these interfaces are related.
Recall that interfaces may inherit from multiple interfaces. Here, for example, IDic-
tionary and IList inherit from IEnumerable and ICollection. Also of special
interest is the GetEnumerator method that is used by interfaces to return the
IEnumerator interface.
<<Interface>>
ICollection <<Interface>>
IDictionaryEnumerator
Count
IsSynchronized Entry
SyncRoot Key
CopyTo Value
<<Interface>> <<Interface>>
IList IDictionary
... ...
Of these interfaces, IDictionary and IList are the most important when con-
sidering the built-in collections provided by the FCL. For this reason, we’ll discuss
them later in the context of the collection classes.
ICollection Interface
This interface provides the minimal information that a collection must implement.
All classes in the System.Collections namespace inherit it (see Table 4-3).
The IsSynchronized and SyncRoot properties require explanation if you are
not yet familiar with threading (discussed in Chapter 13, “Asynchronous Program-
ming and Multithreading”). Briefly, their purpose is to ensure the integrity of data in
170 Chapter 4 ■ Working with Objects in C#
Member Description
int Count Property that returns the number of entries in the col-
lection.
void CopyTo( array, index) Method to copy the contents of the collection to an
array.
IHashCodeProvider Interface
This interface has one member—the GetHashCode method—that uses a custom
hash function to return a hash code value for an object. The Hashtable class uses
this interface as a parameter type in one of its overloaded constructors, but other
than that you will rarely see it.
Member Description
The compiler expands the foreach construct into a while loop that employs
IEnumerable and IEnumerator members:
Core Note
Iterators
The foreach construct provides a simple and uniform way to iterate across mem-
bers of a collection. For this reason, it is a good practice—almost a de facto require-
ment—that any custom collection support foreach. Because this requires that the
collection class support the IEnumerable and IEnumerator interfaces, the tradi-
tional approach is to explicitly implement each member of these interfaces inside the
collection class. Referencing Table 4-4, this means writing code to support the Get-
Enumerator method of the IEnumerable interface, and the MoveNext, Current,
and Reset members of IEnumerator. In essence, it is necessary to build a state
machine that keeps track of the most recently accessed item in a collection and
knows how to move to the next item.
C# 2.0 introduced a new syntax referred to as iterators that greatly simplifies the
task of implementing an iterator pattern. It includes a yield return (also a yield
break) statement that causes the compiler to automatically generate code to imple-
ment the IEnumerable and IEnumerator interfaces. To illustrate, let’s add iterators
to the GenStack collection class that was introduced in the generics discussion in
Chapter 3. (Note that iterators work identically in a non-generics collection.)
Listing 4-7 shows part of the original GenStack class with two new members that
implement iterators: a GetEnumerator method that returns an enumerator to
traverse the collection in the order items are stored, and a Reverse property that
returns an enumerator to traverse the collection in reverse order. Both use yield
return to generate the underlying code that supports foreach iteration.
4.4 Working with .NET Collection Classes and Interfaces 173
This code should raise some obvious questions about iterators: Where is the
implementation of IEnumerator? And how can a method with an IEnumerator
return type or a property with an IEnumerable return type seemingly return a
string value?
174 Chapter 4 ■ Working with Objects in C#
The answer to these questions is that the compiler generates the code to take care
of the details. If the member containing the yield return statement is an IEnu-
merable type, the compiler implements the necessary generics or non-generics ver-
sion of both IEnumerable and IEnumerator; if the member is an IEnumerator
type, it implements only the two enumerator interfaces. The developer’s responsibil-
ity is limited to providing the logic that defines how the collection is traversed and
what items are returned. The compiler uses this logic to implement the IEnumera-
tor.MoveNext method.
The client code to access the GenStack collection is straightforward. An instance
of the GenStack class is created to hold ten string elements. Three items are
added to the collection and are then displayed in original and reverse sequence.
The Reverse property demonstrates how easy it is to create multiple iterators for
a collection. You simply implement a property that traverses the collection in some
order and uses tbe yield return statement(s) to return an item in the collection.
Core Note
IComparable
Unlike the other interfaces in this section, IComparable is a member of the System
namespace. It has only one member, the method CompareTo:
The object in parentheses is compared to the current instance of the object imple-
menting CompareTo, and the returned value indicates the results of the comparison.
Let’s use this method to extend the Chair class so that it can be sorted on its
myPrice field. This requires adding the IComparable inheritance and implement-
ing the CompareTo method.
The code to sort an array of Chair objects is straightforward because all the work
is done inside the Chair class:
IComparer
The previous example allows you to sort items on one field. A more flexible and real-
istic approach is to permit sorting on multiple fields. This can be done using an over-
loaded form of Array.Sort that takes an object that implements IComparer as its
second parameter.
IComparer is similar to IComparable in that it exposes only one member, Com-
pare, that receives two objects. It returns a value of –1, 0, or 1 based on whether the
first object is less than, equal to, or greater than the second. The first object is usually
the array to be sorted, and the second object is a class implementing a custom Com-
pare method for a specific object field. This class can be implemented as a separate
helper class or as a nested class within the class you are trying to sort (Chair).
This code creates a helper class that sorts the Chair objects by the myVendor
field:
If you refer back to the Chair class definition (refer to Figure 4-6 on page 166),
you will notice that there is a problem with this code: myVendor is a private member
and not accessible in this outside class. To make the example work, change it to pub-
lic. A better solution, of course, is to add a property to expose the value of the field.
In order to sort, pass both the array to be sorted and an instance of the helper
class to Sort:
Array.Sort(chairsOrdered,new CompareByVen());
In summary, sorting by more than one field is accomplished by adding classes that
implement Compare for each sortable field.
System.Collections Namespace
The classes in this namespace provide a variety of data containers for managing col-
lections of data. As shown in Figure 4-8, it is useful to categorize them based on the
primary interface they implement: ICollection, IList, or IDictionary.
Collections
Stacks play a useful role for an application that needs to maintain state informa-
tion in order to hold tasks to be “performed later.” The call stack associated with
exception handling is a classic example; stacks are also used widely in text parsing
operations. Listing 4-8 provides an example of some of the basic stack operations.
Three objects are added to the stack. PrintValues enumerates the stack and
lists the objects in the reverse order they were added. The Pop method removes
“Lane” from the top of the stack. A new object is pushed onto the stack and the Peek
method lists it. Note that the foreach statement could also be used to list the con-
tents of the Stack.
ArrayList
The ArrayList includes all the features of the System.Array, but also extends it to
include dynamic sizing and insertion/deletion of items at a specific location in the list.
These additional features are defined by the IList interface from which ArrayList
inherits.
IList Interface
This interface, whose members are listed in Table 4-6, is used to retrieve the con-
tents of a collection via a zero-based numeric index. This permits insertion and
removal at random location within the list.
The most important thing to observe about this interface is that it operates on
object types. This means that an ArrayList—or any collection implementing
IList—may contain types of any kind. However, this flexibility comes at a cost:
Casting must be widely used in order to access the object’s contents, and value types
must be converted to objects (boxed) in order to be stored in the collection. As we
see shortly, C# 2.0 offers a new feature—called generics—that addresses both issues.
However, the basic functionality of the ArrayList, as illustrated in this code seg-
ment, remains the same.
180 Chapter 4 ■ Working with Objects in C#
Interface Description
int Add(object) Adds an item to the end of a list. It returns the value
of the index where the item was added.
void Insert (index, object) Methods to insert a value at a specific index; delete
void RemoveAt (index) the value at a specific index; and remove the first
void Remove (object) occurrence of an item having the specified value.
Hashtable
The Hashtable is a .NET version of a dictionary for storing key-value pairs. It asso-
ciates data with a key and uses the key (a transformation algorithm is applied) to
determine a location where the data is stored in the table. When data is requested,
the same steps are followed except that the calculated memory location is used to
retrieve data rather than store it.
Syntax:
As shown here, the Hashtable inherits from many interfaces; of these, IDic-
tionary is of the most interest because it provides the properties and methods used
to store and retrieve data.
IDictionary Interface
Collections implementing the IDictionary interface contain items that can be
retrieved by an associated key value. Table 4-7 summarizes the most important mem-
bers for working with such a collection.
Member Description
ICollection Keys Properties that return the keys and values of the collection.
ICollection Values
void Add (key, value) Methods to add a key-value pair to a collection, remove
void Clear () a specific key, and remove all items (clear) from the
void Remove (key) collection.
IDictionaryEnumerator Interface
As shown in Figure 4-7 on page 169, IDictionaryEnumerator inherits from
IEnumerator. It adds properties to enumerate through a dictionary by retrieving
keys, values, or both.
Member Description
DictionaryEntry Entry The variable Entry is used to retrieve both the key and
value when iterating through a collection.
object Key Properties that return the keys and values of the current
object Value collection entry.
All classes derived from IDictionary maintain two internal lists of data: one for
keys and one for the associated value, which may be an object. The values are stored
in a location based on the hash code of the key. This code is provided by the key’s
System.Object.GetHashCode method, although it is possible to override this with
your own hash algorithm.
This structure is efficient for searching, but less so for insertion. Because keys may
generate the same hash code, a collision occurs that requires the code be recalcu-
lated until a free bucket is found. For this reason, the Hashtable is recommended
for situations where a large amount of relatively static data is to be searched by key
values.
Create a Hashtable
A parameterless constructor creates a Hashtable with a default number of buckets
allocated and an implicit load factor of 1. The load factor is the ratio of values to
buckets the storage should maintain. For example, a load factor of .5 means that a
hash table should maintain twice as many buckets as there are values in the table.
The alternate syntax to specify the initial number of buckets and load factor is
The following code creates a hash table and adds objects to it:
// Create HashTable
Hashtable chairHash = new Hashtable();
// Add key - value pair to Hashtable
chairHash.Add ("88-00", new Chair(350.0, "Adams", "88-00");
chairHash.Add ("99-03", new Chair(380.0, "Lane", "99-03");
4.4 Working with .NET Collection Classes and Interfaces 183
// or this syntax
chairHash["89-01"] = new Chair(250.0, "Broyhill", "89-01");
There are many ways to add values to a Hashtable, including loading them from
another collection. The preceding example shows the most straightforward
approach. Note that a System.Argument exception is thrown if you attempt to add a
value using a key that already exists in the table. To check for a key, use the Con-
tainsKey method:
// List Keys
foreach (string invenKey in chairHash.Keys)
{ MessageBox.Show(invenKey); }
// List Values
foreach (Chair chairVal in chairHash.Values)
{ MessageBox.Show(chairVal.myVendor);}
Core Note
This section has given you a flavor of working with System.Collections inter-
faces and classes. The classes presented are designed to meet most general-purpose
programming needs. There are numerous other useful classes in the namespace as
well as in the System.Collections.Specialized namespace. You should have
little trouble working with either, because all of their classes inherit from the same
interfaces presented in this section.
System.Collections.Generic Namespace
Recall from Chapter 3 that generics are used to implement type-safe classes, struc-
tures, and interfaces. The declaration of a generic type includes one (or more) type
parameters in brackets (<>) that serve(s) as a placeholder for the actual type to be
used. When an instance of this type is created, the client uses these parameters to
pass the specific type of data to be used in the generic type. Thus, a single generic
class can handle multiple types of data in a type-specific manner.
No classes benefit more from generics than the collections classes, which stored
any type of data as an object in .NET 1.x. The effect of this was to place the burden
of casting and type verification on the developer. Without such verification, a single
ArrayList instance could be used to store a string, an integer, or a custom object.
Only at runtime would the error be detected.
The System.Collections.Generic namespace provides the generic versions
of the classes in the System.Collections namespace. If you are familiar with the
non-generic classes, switching to the generic type is straightforward. For example,
this code segment using the ArrayList:
4.4 Working with .NET Collection Classes and Interfaces 185
The declaration of List includes a type parameter that tells the compiler what
type of data the object may contain—int in this case. The compiler then generates
code that expects the specified type. For the developer, this eliminates the need for
casting and type verification at runtime. From a memory usage and efficiency stand-
point, it also eliminates boxing (conversion to objects) when primitives are stored in
the collection.
System.Collections System.Collections.Generic
Comparer Comparer<T>
Hashtable Dictionary<K,T>
ArrayList List<T>
Queue Queue<T>
SortedList SortedDictionary<K,T>
Stack Stack<T>
ICollection ICollection<T>
IComparable IComparable<T>
IComparer IComparer<T>
IDictionary IDictionary<K,T>
IEnumerable IEnumerable<T>
IEnumerator IEnumerator<T>
IKeyComparer IKeyComparer<T>
IList IList<T>
(not applicable) LinkedList<T>
186 Chapter 4 ■ Working with Objects in C#
The only other points to note regard IEnumerator. Unlike the original version,
the generics version inherits from IDisposable and does not support the Reset
method.
Observe how data is retrieved from the Hashtable. Because data is stored as an
object, verification is required to ensure that the object being retrieved is a Chair
4.5 Object Serialization 187
type; casting is then used to access the members of the object. These steps are unnec-
essary when the type-safe Dictionary class is used in place of the Hashtable.
The Dictionary<K,V> class accepts two type parameters that allow it to be
strongly typed: K is the key type and V is the type of the value stored in the collection.
In this example, the key is a string representing the unique product identifier, and
the value stored in the Dictionary is a Chair type.
Serialization is used primarily for two tasks: to implement Web Services and to
store (persist) collections of objects to a medium from which they can be later resur-
rected. Web Services and their use of XML serialization are discussed in Chapter 18,
“XML Web Services.” This section focuses on how to use binary serialization to store
and retrieve objects. The examples use File I/O (see Chapter 5) methods to read and
write to a file, which should be easily understood within the context of their usage.
Binary Serialization
The BinaryFormatter object that performs binary serialization is found in the
System.Runtime.Serialization.Formatters.Binary namespace. It performs
serialization and deserialization using the Serialize and Deserialize methods,
respectively, which it inherits from the IFormatter interface.
Listing 4-9 provides an example of binary serialization using simple class mem-
bers. A hash table is created and populated with two Chair objects. Next, a
FileStream object is instantiated that points to a file on the local drive where the
serialized output is stored. A BinaryFormatter is then created, and its Serialize
method is used to serialize the hash table’s contents to a file. To confirm the process,
the hash table is cleared and the BinaryFormatter object is used to deserialize the
contents of the file into the hash table. Finally, one of the members from a restored
object in the hash table is printed—verifying that the original contents have been
restored.
[NonSerialized]
public Upholstery myUpholstery;
The primary reason for marking a field NonSerialized is that it may have no
meaning where it is serialized. Because an object graph may be loaded onto a
machine different from the one on which it was stored, types that are tied to system
operations are the most likely candidates to be excluded. These include delegates,
events, file handles, and threads.
Core Note
An event handler for these events is implemented in the object being serialized
and must satisfy two requirements: the attribute associated with the event must be
attached to the method, and the method must have this signature:
To illustrate, here is a method called after all objects have been deserialized. The
binary formatter iterates the list of objects in the order they were deserialized and
calls each object’s OnDeserialized method. This example uses the event handler to
selectively update a field in the object. A more common use is to assign values to
fields that were not serialized.
Note that more than one method can have the same event attribute, and that
more than one attribute can be assigned to a method—although the latter is rarely
practical.
[OptionalField]
private string finish;
The presence of the attribute causes the formatter to assign a default null value
to the finish field, and no exception is thrown. The application may also take advan-
tage of the deserialized event to assign a value to the new field:
192 Chapter 4 ■ Working with Objects in C#
(garbage) and compact the heap memory. This is a complicated process because the
collector must deal with the twin tasks of updating all old references to the new
object addresses and ensuring that the state of the heap is not altered as Garbage
Collection takes place.
Object I Object D
Object H Object B Object B
Object G Object J
Object F Freachable Object I Freachable
Object E Queue Object G Queue
Object D Object F
Object C Object E
Object B Object D Object D
Object A Object B Object G
Before Garbage Collection After Garbage Collection
The details of Garbage Collection are not as important to the programmer as the
fact that it is a nondeterministic (occurs unpredictably) event that deals with man-
aged resources only. This leaves the programmer facing two problems: how to dis-
pose of unmanaged resources such as files or network connections, and how to
dispose of them in a timely manner. The solution to the first problem is to implement
a method named Finalize that manages object cleanup; the second is solved by
adding a Dispose method that can be called to release resources before Garbage
Collection occurs. As we will see, these two methods do not operate autonomously.
Proper object termination requires a solution that coordinates the actions of both
methods.
Core Note
Garbage Collection typically occurs when the CLR detects that some
memory threshold has been reached. However, there is a static method
GC.Collect that can be called to trigger Garbage Collection. It can be
useful under controlled conditions while debugging and testing, but
should not be used as part of an application.
194 Chapter 4 ■ Working with Objects in C#
Object Finalization
Objects that contain a Finalize method are treated differently during both object
creation and Garbage Collection than those that do not contain a Finalize method.
When an object implementing a Finalize method is created, space is allocated on
the heap in the usual manner. In addition, a pointer to the object is placed in the
finalization queue (see Figure 4-9). During Garbage Collection, the GC scans the
finalization queue searching for pointers to objects that are no longer reachable.
Those found are moved to the freachable queue. The objects referenced in this
queue remain alive, so that a special background thread can scan the freachable
queue and execute the Finalize method on each referenced object. The memory
for these objects is not released until Garbage Collection occurs again.
To implement Finalize correctly, you should be aware of several issues:
It turns out that you do not have to implement Finalize directly. Instead, you
can create a destructor and place the finalization code in it. The compiler converts
the destructor code into a Finalize method that provides exception handling,
includes a call to the base class Finalize, and contains the code entered into the
destructor:
Note that an attempt to code both a destructor and Finalize method results in a
compiler error.
As it stands, this finalization approach suffers from its dependency on the GC to
implement the Finalize method whenever it chooses. Performance and scalability
4.6 Object Life Cycle Management 195
are adversely affected when expensive resources cannot be released when they are
no longer needed. Fortunately, the CLR provides a way to notify an object to per-
form cleanup operations and make itself unavailable. This deterministic finalization
relies on a public Dispose method that a client is responsible for calling.
IDisposable.Dispose()
Although the Dispose method can be implemented independently, the recom-
mended convention is to use it as a member of the IDisposable interface. This
allows a client to take advantage of the fact that an object can be tested for the exist-
ence of an interface. Only if it detects IDisposable does it attempt to call the Dis-
pose method. Listing 4-10 presents a general pattern for calling the Dispose
method.
This code takes advantage of the finally block to ensure that Dispose is called
even if an exception occurs. Note that you can shorten this code by replacing the
try/finally block with a using construct that generates the equivalent code:
Using(connObj)
{ connObj.UseResources() }
CleanUp();
IsDisposed = true;
GC.SuppressFinalize(this);
}
}
protected virtual void CleanUp()
{
// cleanup code here
}
~MyConnections() // Destructor that creates Finalize()
{ CleanUp(); }
public void UseResources()
{
// code to perform actions
if(Disposed)
{
throw new ObjectDisposedException
("Object has been disposed of");
}
}
}
// Inheriting class that implements its own cleanup
public class DBConnections: MyConnections
{
protected override void CleanUp()
{
// implement cleanup here
base.CleanUp();
}
}
4.7 Summary
This chapter has discussed how to work with objects. We’ve seen how to create them,
manipulate them, clone them, group them in collections, and destroy them. The
chapter began with a description of how to use a factory design pattern to create
objects. It closed with a look at how object resources are released through automatic
Garbage Collection and how this process can be enhanced programmatically through
the use of the Dispose and Finalize methods. In between, the chapter examined
how to make applications more robust with the use of intelligent exception handling,
how to customize the System.Object methods such as Equals and ToString to
work with your own objects, how cloning can be used to make deep or shallow cop-
ies, and how to use the built-in classes available in the System.Collections and
System.Collections.Generic namespaces.
As a by-product of this chapter, you should now have a much greater appreciation
of the important role that interfaces play in application design. They represent the
base product when constructing a class factory, and they allow you to clone (IClone-
able), sort (IComparer), or enumerate (IEnumerable) custom classes. Knowing
that an object implements a particular interface gives you an immediate insight into
the capabilities of the object.
■ Chapter 6
Building Windows Forms Applications 266
■ Chapter 7
Windows Forms Controls 318
■ Chapter 8
.NET Graphics Using GDI+ 378
■ Chapter 9
Fonts, Text, and Printing 426
■ Chapter 10
Working with XML in .NET 460
■ Chapter 11
ADO.NET 496
■ Chapter 12
Data Binding with Windows Forms Controls 544
C# TEXT
MANIPULATION
AND FILE I/O
This chapter introduces the string handling capabilities provided by the .NET
classes. Topics include how to use the basic String methods for extracting and
manipulating string content; the use of the String.Format method to display num-
bers and dates in special formats; and the use of regular expressions (regexes) to per-
form advanced pattern matching. Also included is a look at the underlying features of
.NET that influence how an application works with text. Topics include how the
Just-In-Time (JIT) compiler optimizes the use of literal strings; the importance of
Unicode as the cornerstone of character and string representations in .NET; and the
built-in localization features that permit applications to automatically take into
account the culture-specific characteristics of languages and countries.
This chapter is divided into two major topics. The first topic focuses on how to
create, represent, and manipulate strings using the System.Char, System.String,
and Regex classes; the second takes up a related topic of how to store and retrieve
string data. It begins by looking at the Stream class and how to use it to process raw
bytes of data as streams that can be stored in files or transmitted across a network.
The discussion then moves to using the TextReader/TextWriter classes to read
and write strings as lines of text. The chapter concludes with examples of how mem-
bers of the System.IO namespace are used to access the Microsoft Windows direc-
tory and file structure.
203
204 Chapter 5 ■ C# Text Manipulation and File I/O
Unicode
NET fully supports the Unicode standard. Its internal representation of a character
is an unsigned 16-bit number that conforms to the Unicode encoding scheme. Two
bytes enable a character to represent up to 65,536 values. Figure 5-1 illustrates why
two bytes are needed.
The uppercase character on the left is a member of the Basic Latin character set
that consists of the original 128 ASCII characters. Its decimal value of 75 can be
depicted in 8 bits; the unneeded bits are set to zero. However, the other three char-
acters have values that range from 310 (0x0136) to 56,609 (0xDB05), which can be
represented by no less than two bytes.
1. Unicode Consortium—www.unicode.org.
5.1 Characters and Unicode 205
Core Note
Core Note
Char k = 'K';
int iCat = (int) char.GetUnicodeCategory(k); // 0
Console.WriteLine(char.GetUnicodeCategory(k)); // UppercaseLetter
char cr = (Char)13;
iCat = (int) char.GetUnicodeCategory(cr); // 14
Console.WriteLine(char.GetUnicodeCategory(cr)); // Control
Unicode
Method Category Description
IsLetter 0, 1, 2, 4 Letter.
Using these methods is straightforward. The main point of interest is that they
have overloads that accept a single char parameter, or two parameters specifying a
string and index to the character within the string.
Console.WriteLine(Char.IsSymbol('+')); // true
Console.WriteLine(Char.IsPunctuation('+')): // false
string str = "black magic";
Console.WriteLine(Char.IsWhiteSpace(str, 5)); // true
char p = '.';
Console.WriteLine(Char.IsPunctuation(p)); // true
Int iCat = (int) char.GetUnicodeCategory(p); // 24
Char p = '(';
Console.WriteLine(Char.IsPunctuation(p)); // true
int iCat = (int) char.GetUnicodeCategory(p); // 20
5.2 The String Class 209
Creating Strings
A string is created by declaring a variable as a string type and assigning a value to it.
The value may be a literal string or dynamically created using concatenation. This is
often a perfunctory process and not an area that most programmers consider when
trying to improve code efficiency. In .NET, however, an understanding of how literal
strings are handled can help a developer improve program performance.
String Interning
One of the points of emphasis in Chapter 1, “Introduction to .NET and C#,” was to
distinguish how value and reference types are stored in memory. Recall that value
types are stored on a stack, whereas reference types are placed on a managed heap.
It turns out that that the CLR also sets aside a third area in memory called the intern
pool, where it stores all the string literals during compilation. The purpose of this
pool is to eliminate duplicate string values from being stored.
210 Chapter 5 ■ C# Text Manipulation and File I/O
Figure 5-2 shows a simplified view of how the strings and their values are stored in
memory.
poem1 •
poem2 •
poem3 • Object3
poem4 • "Christabel"
Object2
Thread Stack "Kubla Khan"
Object1
"Kubla Khan"
Key Pointer
Managed Heap
"Christabel" •
"Kubla Khan" •
Intern Pool
The intern pool is implemented as a hash table. The hash table key is the actual
string and its pointer references the associated string object on the managed heap.
When the JITcompiler compiles the preceding code, it places the first instance of
"Kubla Khan" (poem1) in the pool and creates a reference to the string object on
the managed heap. When it encounters the second string reference to "Kubla
Khan" (poem2), the CLR sees that the string already exists in memory and, instead of
creating a new string, simply assigns poem2 to the same object as poem1. This pro-
cess is known as string interning. Continuing with the example, the String.Copy
method creates a new string poem3 and creates an object for it in the managed heap.
Finally, the string literal associated with poem4 is added to the pool.
To examine the practical effects of string interning, let’s extend the previous exam-
ple. We add code that uses the equivalence (==) operator to compare string values
and the Object.ReferenceEquals method to compare their addresses.
5.2 The String Class 211
The first two statements compare the value of the variables and—as expected—
return a true value. The third statement compares the memory location of the vari-
ables poem3 and poem2. Because they reference different objects in the heap, a
value of false is returned.
The .NET designers decided to exclude dynamically created values from the
intern pool because checking the intern pool each time a string was created would
hamper performance. However, they did include the String.Intern method as a
way to selectively add dynamically created strings to the literal pool.
The String.Intern method searches for the value of poem5 ("Kubla Khan")
in the intern pool; because it is already in the pool, there is no need to add it. The
method returns a reference to the already existing object (Object1) and assigns it to
poem5. Because poem5 and poem1 now point to the same object, the comparison in
the final statement is true. Note that the original object created for poem5 is
released and swept up during the next Garbage Collection.
Core Recommendation
This code segment demonstrates the static and reference forms of the Equals
method:
//
Console.WriteLine(String.Equals(poem1,poem2)); // true
Console.WriteLine(poem1.Equals(poem3)); // true
Console.WriteLine(poem1 == poem3); // equivalent to Equals
Console.WriteLine(poem1 == poem4); // false – case differs
Note that the == operator, which calls the Equals method underneath, is a more
convenient way of expressing the comparison.
Although the Equals method satisfies most comparison needs, it contains no
overloads that allow it to take case sensitivity and culture into account. To address
this shortcoming, the string class includes the Compare method.
Using String.Compare
String.Compare is a flexible comparison method that is used when culture or case
must be taken into account. Its many overloads accept culture and case-sensitive
parameters, as well as supporting substring comparisons.
Syntax:
Parameters:
str1 and str2 Specify strings to be compared.
IgnoreCase Set true to make comparison case-insensitive (default is false).
index1 and Starting position in str1 and str2.
index2
ci A CultureInfo object indicating the culture to be used.
Compare returns an integer value that indicates the results of the comparison. If
the two strings are equal, a value of 0 is returned; if the first string is less than the sec-
ond, a value less than zero is returned; if the first string is greater than the second, a
value greater than zero is returned.
The following segment shows how to use Compare to make case-insensitive and
case-sensitive comparisons:
int result;
string stringUpper = "AUTUMN";
214 Chapter 5 ■ C# Text Manipulation and File I/O
Perhaps even more important than case is the potential effect of culture informa-
tion on a comparison operation. .NET contains a list of comparison rules for each
culture that it supports. When the Compare method is executed, the CLR checks the
culture associated with it and applies the rules. The result is that two strings may
compare differently on a computer with a US culture vis-à-vis one with a Japanese
culture. There are cases where it may be important to override the current culture to
ensure that the program behaves the same for all users. For example, it may be cru-
cial that a sort operation order items exactly the same no matter where the applica-
tion is run.
By default, the Compare method uses culture information based on the
Thread.CurrentThread.CurrentCulture property. To override the default, sup-
ply a CultureInfo object as a parameter to the method. This statement shows how
to create an object to represent the German language and country:
The string values "circle" and "chair" are compared using the US culture, no
culture, and the Czech culture. The first two comparisons return a value indicating
that "circle" > "chair", which is what you expect. However, the result using the
Czech culture is the opposite of that obtained from the other comparisons. This is
because one of the rules of the Czech language specifies that "ch" is to be treated as
a single character that lexically appears after "c".
Core Recommendation
Using String.CompareOrdinal
To perform a comparison that is based strictly on the ordinal value of characters, use
String.CompareOrdinal. Its simple algorithm compares the Unicode value of two
strings and returns a value less than zero if the first string is less than the second; a
value of zero if the strings are equal; and a value greater than zero if the first string is
greater than the second. This code shows the difference between it and the Compare
method:
TextElementEnumerator tEnum =
StringInfo.GetTextElementEnumerator(poem) ;
while (tEnum.MoveNext()) // Step through the string
{
Console.WriteLine(tEnum.Current); // Print current char
}
String Transformations
Table 5-3 summarizes the most important string class methods for modifying a
string. Because the original string is immutable, any string constructed by these
methods is actually a new string with its own allocated memory.
Tag Description
Tag Description
Split( char[]) The char array contains delimiters that are used to break a
string into substrings that are returned as elements in a
string array.
string words = "red,blue orange ";
string [] split = words.Split(new Char []
{' ', ','});
Console.WriteLine(split[2]); // orange
Tag Description
Most of these methods have analogues in other languages and behave as you
would expect. Somewhat surprisingly, as we see in the next section, most of these
methods are not available in the StringBuilder class. Only Replace, Remove, and
Insert are included.
String Encoding
Encoding comes into play when you need to convert between strings and bytes for
operations such as writing a string to a file or streaming it across a network. Charac-
ter encoding and decoding offer two major benefits: efficiency and interoperability.
Most strings read in English consist of characters that can be represented by 8 bits.
Encoding can be used to strip an extra byte (from the 16-bit Unicode memory repre-
sentation) for transmission and storage. The flexibility of encoding is also important
in allowing an application to interoperate with legacy data or third-party data
encoded in different formats.
The .NET Framework supports many forms of character encoding and decoding.
The most frequently used include the following:
Encoding and decoding are performed using the Encoding class found in the
System.Text namespace. This abstract class has several static properties that return
an object used to implement a specific encoding technique. These properties include
ASCII, UTF8, and Unicode. The latter is used for UTF-16 encoding.
An encoding object offers several methods—each having several overloads—for
converting between characters and bytes. Here is an example that illustrates two of
the most useful methods: GetBytes, which converts a text string to bytes, and Get-
String, which reverses the process and converts a byte array to a string.
You can also instantiate the encoding objects directly. In this example, the UTF-8
object could be created with
With the exception of ASCIIEncoding, the constructor for these classes defines
parameters that allow more control over the encoding process. For example, you can
specify whether an exception is thrown when invalid encoding is detected.
5.5 StringBuilder
The primary drawback of strings is that memory must be allocated each time the
contents of a string variable are changed. Suppose we create a loop that iterates 100
times and concatenates one character to a string during each iteration. We could end
up with a hundred strings in memory, each differing from its preceding one by a sin-
gle character.
The StringBuilder class addresses this problem by allocating a work area
(buffer) where its methods can be applied to the string. These methods include ways
to append, insert, delete, remove, and replace characters. After the operations are
complete, the ToString method is called to convert the buffer to a string that can be
assigned to a string variable. Listing 5-1 introduces some of the StringBuilder
methods in an example that creates a comma delimited list.
5.5 StringBuilder 221
All operations occur in a single buffer and require no memory allocation until the
final assignment to csv. Let’s take a formal look at the class and its members.
// Stringbuilder(initial value)
StringBuilder sb1 = new StringBuilder("abc");
// StringBuilder(initial value, initial capacity)
StringBuilder sb2 = new StringBuilder("abc", 16);
// StringBuiler(Initial Capacity, maximum capacity)
StringBuilder sb3 = new StringBuilder(32,128);
int i = 4;
char[] ch = {'w','h','i','t','e'};
string myColor = " orange";
222 Chapter 5 ■ C# Text Manipulation and File I/O
String routine
Start: 1422091687
Stop: 1422100046
Difference: 9359
StringBuilder routine
Start: 1422100046
Stop: 1422100062
Difference: 16
and specifies how it is to be formatted. The CLR creates the output string by con-
verting each data value to a string (using ToString), formatting it according to its
corresponding format item, and then replacing the format item with the formatted
data value. Here is a simple example:
The method has several overloads, but this is the most common and illustrates two
features common to all: a format string and a list of data arguments. Note that Con-
sole.WriteLine accepts the same parameters and can be used in place of
String.Format for console output.
Format args
f = String.Format("There are {0} Students with {1,2:P} passing",20,.75);
As we can see, each format item consists of an index and an optional alignment
and format string. All are enclosed in brace characters:
Table 5-4 Formatting Numeric Values with Standard Numeric Format Strings
Format
Specifier Description Pattern Output
2. Microsoft Windows users can set formats using the Control Panel – Regional Options settings.
226 Chapter 5 ■ C# Text Manipulation and File I/O
Table 5-4 Formatting Numeric Values with Standard Numeric Format Strings (continued)
Format
Specifier Description Pattern Output
The patterns in this table can also be used directly with Console.Write and
Console.WriteLine:
The format specifiers can be used alone to enhance output from the ToString
method:
.NET also provides special formatting characters that can be used to create cus-
tom numeric formats. The most useful characters are pound sign (#), zero (0),
comma (,), period (.), percent sign (%), and semi-colon (;). The following code
demonstrates their use:
The role of these characters should be self-explanatory except for the semicolon
(;), which deserves further explanation. It separates the format into two groups: the
first is applied to positive values and the second to negative. Two semicolons can be
used to create three groups for positive, negative, and zero values, respectively.
Format
Specifier Description Example—English Example—German
D Long date pattern Monday, January 19, 2004 Montag, 19 Januar, 2004
f Full date/time pattern Monday, January 19, 2004 Montag, 19 Januar, 2004
(short time) 4:05 PM 16:05
F Full date/time pattern Monday, January 19, 2004 Montag, 19 Januar, 2004
(full time) 4:05:20 PM 16:05:20
Format
Specifier Description Example—English Example—German
U Universal Sortable Monday, January 19, 2004 Montag, 19. Januar, 2004
Date-Time pattern. 21:05:20 PM 21:05:20
Uses universal time.
Here are some concrete examples that demonstrate date formatting. In each case,
an instance of a DateTime object is passed an argument to a format string.
If none of the standard format specifiers meet your need, you can construct a cus-
tom format from a set of character sequences designed for that purpose. Table 5-6
lists some of the more useful ones for formatting dates.
The last statement uses the special custom format "dddd" to print the day of the
week. This is favored over the DateTime.DayofWeek enum property that returns
only an English value.
NumberFormatInfo.CurrentInfo.<property>
CultureInfo.CurrentCulture.NumberFormat.<property>
The first statement uses the static property CurrentInfo and implicitly uses the
current culture. The second statement specifies a culture explicitly (CurrentCul-
ture) and is suited for accessing properties associated with a non-current Culture-
Info instance.
5.6 Formatting Numeric and DateTime Values 231
In summary, .NET offers a variety of standard patterns that satisfy most needs to
format dates and numbers. Behind the scenes, there are two classes, NumberFor-
matInfo and DateTimeFormatInfo, that define the symbols and rules used for for-
matting. .NET provides each culture with its own set of properties associated with an
instance of these classes.
232 Chapter 5 ■ C# Text Manipulation and File I/O
Syntax:
Parameters:
pattern Regular expression used for pattern matching.
RegexOptions An enum whose values control how the regex is applied.
Values include:
CultureInvariant—Ignore culture.
IgnoreCase—Ignore upper- or lowercase.
RightToLeft—Process string right to left.
Example:
As the example shows, creating a Regex object is quite simple. The first parame-
ter to its constructor is a regular expression. The optional second parameter is one or
more (separated by |) RegexOptions enum values that control how the regex is
applied.
Regex Methods
The Regex class contains a number of methods for pattern matching and text manip-
ulation. These include IsMatch, Replace, Split, Match, and Matches. All have
instance and static overloads that are similar, but not identical.
Core Recommendation
Let’s now examine some of the more important Regex methods. We’ll keep the
regular expressions simple for now because the emphasis at this stage is on under-
standing the methods—not regular expressions.
IsMatch()
This method matches the regular expression against an input string and returns a
boolean value indicating whether a match is found.
Replace()
This method returns a string that replaces occurrences of a matched pattern with a
specified replacement string. This method has several overloads that permit you to
specify a start position for the search or control how many replacements are made.
234 Chapter 5 ■ C# Text Manipulation and File I/O
Syntax:
The count parameter denotes the maximum number of matches; startat indi-
cates where in the string to begin the matching process. There are also versions of
this method—which you may want to explore further—that accept a MatchEvalua-
tor delegate parameter. This delegate is called each time a match is found and can
be used to customize the replacement process.
Here is a code segment that illustrates the static and instance forms of the
method:
string newStr;
newStr = Regex.Replace("soft rose","o","i"); // sift rise
// instance method
Regex myRegex = new Regex("o"); // regex = "o"
// Now specify that only one replacement may occur
newStr = myRegex.Replace("soft rose","i",1); // sift rose
Split()
This method splits a string at each point a match occurs and places that matching
occurrence in an array. It is similar to the String.Split method, except that the
match is based on a regular expression rather than a character or character string.
Syntax:
Parameters:
input The string to split.
count The maximum number of array elements to return. A count value of 0
results in as many matches as possible. If the number of matches is
greater than count, the last match consists of the remainder of the string.
startat The character position in input where the search begins.
pattern The regex pattern to be matched against the input string.
5.7 Regular Expressions 235
This short example parses a string consisting of a list of artists’ last names and
places them in an array. A comma followed by zero or more blanks separates the
names. The regular expression to match this delimiter string is: ",[ ]*". You will
see how to construct this later in the section.
Syntax:
Console.WriteLine(verseMatch.Index); // 3
//
string newPatt = "K(..)"; //contains group(..)
Match kMatch = Regex.Match(verse, newPatt);
while (kMatch.Success) {
Console.Write(kMatch.Value); // -->Kub -->Kha
Console.Write(kMatch.Groups[1]); // -->ub -->ha
kMatch = kMatch.NextMatch();
}
This example uses NextMatch to iterate through the target string and assign each
match to kMatch (if NextMatch is left out, an infinite loop results). The parentheses
surrounding the two dots in newPatt break the pattern into groups without affecting
the actual pattern matching. In this example, the two characters after K are assigned
to group objects that are accessed in the Groups collection.
Member Description
Index Property returning the position in the string where the first character
of the match is found.
Groups A collection of groups within the class. Groups are created by placing
sections of the regex with parentheses. The text that matches the pat-
tern in parentheses is placed in the Groups collection.
NextMatch() Returns a new Match with the results from the next match operation,
beginning with the character after the previous match, if any.
Sometimes, an application may need to collect all of the matches before process-
ing them—which is the purpose of the MatchCollection class. This class is just a
container for holding Match objects and is created using the Regex.Matches
method discussed earlier. Its most useful properties are Count, which returns the
number of captures, and Item, which returns an individual member of the collec-
tion. Here is how the NextMatch loop in the previous example could be rewritten:
+ Match one or more occurrences of the to+ matches too and tooo. It
previous item. does not match t.
{n,m} Match at least n and no more than m te{1,2}n matches ten and
occurrences of the previous character. teen.
\ Treat the next character literally. Used to A\+B matches A+B. The slash
match characters that have special (\) is required because + has
meaning such as the patterns +, *, and ?. special meaning.
. (dot) Matches any single character. Does not a.c matches abc.
match a newline. It does not match abcc.
This is the most straightforward approach: Each character in the Social Security
Number matches a corresponding pattern in the regular expression. It’s easy to see,
however, that simply repeating symbols can become unwieldy if a long string is to be
matched. Repetition characters improve this:
@"^\d{3}-\d{2}-\d{4}$"
These positional characters do not take up any space in the expression—that is,
they indicate where matching may occur but are not involved in the actual matching
process.
As a final refinement to the SSN pattern, let’s break it into groups so that the three
sets of numbers separated by dashes can be easily examined. To create a group, place
parentheses around the parts of the expression that you want to examine indepen-
dently. Here is a simple code example that uses the revised pattern:
We now have a useful pattern that incorporates position, repetition, and group
characters. The approach that was used to create this pattern—started with an obvi-
ous pattern and refined it through multiple stages—is a useful way to create complex
regular expressions (see Figure 5-4).
Group 1
Group 2
Group 3
@"^(\d{3})-(\d{2})-(\d{4})$"
Groups can be named to make them easier to work with. The name designator is
placed adjacent to the opening parenthesis using the syntax ?<name>. To demon-
strate the use of groups, let’s suppose we need to parse a string containing the fore-
casted temperatures for the week (for brevity, only two days are included):
The regex to match this includes two groups: day and temps. The following code
creates a collection of matches and then iterates through the collection, printing the
content of each group:
string rgPatt =
@"(?<day>[a-zA-Z]+)\s*(?<temps>Hi:\d+\s*Lo:\d+)";
MatchCollection mc = Regex.Matches(txt, rgPatt); //Get matches
foreach(Match m in mc)
{
Console.WriteLine("{0} {1}",
m.Groups["day"],m.Groups["temps"]);
}
//Output: Monday Hi:88 Lo:56
// Tuesday Hi:91 Lo:61
Core Note
There are times when you do not want the presence of parentheses to
designate a group that captures a match. A common example is the use
of parentheses to create an OR expression—for example, (an|in|on).
To make this a non-capturing group, place ?: inside the parentheses—
for example, (?:an|in|on).
Backreferencing a Group
It is often useful to create a regular expression that includes matching logic based on
the results of previous matches within the expression. For example, during a gram-
matical check, word processors flag any word that is a repeat of the preceding
word(s). We can create a regular expression to perform the same operation. The
secret is to define a group that matches a word and then uses the matched value as
part of the pattern. To illustrate, consider the following code:
foreach(Match m in mc) {
Console.WriteLine(m.Groups[1]); // --> and
}
This code matches only the repeated words. Let’s examine the regular expression:
Text/Pattern Description
A group can also be referenced by name rather than number. The syntax for this
backreference is \k followed by the group name enclosed in <>:
patt = @"(?<word>\b[a-zA-Z]+\b)\s\k<word>";
The regular expression assigns the last and first name to groups 1 and 2. The third
parameter in the Replace method allows these groups to be referenced by placing $
in front of the group number. In this case, the effect is to replace the entire matched
name with the match from group 2 (first name) followed by the match from group 1
(last name).
5.7 Regular Expressions 243
Parsing Numbers
String myText = "98, 98.0, +98.0, +98";
string numPatt = @"\d+"; // Integer
numPatt = @"(\d+\.?\d*)|(\.\d+)"; // Allow decimal
numPatt = @"([+-]?\d+\.?\d*)|([+-]?\.\d+)"; // Allow + or -
Note the use of the OR (|) symbol in the third line of code to offer alternate pat-
terns. In this case, it permits an optional number before the decimal.
The following code uses the ^ character to anchor the pattern to the beginning of
the line. The regular expression contains a group that matches four bytes at a time.
The * character causes the group to be repeated until there is nothing to match.
Each time the group is applied, it captures a 4-digit hex number that is placed in the
CaptureCollection object.
Figure 5-5 shows the hierarchical relationship among the Match, GroupCollec-
tion, and CaptureCollection classes.
Group name
Regex: @"^(?<hex4>[a-fA-F\d]{4})*"
Match = 00AA001CFF0C
GroupCollection
Group[0]
Group[hex4]
CaptureCollection
00AA
001C
FF0C
Figure 5-5 Hex numbers captured by regular expression
244 Chapter 5 ■ C# Text Manipulation and File I/O
System.Object
FileStream StringWriter
Compression.GZipStream StreamReader
StringReader
Table 5-10 summarizes some of its important members. Not included are methods
for asynchronous I/O, a topic covered in Chapter 13, “Asynchronous Programming
and Multithreading.”
Member Description
Read(byte array, offset, count) Reads a sequence of bytes from the stream
ReadByte() and advances the position within the stream to
the number of bytes read. ReadByte reads
one byte. Read returns number of bytes read;
ReadByte returns –1 if at end of the stream.
These methods and properties provide the bulk of the functionality for the
FileStream, MemoryStream, and BufferedStream classes, which we examine next.
FileStreams
A FileStream object is created to process a stream of bytes associated with a back-
ing store—a term used to refer to any storage medium such as disk or memory. The
following code segment demonstrates how it is used for reading and writing bytes:
246 Chapter 5 ■ C# Text Manipulation and File I/O
try
{
// Create FileStream object
FileStream fs = new FileStream(@"c:\artists\log.txt",
FileMode.OpenOrCreate, FileAccess.ReadWrite);
byte[] alpha = new byte[6] {65,66,67,68,69,70}; //ABCDEF
// Write array of bytes to a file
// Equivalent to: fs.Write(alpha,0, alpha.Length);
foreach (byte b in alpha) {
fs.WriteByte(b);}
// Read bytes from file
fs.Position = 0; // Move to beginning of file
for (int i = 0; i< fs.Length; i++)
Console.Write((char) fs.ReadByte()); //ABCDEF
fs.Close();
catch(Exception ex)
{
Console.Write(ex.Message);
}
Creating a FileStream
The FileStream class has several constructors. The most useful ones accept the
path of the file being associated with the object and optional parameters that define
file mode, access rights, and sharing rights. The possible values for these parameters
are shown in Figure 5-7.
The FileMode enumeration designates how the operating system is to open the
file and where to position the file pointer for subsequent reading or writing. Table
5-11 is worth noting because you will see the enumeration used by several classes in
the System.IO namespace.
Value Description
Append Opens an existing file or creates a new one. Writing begins at the end
of the file.
CreateNew Creates a new file. An exception is thrown if the file already exists.
Truncate Opens an existing file, removes its contents, and positions the file
pointer to the beginning of the file.
The FileAccess enumeration defines how the current FileStream may access
the file; FileShare defines how file streams in other processes may access it. For
example, FileShare.Read permits multiple file streams to be created that can
simultaneously read the same file.
MemoryStreams
As the name suggests, this class is used to stream bytes to and from memory as a sub-
stitute for a temporary external physical store. To demonstrate, here is an example
that copies a file. It reads the original file into a memory stream and then writes this
to a FileStream using the WriteTo method:
BufferedStreams
One way to improve I/O performance is to limit the number of reads and writes to an
external device—particularly when small amounts of data are involved. Buffers have
long offered a solution for collecting small amounts of data into larger amounts that
could then be sent more efficiently to a device. The BufferedStream object con-
tains a buffer that performs this role for an underlying stream. You create the object
by passing an existing stream object to its constructor. The BufferedStream then
performs the I/O operations, and when the buffer is full or closed, its contents are
flushed to the underlying stream. By default, the BufferedStream maintains a
buffer size of 4096 bytes, but passing a size parameter to the constructor can change
this.
Buffers are commonly used to improve performance when reading bytes from an
I/O port or network. Here is an example that associates a BufferedStream with an
underlying FileStream. The heart of the code consists of a loop in which Fill-
Bytes (simulating an I/O device) is called to return an array of bytes. These bytes are
written to a buffer rather than directly to the file. When fileBuffer is closed, any
remaining bytes are flushed to the FileStream fsOut1. A write operation to the
physical device then occurs.
Parameters:
path Path and name of file to be opened.
s Previously created Stream object—typically a FileStream.
append Set to true to append data to file; false overwrites.
encoding Specifies how characters are encoded as they are written to a file. The
default is UTF-8 (UCS Transformation Format) that stores characters in
the minimum number of bytes required.
This example creates a StreamWriter object from a FileStream and writes two
lines of text to the associated file:
Member Description
Core Note
Core Recommendation
StreamWriter StreamReader
CryptoStream CryptoStream
FileStream FileStream
File
We use the DES algorithm in our example, but we could have chosen any of the
others because implementation details are identical. First, an instance of the class is
created. Then, its key and IV (Initialization Vector) properties are set to the same
key value. DES requires these to be 8 bytes; other algorithms require different
lengths. Of course, the key is used to encrypt and decrypt data. The IV ensures that
repeated text is not encrypted identically. After the DES object is created, it is passed
254 Chapter 5 ■ C# Text Manipulation and File I/O
WriteEncrypt encrypts the message and writes it to the file stream using a
StreamWriter object that serves as a wrapper for a CrytpoStream object. Cryp-
toStream has a lone constructor that accepts the file stream, an object encapsulating
the DES algorithm logic, and an enumeration specifying its mode.
// Encrypt FileStream
private static void WriteEncrypt(FileStream fs, string msg) {
// (1) Create Data Encryption Standard (DES) object
DESCryptoServiceProvider crypt = new
DESCryptoServiceProvider();
// (2) Create a key and Initialization Vector –
// requires 8 bytes
crypt.Key = new byte[] {71,72,83,84,85,96,97,78};
crypt.IV = new byte[] {71,72,83,84,85,96,97,78};
// (3) Create CryptoStream stream object
CryptoStream cs = new CryptoStream(fs,
crypt.CreateEncryptor(),CryptoStreamMode.Write);
// (4) Create StreamWriter using CryptoStream
StreamWriter sw = new StreamWriter(cs);
sw.Write(msg);
sw.Close();
cs.Close();
}
System.Object
Directory
File
FileSystemInfo <<abstract>>
DirectoryInfo
FileInfo
FileSystemInfo
The FileSystemInfo class is a base class for DirectoryInfo and FileInfo. It
defines a range of members that are used primarily to provide information about a
file or directory. The abstract FileSystemInfo class takes advantage of the fact that
files and directories share common features. Its properties include CreationTime,
LastAccessTime, LastWriteTime, Name, and FullName. It also includes two
important methods: Delete to delete a file or directory and Refresh that updates
the latest file and directory information.
Here is a quick look at some of the FileSystemInfo members using Directory-
Info and FileInfo objects. Note the use of the Refresh method before checking
the directory and file attributes.
// DirectoryInfo
string dir = @"c:\artists";
DirectoryInfo di = new DirectoryInfo(dir);
di.Refresh();
DateTime IODate = di.CreationTime;
Console.WriteLine("{0:d}",IODate) // 10/9/2001
// FileInfo
string file = @"C:\artists\manet.jpg";
FileInfo fi = new FileInfo(file);
if (fi.Exists) {
fi.Refresh();
IODate = fi.CreationTime;
Console.WriteLine("{0:d}",IODate); // 5/15/2004
Console.WriteLine(fi.Name); // monet.txt
Console.WriteLine(fi.Extension); // .txt
FileAttributes attrib = fi.Attributes;
Console.WriteLine((int) attrib); // 32
Console.WriteLine(attrib); // Archive
}
DirectoryInfo Directory
Create() Create a directory or subdirectory. CreateDirectory() Pass the string path to the method.
CreateSubdirectory() Failure results in an exception.
Parent Retrieves parent directory of current GetParent() Retrieves parent directory of speci-
path. fied path.
257
258 Chapter 5 ■ C# Text Manipulation and File I/O
Let’s look at some examples using both static and instance methods to manipulate
and list directory members. The sample code assumes the directory structure shown
in Figure 5-10.
C:\
\artists
\impressionists
monet.txt
monet.htm
sisley.txt
\expressionists
scheile.txt
wollheim.txt
Create a Subdirectory
This code adds a subdirectory named cubists below expressionists:
// DirectoryInfo
string curPath= @"c:\artists\expressionists";
di = new DirectoryInfo(curPath);
if (di.Exists) di.CreateSubdirectory(newPath);
Delete a Subdirectory
This code deletes the cubists subdirectory just created:
// DirectoryInfo
DirectoryInfo di = new DirectoryInfo(newPath);
If (di.Exists) di.Delete();
5.9 System.IO: Directories and Files 259
// DirectoryInfo
public static void ShowDir(DirectoryInfo sourceDir,
int recursionLvl)
{
if (recursionLvl<= Depth) // Limit subdirectory search depth
{
// Process the list of files found in the directory
Console.WriteLine(sourceDir.FullName);
foreach( FileInfo fileName in
260 Chapter 5 ■ C# Text Manipulation and File I/O
sourceDir.GetFiles("s*.*"))
Console.WriteLine(" "+fileName);
// Use recursion to process subdirectories
foreach(DirectoryInfo subDir in sourceDir.GetDirectories())
ShowDir2(subDir,recursionLvl+1); // Recursive call
}
}
The method is called with two parameters: a DirectoryInfo object that encap-
sulates the path and an initial depth of 0.
Method Returns
Path.GetDirectoryName(fullPath) c:\artists\impressionists
Path.GetExtension(fullPath) .htm
GetFileName(fullPath) monet.htm
GetFullPath(fullPath) c:\artists\impressionists\monet.htm
GetPathRoot(fullPath) c:\
Path.HasExtension(fullPath) true
5.9 System.IO: Directories and Files 261
The FileInfo.Open method is the generic and most flexible way to open a file:
Create, OpenRead, and OpenWrite are specific cases of Open that offer an
easy-to-use method that returns a FileStream object and requires no parameters.
Similarly, the OpenText, AppendText, and CreateText methods return a Stream-
Reader or StreamWriter object.
The decision to create a FileStream (or StreamReader/StreamWriter) using
FileInfo or the FileStream constructor should be based on how the underlying
file is used in the application. If the file is being opened for a single purpose, such as
for input by a StreamReader, creating a FileStream directly is the best approach.
If multiple operations are required on the file, a FileInfo object is better. This
example illustrates the advantages of using FileInfo. First, it creates a FileStream
that is used for writing to a file; then, another FileStream is created to read the
file’s contents; finally, FileInfo.Delete is used to delete the file.
5.10 Summary 263
5.10 Summary
The demands of working with text have increased considerably from the days when it
meant dealing with 7-bit ASCII or ANSII characters. Today, the Unicode standard
defines the representation of more than 90,000 characters comprising the world’s
alphabets. We’ve seen that .NET fully embraces this standard with its 16-bit charac-
ters. In addition, it supports the concept of localization, which ensures that a
machine’s local culture information is taken into account when manipulating and rep-
resenting data strings.
String handling is facilitated by a rich set of methods available through the
String and StringBuilder classes. A variety of string comparison methods are
available with options to include case and culture in the comparisons. The
String.Format method is of particular note with its capacity to display dates and
numbers in a wide range of standard and custom formats. String manipulation and
concatenation can result in an inefficient use of memory. We saw how the String-
Builder class is an efficient alternative for basic string operations. Applications that
require sophisticated pattern matching, parsing, and string extraction can use regular
expressions in conjunction with the Regex class.
The System.IO namespace provides a number of classes for reading and writing
data: The FileStream class is used to process raw bytes of data; MemoryStream and
BufferedStream allow bytes to be written to memory or buffered; the Stream-
Reader and StreamWriter classes support the more traditional line-oriented I/O.
Operations related to managing files and directories are available as methods on the
File, FileInfo, Directory, and DirectoryInfo classes. These are used to cre-
ate, copy, delete, and list files and directories.
264 Chapter 5 ■ C# Text Manipulation and File I/O
a. fs = fi.OpenWrite();
b. fs = fi.Create();
c. fs = fi.CreateText();
This chapter is aimed at developers responsible for creating Graphical User Interface
(GUI) applications for the desktop—as opposed to applications that run on a Web
server or mobile device. The distinction is important because .NET provides separate
class libraries for each type of application and groups them into distinct namespaces:
267
268 Chapter 6 ■ Building Windows Forms Applications
This is not a chapter about the principles of GUI design, but it does demonstrate
how adding amenities, such as Help files and a tab order among controls, improves
form usability. Controls, by the way, are discussed only in a generic sense. A detailed
look at specific controls is left to Chapter 7, “Windows Forms Controls.”
Recall from Chapter 1, “Introduction to .NET and C#,” that command-line com-
pilation requires providing a target output file and a reference to any required assem-
blies. In this case, we include the System.Windows.Forms assembly that contains
the necessary WinForms classes. To compile, save the preceding source code as
winform.cs and enter the following statement at the command prompt:
After it compiles, run the program by typing winform; the screen shown in Fig-
ure 6-1 should appear. The output consists of a parent form and a second form cre-
ated by clicking the button. An important point to note is that the parent form cannot
be accessed as long as the second window is open. This is an example of a modal
form, where only the last form opened can be accessed. The alternative is a modeless
form, in which a parent window spawns a child window and the user can access
either the parent or child window(s). Both of these are discussed later.
270 Chapter 6 ■ Building Windows Forms Applications
1. Form Creation.
The parent form is an instance of the class SimpleForm, which inherits
from Form and defines the form’s custom features. The form—and pro-
gram—is invoked by passing the instance to the Application.Run
method.
2. Create Button Control.
A control is placed on a form by creating an instance of the control and
adding it to the form. Each form has a Controls property that
returns a Control.Collection type that represents the collection of
controls contained on a form. In this example, the Controls.Add
method is used to add a button to the form. Note that a corresponding
Remove method is also available to dynamically remove a control from
its containing form. An IDE uses this same Add method when you
drag a control onto a form at design time. However, if you want to add
or delete controls at runtime, you will be responsible for the coding.
Controls have a number of properties that govern their appearance.
The two most basic ones are Size and Location. They are imple-
mented as:
button1.Size = new Size(72,24); // width, height
button1.Location = new Point(96,112); //x,y
The struct Size has a constructor that takes the width and height as
parameters; the constructor for Point accepts the x and y coordinates
of the button within the container.
6.2 Windows.Forms Control Classes 271
Core Note
.NET 2.0 adds a feature known as Partial Types, which permits a class to
be physically separated into different files. To create a partial class,
place the keyword partial in front of class in each file containing a
segment of the class. Note that only one class declaration should specify
Forms inheritance. The compilation process combines all the files into
one class—identical to a single physical class. For Windows
applications, partial classes seem something of a solution in search of a
problem. However, for Web applications, they serve a genuine need, as
is discussed in Chapter 16, “ASP.NET Web Forms and Controls.”
This exercise should emphasize the fact that working with forms is like working
with any other classes in .NET. It requires gaining a familiarity with the class mem-
bers and using standard C# programming techniques to access them.
Control
ScrollableControl DataGridView
Label
Container Control ListView
PictureBox
Form TreeView
GroupBox
User Form
Control Properties
Table 6-1 defines some of the properties common to most types that inherit from
Control.
Size and Size A Size object that exposes the width and height.
position
Location Location is a Point object that specifies the x and y
coordinates of the top left of the control.
6.2 Windows.Forms Control Classes 273
Size and Width, Height, These int values are derived from the size and loca-
position Top, Left, tion of the object. For example, Right is equal to
(continued) Right Left + Width; Bottom is equal to Top + Height.
Color and BackColor, Specifies the background and foreground color for
appearance ForeColor the control. Color is specified as a static property of
the Color structure.
Focus TabIndex int value that indicates the tab order of this control
within its container.
Keyboard MouseButtons Returns the current state of the mouse buttons (left,
and mouse right, and middle).
274 Chapter 6 ■ Building Windows Forms Applications
Keyboard MousePosition Returns a Point type that specifies the cursor position.
and mouse
(continued) ModifierKeys Indicates which of the modifier keys (Shift, Ctrl, Alt)
is pressed.
A control can be resized during runtime by assigning a new Size object to it. This
code snippet demonstrates how the Click event handler method can be used to
change the size of the button when it is clicked:
A Rectangle object is created with its upper-left corner at the x,y coordinates
(20,40) and its lower-right coordinates at (100,80). This corresponds to a width of 80
pixels and a height of 40 pixels.
TextBox1.Dock = DockStyle.Top;
Figure 6-3 illustrates how docking affects a control’s size and position as the form
is resized. This example shows four text boxes docked, as indicated.
Resizing the form does not affect the size of controls docked on the left or right.
However, controls docked to the top and bottom are stretched or shrunk horizontally
so that they take all the space available to them.
Core Note
The Form class and all other container controls have a DockPadding
property that can be set to control the amount of space (in pixels)
between the container’s edge and the docked control.
276 Chapter 6 ■ Building Windows Forms Applications
Figure 6-3 Control resizing and positioning using the Dock property
Figure 6-4 How anchoring affects the resizing and positioning of controls
The distance between the controls’ anchored edges remains unchanged as the
form is stretched. The PictureBox (1) is stretched horizontally and vertically so that
it remains the same distance from all edges; the Panel (2) control maintains a con-
stant distance from the left and bottom edge; and the Label (3), which is anchored
only to the top, retains its distance from the top, left, and right edges of the form.
The code to define a control’s anchor position sets the Anchor property to values
of the AnchorStyles enumeration (Bottom, Left, None, Right, Top). Multiple
values are combined using the OR ( | ) operator:
Observe two things in the figure: First, even though labels have a tab order, they
are ignored and never gain focus; and second, controls in a container have a tab order
that is relative to the container—not the form.
A control’s tab order is determined by the value of its TabIndex property:
In VS.NET, you can set this property directly with the Property Manager, or by
selecting ViewTabOrder and clicking the boxes over each control to set the value. If
you do not want a control to be included in the tab order, set its TabStop value to
false. This does not, however, prevent it from receiving focus from a mouse click.
When a form is loaded, the input focus is on the control (that accepts mouse or
keyboard input) with the lowest TabIndex value. During execution, the focus can be
given to a selected control using the Focus method:
if(textBox1.CanFocus)
{ textBox1.Focus(); }
278 Chapter 6 ■ Building Windows Forms Applications
Applying this code to Figure 6-5 results in all controls on the main form being
listed hierarchically. A control is listed followed by any child it may have.
6.2 Windows.Forms Control Classes 279
Control Events
When you push a key on the keyboard or click the mouse, the control that is the tar-
get of this action fires an event to indicate the specific action that has occurred. A
registered event handling routine then responds to the event and formulates what
action to take.
The first step in handling an event is to identify the delegate associated with the
event. You must then register the event handling method with it, and make sure the
method’s signature matches the parameters specified by the delegate. Table 6-2 sum-
marizes the information required to work with mouse and keyboard triggered events.
Built-In Delegate/
Event Parameters Description
To illustrate this, let’s consider an example that changes the background color on a
text box when a mouse passes over it. The following code sets up delegates to call
OnMouseEnter and OnMouseLeave to perform the background coloring:
The event handler methods match the signature of the EventHandler delegate
and cast the sender parameter to a Control type to access its properties.
Core Note
The delegates for the MouseDown, MouseUp, and MouseMove events take a sec-
ond argument of the MouseEventArgs type rather than the generic EventArgs
type. This type reveals additional status information about a mouse via the properties
shown in Table 6-3.
The properties in this table are particularly useful for applications that must track
mouse movement across a form. Prime examples are graphics programs that rely on
mouse location and button selection to control onscreen drawing. To illustrate, List-
ing 6-3 is a simple drawing program that draws a line on a form’s surface, beginning
at the point where the mouse key is pressed and ending where it is raised. To make it
a bit more interesting, the application draws the line in black if the left button is
dragged, and in red if the right button is dragged.
6.2 Windows.Forms Control Classes 281
Property Description
Button Indicates which mouse button was pressed. The attribute value is
defined by the MouseButtons enumeration: Left, Middle, None,
Right
The KeyPress event is only fired for printable character keys; it ignores
non-character keys such as Alt or Shift. To recognize all keystrokes, it is necessary to
turn to the KeyDown and KeyUp events. Their event handlers receive a KeyEvent-
Args type parameter that identifies a single keystroke or combination of keystrokes.
Table 6-4 lists the important properties provided by KeyEventArgs.
Member Description
Alt, Control, Boolean value that indicates whether the Alt, Control, or Shift key was
Shift pressed.
KeyCode Returns the key code for the event. This code is of the Keys enumera-
tion type.
KeyData Returns the key data for the event. This is also of the Keys enumera-
tion type, but differs from the KeyCode in that it recognizes multiple
keys.
Modifiers Indicates which combination of modifier keys (Alt, Ctrl, and Shift) was
pressed.
• The Alt, Control, and Shift properties are simply shortcuts for
comparing the KeyCode value with the Alt, Control, or Shift
member of the Keys enumeration.
• KeyCode represents a single key value; KeyData contains a value for a
single key or combination of keys pressed.
• The Keys enumeration is the secret to key recognition because its
members represent all keys. If using Visual Studio.NET, Intellisense
lists all of its members when the enum is used in a comparison; other-
wise, you need to refer to online documentation for the exact member
name because the names are not always what you expect. For exam-
ple, digits are designated by D1, D2, and so on.
284 Chapter 6 ■ Building Windows Forms Applications
The preceding code segment showed how to use KeyPress to ensure a user
presses only number keys (0–9). However, it does not prevent one from pasting
non-numeric data using the Ctrl-V key combination. A solution is to use the KeyDown
event to detect this key sequence and set a flag notifying the KeyPress event han-
dler to ignore the attempt to paste.
In this example, two event handlers are registered to be called when a user
attempts to enter data into a text box using the keyboard. KeyDown is invoked first,
and it sets paste to true if the user is pushing the Ctrl-V key combination. The
KeyPress event handler uses this flag to determine whether to accept the key
strokes.
This program displays the following values for the selected KeyEventArgs prop-
erties when Ctrl-V is pressed:
Modifier: Control
KeyCode: V
KeyData: Control, V
6.3 The Form Class 285
Opacity Gets or sets the opacity of the form and all of its
controls. The maximum value (least transparent) is
1.0. Does not work with Windows 95/98.
Size and AutoScale Indicates whether the form adjusts its size to
position accommodate the size of the font used.
ClientSize Size of the form excluding borders and the title bar.
Owner forms Owner The form designated as the owner of the form.
Form Opacity
A form’s opacity property determines its level of transparency. Values ranges from
0 to 1.0, where anything less than 1.0 results in partial transparency that allows ele-
ments beneath the form to be viewed. Most forms work best with a value of 1.0, but
adjusting opacity can be an effective way to display child or TopMost forms that hide
an underlying form. A common approach is to set the opacity of such a form to 1.0
when it has focus, and reduce the opacity when it loses focus. This technique is often
used with search windows that float on top of the document they are searching.
Let’s look at how to set up a form that sets its opacity to 1 when it is active and to
.8 when it is not the active form. To do this, we take advantage of the Deactivate
and Activated events that are triggered when a form loses or gains focus. We first
set up the delegates to call the event handling routines:
Form Transparency
Opacity affects the transparency of an entire form. There is another property,
TransparencyKey, which can be used to make only selected areas of a form totally
transparent. This property designates a pixel color that is rendered as transparent
when the form is drawn. The effect is to create a hole in the form that makes any area
below the form visible. In fact, if you click a transparent area, the event is recognized
by the form below.
The most popular use of transparency is to create non-rectangular forms. When
used in conjunction with a border style of FormBorderStyle.None to remove the
288 Chapter 6 ■ Building Windows Forms Applications
title bar, a form of just about any geometric shape can be created. The next example
illustrates how to create and use the cross-shaped form in Figure 6-7.
The only requirement in creating the shape of the form is to lay out the transpar-
ent areas in the same color as the transparency key color. Be certain to select a color
that will not be used elsewhere on the form. A standard approach is to set the back
color of the form to the transparency key color, and draw an image in a different
color that it will appear as the visible form.
To create the form in Figure 6-7, place Panel controls in each corner of a stan-
dard form and set their BackColor property to Color.Red. The form is created and
displayed using this code:
This achieves the effect of making the panel areas transparent and removing the
title bar. With the title bar gone, it is necessary to provide a way for the user to
move the form. This is where the mouse event handling discussed earlier comes to
the rescue.
At the center of the form is a multiple arrow image displayed in a PictureBox
that the user can click and use to drag the form. Listing 6-4 shows how the Mouse-
Down, MouseUp, and MouseMove events are used to implement form movement.
6.3 The Form Class 289
The logic is straightforward. When the user clicks the PictureBox, the coordi-
nates are recorded as lastPoint. As the user moves the mouse, the Location
property of the form is adjusted to reflect the difference between the new coordi-
nates and the original saved location. When the mouse button is raised, lastPoint
is cleared. Note that a complete implementation should also include code to handle
form resizing.
The form’s initial location can also be set by the form that creates and displays the
form object:
This code creates an instance of the form opaque and sets its TopMost property so
that the form is always displayed on top of other forms in the same application. The
DeskTopLocation property sets the form’s initial location. For it to work, however,
the StartPostion property must first be set to FormStartPosition.Manual.
Core Note
A form’s size can be set using either its Size or ClientSize property. The latter
is usually preferred because it specifies the workable area of the form—the area that
excludes the title bar, scrollbars, and any edges. This property is set to an instance of
a Size object:
It is often desirable to position or size a form relative to the primary (.NET sup-
ports multiple screens for an application) screen size. The screen size is available
through the Screen.PrimaryScreen.WorkingArea property. This returns a rect-
angle that represents the size of the screen excluding task bars, docked toolbars, and
docked windows. Here is an example that uses the screen size to set a form’s width
and height:
int w = Screen.PrimaryScreen.WorkingArea.Width;
int h = Screen.PrimaryScreen.WorkingArea.Height;
this.ClientSize = new Size(w/4,h/4);
After a form is active, you may want to control how it can be resized. The aptly
named MinimumSize and MaximumSize properties take care of this. In the follow-
ing example, the maximum form size is set to one-half the width and height of the
working screen area:
Setting both width and height to zero removes any size restrictions.
292 Chapter 6 ■ Building Windows Forms Applications
Displaying Forms
After a main form is up and running, it can create instances of new forms and display
them in two ways: using the Form.ShowDialog method or the Form.Show method
inherited from the Control class. Form.ShowDialog displays a form as a modal
dialog box. When activated, this type of form does not relinquish control to other
forms in the application until it is closed. Dialog boxes are discussed at the end of this
section.
Form.Show displays a modeless form, which means that the form has no relation-
ship with the creating form, and the user is free to select the new or original form. If
the creating form is not the main form, it can be closed without affecting the new
form; closing the main form automatically closes all forms in an application.
Form activated Form.Activated This occurs when the user selects the
form. This becomes an “active” form.
Closing a Form
The Closing event occurs as a form is being closed and provides the last opportu-
nity to perform some cleanup duties or prevent the form from closing. This event
uses the CancelEventHandler delegate to invoke event handling methods. The
delegate defines a CancelEventArgs parameter that contains a Cancel property,
which is set to true to cancel the closing. In this example, the user is given a final
prompt before the form closes:
being processed and a Search button that, when clicked, passes control to a search
form. The search form has a Textbox that accepts text to be searched for in the main
form’s document. By default, the search phrase is any highlighted text in the docu-
ment; it can also be entered directly by the user.
When the Find Next button is pushed, the application searches for the next occur-
rence of the search string in the main document. If an occurrence is located, it is
highlighted. To make it more interesting, the form includes options to search forward
or backward and perform a case-sensitive or case-insensitive search.
The main challenge in developing this application is to determine how each form
makes the content of its controls available to the other form. DocForm, the main
form, must expose the contents of documentText so that the search form can search
the text in it and highlight an occurrence of matching text. The search form,
SearchForm, must expose the contents of txtSearch, the TextBox containing the
search phrase, so that the main form can set it to the value of any highlighted text
before passing control to the form.
DocForm shares the contents of documentText through a text box field myText
that is assigned the value of documentText when the form loads. Setting myText to
public static enables the search form to access the text box properties by simply
qualifying them with DocForm.myText.
296 Chapter 6 ■ Building Windows Forms Applications
DocForm, as well as any object creating an instance of SearchForm, can set this
property. Now let’s look at the remaining code details of the two forms.
This relationship affects the user’s interaction with the form in three ways: The
owned form is always on top of the owner form even if the owner is active; closing
the owner form also closes the owned form; and minimizing the owner form mini-
mizes all owned forms and results in only one icon in the task bar.
6.3 The Form Class 299
Note that although modal forms exhibit the features of an owned form, the Owner
property must be set to establish an explicit relationship.
MessageBox
The MessageBox class uses its Show method to display a message box that may con-
tain text, buttons, and even an icon. The Show method includes these overloads:
Syntax:
DialogResult. The method returns one of the enum members Abort, Cancel,
Ignore, No, None, OK, Retry, and Yes.
300 Chapter 6 ■ Building Windows Forms Applications
Figure 6-9, which is created with the following statement, provides a visual sum-
mary of these parameter options:
ShowDialog
The ShowDialog method permits you to create a custom form that is displayed in
modal mode. It is useful when you need a dialog form to display a few custom fields
of information. Like the MessageBox, it uses buttons to communicate with the user.
The form used as the dialog box is a standard form containing any controls you
want to place on it. Although not required, the form’s buttons are usually imple-
mented to return a DialogResult enum value. The following code handles the
Click event for the two buttons shown on the form in Figure 6-10:
6.3 The Form Class 301
To complete the form, we also need to set a default button and provide a way for
the form to be cancelled if the user presses the Esc key. This is done by setting the
form’s AcceptButton and CancelButton properties in the form’s constructor.
The code that creates and displays the form is similar to previous examples. The
only difference is that the new form instance calls its ShowDialog method and
returns a DialogResult type result.
No special classes are required to create an MDI application. The only require-
ment is that one form be designated the container by setting its IsMdiContainer
property to true. Child forms are designated by setting their MdiParent property to
the container form.
The MDI form in Figure 6-11 shows the three elements that comprise an MDI
form: the parent container; the child form(s); and a menu to manage the creation,
selection, and arrangement of the child forms.
The container form is created by including this statement in its constructor:
this.IsMdiContainer = true;
By tradition, child forms are created by selecting an option on the File menu such
as File-New or File-Open. The supporting code creates an instance of the child form
and sets its MdiParent property.
A variable that counts the number of forms created is appended to each form’s
Text property to uniquely identify it.
6.3 The Form Class 303
The first step is to declare the main menu object and the menu items as class vari-
ables. (To avoid repetition, code for all menu items is not shown.)
The main menu and menu items are created inside the class constructor:
Next, the menu hierarchy is established by adding menu items to the main menu
object to create the menu bar. The menu bar items then have menu items added to
their MenuItems collection, which creates the drop-down menu.
• The Add and AddRange methods add a single or multiple menu items
to the MenuItems collection.
• Setting a menu item’s MdiList property to true causes a list of child
forms to appear in the menu below that menu item (Invoice1 and
Invoice2 are listed in Figure 6-12).
• To place a menu on a form, set the form’s Menu property to the Main-
Menu object.
The final step is to set up event handling code that provides logic to support the
menu operations. Here is the code to define a delegate and method to support an
event fired by clicking the File–New menu item. The code creates a new instance of
invForm each time this menu item is clicked.
myForm.MdiParent = this;
mdiCt += mdiCt; //Count number of forms created
myForm.Text= "Invoice" + mdiCt.ToString();
myForm.Show();
}
The Window option on the menu bar has submenu items that let you rearrange
the child forms within the MDI container. The LayoutMdi method of a form makes
implementing this almost trivial. After setting up delegates in the usual manner, cre-
ate the event handling routines:
The methods reorder the window by passing the appropriate MdiLayout enumer-
ation value to the LayoutMdi method.
MenuItem Properties
The .NET menu system is designed with the utilitarian philosophy that the value of a
thing depends on its utility. Its menu item is not a thing of beauty, but it works. Here
are some of its more useful properties:
Enabled. Setting this to false, grays out the button and makes it unavailable.
Checked. Places a checkmark beside the menu item text.
RadioCheck. Places a radio button beside the menu item text; Checked must also
be true.
BreakBar or Break. Setting this to true places the menu item in a new column.
Shortcut. Defines a shortcut key from one of the Shortcut enum members. These
members represent a key or key combination (such as Shortcut.AltF10) that
causes the menu item to be selected when the keys are pressed. On a related matter,
note that you can also place an & in front of a letter in the menu item text to produce
a hot key that causes the item to be selected by pressing Alt-letter.
6.4 Working with Menus 307
Context Menus
In addition to the MainMenu and MenuItem classes that have been discussed, there is
a ContextMenu class that also inherits from the Menu class. This ContextMenu class
is associated with individual controls and is used most often to provide a context-
sensitive pop-up menu when the user right-clicks on a control.
The statements to construct a menu based on ContextMenu are the same as with
a MainMenu. The only difference is that visually there is no top-level menu bar, and
the menu is displayed near the control where it is invoked.
A menu can be associated with multiple controls, or each control can have its own
menu. Typically, one menu is used for each control type. For example, you might
have a context menu for all
TextBox controls, and another for buttons. To illustrate, let’s create a menu that
colors the background of a TextBox control (see Figure 6-14).
contextMenu1.MenuItems.Add("White Background",
new System.EventHandler(this.menuItem_Click));
contextMenu1.MenuItems.Add("Beige Background",
new System.EventHandler(this.menuItem_Click));
A right-click on txtSearch causes the menu to pop up. Click one of the menu
items and this event handling routine is called:
The two most important things to note in this example are that the argument
sender identifies the selected menu item and that the context menu property
SourceControl identifies the control associated with the event. This capability to
identify the control and the menu item enables one method to handle events from
any control on the form or any menu item in the context menu.
• Easy-to-use ToolTips that are displayed when the mouse moves over a
control. These are specified as a control property provided by the
ToolTip component.
• The HelpProvider component is an “extender” that adds properties to
existing controls. These properties enable the control to reference
Microsoft HTML Help (.chm ) files.
• A custom event handler can be written to implement code that explic-
itly handles the Control.HelpRequested event that is fired by
pressing the F1 key or using a Help button.
ToolTips
This component adds mouseover capabilities to controls. If using VS.NET, you sim-
ply select the ToolTip control from the tool box and add it to your form. The effect
of this is to add a string property (ToolTip on toolTip1) to each control, whose
value is displayed when the mouse hovers over the control.
Of more interest is using ToolTips within a program to dynamically provide anno-
tation for objects on a screen. Maps, which can be created dynamically in response to
user requests, offer a prime example. They typically contain points of interest imple-
mented as labels or picture boxes. As an example, consider the display in Figure
6-15, which shows a constellation and labels for its most important stars. When a user
passes the cursor over the label, tool tip text describing the star is shown.
Figure 6-15 ToolTip information is displayed when mouse passes over name
310 Chapter 6 ■ Building Windows Forms Applications
Listing 6-7 shows a portion of the code that creates the form displayed in the fig-
ure. The form’s BackGroundImage property is set to the image representing the
constellation. Labels are placed on top of this that correspond to the position of three
stars (code for only one star is shown). The Tag property of each label is set to the
description of the star, and a ToolTip associates this information with the label using
the SetToolTip method.
Core Note
A recommended approach is to create one event handler routine and have each
control invoke it. As an example, the following code defines delegates for two text
boxes that notify the ShowHelp method when the HelpRequested event occurs.
This method uses either a Tag property associated with each control or the control’s
name to specify help germane to that control.
The event handler receives two arguments: the familiar sender that identifies the
control that has focus when the event occurs and HelpEventArgs, which has Han-
dled and MousePos as its only two properties. Handled is set to indicate the event
has been handled. MousePos is a Point type that specifies the location of the cursor
on the form.
The method provides context-sensitive help by identifying the active control and
using this knowledge to select the Help text to be displayed. In this example, the first
technique displays the tag property of a control as the Help message. The second—
and more interesting technique—uses Help.ShowHelp to display a section of an
HTML file that uses the control’s name as an anchor tag. Specifically, it looks inside
ctest.chm for the customers.htm page. Then, it searches that page for a named
anchor tag such as <a name=ssn> . If found, it displays the HTML at that location.
ShowHelp is the most useful method of the Help class. It includes several over-
loads to show compiled Help files (.chm) or HTML files in an HTML Help format.
Add the HelpProvider to a form by selecting it from the tool box. Then, set its
HelpNameSpace property to the name of the HTML or .chm file that the underlying
ShowHelp method should reference (this corresponds to the URL parameter).
Each control on the form adds four extended properties:
Help is not enabled on a control that has ShowHelp set to false. If it is set to
true, but the other properties are not set, the file referenced in HelpNameSpace is
displayed. A popular Help configuration is to set only the HelpString value so that
the Help button brings up a short specific message and F1 brings up an HTML page.
A base form must provide a namespace for the derived form to reference it. The
following code defines a Products namespace for our example:
namespace Products
{
public class ProductForm : System.Windows.Forms.Form
{
314 Chapter 6 ■ Building Windows Forms Applications
To inherit this form, a class uses the standard inheritance syntax and designates
the base class by its namespace and class name:
As a final step, the compiler must be given a reference to the external assembly
ADCFormLib so that the base class can be located. If using VS.NET, you use the
Project-AddReference menu option to specify the assembly; from the command line,
the reference flag is used.
Overriding Events
Suppose the base form contains a button that responds to a click by calling event
handler code to close the form. However, in your derived form, you want to add
some data verification checks before the form closes. One’s instinct is to add a dele-
gate and event handler method to respond to the button Click event in the derived
form. However, this does not override the original event handler in the base form,
and both event handling routines get called. The solution is to restructure the origi-
nal event handler to call a virtual method that can be overridden in the derived form.
Here is sample code for the base form:
The derived form simply overrides the virtual method and includes its own code
to handle the event:
6.7 Summary
Despite the migration of applications to the Internet, there is still a compelling need
for Windows Forms programming. Windows Forms provide features and functional-
ity superior to those of Web Forms. Moreover, the majority of real-world applications
continue to run on local area networks. The .NET Framework Class Library provides
a rich set of classes to support forms programming. The Control class at the top of
the hierarchy provides the basic properties, methods, and events that allow controls
to be positioned and manipulated on a form. Keyboard and mouse events enable a
program to recognize any keys or mouse buttons that are clicked, as well as cursor
position.
The Form class inherits all of the Control class members and adds to it proper-
ties that specify form appearance, position, and relationship with other forms. A form
created by another form may be modal, which means it does not relinquish focus
until it is closed, or modeless, in which case focus can be given to either form. In a
multiple document interface (MDI) application, one form serves as a container to
hold child forms. The container usually provides a menu for selecting or rearranging
its child forms.
.NET includes a HelpRequested event that is fired when a user pushes the F1
key. This can be combined with the Help.ShowHelp method, which supports com-
piled HTML (.chm) files, to enable a developer to provide context-sensitive help on
a form.
316 Chapter 6 ■ Building Windows Forms Applications
9. Describe how a base form can structure its event handling code so
that an inherited form can override an event.
This page intentionally left blank
WINDOWS FORMS
CONTROLS
Topics in This Chapter
The previous chapter introduced the Control class and the methods, properties,
and events it defines for all controls. This chapter moves beyond that to examine the
specific features of individual controls. It begins with a survey of the more important
.NET controls, before taking an in-depth look at how to implement controls such as
the TextBox, ListBox, TreeView, and ListView. Also included is a discussion of
the .NET drag-and-drop features that are used to move or copy data from one con-
trol to another.
Windows Forms (WinForms) are not restricted to using the standard built-in con-
trols. Custom GUI controls can be created by extending an existing control, building
a totally new control, or fashioning a user control from a set of related widgets.
Examples illustrate how to extend a control and construct a user control. The chapter
concludes with a look at resource files and how they are used to create GUI applica-
tions that support users from multiple countries and cultures.
319
320 Chapter 7 ■ Windows Forms Controls
Figure 7-1 shows the inheritance hierarchy of the Windows Forms controls. The
controls marked by an asterisk (*) exist primarily to provide backward compatibility
between .NET 2.0 and .NET 1.x. Specifically, the DataGrid has been superseded by
the DataGridView, the StatusBar by the StatusStrip, and the ToolBar by the
ToolStrip. Table 7-1 provides a summary of the more frequently used controls in
this hierarchy.
7.1 A Survey of .NET Windows Forms Controls 321
ListBox Displays a list of items— May contain simple text or objects. Its
one or more of which may methods, properties, and events allow
be selected. items to be selected, modified, added,
and sorted.
322 Chapter 7 ■ Windows Forms Controls
ListView Displays items and May take a grid format where each row
subitems. represents a different item and sub-
items. It also permits items to be dis-
played as icons.
StatusStrip Provides a set of panels that Provides a status bar that is used to
indicate program status. provide contextual status information
about current form activities.
This chapter lacks the space to provide a detailed look at each control. Instead, it
takes a selective approach that attempts to provide a flavor of the controls and fea-
tures that most benefit the GUI developer. Notable omissions are the DataGrid-
View control, which is included in the discussion of data binding in Chapter 12,
“Data Binding with Windows Forms Controls,” and the menu controls that were dis-
cussed in Chapter 6, “Building Windows Forms Applications.”
The constructor creates a button instance with no label. The button’s Text prop-
erty sets its caption and can be used to define an access key (see Handling Button
Events section); its Image property is used to place an image on the button’s back-
ground.
ImageAlign Specifies the position of the image on the button. It is set to a value
of the ContentAlignment enum:
button1.ImageAlign = ContentAlignment.MiddleRight;
TextAlign Specifies the position of text on the image using the Content-
Alignment value.
Note that a button’s Click event can also occur in cases when the button does not
have focus. The AcceptButton and CancelButton form properties can specify a but-
ton whose Click event is triggered by pushing the Enter or Esc keys, respectively.
Core Suggestion
The constructor creates an unchecked check box with no label. The Text and
Image properties allow the placement of an optional text description or image beside
the box.
7.2 Button Classes, Group Box, Panel, and Label 325
Listing 7-1 presents a sample of the code that is used to place the radio buttons on
the GroupBox control and make them transparent so as to reveal the background
image.
Note that the BackColor property of the radio button is set to Color.Trans-
parent. This allows the background image of groupBox1 to be displayed. By
default, BackColor is an ambient property, which means that it takes the color of its
parent control. If no color is assigned to the radio button, it takes the BackColor of
groupBox1 and hides the image.
The constructor creates an untitled GroupBox having a default width of 200 pixels
and a default height of 100 pixels.
328 Chapter 7 ■ Windows Forms Controls
Its single constructor creates a borderless container area that has scrolling dis-
abled. By default, a Panel takes the background color of its container, which makes
it invisible on a form.
Because the GroupBox and Panel serve the same purpose, the programmer is
often faced with the choice of which to use. Here are the factors to consider in select-
ing one:
• A GroupBox may have a visible caption, whereas the Panel does not.
• A GroupBox always displays a border; a Panel’s border is determined
by its BorderStyle property. It may be set to BorderStyle.None,
BorderStyle.Single, or BorderStyle.Fixed3D.
• A GroupBox does not support scrolling; a Panel enables automatic
scrolling when its AutoScroll property is set to true.
This “no-frills” control has a single parameterless constructor and two properties
worth noting: a FlowDirection property that specifies the direction in which controls
7.2 Button Classes, Group Box, Panel, and Label 329
TableLayoutPanel Control
Figure 7-5 shows the grid layout that results from using a TableLayoutPanel
container.
This code segment creates a TableLayoutPanel and adds the same four controls
used in the previous example. Container properties are set to define a layout grid
that has two rows, two columns, and uses an Inset border style around each cell.
Controls are always added to the container moving left-to-right, top-to-bottom.
The GrowStyle property is worth noting. It specifies how controls are added to
the container when all of its rows and columns are filled. In this example, AddCol-
umns specifies that a column be added to accommodate new controls. The other
options are AddRows and None; the latter causes an exception to be thrown if an
attempt is made to add a control when the panel is filled.
The constructor creates an instance of a label having no caption. Use the Text
property to assign a value to the label. The Image, BorderStyle, and TextAlign
properties can be used to define and embellish the label’s appearance.
One of its less familiar properties is UseMnemonic. By setting it to true and plac-
ing a mnemonic (& followed by a character) in the label’s text, you can create an
access key. For example, if a label has a value of &Sum, pressing Alt-S shifts the focus
to the control (based on tab order) following the label.
The constructor creates an empty (Image = null) picture box that has its Size-
Mode property set so that any images are displayed in the upper-left corner of the
box.
The two properties to be familiar with are Image and SizeMode. Image, of
course, specifies the graphic to be displayed in the PictureBox. SizeMode specifies
how the image is rendered within the PictureBox. It can be assigned one of four
values from the PictureBoxSizeMode enumeration:
Figure 7-7 illustrates some of the features of the PictureBox control. It consists
of a form with three small picture boxes to hold thumbnail images and a larger pic-
ture box to display a full-sized image. The large image is displayed when the user
double-clicks on a thumbnail image.
The code, given in Listing 7-2, is straightforward. The event handler ShowPic
responds to each DoubleClick event by setting the Image property of the large
PictureBox ( bigPicture ) to the image contained in the thumbnail. Note that the
original images are the size of bigPicture and are automatically reduced (by setting
SizeMode) to fit within the thumbnail picture boxes.
332 Chapter 7 ■ Windows Forms Controls
The constructor creates a TextBox that accepts one line of text and uses the color
and font assigned to its container. From such humble origins, the control is easily
transformed into a multi-line text handling box that accepts a specific number of
characters and formats them to the left, right, or center. Figure 7-8 illustrates some
of the properties used to do this.
The text is placed in the box using the Text property and AppendText method:
txtPoetry.Text =
"In Xanadu did Kubla Khan\r\na stately pleasure dome decree,";
txtPoetry.AppendText("\r\nWhere Alph the sacred river ran");
A couple of other TextBox properties to note are ReadOnly, which prevents text
from being modified, and PasswordChar, which is set to a character used to mask
characters entered—usually a password.
This leaves the developer with the task of handling unwanted carriage returns.
Two approaches are available: capture the keystrokes as they are entered or extract
the characters before storing the text. The first approach uses a keyboard event han-
dler, which you should be familiar with from the previous chapter.
Setting Handled to true prevents the carriage return/linefeed from being added
to the text box. This works fine for keyboard entry but has no effect on a
cut-and-paste operation. To cover this occurrence, you can use the keyboard han-
dling events described in Chapter 6 to prevent pasting, or you can perform a final
verification step that replaces any returns with a blank or any character of your
choice.
Core Note
lstArtists.Items.Add("Monet");
lstArtists.Items.Add("Rembrandt");
lstArtists.Items.Add("Manet");
lstArtists.Items.Insert(0, "Botticelli"); //Place at top
Core Note
List boxes may also contain objects. Because an object may have many members,
this raises the question of what is displayed in the TextBox list. Because by default a
ListBox displays the results of an item’s ToString method, it is necessary to over-
ride this System.Object method to return the string you want displayed. The fol-
lowing class is used to create ListBox items:
DDate = death;
Country = ctry;
firstname = fname;
lastname = lname;
}
public override string ToString() {
return (lastname+" , "+firstname);
}
public string GetLName {
get{ return lastname;}
}
public string GetFName {
get{ return firstname;}
}
}
ToString has been overridden to return the artist’s last and first names, which
are displayed in the ListBox. The ListBox (Figure 7-9) is populated using these
statements:
lstArtists.Items.Add
(new Artist("1832", "1883", "Edouard", "Manet","Fr" ));
lstArtists.Items.Add
(new Artist("1840", "1926", "Claude", "Monet","Fr"));
lstArtists.Items.Add
(new Artist("1606", "1669", "Von Rijn", "Rembrandt","Ne"));
lstArtists.Items.Add
(new Artist("1445", "1510", "Sandre", "Botticelli","It"));
Figure 7-9 ListBox items: (A) Default and (B) Custom drawn
338 Chapter 7 ■ Windows Forms Controls
The SelectedItem property returns the item selected in the ListBox. This
object is assigned to myArtist using the as operator, which ensures the object is an
Artist type. The SelectedIndex property can also be used to reference the
selected item:
lstArtists.DrawMode = DrawMode.OwnerDrawFixed;
lstArtists.ItemHeight = 16; // Height (pixels) of item
lstArtists.DrawItem += new DrawItemEventHandler(DrawList);
Member Description
ForeColor Foreground color of the control. This is the color of the text
displayed.
Member Description
Index The index in the control where the item is being drawn.
State The state of the item being drawn. This value is a DrawItem-
State enumeration. For a ListBox, its value is Selected
(1) or None(0).
DrawFocusRectangle() Draws the focus rectangle around the item if it has focus.
Index is used to locate the item. Font, BackColor, and ForeColor return the
current preferences for each. Bounds defines the rectangular area circumscribing
the item and is used to indicate where drawing should occur. State is useful for
making drawing decisions based on whether the item is selected. This is particularly
useful when the ListBox supports multiple selections. We looked at the Graphics
object briefly in the last chapter when demonstrating how to draw on a form. Here, it
is used to draw in the Bounds area. Finally, the two methods, DrawBackground and
DrawFocusRectangle, are used as their name implies.
The event handler to draw items in the ListBox is shown in Listing 7-3. Its
behavior is determined by the operation being performed: If an item has been
selected, a black border is drawn in the background to highlight the selection; if an
item is added, the background is filled with a color corresponding to the artist’s coun-
try, and the first and last names of the artist are displayed.
The routine does require knowledge of some GDI+ concepts (see Chapter 8,
“.NET Graphics Using GDI+”). However, the purpose of the methods should be
clear from their name and context: FillRectangle fills a rectangular area defined
by the Rectangle object, and DrawString draws text to the Graphics object using
a font color defined by the Brush object. Figure 7-9(B) shows the output.
Visually, the ComboBox control consists of a text box whose contents are available
through its Text property and a drop-down list from which a selected item is avail-
able through the SelectedItem property. When an item is selected, its textual rep-
resentation is displayed in the text box window. A ComboBox can be useful in
constructing questionnaires where the user selects an item from the drop-down list
or, optionally, types in his own answer. Its construction is similar to the ListBox:
342 Chapter 7 ■ Windows Forms Controls
The CheckedListBox is a variation on the ListBox control that adds a check box
to each item in the list. The default behavior of the control is to select an item on the
first click, and check or uncheck it on the second click. To toggle the check on and off
with a single click, set the CheckOnClick property to true.
Although it does not support multiple selections, the CheckedListBox does
allow multiple items to be checked and includes them in a CheckedItems collec-
tion. The code here loops through a collection of Artist objects that have been
checked on the control:
You can also iterate through the collection and explicitly determine the checked
state:
The View property specifies one of five layouts for the control:
• Details. An icon and item’s text are displayed in column one. Sub-
items are displayed in the remaining columns.
• LargeIcon. A large icon is shown for each item with a label below the
icon.
• List. Each item is displayed as a small icon with a label to its right.
The icons are arranged in columns across the control.
• SmallIcon. Each item appears in a single column as a small icon with
a label to its right.
• *Tile. Each item appears as a full-size icon with the label and sub-
item details to the right of it. Only available for Windows XP and 2003.
344 Chapter 7 ■ Windows Forms Controls
Core Note
After the Details view is selected, other properties that define the control’s
appearance and behavior are set:
These properties automatically sort the items, permit the user to drag columns
around to rearrange their order, and cause a whole row to be highlighted when the
user selects an item.
Caption is the text to be displayed. Width specifies the column’s width in pixels.
It is set to –1 to size automatically to the largest item in the column, or –2 to size to
the width of the header.
The Add method creates and adds a ColumnHeader type to the ListView’s Col-
umns collection. The method also has an overload that adds a ColumnHeader object
directly:
7.5 The ListView and TreeView Classes 345
ColumnHeader cHeader:
cHeader.Text = "Artist";
cHeader.Width = -2;
cHeader.TextAlign = HorizontalAlignment.Left;
ListView.Columns.Add(ColumnHeader cHeader);
Constructors:
The following code demonstrates how different overloads can be used to create
the items and subitems shown earlier in Figure 7-8:
To display the items, add them to the Items collection of the ListView control:
Specifying Icons
Two collections of images can be associated with a ListView control as ImageList
properties: LargeImageList, which contains images used in the LargeIcon view;
346 Chapter 7 ■ Windows Forms Controls
and SmallImageList, which contains images used in all other views. Think of these
as zero-based arrays of images that are associated with a ListViewItem by the
imageIndex parameter in the ListViewItem constructor. Even though they are
referred to as icons, the images may be of any standard graphics format.
The following code creates two ImageList objects, adds images to them, and
assigns them to the LargeImageList and SmallImageList properties:
An index of 1 selects the cezanne.gif images as the large and small icons. Spec-
ifying an index not in the ImageList results in the icon at index 0 being displayed. If
neither ImageList is defined, no icon is displayed. Figure 7-12 shows the ListView
from Figure 7-11 with its view set to View.LargeIcon:
listView1.View = View.LargeIcon;
There are a couple of things to be aware of when working with these collections.
First, the first subitem (index 0) element actually contains the text for the item—not
a subitem. Second, the ordering of subitems is not affected by rearranging columns
in the ListView control. This changes the appearance but does not affect the under-
lying ordering of subitems.
The same logic is used to list only selected items (MultiSelect = true permits
multiple items to be selected). The only difference is that the iteration occurs over
the ListView.SelectedItems collection:
{
string ItemText = listView1.FocusedItem.Text;
}
Note that this code can also be used with the Click events because they also use
the EventHandler delegate. The MouseDown and MouseUp events can also be used
to detect the current item. Here is a sample MouseDown event handler:
The following code implements the logic: When a column is clicked, the event
handler creates an instance of the ListViewItemComparer class by passing it the
column that was clicked. This object is assigned to the ListViewItemSorter prop-
erty, which causes sorting to occur. Sorting with the IComparer interface is dis-
cussed in Chapter 4, “Working with Objects in C#”).
{
// Setting this property immediately sorts the
// ListView using the ListViewItemComparer object
this.listView1.ListViewItemSorter =
new ListViewItemComparer(e.Column);
}
// Class to implement the sorting of items by columns
class ListViewItemComparer : IComparer
{
private int col;
public ListViewItemComparer()
{
col = 0; // Use as default column
}
public ListViewItemComparer(int column)
{
col = column;
}
// Implement IComparer.Compare method
public int Compare(object x, object y)
{
string xText = ((ListViewItem)x).SubItems[col].Text;
string yText = ((ListViewItem)y).SubItems[col].Text;
return String.Compare(xText, yText);
}
}
Figure 7-13 Using TreeView control (left) and ListView (right) to list enum values
Appearance BackColor, Sets the background color and text color of the
ForeColor node.
Let’s look at how TreeView and TreeNode members are used to perform funda-
mental TreeView operations.
TreeNode tNode;
// Add parent node to treeView1 control
tNode = treeView1.Nodes.Add("A");
// Add child node: two overloads available
tNode.Nodes.Add(new TreeNode("C"));
tNode.Nodes.Add("D"));
// Insert node after C
tNode.Nodes.Insert(1,new TreeNode("E"));
// Add parent node to treeView1 control
tNode = treeView1.Nodes.Add("B");
A B
C E D A
C E D
At this point, we still need to add a copy of node A and its subtree to the parent
node B. This is done by cloning the A subtree and adding it to node B. Node A is ref-
erenced as treeView1.Nodes[0] because it is the first node in the control’s collec-
tion. Note that the Add method appends nodes to a collection, and they can be
referenced by their zero-based position within the collection:
tNode = treeView1.Nodes[0];
while (tNode != null) {
MessageBox.Show(tNode.Text);
tNode = tNode.NextNode;
}
parent nodes consist of unique namespace names, and the child nodes are the types
contained in the namespaces. To include only enum types, a check is made to ensure
that the type inherits from System.Enum.
Notice how reflection is used. The static Assembly.Load method is used to cre-
ate an Assembly type. The Assembly.GetTypes is then used to return a Type
array containing all types in the designated assembly.
refAssembly = Assembly.Load(assem);
foreach (Type t in refAssembly.GetTypes())
The Type.FullName property returns the name of the type, which includes the
namespace. This is used to extract the enum name and the namespace name. The
Type is stored in the Tag field of the child nodes and is used later to retrieve the
members of the enum.
After the TreeView is built, the final task is to display the field members of an
enumeration when its node is clicked. This requires registering an event handler to
be notified when an AfterSelect event occurs:
tvEnum.AfterSelect += new
TreeViewEventHandler(tvEnum_AfterSelect);
The event handler identifies the selected node from the TreeViewEvent-
Args.Node property. It casts the node’s Tag field to a Type class (an enumerator in
this case) and uses the GetMembers method to retrieve the type’s members as Mem-
berInfo types. The name of each field member—exposed by the Member-
Info.Name property—is displayed in the ListView:
// ListView lView;
// lView.View = View.List;
private void tvEnum_AfterSelect(Object sender,
TreeViewEventArgs e)
{
TreeNode tn = e.Node; // Node selected
ListViewItem lvItem;
7.6 The ProgressBar, Timer, and StatusStrip Classes 355
Building a StatusStrip
Let’s now build a form that includes a multi-pane StatusStrip. As shown in Figure
7-15, the strip consists of a label, progress bar, and panel controls. The label (Tool-
StripLabel) provides textual information describing the overall status of the appli-
cation. The progress bar is implemented as a ToolStripProgressBar object. It is
functionally equivalent to a ProgressBar, but inherits from ToolStripItem. A
356 Chapter 7 ■ Windows Forms Controls
StatusStripPanel shows the elapsed time since the form was launched. An event
handler that is triggered by a timer updates both the progress bar and clock panel
every five seconds.
Listing 7-5 contains the code to create the StatusStrip. The left and right ends
of the progress bar are set to represent the values 0 and 120, respectively. The bar is
set to increase in a step size of 10 units each time the PerformStep method is exe-
cuted. It recycles every minute.
The Timer controls when the bar is incremented and when the elapsed time is
updated. Its Interval property is set to a value that controls how frequently its
Tick event is fired. In this example, the event is fired every 5 seconds, which results
in the progress bar being incremented by 10 units and the elapsed time by 5 seconds.
The StatusStripPanel that displays the elapsed time has several properties
that control its appearance and location. In addition to those shown here, it has an
Image property that allows it to display an image. The StatusStripPanel class
358 Chapter 7 ■ Windows Forms Controls
inherits from the ToolStripLabel class that is used in the first pane. Both can be
used to display text, but the panel includes a BorderStyle property that Tool-
StripLabel lacks.
1. A control that derives from an existing control and extends its func-
tionality.
2. A control that can serve as container to allow multiple controls to
interact. This type of control is referred to as a user control. It derives
directly from System.Windows.Forms.UserControl rather than
Control, as do standard controls.
3. A control that derives directly from the Control class. This type of
control is built “from scratch,” and it is the developer’s responsibility
to draw its GUI interface and implement the methods and properties
that allow it to be manipulated by code.
Let’s now look at how to extend an existing control and create a user control.
Extending a Control
The easiest way to create a custom control is to extend an existing one. To demon-
strate this, let’s derive a TextBox that accepts only digits. The code is quite simple.
Create a new class NumericTextBox with TextBox as its base class. The only code
required is an event handler to process the KeyPress event and accept only a digit.
{
if (! char.IsDigit(e.KeyChar)) e.Handled = true;
}
}
After the extended control is compiled into a DLL file, it can be added to any
form.
A UserControl Example
As an example, let’s create a control that can be used to create a questionnaire. The
control consists of a label whose value represents the question, and three radio but-
tons contained on a panel control that represent the user’s choice of answers. The
control exposes three properties: one that assigns the question to the label, one to set
the background color of the panel control, and another that identifies the radio but-
ton associated with the user’s answer.
Figure 7-16 shows the layout of the user control and the names assigned to each
contained control.
Here is how the members are represented as fields within the UserControl1
class:
Listing 7-6 contains the code for three properties: SetQ that sets the label’s text
property to the question, PanelColor that sets the color of the panel, and Choice,
which returns the answer selected by the user as a Choices enum type.
Figure 7-17 provides an example of using this new control. In this example, we
place two control instances on the form and name them Q1 and Q2:
The final step in the application is to do something with the results after the ques-
tionnaire has been completed. The following code iterates through the controls on
the form when the button is clicked. When a UserControl1 type is encountered, its
Choice property is used to return the user’s selection.
[Browsable(true),
Category("QControl"),
Description("Color of panel behind question block")]
public Color PanelColor
{
set {panel1.BackColor = value;}
get {return (panel1.BackColor);}
}
7.8 Using Drag and Drop with Controls 363
Member Description
All The data is moved to the target control, and scrolling occurs in the target
control to display the newly positioned data.
Move The data is moved from the source to the target control.
As the mouse moves across the form, the DoDragDrop method determines the
control under the current cursor location. If this control has its AllowDrop property
set to true, it is a valid drop target and its DragEnter event is raised. The
DragEnter event handler has two tasks: to verify that the data being dragged is an
acceptable type and to ensure the requested action (Effect) is acceptable. When
the actual drop occurs, the destination control raises a DragDrop event. This event
handler is responsible for placing the data in the target control (see Figure 7-18).
Source Control
MouseDown event
Target Control
N
Perform any housekeeping. Is data type correct?
DragDrop event
After the DragDrop event handler finishes, the source control performs any
cleanup operations. For example, if the operation involves moving data—as opposed
to copying—the data must be removed from the source control.
To demonstrate these ideas, let’s create an application that assigns players to a
team from a roster of available players (see Figure 7-19). Team A is created by drag-
ging names from the Available Players to the Team A list. Both lists are implemented
with list boxes, and the Available Players list is set for single selection.
A name is selected by pressing the right mouse button and dragging the name to
the target list. To add some interest, holding the Ctrl key copies a name rather than
moving it.
After the form and controls are created, the first step is to set up the source con-
trol (lstPlayers) to respond to the MouseDown event and the target control (lst-
TeamA) to handle the DragEnter and DragDrop events:
lstPlayers.MouseDown +=
new MouseEventHandler(Players_MouseDown);
lstTeamA.DragEnter += new DragEventHandler(TeamA_DragEnter);
lstTeamA.DragDrop += new DragEventHandler(TeamA_Drop);
7.8 Using Drag and Drop with Controls 365
The next step is to code the event handlers on the source and target control(s) that
implement the drag-and-drop operation.
Member Description
Data Returns the IDataObject that contains data associated with this
operation. This object implements methods that return information
about the data. These include GetData, which fetches the data, and
GetDataPresent, which checks the data type.
KeyState Returns the state of the Alt key, Ctrl key, Shift key, and mouse buttons
as an integer:
1—Left mouse button 8—Ctrl key
2—Right mouse button 16—Middle mouse button
4—Shift key 32—Alt key
Listing 7-8 shows the code for the two event handlers.
Core Note
Drag and drop is not just for text. The DataFormats class predefines the
formats that can be accepted as static fields. These include Bitmap,
PenData, WaveAudio, and numerous others.
7.9 Using Resources 369
This code works fine as long as the file schiele1.jpg exists in the root directory
of the user’s computer. However, relying on the directory path to locate this file has
two obvious disadvantages: The file could be deleted or renamed by the user, and it’s
an external resource that has to be handled separately from the code during installa-
tion. Both problems can be solved by embedding the image in the assembly rather
than treating it as an external resource.
Consider a GUI application that is to be used in multiple countries with different
languages. The challenge is to adapt the screens to each country. At a minimum, this
requires including text in the native language, and may also require changing images
and the location of controls on the form. The ideal solution separates the logic of the
program from the user interface. Such a solution treats the GUI for each country as
an interchangeable resource that is loaded based on the culture settings (the country
and language) of the computer.
The common denominator in these two examples is the need to bind an external
resource to an application. .NET provides special resource files that can be used to
hold just about any nonexecutable data such as strings, images, and persisted data.
These resource files can be included in an assembly—obviating the need for external
files—or compiled into satellite assemblies that can be accessed on demand by an
application’s main assembly.
Let’s now look at the basics of working with resource files and how to embed them in
assemblies; then, we will look at the role of satellite assemblies in localized applications.
.txt resgen.exe
Resources
• strings
• images ResourceWriter .resources
• cursors
1. Create a text file with the name/value strings to be used in the applica-
tion. The file takes this format:
;German version (this is a comment)
Language=German
Select=Wählen Sie aus
Page=Seite
Previous=Vorherig
Next=Nächst
2. Convert the text file to a .resources file using the Resource File
Generator utility resgen.exe:
> resgen german.txt german.resources
Note that the text editor used to create the text file should save it
using UTF-8 encoding, which resgen expects by default.
7.9 Using Resources 371
tn1.Image = Image.FromFile("C:\\schiele1.jpg");
372 Chapter 7 ■ Windows Forms Controls
ResourceManager rm = new
ResourceManager("myresources",
Assembly.GetExecutingAssembly());
// Extract image from resources in assembly
tn1.Image = (Bitmap) rm.GetObject("artistwife");
The resultant file contains XML header information followed by name/value tags
for each resource entry. The actual data—an image in this case—is stored between
the value tags. Here is a section of the file myresources.resx when viewed in a
text editor:
Note that although this example stores only one image in the file, a .resx file can
contain multiple resource types.
If the second parameter is not included, the output file will have the same base
name as the source file. Also, note that this utility can be used to create a
.resources file from a .resx file. The syntax is the same as in the preceding exam-
ple—just reverse the parameters.
looks at the culture settings of the computer it is running on and pulls in the appro-
priate resources. This little bit of magic is accomplished by associating resource files
with the CultureInfo class that designates a language, or language and culture. The
resource files are packaged as satellite assemblies, which are resource files stored as
DLLs.
Core Note
The easiest way to test an application with other culture settings is to set
the CurrentUICulture to the desired culture. The following statement,
for example, is placed before InitializeComponent() in VS.NET to
set the specific culture to German:
System.Threading.Thread.CurrentThread.CurrentUICulture =
new System.Globalization.CultureInfo("de-DE");
Placing the satellite assembly in the proper folder makes it immediately available
to the executable and does not require compiling the application.
7.10 Summary
There are more than 50 GUI controls available in the .NET Framework Class
Library. This chapter has taken a selective look at some of the more important ones.
They all derive from the System.Windows.Forms.Control class that provides the
inherited properties and methods that all the controls have in common.
Although each control is functionally unique, it is possible to create a taxonomy of
controls based on similar characteristics and behavior. The button types, which are
used to intitiate an action or make a selection, include the simple Button, Check-
Box, and RadioButton. These are often grouped using a GroupBox or Panel con-
trol. The TextBox can be used to hold a single line of text or an entire document.
Numerous methods are available to search the box and identify selected text within
it. The PictureBox is available to hold images and has a SizeMode property that is
used to position and size an image within the box.
Several controls are available for presenting lists of data. The ListBox and Com-
boBox display data in a simple text format. However, the underlying data may be a
class object with multiple properties. The TreeView and ListView are useful for
displaying data with a hierarchical relationship. The ListView can display data in
multiple views that include a grid layout and icon representation of data. The Tree-
View presents a tree metaphor to the developer, with data represented as parent and
child nodes.
Most of the controls support the drag-and-drop operation that makes it easy to
move or copy data from one control to another. The source control initiates the
action by calling a DoDragDrop method that passes the data and permissible effects
to the target control.
For applications that require nonstandard controls, .NET lets you create custom
controls. They may be created from scratch, derived from an existing control, or cre-
ated as a combination of controls in a user control container.
5. What property and value are set on a ListView to display its full con-
tents in a grid layout?
9. What class is used to read text from a text resource file embedded in
an assembly? What method is used to read values from the file?
.NET GRAPHICS
USING GDI+
Very few programmers are artists, and only a minority of developers is involved in the
world of gaming where graphics have an obvious justification. Yet, there is something
compelling about writing an application that draws on a computer screen. For one
thing, it’s not difficult. An array of built-in functions makes it easy to create geometric
objects, color them, and even animate them. In this regard, .NET should satisfy the
would-be artist that resides in many programmers.
To understand the .NET graphics model, it is useful to look at its predecessor—
the Win32 Graphical Device Interface (GDI). This API introduced a large set of
drawing objects that could be used to create device independent graphics. The idea
was to draw to a logical coordinate system rather than a device specific coordinate
system—freeing the developer to concentrate on the program logic and not device
details. .NET essentially takes this API, wraps it up in classes that make it easier to
work with, and adds a wealth of new features.
The graphics classes are collectively called GDI+. This chapter looks at the under-
lying principles that govern the use of the GDI+, and then examines the classes and
the functionality they provide. Several programming examples are included that
should provide the tools you will need to further explore the .NET graphics
namespaces.
Keep in mind that GDI+ is not restricted to WinForms applications. Its members
are also available to applications that need to create images dynamically for the Inter-
net (Web Forms and Web Services).You should also recognize that GDI+ is useful
for more than just games or graphics applications. Knowledge of its classes is essen-
tial if you want to design your own controls or modify the appearance of existing
ones.
379
380 Chapter 8 ■ .NET Graphics Using GDI+
System.Drawing
This figure does not depict inheritance, but a general hierarchical relationship
between the GDI+ namespaces. System.Drawing is placed at the top of the chart
because it contains the basic objects required for any graphic output: Pen, Brush,
Color, and Font. But most importantly, it contains the Graphics class. This class is
an abstract representation of the surface or canvas on which you draw. The first
requirement for any drawing operation is to get an instance of this class, so the
Graphics object is clearly a fruitful place to begin the discussion of .NET graphics.
Listing 8-1 contains the code for the Click event handlers associated with each
button. When the Decorate Panel button (btnDecor) is clicked, a Graphics object
is created and used to draw a rectangle around the edge of the panel as well as a hor-
izontal line through the middle. When the Refresh button (btnRefresh) is clicked,
the panel’s Invalidate method is called to redraw all or half of the panel. (More on
the Invalidate command is coming shortly.)
382 Chapter 8 ■ .NET Graphics Using GDI+
using System.Drawing;
//
private void btnDecor_Click(object sender, System.EventArgs e)
{
// Create a graphics object to draw on panel1
Graphics cg = this.panel1.CreateGraphics();
try (
int pWidth = panel1.ClientRectangle.Width;
int pHeight = panel1.ClientRectangle.Height;
// Draw a rectangle around border
cg.DrawRectangle(Pens.Black,2,2,pWidth-4, pHeight-4);
// Draw a horizontal line through the middle
cg.DrawLine(Pens.Red,2,(pHeight-4)/2,pWidth-4,
(pHeight-4)/2);
}
finally {
cg.Dispose(); // You should always dispose of object
}
}
private void btnRefresh_Click(object sender,
System.EventArgs e)
{
// Invokes Invalidate to repaint the panel control
if (this.radAll.Checked) // Radio button - All
{
// Redraw panel1
this.panel1.Invalidate();
} else {
// Redraw left half of panel1
Rectangle r = new
Rectangle(0,0,panel1.ClientRectangle.Width/2,
ClientRectangle.Height);
this.panel1.Invalidate(r); // Repaint area r
this.panel1.Update(); // Force Paint event
}
}
The btnDecor Click event handler uses the DrawRectangle and DrawLine
methods to adorn panel1. Their parameters—the coordinates that define the
shapes—are derived from the dimensions of the containing panel control. When the
drawing is completed, the Dispose method is used to clean up system resources
8.1 GDI+ Overview 383
held by the object. (Refer to Chapter 4, “Working with Objects in C#,” for a discus-
sion of the IDisposable interface.) You should always dispose of the Graphics
object when finished with it. The try-finally construct ensures that Dispose is
called even if an interrupt occurs. As shown in the next example, a using statement
provides an equivalent alternative to try-finally.
The btnRefresh Click event handler is presented as a way to provide insight
into how forms and controls are drawn and refreshed in a WinForms environment. A
form and its child controls are drawn (displayed) in response to a Paint event. Each
control has an associated method that is responsible for drawing the control when the
event occurs. The Paint event is triggered when a form or control is uncovered,
resized, or minimized and restored.
Each control inherits the Handle property from the Control class. This property
can be used with the FromHwnd method as an alternative to the Control.Create-
Graphics method. The following routine uses this approach to draw lines on
panel1 when a MouseDown event occurs on the panel (see Figure 8-3).
Note that the Graphics object is created inside a using statement. This statement
generates the same code as a try-finally construct that includes a g.Dispose()
statement in the finally block.
Note: Passing a true value for the invalidatechildren parameter causes all
child controls to be redrawn.
Invalidate requests a Paint event, but does not force one. It permits the oper-
ating system to take care of more important events before invoking the Paint event.
8.1 GDI+ Overview 385
To force immediate action on the paint request, follow the Invalidate statement
with a call to Control.Update.
Let’s look at what happens on panel1 after a Paint event occurs. Figure 8-4
shows the consequences of repainting the left half of the control. The results are
probably not what you desire: half of the rectangle and line are now gone. This is
because the control’s paint event handler knows only how to redraw the control. It
has no knowledge of any drawing that may occur outside of its scope. An easy solu-
tion in this case is to call a method to redraw the rectangle and line after calling
Invalidate. But what happens if Windows invokes the Paint event because half of
the form is covered and uncovered by another window? This clears the control and
our code is unaware it needs to redraw the rectangle and line. The solution is to han-
dle the drawing within the Paint event handler.
Core Note
When a form is resized, regions within the original area are not redrawn.
To force all of a control or form to be redrawn, pass the following
arguments to its SetStyle method. Only use this when necessary,
because it slows down the paint process.
this.SetStyle(ControlStyles.ResizeRedraw, true);
perform the drawing inside the paint event handler. To do this, first register our
event handler with the PaintEventHandler delegate:
Next, set up the event handler with the code to draw a rectangle and horizontal
line on the panel. The Graphics object is made available through the PaintEvent-
Args parameter.
The Control.OnPaint method is called when a Paint event occurs. Its role is
not to implement any functionality, but to invoke the delegates registered for the
event. To ensure these delegates are called, you should normally invoke the OnPaint
method within the event handler. The exception to this rule is: To avoid screen flick-
ering, do not call this method if painting the entire surface of a control.
Painting is a slow and expensive operation. For this reason, PaintEventArgs
provides the ClipRectangle property to define the area that is displayed when
drawing occurs. Any drawing outside this area is automatically clipped. However, it is
important to realize that clipping affects what is displayed—it does not prevent the
drawing code from being executed. Thus, if you have a time-consuming custom paint
routine, the entire painting process will occur each time the routine is called, unless
you include logic to paint only what is needed.
The following example illustrates how to draw selectively. It paints a pattern of
semi-randomly colored rectangles onto a form’s panel (see Figure 8-5). Before each
rectangle is drawn, a check is made to confirm that the rectangle is in the clipping area.
{
// FromArgb is discussed in Color section
Brush b = new SolidBrush(Color.FromArgb((i*j)%255,
(i+j)%255, ((i+j)*j)%255));
g.FillRectangle(b,r);
g.DrawRectangle(Pens.White,r);
}
}
}
}
The key to this code is the Rectangle.IntersectsWith method that checks for
the intersection of two rectangles. In this case, it tests for overlap between the rectangle
to be drawn and the clip area. If the rectangle intersects the clip area, it needs to be
drawn. Thus, the method can be used to limit the portion of the screen that has to be
repainted. To test the effects, this code was run with and without the IntersectsWith
method. When included, the event handler required 0 to 17 milliseconds—depending
on the size of the area to be repainted. When run without IntersectsWith, the event
handler required 17 milliseconds to redraw all the rectangles.
Another approach to providing custom painting for a form or control is to create a
subclass that overrides the base class’s OnPaint method. In this example, myPanel
is derived from the Panel class and overrides the OnPaint method to draw a custom
diagonal line through the center of the panel.
Unless the new subclass is added to a class library for use in other applications, it
is simpler to write an event handler to provide custom painting.
The following code segments draw the shapes shown in Figure 8-6. To keep things
simple, the variables x and y are used to specify the location where the shape is
drawn. These are set to the coordinates of the upper-left corner of a shape.
These code segments illustrate how easy it is to create simple shapes with a mini-
mum of code. .NET also makes it easy to create more complex shapes by combining
primitive shapes using the GraphicsPath class.
• It automatically connects the last point of a line or arc to the first point
of a succeeding line or arc.
• Its CloseFigure method can be used to automatically close open
shapes, such as an arc. The first and last points of the shape are
connected.
• Its StartFigure method prevents the previous line from being auto-
matically connected to the next line.
• Its Dispose method should always be called when the object is no
longer in use.
The following code creates and displays the Infinity Cross shown in Figure 8-7. It
is constructed by adding five polygons to the GraphicsPath container object. The
Graphics object then draws the outline and fills in the cross.
Instead of drawing and filling each polygon separately, we use a single DrawPath
and FillPath statement to do the job.
The GraphicsPath class has several methods worth exploring—AddCircle,
AddArc, AddEllipse, AddString, Warp, and others—for applications that require
the complex manipulation of shapes. One of the more interesting is the Transform
method that can be used to rotate or shift the coordinates of a DrawPath object. This
following code segment offers a taste of how it works. A transformation matrix is cre-
ated with values that shift the x coordinates by 50 units and leave the y coordinates
unchanged. This Transform method applies the matrix to the DrawPath and shifts
the coordinates; the shape is then drawn 50 units to the right of the first shape.
bool Rectangle.Contains(Point(x,y))
bool GraphicsPath.IsVisible(Point(x,y))
392 Chapter 8 ■ .NET Graphics Using GDI+
this.pictureBox1.MouseDown += new
MouseEventHandler(down_Picture);
The following code implements event handler logic to determine if the mouse
down occurs within the boundary of any shape.
After you have a basic understanding of how to create and use shapes, the next
step is to enhance these shapes with eye catching graphical effects such as gradients,
textured colors, and different line styles and widths. This requires an understanding
of the System.Drawing classes: Pen, Brush, and Color.
Pens
The Graphics object must receive an instance of the Pen class to draw a shape’s out-
line. Our examples thus far have used a static property of the Pens class—
Pens.Blue, for example—to create a Pen object that is passed to the Graphics
object. This is convenient, but in many cases you will want to create your own Pen
object in order to use non-standard colors and take advantage of the Pen properties.
Constructors:
Example:
The constructors allow you to create a Pen object of a specified color and width.
You can also set its attributes based on a Brush object, which we cover later in this
section. Note that the Pen class inherits the IDisposable interface, which means
that you should always call the Pen object’s Dispose method when finished with it.
Besides color and width, the Pen class offers a variety of properties that allow you
to control the appearance of the lines and curves the Pen object draws. Table 8-1
contains a partial list of these properties.
Member Description
Member Description
DashCap The cap style used at the beginning and end of dashes in a dashed
line. A cap style is a graphic shape such as an arrow.
DashOffset Distance from start of a line to the beginning of its dash pattern.
DashStyle The type of dashed lines used. This is based on the DashStyle
enumeration.
StartCap The cap style used at the beginning and end of lines. This comes
EndCap from the LineCap enumeration that includes arrows, diamonds, and
squares—for example, LineCap.Square.
DashStyle
This property defines the line style, which can be Solid, Dash, Dot, DashDot,
DashDotDot, or Custom (see Figure 8-9). The property’s value comes from the
DashStyle enumeration.
Dash
DashDot
DashDotDot
Dot
Solid
Graphics g = pictureBox1.CreateGraphics();
Pen p1 = new Pen(Color.Black, 5);
p1.StartCap = LineCap.DiamondAnchor;
p1.EndCap = LineCap.RoundAnchor;
int yLine = 20;
foreach(string ds in Enum.GetNames(typeof(DashStyle)))
{
if (ds != "Custom") // Ignore Custom DashStyle type
{
// Parse creates an enum type from a string
p1.DashStyle = (DashStyle)Enum.Parse(
typeof(DashStyle), ds);
g.DrawLine(p1,20,yLine,120,yLine);
g.DrawString(ds,new Font("Arial",10),Brushes.Black,
140,yLine-8);
yLine += 20;
}
}
The code loops through the DashStyle enumeration and draws a line for each
enum value except Custom. It also uses the DrawString method to display the name
of the enumeration values. This method is discussed in Chapter 9.
Brushes
Brush objects are used by these Graphics methods to create filled geometric shapes:
All of these receive a Brush object as their first argument. As with the Pen class,
the easiest way to provide a brush is to use a predefined object that represents one of
the standard colors—for example, Brushes.AntiqueWhite. To create more inter-
esting effects, such as fills with patterns and gradients, it is necessary to instantiate
your own Brush type. Unlike the Pen class, you cannot create an instance of the
abstract Brush class; instead, you use one of its inheriting classes summarized in
Table 8-2.
396 Chapter 8 ■ .NET Graphics Using GDI+
Table 8-2 Brush Types That Derive from the Brush Class
Note that all Brush classes have a Dispose method that should be called to
destroy the Brush object when it is no longer needed.
The two most popular of these classes are HatchBrush, which is handy for creat-
ing charts, and LinearGradientBrush, for customizing the background of controls.
Let’s take a closer look at both of these.
Constructors:
Parameters:
hStyle HatchStyle enumeration that specifies the hatch pattern.
forecolor The color of the lines that are drawn.
backcolor Color of the space between the lines (black is default).
8.2 Using the Graphics Object 397
Graphics g = pictureBox1.CreateGraphics();
// Fill Rectangle with DarkVertical pattern
Brush b = new HatchBrush(HatchStyle.DarkVertical,
Color.Blue,Color.LightGray);
g.FillRectangle(b,20,20,80,60);
// Fill Rectangle with DottedDiamond pattern
b = new HatchBrush(HatchStyle.DottedDiamond,
Color.Blue,Color.LightGray);
g.FillRectangle(b,120,20,80,60);
DarkVertical DottedDiamond
Cross Weave
SmallGrid Percent10
Figure 8-10 Using HatchBrush with some of the available hatch styles
create a gradient brush and then work with examples that demonstrate the more use-
ful properties and methods of the LinearGradientBrush class.
Constructors:
Parameters:
rect Rectangle specifying the bounds of the gradient.
color1 The start color in the gradient.
color2 The end color in the gradient.
angle The angle in degrees moving clockwise from the x axis.
LinearGradientMode A LinearGradientMode enum value:
Horizontal, Vertical, BackwardDiagonal,
ForwardDiagonal
b.SetBlendTriangularShape(.5f);
g.FillRectangle(b,rb);
1 2 3 4
The main point of interest in this code is the use of the SetBlendTriangular-
Shape method to create the blending effect shown in the third rectangle in Figure
8-11. This method takes an argument between 0 and 1.0 that specifies a relative
focus point where the end color is displayed. The gradient then “falls off” on either
side of this point to the start color.
The fourth rectangle in the figure is created by repeating the original brush pat-
tern. The following code defines a small gradient brush that is used to fill a larger
rectangle:
Notice how the light and dark colors are reversed horizontally before each repeat
occurs: [light-dark][dark-light]. The WrapMode property determines how the repeated
gradient is displayed. In this example, it is set to the WrapMode enum value of Tile-
FlipX, which causes the gradient to be reversed horizontally before repeating. The
most useful enum values include the following:
b = new LinearGradientBrush(rb,Color.Empty,Color.Empty,0);
ColorBlend myBlend = new ColorBlend();
// Specify colors to include in gradient
myBlend.Colors = new Color[]
{Color.Red, Color.White, Color.Blue,};
// Position of colors in gradient
myBlend.Positions = new float[] {0f, .5f, 1f};
b.InterpolationColors = myBlend; // Overrides constructor colors
Colors
.NET implements the Color object as a structure that includes a large number of
colors predefined as static properties. For example, when a reference is made to
Color.Indigo, the returned value is simply the Indigo property. However, there is
more to the structure than just a list of color properties. Other properties and meth-
ods permit you to deconstruct a color value into its internal byte representation or
build a color from numeric values. To appreciate this, let’s look at how colors are rep-
resented.
Computers—as opposed to the world of printing—use the RGB (red/green/blue)
color system to create a 32-bit unsigned integer value that represents a color. Think
of RGB as a three-dimensional space with the red, green, and blue values along each
axis. Any point within that space represents a unique RGB coordinate value. Throw
in a fourth component, the alpha value—that specifies the color’s transparency—and
you have the 4-byte AlphaRGB (ARGB) value that defines a color. For example,
Indigo has RGB values of 75, 0, 130, and an alpha value of 255 (no transparency).
This is represented by the hex value 4B0082FF.
Colors can also be represented by the HSL (hue/saturation/luminosity) and HSB
(hue/saturation/brightness) color spaces. While RGB values follow no easily discern-
ible pattern, HSL and HSB are based on the standard color wheel (see Figure 8-12)
that presents colors in an orderly progression that makes it easy to visualize the
sequence. Hue is represented as an angle going counterclockwise around the wheel.
The saturation is the distance from the center of the wheel toward the outer edge.
Colors on the outer edge have full saturation. Brightness measures the intensity of a
color. Colors shown on the wheel have 100% brightness, which decreases as they are
8.2 Using the Graphics Object 401
green yellow
120˚ 60˚
180˚ 0˚ red
cyan
240˚ 300˚
blue magenta
FromArgb allows you to specify a color by RGB and alpha values, which makes it
easy to change the transparency of an existing color. Here are some of its overloads:
// (r, g, b)
Color slate1 = Color.FromArgb (112, 128, 144);
// (alpha, r, g, b)
Color slate2 = Color.FromArgb (255, 112, 128, 144);
// (alpha, Color)
Color lightslate = Color.FromArgb(50, slate2 );
402 Chapter 8 ■ .NET Graphics Using GDI+
The individual HSB values of a color can be extracted using the Color.GetHue,
GetSaturation, and GetBrightness methods. The hue is measured in degrees as
a value from 0.0 to 360.0. Saturation and brightness have values between 0 and 1.
Observe in Figure 8-12 that the hue of 210 degrees (moving clockwise from 0)
falls between cyan and blue on the circle—which is where you would expect to find a
slate gray color.
of the screen displays RGB and HSB values obtained using the properties and meth-
ods discussed earlier.
Figure 8-13 Example: Color viewer demonstrates working with colors and gradients
The code for this application is shown in Listings 8-2 and 8-3. The former contains
code to populate the TreeNode structure with the color nodes; Listing 8-3 shows the
methods used to display the selected color and its color space values. The routine
code for laying out controls on a form is excluded.
When the application is executed, the form’s constructor calls BuildWheel to cre-
ate the tree structure of parent nodes that represent the 12 color categories. Then,
when the Build Color Tree button is clicked, the Click event handler loops through
the KnownColor enum value (excluding system colors) and calls InsertColor to
insert the color under the correct parent node. The Tag field of the added node
(color) is set to an HSB struct that contains the hue, saturation, and brightness for
the color. Nodes are stored in ascending order of Hue value. (See Chapter 7, “Win-
dows Forms Controls,” for a discussion of the TreeNode control.)
406 Chapter 8 ■ .NET Graphics Using GDI+
Listing 8-3 contains the code for both the node selection event handler and for
ShowColor, the method that displays the color, draws the brightness scale, and fills
all the text boxes with RGB and HSB values.
The code incorporates several of the concepts already discussed in this section. Its
main purpose is to demonstrate how Color, Graphics, and Brush objects work
together. A SolidBrush is used to fill panel1 with a color sample, a gradient brush
creates the brightness scale, and Color properties provide the RGB values displayed
on the screen.
8.3 Images
GDI+ provides a wide range of functionality for working with images in a runtime
environment. It includes support for the following:
The two most important classes for handling images are the Image and Bitmap
class. Image is an abstract class that serves as a base class for the derived Bitmap class.
It provides useful methods for loading and storing images, as well as gleaning informa-
tion about an image, such as its height and width. But for the most part, working with
408 Chapter 8 ■ .NET Graphics Using GDI+
images requires the creation of objects that represent raster images. This responsibility
devolves to the Bitmap class, and we use it exclusively in this section.
Tasks associated with using images in applications fall into three general categories:
In both cases, the image stored in bmp is the same size as the image in the file.
Another Bitmap constructor can be used to scale the image as it is loaded. This code
loads and scales an image to half its size:
int w = Image.FromFile(fname).Width;
int h = Image.FromFile(fname).Height;
Size sz= new Size(w/2,h/2);
bmp = new Bitmap(Image.FromFile(fname), sz); //Scales
The Save method has five overloads; its simplest forms take the name of the file
to be written to as its first parameter and an optional ImageFormat type as its sec-
ond. The ImageFormat class has several properties that specify the format of the
image output file. If you have any experience with image files, you already know that
the format plays a key role in the size of the file. In this example, the new JPEG file is
less than one-fourth the size of the original GIF file.
To support multiple file formats, GDI+ uses encoders to save images to a file and
decoders to load images. These are referred to generically as codecs (code-decode).
An advantage of using codecs is that new image formats can be supported by writing
a decoder and encoder for them.
.NET provides the ImageCodecInfo class to provide information about installed
image codecs. Most applications allow GDI+ to control all aspects of loading and
storing image files, and have no need for the codecs information. However, you may
want to use it to discover what codecs are available on your machine. The following
code loops through and displays the list of installed encoders (see Figure 8-14):
// Using System.Drawing.Imaging
string myList="";
foreach(ImageCodecInfo co in ImageCodecInfo.GetImageEncoders())
myList = myList +"\n"+co.CodecName;
Console.WriteLine(myList);
DrawImage has some 30 overloaded versions that give you a range of control over
sizing, placement, and image selection. Many of these include a destination rectan-
gle, which forces the source image to be resized to fit the rectangle. Other variations
include a source rectangle that permits you to specify a portion of the source image
to be displayed; and some include both a destination and source rectangle.
The following examples capture most of the basic effects that can be achieved.
Note that the source image is 192×160 pixels for all examples, and the destination
panel is 96×80 pixels.
The DrawImage variations shown here illustrate many familiar image effects:
zoom in and zoom out are achieved by defining a destination rectangle larger (zoom
in) or smaller (zoom out) than the source image; image skewing and rotation are
products of mapping the corners of the original image to three destination points, as
shown in the figure to the left of Example 5.
Manipulating Images
.NET also supports some more advanced image manipulation techniques that allow
an application to rotate, mirror, flip, and change individual pixels in an image. We’ll
look at these techniques and also examine the advantage of building an image in
memory before displaying it to a physical device.
412 Chapter 8 ■ .NET Graphics Using GDI+
Recall from an earlier example that the destination points are the new coordinates
of the upper-left, upper-right, and lower-left corners of the source image. Figure
8-15 illustrates the effects that can be achieved by altering the destination points.
a b b a c c
a
b
c c a b
Original Mirrored Flipped Rotated 90˚
The following code is used to create a mirrored image from the original image.
Think of the image as a page that has been turned over from left to right: points a and
b are switched, and point c is now the lower-right edge.
// Rotate 90 degrees
bmp.RotateFlip(RotateFlipType.Rotate90FlipNone);
// Rotate 90 degrees and flip along the vertical axis
bmp.RotateFlip(RotateFlipType.Rotate90FlipY);
// Flip horizontally (mirror)
bmp.RotateFlip(RotateFlipType.RotateNoneFlipX);
The most important thing to recognize about this method is that it changes the
actual image in memory—as opposed to DrawImage, which simply changes it on the
drawing surface. For example, if you rotate an image 90 degrees and then rotate it 90
degrees again, the image will be rotated a total of 180 degrees in memory.
The following code creates a Bitmap object bmpMem that serves as a buffer where
the pixels are swapped on the flag before it is displayed. We use the Graphics.From-
414 Chapter 8 ■ .NET Graphics Using GDI+
Image method to obtain a Graphics object that can write to the image in memory.
Other new features to note are the use of GetPixel and SetPixel to read and write
pixels on the image.
Core Note
menu options are applied to this panel. The smaller panel is where part of the main
image is copied. The + and – buttons zoom in and out, respectively.
The copying process is the most interesting part of the application. A user selects a
rectangular area of the image by pressing the mouse button and dragging the mouse
over the image. When the mouse button is raised, the selected area can be copied to
the smaller panel by choosing Image-Copy from the menu.
Figure 8-17 User interface for Image Viewer [Source: Lourve, Paris]
The code for this project is presented in three sections: the menu operations,
drawing the selection rectangle, and the functions associated with the small panel
(panel2).
Listing 8-4 contains the code for the menu options to load an image into the view-
ing panel, flip an image, mirror an image, and refresh the viewing panel. The
Image-Copy option is discussed in the code related to manipulating the image on
panel2.
416 Chapter 8 ■ .NET Graphics Using GDI+
The file loading routine displays a dialog box for the user to enter the image file
name. If this image file exists, it is opened and displayed in panel1. If the image is
larger than the panel, it is cropped.
The method that mirrors an image first creates a temporary Bitmap and uses
DrawImage, as described earlier, to mirror the image to its surface. The mirrored
image is displayed in the panel and saved in newBmp. Flipping could be done in a
similar way, but for demonstration purposes, we use the RotateFlip method to
directly transform newBmp before it is displayed.
The screen refresh routine simply calls Invalidate and Update to redraw the
image on panel1. The main effect of this is to remove any selection rectangle (dis-
cussed next) that has been drawn on the image.
The following fields, defined at the form level, are used to keep status information:
When a MouseDown occurs, origPoint is set to the x,y coordinates and serves as
the origin of the rectangle that is to be drawn. Dragging the mouse results in a rect-
angle being displayed that tracks the mouse movement. The MouseMove event han-
dler must draw the rectangle at the new position and erase the previous rectangle. It
uses lastPoint and origPoint to determine the part of the image to redraw in
order to erase the previous rectangle. The new rectangle is determined by the cur-
rent mouse coordinates and origPoint. When the MouseUp event occurs, rectSel
is set to the final rectangle.
The logic for creating the rectangles is based on establishing a point of origin
where the first MouseDown occurs. The subsequent rectangles then attempt to use
that point’s coordinates for the upper left corner. However, if the mouse is moved to
the left of the origin, the upper left corner must be based on this x value. This is why
the MouseUp and MouseMove routines check to see if the current x coordinate e.x is
less than that of the origin.
420 Chapter 8 ■ .NET Graphics Using GDI+
if (selectStatus)
{
Graphics g = panel2.CreateGraphics();
g.FillRectangle(Brushes.White,panel2.ClientRectangle);
Rectangle rd = new
Rectangle(0,0,rectSel.Width,rectSel.Height);
Bitmap temp = new Bitmap(rectSel.Width,rectSel.Height);
Graphics gi = Graphics.FromImage(temp);
// Draw selected portion of image onto temp
gi.DrawImage(newBmp,rd,rectSel,GraphicsUnit.Pixel);
smallBmp = temp; // save image displayed on panel2
// Draw image onto panel2
g.DrawImage(smallBmp,rd);
g.Dispose();
resizeLevel = 0; // Keeps track of magnification/reduction
}
The plus (+) and minus (–) buttons are used to enlarge or reduce the image on
panel2. The actual enlargement or reduction is performed in memory on small-
Bmp, which holds the original copied image. This is then drawn to the small panel. As
shown in the code here, the magnification algorithm is quite simple: The width and
height of the original image are increased in increments of .25 and used as the
dimensions of the target rectangle.
// Enlarge image
Graphics g = panel2.CreateGraphics();
if (smallBmp != null)
{
resizeLevel= resizeLevel+1;
float fac= (float) (1.0+(resizeLevel*.25));
int w = (int)(smallBmp.Width*fac);
int h = (int)(smallBmp.Height*fac);
Rectangle rd= new Rectangle(0,0,w,h); // Destination rect.
Bitmap tempBmp = new Bitmap(w,h);
Graphics gi = Graphics.FromImage(tempBmp);
// Draw enlarged image to tempBmp Bitmap
8.3 Images 421
gi.DrawImage(smallBmp,rd);
g.DrawImage(tempBmp,rd); // Display enlarged image
gi.Dispose();
}
g.Dispose();
The code to reduce the image is similar, except that the width and height of the
target rectangle are decremented by a factor of .25:
resizeLevel= (resizeLevel>-3)?resizeLevel-1:resizeLevel;
float fac= (float) (1.0+(resizeLevel*.25));
int w = (int)(smallBmp.Width*fac);
int h =(int) (smallBmp.Height*fac);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern int BitBlt(
IntPtr hDestDC, // Handle to target device context
int xDest, // x coordinate of destination
int yDest, // y coordinate of destination
int nWidth, // Width of memory being copied
int nHeight, // Height of memory being copied
IntPtr hSrcDC, // Handle to source device context
422 Chapter 8 ■ .NET Graphics Using GDI+
Always pair each GetHdc with a ReleaseHdc, and only place calls to GDI func-
tions within their scope. GDI+ operations between the statements are ignored.
8.5 Test Your Understanding 423
8.4 Summary
6. You are drawing an image that is 200×200 pixels onto a panel that is
100×100 pixels. The image is contained in the Bitmap bmp, and the
following statement is used:
g.DrawImage(bmp, panel1.ClientRectangle);
What percent of the image is displayed?
a. 25%
b. 50%
c. 100%
Graphics g = panel1.CreateGraphics();
g.SmoothingMode = SmoothingMode.AntiAlias;
GraphicsPath gp = new GraphicsPath();
gp.AddLine(10,170,30,170);
gp.AddLine(40,50,50,20);
8.5 Test Your Understanding 425
gp.StartFigure();
gp.AddLine(16,100,100,100);
gp.AddLine(50,20,145,100);
gp.AddLine(100,100,190,180);
gp.StartFigure();
gp.AddArc(65,10,120,180,180,80);
g.DrawPath(new Pen(Color.Black,2),gp);
gp.StartFigure();
gp.AddArc(65,5,120,100,200,70);
Chapter 8, “.NET Graphics Using GDI+,” focused on the use of GDI+ for creating
graphics and working with images. This chapter continues the exploration of GDI+
with the focus shifting to its use for “drawing text.” One typically thinks of text as
being printed—rather than drawn. In the .NET world, however, the display and ren-
dering of text relies on techniques similar to those required to display any graphic: a
Graphics object must be created, and its methods used to position and render text
strings—the shape of which is determined by the font used.
This chapter begins with a look at fonts and an explanation of the relationship
between fonts, font families, and typefaces. It then looks at the classes used to create
fonts and the options for sizing them and selecting font styles. The Graphics.Draw-
String method is the primary tool for rendering text and has several overloads that
support a number of formatting features: text alignment, text wrapping, the creation
of columnar text, and trimming, to name a few.
The chapter concludes with a detailed look at printing. An incremental approach
is taken. A simple example that illustrates the basic classes is presented. Building on
these fundamentals, a more complex and realistic multi-page report with headers,
multiple columns, and end-of-page handling is presented. Throughout, the emphasis
is on understanding the GDI+ classes that support printer selection, page layout, and
page creation.
427
428 Chapter 9 ■ Fonts, Text, and Printing
9.1 Fonts
GDI+ supports only OpenType and TrueType fonts. Both of these font types are
defined by mathematical representations that allow them to be scaled and rotated
easily. This is in contrast to raster fonts that represent characters by a bitmap of a
predetermined size. Although prevalent a few years ago, these font types are now
only a historical footnote in the .NET world.
The term font is often used as a catchall term to describe what are in fact type-
faces and font families. In .NET, it is important to have a precise definition for these
terms because both Font and FontFamily are .NET classes. Before discussing how
to implement these classes, let’s look at the proper definition of these terms.
8 10 12 . . . 8 10 12 . . . Font
Font Families
The FontFamily class has two primary purposes: to create a FontFamily object
that is later used to create an instance of a Font, or to provide the names of all font
families available on the user’s computer system.
9.1 Fonts 429
Parameters:
name A font family name such as Arial or Tahoma.
As a rule, the more specific you are about the font family, the more control you
have over the appearance of the application, which makes the first constructor pref-
erable in most cases. However, use a generic font family if you are unsure about the
availability of a specific font on a user’s computer.
Constructors:
Parameters:
emSize The size of the font in terms of the GraphicsUnit. The default
GraphicsUnit is Point.
style A FontStyle enumeration value: Bold, Italic, Regular, Strikeout,
or Underline. All font families do not support all styles.
unit The units in which the font is measured. This is one of the Graphics-
Unit enumeration values:
Point—1/72nd inch Inch
Display—1/96th inch Millimeter
Document—1/300th inch Pixel (based on device resolution)
Creating a Font
Creating fonts is easy; the difficulty lies in deciding which of the many constructors
to use. If you have already created a font family, the simplest constructor is
The simplest approach, without using a font family, is to pass the typeface name
and size to a constructor:
The second example illustrates how to combine styles. Note that if a style is speci-
fied that is not supported by the font family, an exception will occur.
The Font class implements the IDisposable interface, which means that a font’s
Dispose method should be called when the font is no longer needed. As we did in
the previous chapter with the Graphics object, we can create the Font object inside
a using construct, to ensure the font resources are freed even if an exception occurs.
Baseline
Descender Line
Key Ascent
Descent
Font
Height
Table 9-1 lists the methods and properties used to retrieve the metrics associated
with the em height, descent, ascent, and total line space for a font family. Most of the
values are returned as design units, which are quite easy to convert to a Graphics-
Unit. The key is to remember that the total number of design units in the em is
equivalent to the base size argument passed to the Font constructor. Here is an
example of retrieving metrics for an Arial 20-point font:
The primary value of this exercise is to establish familiarity with the terms and
units used to express font metrics. Most applications that print or display lines of text
are interested primarily in the height of a line. This value is returned by the
Font.GetHeight method and is also available through the Graphics.Measure-
String method described in the next section.
Table 9-1 Using Font and FontFamily to Obtain Font Metrics (continued)
Definitions:
cell height = ascent + descent
em height = cell height – internal leading
line spacing = cell height + external leading
Overloads:
Example:
In this example, the upper-left corner of the text string is located at the x,y coordi-
nate 20 pixels from the left edge and 5 pixels from the top of the drawing surface. If
the printed text extends beyond the boundary of the drawing surface, the text is trun-
cated. You may want this in some cases, but more often you’ll prefer that long lines
be broken and printed as multiple lines.
Word wrapping is often preferable to line truncation, but raises the problem of
determining how many lines of text must be accommodated. If there are more lines
of text than can fit into the rectangle, they are truncated. To avoid truncation, you
could calculate the height of the required rectangle by taking into account the font
(f), total string length(s), and rectangle width (w). It turns out that .NET Graph-
ics.MeasureString method performs this exact operation. One of its overloads
takes the string, font, and desired line width as arguments, and returns a SizeF
object whose Width and Height properties provide pixel values that define the
required rectangle.
Using this method, the preceding code can be rewritten to handle the dynamic
creation of the bounding rectangle:
g.Drawstring(s,regFont,Brushes.Black, rf);
// Draw rectangle around text
g.DrawRectangle(Pens.Red,20F,5F,rf.Width, rf.Height);
Note that DrawString recognizes newline (\r\n) characters and creates a line
break when one is encountered.
Member Description
FormatFlags This bit-coded property provides a variety of options for controlling print
layout when printing within a rectangle.
StringFormatFlags.DirectionVertical—Draws text from
top-to-bottom.
StringFormatFlags.LineLimit—Only entire lines are displayed
within the rectangle.
StringFormatFlags.NoWrap—Disables text wrapping. The result is
that text is printed on one line only, irrespective of the rectangle’s height.
436 Chapter 9 ■ Fonts, Text, and Printing
Core Note
If no tab stops are specified, default tab stops are set up at intervals equal
to four times the size of the font. A 10-point font would have default tabs
every 40 points.
As shown in Table 9-2, the SetTabStops method takes two arguments: the offset
from the beginning of the line and an array of floating point values that specify the
distance between tab stops. Here is an example that demonstrates various ways to
define tab stops:
float[] tStops = {50f, 100f, 100f}; //Stops at: 50, 150, and 250
float[] tStops = {50f}; // Stops at: 50, 100, 150
You can see that it is not necessary to specify a tab stop for every tab. If a string
contains a tab for which there is no corresponding tab stop, the last tab stop in the
array is repeated. Listing 9-1 demonstrates using tabs to set column headers.
Figure 9-3 shows the four-column output from this code. Note that the second
column begins at the x coordinate 150, which is the first tab stop (140) plus the x
coordinate (10) specified in DrawString.
Core Note
The use of tab spaces only supports left justification for proportionate
fonts. If you need right justification—a virtual necessity for displaying
financial data—pass a rectangle that has the appropriate coordinates to
the DrawString method. Then, set the Alignment property of
StringFormat to StringAlignment.Far.
438 Chapter 9 ■ Fonts, Text, and Printing
strFmt.Alignment = StringAlignment.Center;
strFmt.LineAlignment = StringAlignment.Center;
9.3 Printing
The techniques discussed in Sections 9.1 and 9.2 are device independent, which
means they can be used for drawing to a printer as well as a screen. This section deals
specifically with the task of creating reports intended for output to a printer. For
complex reporting needs, you may well turn to Crystal Reports—which has special
support in Visual Studio.NET—or SQL Server Reports. However, standard reports
can be handled quite nicely using the native .NET classes available. Moreover, this
approach enables you to understand the fundamentals of .NET printing and apply
your knowledge of event handling and inheritance to customize the printing process.
Overview
The PrintDocument class—a member of the System.Drawing.Printing
namespace—provides the methods, properties, and events that control the print pro-
cess. Consequently, the first step in setting up a program that sends output to a
printer is to create a PrintDocument object.
440 Chapter 9 ■ Fonts, Text, and Printing
Figure 9-4 PrintDocument events that occur during the printing process
An event handler wired to the PrintPage event contains the logic and statements
that produce the output. This routine typically determines how many lines can be
printed—based on the page size—and contains the DrawString statements that
generate the output. It is also responsible for notifying the underlying print control-
ler whether there are more pages to print. It does this by setting the HasMorePages
property, which is passed as an argument to the event handler, to true or false.
The basic PrintDocument events can be integrated with print dialog boxes that
enable a user to preview output, select a printer, and specify page options. Listing 9-2
displays a simple model for printing that incorporates the essential elements
required. Printing is initiated when btnPrint is clicked.
This simple example illustrates the rudiments of printing, but does not address
issues such as handling multiple pages or fitting multiple lines within the boundaries
of a page. To extend the code to handle these real-world issues, we need to take a
closer look at the PrintDocument class.
PrintDocument Class
Figure 9-5 should make it clear that the PrintDocument object is involved in just
about all aspects of printing: It provides access to paper size, orientation, and docu-
ment margins through the DefaultPageSettings class; PrinterSettings allows
selection of a printer as well as the number of copies and range of pages to be printed;
and event handlers associated with the PrintDocument events take care of initializa-
tion and cleanup chores related to the printing process. The PrintController class
works at a level closer to the printer. It is used behind the scenes to control the print
preview process and tell the printer exactly how to print a document.
442 Chapter 9 ■ Fonts, Text, and Printing
PrintDocument
PageSettings DefaultPageSettings
RectangleF Bounds—Size of page
bool Landscape—Gets or sets orientation
Margins Margins—Gets or sets left, right, top, and bottom page margins
PaperSize PaperSize—Width and height of paper in 1/100th inches
PrinterSettings PrinterSettings
int Copies—Number of copies to print
int FromPage—First page to print
int ToPage—Last page to print
stringCollection InstalledPrinters—Printers installed
on the computer
string PrinterName—Name of target printer
PrintController PrintController
StandardPrintController
PrintControllerwithStatusDialog
PreviewPrintController
events
BeginPrint—Occurs when Print is called, before first page is printed
EndPrint—Occurs after last page is printed
PrintPage—Occurs when page needs to be printed
QueryPageSettingsEvent—Occurs before each PrintPage event
Printer Settings
The PrinterSettings object maintains properties that specify the printer to be
used and how the document is to be printed—page range, number of copies, and
whether collating or duplexing is used. These values can be set programmatically or
by allowing the user to select them from the Windows PrintDialog component.
Note that when a user selects an option on the PrintDialog dialog box, he is actu-
ally setting a property on the underlying PrinterSettings object.
9.3 Printing 443
Selecting a Printer
The simplest approach is to display the PrintDialog window that contains a
drop-down list of printers:
You can also create your own printer selection list by enumerating the
InstalledPrinters collection:
string printer=
printerList.Items[printerList.SelectedIndex].ToString();
pd.PrinterSettings.PrinterName = printer;
pDialog.AllowSomePages = true;
pd.PrinterSettings.FromPage =1;
pd.PrinterSettings.ToPage = maxPg;
pd.PrinterSettings.MinimumPage=1;
pd.PrinterSettings.MaximumPage= maxPg;
if (pDialog.ShowDialog()== DialogResult.OK)
{
maxPg = 5; // Last page to print
currPg= 1; // Current page to print
if (pDialog.PrinterSettings.PrintRange ==
PrintRange.SomePages)
{
currPg = pd.PrinterSettings.FromPage;
maxPg = pd.PrinterSettings.ToPage;
}
pd.Print(); // Invoke PrintPage event
}
This code assigns the first and last page to be printed to currPg and maxPg.
These both have class-wide scope and are used by the PrintPage event handler to
determine which pages to print.
foreach (PrinterResolution pr in
pd.PrinterSettings.PrinterResolutions)
{
if (pr.Kind == PrinterResolutionKind.High)
{
pd.PageSettings.PrinterResolution = pr;
break;
}
}
9.3 Printing 445
Page Settings
The properties of the PageSettings class define the layout and orientation of the
page being printed to. Just as the PrinterSettings properties correspond to the
PrintDialog, the PageSettings properties reflect the values of the PageSetup-
Dialog.
This dialog box lets the user set all the margins, choose landscape or portrait ori-
entation, select a paper type, and set printer resolution. These values are exposed
through the DefaultPageSettings properties listed in Figure 9-5. As we will see,
they are also made available to the PrintPage event handler through the Print-
PageEventArgs parameter and to the QueryPageSettingsEvent through its
QueryPageSettingsEventArgs parameter. The latter can update the values,
whereas PrintPage has read-only access.
850
1100
100
MarginBounds: (100,100,650,900)
Figure 9-6 illustrates the layout of a page that has the following DefaultPage-
Settings values:
Bounds.X = 0;
Bounds.Y = 0;
Bounds.Width = 850;
446 Chapter 9 ■ Fonts, Text, and Printing
Bounds.Height = 1100;
PaperSize.PaperName = "Letter";
PaperSize.Height = 1100;
PaperSize.Width = 850;
Margins.Left = 100;
Margins.Right = 100;
Margins.Top = 100;
Margins.Bottom = 100;
Core Note
PrintDocument Events
Four PrintDocument events are triggered during the printing process: Begin-
Print, QueryPageSettingsEvent, PrintPage, and EndPrint. As we’ve already
seen, PrintPage is the most important of these from the standpoint of code devel-
opment because it contains the logic and statements used to generate the printed
output. It is not necessary to handle the other three events, but they do provide a
handy way to deal with the overhead of initialization and disposing of resources when
the printing is complete.
BeginPrint Event
This event occurs when the PrintDocument.Print method is called and is a useful
place to create font objects and open data connections. The PrintEventHandler
delegate is used to register the event handler routine for the event:
This simple event handler creates the font to be used in the report. The font must
be declared to have scope throughout the class.
EndPrint Event
This event occurs after all printing is completed and can be used to destroy resources
no longer needed. Associate an event handler with the event using
This simple event handler disposes of the font created in the BeginPrint handler:
QueryPageSettingsEvent Event
This event occurs before each page is printed and provides an opportunity to adjust
the page settings on a page-by-page basis. Its event handler is associated with the
event using the following code:
pd.QueryPageSettings += new
QueryPageSettingsEventHandler(Rpt_Query);
The second argument to this event handler exposes a PageSettings object and a
Cancel property that can be set to true to cancel printing. This is the last opportunity
before printing to set any PageSettings properties, because they are read-only in the
PrintPage event. This code sets special margins for the first page of the report:
This event handler should be implemented only if there is a need to change page
settings for specific pages in a report. Otherwise, the DefaultPageSettings prop-
erties will prevail throughout.
PrintPage Event
The steps required to create and print a report fall into two categories: setting up the
print environment and actually printing the report. The PrinterSettings and
PageSettings classes that have been discussed are central to defining how the
report will look. After their values are set, it’s the responsibility of the PrintPage
event handler to print the report to the selected printer, while being cognizant of the
paper type, margins, and page orientation.
Figure 9-7 lists some of the generic tasks that an event handler must deal with in
generating a report. Although the specific implementation of each task varies by appli-
cation, it’s a useful outline to follow in designing the event handler code. We will see
an example shortly that uses this outline to implement a simple report application.
Property Description
Cancel Boolean value that can be set to true to cancel the printing.
prevDialog.ShowDialog();
After this method is called, the same steps are followed as in actually printing the
document. The difference is that the output is displayed in a special preview window
(see Figure 9-8). This provides the obvious advantage of using the same code for
both previewing and printing.
450 Chapter 9 ■ Fonts, Text, and Printing
A Report Example
This example is intended to illustrate the basic elements of printing a multi-page
report. It includes a data source that provides an unknown number of records, a title
and column header for each page, and detailed rows of data consisting of left-justi-
fied text and right-justified numeric data.
1000761,1050,2PC/DRESSER/MIRROR,185.50
A StreamReader object is used to load the data and is declared to have class-wide
scope so it is available to the PrintPage event handler:
The PrintPage event handler uses the StreamReader to input each inventory
record from the text file as a string. The string is split into separate fields that are
stored in the prtLine array. The event handler also contains logic to recognize page
breaks and perform any column totaling desired.
9.3 Printing 451
The fonts and StreamReader are initialized in the BeginPrint event handler.
The corresponding EndPrint event handler then disposes of the two fonts and
closes the StreamReader.
This form of the GetHeight method returns a value based on the GraphicsUnit
of the Graphics object passed to it. By default, the Graphics object passed to the
BeginPrint event handler has a GraphicsUnit of 100 dpi. The margin values and
all coordinates in the example are in hundredths of an inch. .NET takes care of auto-
matically scaling these units to match the printer’s resolution.
prevDialog.Document = rpd;
prevDialog.ShowDialog(); // Preview Report
// Show Print Dialog and print report
PrintDialog pDialog = new PrintDialog();
pDialog.Document = rpd;
if (pDialog.ShowDialog() == DialogResult.OK)
{
rpd.Print();
}
The preceding code takes advantage of the new constructor to pass in the title
when the object is created. It also sets the two fonts used in the report.
This example is easily extended to include page numbering and footers. For fre-
quent reporting needs, it may be worth the effort to create a generic report generator
that includes user selectable data source, column headers, and column totaling
options.
9.4 Summary 457
9.4 Summary
This chapter has focused on using the GDI+ library to display and print text. The
first section explained how to create and use font families and font classes. The
emphasis was on how to construct fonts and understand the elements that comprise a
font by looking at font metrics.
After a font has been created, the Graphics.DrawString method is used to
draw a text string to a display or printer. Its many overloads permit text to be drawn
at specific coordinates or within a rectangular area. By default, text printed in a rect-
angle is left justified and wrapped to the next line when it hits the bounds of the rect-
angle. This default formatting can be overridden by passing a StringFormat object
to the DrawString method. The StringFormat class is the key to .NET text for-
matting. It is used to justify text, specify how text is truncated, and set tab stops for
creating columnar output.
GDI+ provides several classes designed to support printing to a printer. These
include the following:
An example for printing a columnar report demonstrated how these classes can be
combined to create an application that provides basic report writing. As a final exam-
ple, we illustrated how the shortcomings of the PrintDocument class can be over-
come by creating a custom PrintDocument class that preserves data encapsulation.
458 Chapter 9 ■ Fonts, Text, and Printing
2. What is the default unit of measurement for a font? What size is it (in
inches)?
Extensible Markup Language (XML) plays a key role in the .NET universe. Configu-
ration files that govern an application or Web page’s behavior are deployed in XML;
objects are stored or streamed across the Internet by serializing them into an XML
representation; Web Services intercommunication is based on XML; and as we see in
Chapter 11, “ADO.NET,” .NET methods support the interchange of data between
an XML and relational data table format.
XML describes data as a combination of markup language and content that is
analogous to the way HTML describes a Web page. Its flexibility permits it to easily
represent flat, relational, or hierarchical data. To support one of its design goals—
that it “should be human-legible and reasonably clear”1—it is represented in a
text-only format. This gives it the significant advantage of being platform
independent, which has made it the de facto standard for transmitting data over the
Internet.
This chapter focuses on pure XML and the classes that reside in the System.Xml
namespace hierarchy. It begins with basic background information on XML: how
schemas are used to validate XML data and how style sheets are used to alter the way
XML is displayed. The remaining sections present the .NET classes that are used to
read, write, update, and search XML documents. If you are unfamiliar with .NET
XML, you may surprised how quickly you become comfortable with reading and
searching XML data. Extracting information from even a complex XML structure is
461
462 Chapter 10 ■ Working with XML in .NET
refreshingly easy with the XPath query language—and far less tedious than the origi-
nal search techniques that required traversing each node of an XML tree. In many
ways, it is now as easy to work with XML as it is to work with relational data.
The purpose of this section is to introduce XML concepts and terminology, as well
as some .NET techniques for performing the preceding tasks. Of the five tasks, all
are covered in this section, with the exception of reading and querying XML data,
which is presented in later sections.
In comparing Listings 10-1 and 10-2, it should be obvious that the XML elements
are a direct rendering of the public properties defined for the movies class. The only
exceptional feature in the code is the XmlElement attribute, which will be discussed
shortly.
To transform the class in Listing 10-2 to the XML in Listing 10-1, we follow the
three steps shown in the code that follows. First, the objects to be serialized are cre-
ated and stored in an array. Second, an XmlSerializer object is created. Its con-
structor (one of many constructor overloads) takes the object type it is serializing as
the first parameter and an attribute as the second. The attribute enables us to assign
“films” as the name of the root element in the XML output. The final step is to exe-
cute the XmlSerializer.Serialize method to send the serialized output to a
selected stream—a file in this case.
Serialization Attributes
By default, the elements created from a class take the name of the property they rep-
resent. For example, the movie_Title property is serialized as a <movie_Title>
element. However, there is a set of serialization attributes that can be used to over-
ride the default serialization results. Listing 10-2 includes an XmlElement attribute
whose purpose is to assign a name to the XML element that is different than that of
the corresponding property or field. In this case, the rank property name is
replaced with AFIRank in the XML.
There are more than a dozen serialization attributes. Here are some other com-
monly used ones:
As should be evident from this small sample, the XML Schema language has a
rather complex syntax. Those interested in all of its details can find them at the URL
shown in the first line of the schema. For those with a more casual interest, the most
important thing to note is that the heart of the document is a description of the valid
types that may be contained in the XML data that the schema describes. In addition
to the string and int types shown here, other supported types include boolean,
double, float, dateTime, and hexBinary.
The types specified in the schema are designated as simple or complex. The
complextype element defines any node that has children or an attribute; the
simpletype has no attribute or child. You’ll encounter many schemas where the
simple types are defined at the beginning of the schema, and complex types are later
defined as a combination of simple types.
The next step is to add the schema that will be used for validating to the reader’s
schema collection. Finally, the XmlValidatingReader is used to read the stream of
XML nodes. Exception handling is used to display any validation error that occurs.
XSL Document
Before the XslTransform class can be applied, an XSLT style sheet that
describes the transformation must be created. Listing 10-5 contains the style sheet
that will be used. As you can see, it is a mixture of HTML markup, XSL elements,
and XSL commands that displays rows of movie information with three columns. The
XSL elements and functions are the key to the transformation. When the XSL style
sheet is processed, the XSL elements are replaced with the data from the original
XML document.
470 Chapter 10 ■ Working with XML in .NET
Core Note
It takes only a small leap from this simple XSLT example to appreciate the poten-
tial of being able to transform XML documents dynamically. It is a natural area of
growth for Web Services and Web pages that now on demand accept input in one
format, transform it, and serve the output up in a different format.
472 Chapter 10 ■ Working with XML in .NET
XmlReader
<< abstract >>
.Create()
XmlReader Class
XmlReader is an abstract class possessing methods and properties that enable an appli-
cation to pull data from an XML file one node at a time in a forward-only, read-only
manner. A depth-first search is performed, beginning with the root node in the docu-
ment. Nodes are inspected using the Name, NodeType, and Value properties.
XmlReader serves as a base class for the concrete classes XmlTextReader and
XmlNodeReader. As an abstract class, XmlReader cannot be directly instantiated;
however, it has a static Create method that can return an instance of the XmlReader
class. This feature became available with the release of .NET Framework 2.0 and is
recommended over the XmlTextReader class for reading XML streams.
10.2 Techniques for Reading XML Data 473
Listing 10-6 illustrates how to create an XmlReader object and use it to read the
contents of a short XML document file. The code is also useful for illustrating how
.NET converts the content of the file into a stream of node objects. It’s important to
understand the concept of nodes because an XML or HTML document is defined
(by the official W3C Document Object Model (DOM) specification2) as a hierarchy
of node objects.
2. W3C Document Object Model (DOM) Level 3 Core Specification, April, 2004,
http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html
474 Chapter 10 ■ Working with XML in .NET
settings.ConformanceLevel = ConformanceLevel.Fragment;
specifies that the input must conform to the standards that define an XML 1.0 docu-
ment fragment—an XML document that does not necessarily have a root node.
This object and the name of the XML document file are then passed to the Cre-
ate method that returns an XmlReader instance:
Programs that use XmlReader typically implement a logic pattern that consists of
an outer loop that reads nodes and an inner switch statement that identifies the
node using an XMLNodeType enumeration. The logic to process the node informa-
tion is handled in the case blocks:
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
// Attributes are contained in elements
while(reader.MoveToNextAttribute())
{
Console.WriteLine(reader.Name+reader.Value);
}
break;
case XmlNodeType.Text:
// Process ..
break;
case XmlNodeType.EndElement
// Process ..
break;
}
}
The Element, Text, and Attribute nodes mark most of the data content in an
XML document. Note that the Attribute node is regarded as metadata attached to
an element and is the only one not exposed directly by the XmlReader.Read
method. As shown in the preceding code segment, the attributes in an Element can
be accessed using the MoveToNextAttribute method.
476 Chapter 10 ■ Working with XML in .NET
Table 10-1 summarizes the node types. It is worth noting that these types are not
an arbitrary .NET implementation. With the exception of Whitespace and Xml-
Declaration, they conform to the DOM Structure Model recommendation.
ProcessingInstruction Useful for providing information about how the data was gen-
erated or how to process it.
Example:
<?pi1 Requires IE 5.0 and above ?>
XmlNodeReader Class
The XmlNodeReader is another forward-only reader that processes XML as a stream
of nodes. It differs from the XmlReader class in two significant ways:
In Listing 10-8, an XmlNodeReader object is used to list the movie title and year
from the XML-formatted movies database. The code contains an interesting twist:
The XmlNodeReader object is not used directly, but instead is passed as a parameter
to the constructor of an XmlReader object. The object serves as a wrapper that per-
forms the actual reading. This approach has the advantage of allowing the XmlSet-
tings values to be assigned to the reader.
Refer to Listing 10-1 and you can see that this selects the second movies element
group, which contains information on Casablanca.
If your application requires read-only access to XML data and the capability to
read selected subtrees, the XmlNodeReader is an efficient solution. When updating,
writing, and searching become requirements, a more sophisticated approach is
required; we’ll look at those techniques later in this section.
Default
Property Value Description
Default
Property Value Description
Note that a detected error does not stop processing. This means that all the XML
data can be checked in one pass without restarting the program.
int age;
if(reader.Name == "Age") age= reader.ReadValueAsInt32();
XML that corresponds to the public properties or fields of a class can be read
directly into an instance of the class with the ReadAsObject method. This fragment
reads the XML data shown in Listing 10-1 into an instance of the movies class. Note
that the name of the field or property must match an element name in the XML data.
{
public int movie_ID;
public string movie_Title;
public string movie_Year;
private string director;
public string bestPicture;
public string movie_Director
{
set { director = value; }
get { return (director); }
}
}
Listing 10-9 illustrates the basic principles involved in using the XmlWriter class.
Not surprisingly, there are a lot of similarities to the closely related XmlReader class.
Both use the Create method to create an object instance, and both have constructor
overloads that accept a settings object—XmlWriterSettings, in this case—to
define the behavior of the reader or writer. The most important of these setting prop-
erties is the conformance level that specifies either document or fragment (a subtree)
conformance.
A series of self-describing methods, which support all the node types listed in
Table 10-1, generate the XML. Note that exception handling should always be
enabled to trap any attempt to write an invalid name or character.
Before leaving the topic of XML writing, note that .NET also provides
XmlTextWriter and XmlNodeWriter classes as concrete implementations of the
abstract XmlWriter class. The former does not offer any significant advantages over
the XmlWriter. The node writer is a bit more useful. It creates a DOM tree in
memory that can be processed using the many classes and methods designed for that
task. Refer to .NET documentation for XmlNodeWriter details.
10.4 Using XPath to Search XML 485
XPathNavigator
XmlDocument XPathDocument
XmlDataDocument
Figure 10-4 XML classes that support XPath navigation
Table 10-3 summarizes commonly used XPath operators and provides an example
of using each.
Operator Description
Child operator (/) References the root of the XML document, where the expression
begins searching. The following expression returns the
last_name node for each director in the table:
/films/directors/last_name
Recursive descendant This operator indicates that the search should include descen-
operator (//) dants along the specified path. The following all return the same
set of last_name nodes. The difference is that the first begins
searching at the root, and second at each directors node:
//last_name
//directors//last_name
Wildcard operator (*) Returns all nodes below the specified path location. The follow-
ing returns all nodes that are descendants of the movies node:
//movies/*
Current operator (.) Refers to the currently selected node in the tree, when navigat-
ing through a tree node-by-node. It effectively becomes the root
node when the operator is applied. In this example, if the current
node is a directors node, this will find any last_name child
nodes:
.//last_name
488 Chapter 10 ■ Working with XML in .NET
Operator Description
Parent operator (..) Used to represent the node that is the parent of the current
node. If the current node were a movies node, this would use
the directors node as the start of the path:
../last_name
Attribute operator (@) Returns any attributes specified. The following example would
return the movie’s runtime assuming there were attributes such
as <movie_ID time="98"> included in the XML.
//movies//@time
Collection operator ([ ]) Uses brackets just as the filter, but specifies a node based on an
ordinal value. Is used to distinguish among nodes with the same
name. This example returns the node for the second movie,
Raging Bull:
//movies[2] (Index is not 0 based.)
Union operator (|) Returns the union of nodes found on specified paths. This exam-
ple returns the first and last name of each director:
//last_name | //first_name
Note that the filter operator permits nodes to be selected by their content. There
are a number of functions and operators that can be used to specify the matching cri-
teria. Table 10-4 lists some of these.
Function/Operator Description
Example: "//movies[position()=2]"
10.4 Using XPath to Search XML 489
Table 10-4 Functions and Operators used to Create an XPath Filter (continued)
Function/Operator Description
Example: "//movies[contains(movie_Title,'Tax')]"
starts-with(node,string) Matches if node value begins with specified
string.
Example: "//movies[starts-with(movie_Title,'A')]"
substring-after(string,string) Extracts substring from the first string that fol-
lows occurrence of second string.
Example: "//movies[substring(movie_Title,2,1)='a']"
Core Note
This class should be used only when its hybrid features add value to an applica-
tion. Otherwise, use XmlDocument if updates are required or XPathDocument if the
data is read-only.
films
This example uses the XmlDocument class to represent the tree for which we will
remove one movies element and add another one. XPath is used to locate the mov-
ies node for Raging Bull along the path containing Scorsese as the director:
"//directors[last_name='Scorsese']/movies[movie_Title=
'Raging Bull']"
This node is deleted by locating its parent node, which is on the level directly
above it, and executing its RemoveChild method.
Adding a node requires first locating the node that will be used to attach the new
node. Then, the document’s Createxxx method is used to generate an XmlNode or
XmlNode-derived object that will be added to the tree. The node is attached using
the current node’s AppendChild, InsertAfter, or InsertBefore method to posi-
tion the new node in the tree. In this example, we add a movies element that contains
information for the movie Goodfellas.
10.5 Summary
To work with XML, a basic understanding of the XML document, schema, and style
sheet is required. An XML document, which is a representation of information based
on XML guidelines, can be created in numerous ways. The XmlSerializer class
can be used when the data takes the form of an object or objects within a program.
After the XML document is created, a schema can be derived from it using the XML
494 Chapter 10 ■ Working with XML in .NET
Schema Definition (XSD) tool. Several classes use the schema to provide automatic
document validation. The usefulness of XML data is extended by the capability to
transform it into virtually any other format using an XML style sheet. The style sheet
defines a set of rules that are applied during XML Style Sheet Transformation
(XSLT).
XML data can be processed as a stream of nodes or an in-memory tree known as a
Document Object Model (DOM). The XmlReader and XmlNodeReader classes
provide an efficient way to process XML as a read-only, forward-only stream. The
XmlReader, XPathDocument, and XmlDataReader classes offer methods for pro-
cessing nodes in the tree structure.
In many cases, data extraction from an XML tree can be best achieved using a
query, rather than traversing the tree nodes. The XPath expression presents a rich,
standardized syntax that is easily used to specify criteria for extracting a node, or mul-
tiple nodes, from an XML tree.
4. Using the XML data from Listing 10-10, show the node values
returned by the following XPath expressions:
a. //movies[substring( movie_Title,2,1)='a']
b. //movies[2]
c. //movies[movie_Year >= 1978]
d. //directors[last_name='Scorsese']
/movies/movie_Title
ADO.NET is based on a flexible set of classes that allow data to be accessed from
within the managed environment of .NET. These classes are used to access a variety
of data sources including relational databases, XML files, spreadsheets, and text files.
Data access is through an API, known as a managed data provider. This provider may
be written specifically for a database, or may be a more generic provider such as
OLE DB or ODBC (Open DataBase Connectivity). Provider classes expose a con-
nection object that binds an application to a data source, and a command object that
supports the use of standard SQL commands to fetch, add, update, or delete data.
ADO.NET supports two broad models for accessing data: disconnected and con-
nected. The disconnected model downloads data to a client’s machine where it is
encapsulated as an in-memory DataSet that can be accessed like a local relational
database. The connected model relies on record-by-record access that requires an
open and sustained connection to the data source. Recognizing the most appropriate
model to use in an application is at the heart of understanding ADO.NET. This chap-
ter examines both models—offering code examples that demonstrate the classes
used to implement each.
497
498 Chapter 11 ■ ADO.NET
Command Object
"select * from Actors" Sql Connection
ODP.NET
MS SQL Oracle MS
MySQL
Server Access
contrast, far fewer .NET data providers exist. To provide a bridge to these preexisting
OLE DB interfaces, .NET includes an OleDB data provider that functions as a thin
wrapper to route calls into the native OLE DB. Because interoperability with COM
requires switching between managed and unmanaged code, performance can be
severely degraded.1
As we see in the next section, writing code to use OLE DB is essentially the same
as working with a .NET data provider. In fact, new .NET classes provide a “factory”
that can dynamically produce code for a selected provider. Consequently, responding
to a vendor’s upgrade from OLE DB to a custom provider should have no apprecia-
ble effect on program logic.
Because these are abstract classes, the developer is responsible for specifying the
vendor’s specific implementation within the code. As we see next, the object names
can be hard coded or provided generically by a provider factory class.
1. Test results for .NET 1.1 have shown the SQL Client provider to be up to 10 times faster than
OLE DB.
500 Chapter 11 ■ ADO.NET
Provider Factories
Each data provider registers a ProviderFactory class and a provider string in the
machine.config file. The available providers can be listed using the static GetFac-
toryClasses method of the DbProviderFactories class. As this code shows, the
method returns a DataTable containing four columns of information about the pro-
vider.
DataTable tb = DbProviderFactories.GetFactoryClasses();
foreach (DataRow drow in tb.Rows )
{
StringBuilder sb = new StringBuilder("");
for (int i=0; i<tb.Columns.Count; i++)
{
sb.Append((i+1).ToString()).Append(drow[i].ToString());
sb.Append("\n");
}
Console.WriteLine(sb.ToString());
}
Running this code for ADO.NET 2.0 lists four Microsoft written providers:
Odbc, OleDb, OracleClient, and SqlClient. Figure 11-2 shows output for the
SqlClient provider.
To use these providers, your code must create objects specific to the provider. For
example, the connection object for each would be an OdbcConnection, OleDbCon-
nection, OracleConnection, or SqlConnection type. You can create the objects
supplied by the providers directly:
This approach requires only that a provider and connection string be provided. For
example, we can easily switch this to an ODBC provider by changing two statements:
Note that the factory class provides a series of Create methods that returns the
objects specific to the data provider. These methods include CreateCommand,
CreateConnection, and CreateDataAdapter.
Connected Model
In the ADO.NET connected mode, an active connection is maintained between an
application’s DataReader object and a data source. A row of data is returned from
the data source each time the object’s Read method is executed. The most important
11.2 Data Access Models: Connected and Disconnected 503
characteristic of the connected model is that it reads data from a resultset (records
returned by a SQL command) one record at a time in a forward-only, read-only man-
ner. It provides no direct way to update or add data. Figure 11-4 depicts the relation-
ship between the DataReader, Command, and Connection classes that comprise the
connected model.
The following code segment illustrates these steps with a SqlClient data pro-
vider. The code reads movie titles from the database and displays them in a ListBox
control. Note that the DataReader, Command, and Connection objects are
described in detail later in this chapter.
Disconnected Model
The concept behind the disconnected model is quite simple: Data is loaded—using a
SQL command—from an external source into a memory cache on the client’s
machine; the resultset is manipulated on the local machine; and any updates are
passed from data in memory back to the data source.
The model is “disconnected” because the connection is only open long enough to
read data from the source and make updates. By placing data on the client’s machine,
server resources—data connections, memory, processing time—are freed that would
otherwise be required to manipulate the data. The drawback is the time required to
load the resultset, and the memory used to store it.
As Figure 11-5 illustrates, the key components of the disconnected model are the
DataApdapter and DataSet. The DataAdapter serves as a bridge between the
data source and the DataSet, retrieving data into the tables that comprise the
DataSet and pushing changes back to the data source. A DataSet object functions
as an in-memory relational database that contains one or more DataTables, along
with optional relationships that bind the tables. A DataTable contains rows and col-
umns of data that usually derive from a table in the source database.
DataSet DataAdapter
Fill( )
SelectCommand Retrieve Data
DataTable
InsertCommand
UpdateCommand Update
DataTable
DeleteCommand DataBase
Figure 11-5 DataAdapter is used in ADO.NET disconnected mode
Core Note
Each data provider supplies its own data adapter. Thus, if you look
through the System.Data child namespaces (SqlClient,
OracleClient, Oledb), you’ll find a SqlDataAdapter,
OracleDataAdapter, and OleDbDataAdapter, among others. An easy
way to acquire the desired adapter in your application is to call the
DbProviderFactory.CreateDataAdapter method to return an
instance of it.
After an empty DataSet is created, the DataAdapter’s Fill method creates a table
movies in the DataSet and fills it with rows of data returned by the SQL command.
Each column of the table corresponds to a column in the source data table. Behind
the scenes, the data transfer is performed by creating a SqlDataReader that is
closed after the transfer is complete.
The data in the table is then used to populate a list box by looping through the
rows of the table. As we see in the next chapter, we could achieve the same effect by
binding the list control to the table—a mechanism for automatically filling a control
with data from a bound source.
Connection Classes
There are multiple connection classes in ADO.NET—each specific to a data pro-
vider. These include SqlConnection, OracleConnection, OleDBConnection,
and OdbcConnection. Although each may include custom features, ADO.NET
compatibility requires that a connector class implement the IDbConnection inter-
face. Table 11-1 summarizes the members defined by this interface.
Core Note
Connection String
The connection string specifies the data source and necessary information required
to access the data source, such as password and ID. In addition to this basic informa-
tion, the string can include values for fields specific to a data provider. For example, a
SQL Server connection string can include values for Connection Timeout and
Packet Size (size of network packet).
Table 11-2 offers a representative list of commonly used connection strings.
The connection string is used to create the connection object. This is typically
done by passing the string to the constructor of the connection object.
SqlConnection "server=MYSERVER;
Using SQL Server authentication. uid=filmsadmin;
pwd=bogart;
database=films;"
Or
"Data Source=MYSERVER;User ID=filmsadmin;
password=bogart;Initial Catalog=films;"
SqlConnection "server=MYSERVER;
Using Windows authentication. database=films;
Trusted_Connection=yes"
OleDbConnection "Provider=Microsoft.Jet.OLEDB.4.0;
Connects to a Microsoft Access Data Source=c:\\movies.mdb;"
database. For Internet applications, you may not be able to
specify a physical path. Use MapPath to convert a
virtual path into a physical path:
string path=
Server.MapPath("/data/movies.mdb");
Data Source="+path+";"
A connection string can also be built using a safer, object-oriented manner using
one of the ConnectionStringBuilder classes supplied by a managed data pro-
vider.2 As this code demonstrates, the values comprising the connection string are
assigned to properties of this class. Internally, the object constructs a string from
these properties and exposes it as a ConnectionString property.
2. SqlClient, Oracle, OleDB, and ODBC implementations are available with ADO.NET 2.0.
11.3 ADO.NET Connected Model 509
Method Description
Core Note
Connection Pooling
Creating a connection is a time-consuming process—in some cases taking longer
than the subsequent commands take to execute. To eliminate this overhead,
ADO.NET creates a pool of identical connections for each unique connection string
request it receives. This enables future requests with that connection string to be sat-
isfied from the pool, rather than by reconnecting to the server and performing the
overhead to validate the connection.
There are several rules governing connection pooling that you should be aware of:
Under SQL Server, you control the behavior of connection pooling by including
key-value pairs in the connection string. These keywords can be used to set mini-
mum and maximum numbers of connections in the pool, and to specify whether a
connection is reset when it is taken from the pool. Of particular note is the Life-
time keyword that specifies how long a connection may live until it is destroyed. This
value is checked when a connection is returned to the pool. If the connection has
been open longer than its Lifetime value, it is destroyed.
This code fragment demonstrates the use of these keywords for SqlClient:
cnString = "Server=MYSERVER;Trusted_Connection=yes;
database=films;" +
"connection reset=false;" +
"connection Lifetime=60;" + // Seconds
"min pool size=1;" +
"max pool size=50"; // Default=100
SqlConnection conn = new SqlConnection(cnString);
11.3 ADO.NET Connected Model 511
In situations where multiple data providers may be used, a provider factory pro-
vides a more flexible approach. The factory is created by passing its constructor a
string containing the data provider. The factory’s CreateCommand method is used to
return a command object.
Note that DbCommand is an abstract class that implements the IDbCommand inter-
face. It assumes the role of a generic command object. This can eliminate the need to
cast the returned command object to a specific provider’s command object such as
512 Chapter 11 ■ ADO.NET
Executing a Command
The SQL command assigned to the CommandText property is executed using one of
the four command methods in Table 11-4.
Method Description
ExecuteNonQuery Executes an action query and returns the number of rows affected:
cmd.CommandText = "DELETE movies WHERE movie_ID=220";
int ct = cmd.ExecuteNonQuery();
ExecuteScalar Executes a query and returns the value of the first column in the first
row of the resultset as a scalar value.
cmd.CommandText="SELECT COUNT(movie_title)
FROM movies";
int movieCt = (int) cmd.ExecuteScalar();
ExecuteXmlReader Available for SQL Server data provider only. Returns an XmlReader
object that is used to access the resultset. XmlReader is discussed in
Chapter 10, “Working with XML in .NET.”
Some data providers take advantage of this parameter to optimize query execution.
The list of values for the CommandBehavior enumeration includes the following:
11.3 ADO.NET Connected Model 513
• SingleRow. Indicates that the query should return one row. Default
behavior is to return multiple resultsets.
• SingleResult. The query is expected to return a single scalar value.
• KeyInfo. Returns column and primary key information. It is used
with a data reader’s GetSchema method to fetch column schema
information.
• SchemaOnly. Used to retrieve column names for the resultset.
Example:
dr=cmd.ExecuteReader(CommandBehavior.SchemaOnly);
string col1= dr.GetName(0); // First column name
• SequentialAccess. Permits data in the returned row to be accessed
sequentially by column. This is used with large binary (BLOB) or text
fields.
• CloseConnection. Close connection when reader is closed.
cmd.Parameters.Add(@TotalPages", SqlDbType.Int);
cmd.Parameters[0].Direction= ParameterDirection.Input;
cmd.Parameters[0].Value= 1; // Retrieve first page
cmd.Parameters[1].Direction=ParameterDirection.Output;
cmd.CommandTimeout=10; // Give command 10 seconds to execute
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read()){
// do something with results
}
rdr.Close(); // Must close before reading parameters
int totpages= cmd.Parameters[1].Value;
This example uses the SqlClient data provider. With a couple of changes, OleDb
can be used just as easily. The primary difference is in the way they handle parame-
ters. SqlClient requires that the parameter names match the names in the stored
procedure; OleDb passes parameters based on position, so the name is irrelevant. If
the procedure sends back a return code, OleDB must designate the first parameter in
the list to handle it. SqlClient simply adds a parameter—the name is unimpor-
tant—that has its direction set to ReturnValue.
return 0
end
Not only is the parameterized version more readable and less prone to syntactical
error, but it also provides a significant benefit: It automatically handles the problem
of placing double single quotes ('') in a SQL command. This problem occurs when
attempting to store a value such as O’Quinn, which has an embedded quote that
conflicts with SQL syntax. Parameters eliminate the usual approach to search each
string and replace an embedded single quote with a pair of single quotes.
516 Chapter 11 ■ ADO.NET
DataReader Object
As we have seen in several examples, a DataReader exposes the rows and columns
of data returned as the result of executing a query. Row access is defined by the
IDataReader interface that each DataReader must implement; column access is
defined by the IDataRecord interface. We’ll look at the most important members
defined by these interfaces as well as some custom features added by data providers.
The two things to note are the construction of the CommandString with multiple
queries and the use of the NextResult method to determine if results from another
query are present.
Core Note
The GetString method has the advantage of mapping the database contents to a
native .NET data type. The other approaches return object types that require cast-
ing. For this reason, use of the Get methods is recommended. Note that although
GetString does not require casting, it does not perform any conversion; thus, if the
data is not of the type expected, an exception is thrown.
Many applications rely on a separate data access layer to provide a DataReader.
In such cases, the application may require metadata to identify column names, data
types, and other columnar information. Column names, which are useful for generat-
ing report headings, are readily available through the GetName method:
DataSet
DataRelationCollection
Hashtable Item
PropertyCollection
DataTableCollection
DataTable
DataRow
DataRowCollection
DataColumn
DataColumnCollection
DataView
ChildRelations
ParentRelations
The discussion of the DataSet class begins with its most important member—the
DataTable collection.
DataTables
One step below the DataSet in the disconnected model hierarchy is the DataTable
collection. This collection—accessed through the DataSet.Tables property—
stores data in a row-column format that mimics tables in a relational database. The
DataTable class has a rich set of properties and methods that make it useful as a
stand-alone data source or as part of a table collection in a DataSet. The most
important of these are the Columns and Rows properties, which define the layout
and content of a table.
DataColumns
The DataTable.Columns property exposes a collection of DataColumn objects that
represent each data field in the DataTable. Taken together, the column properties
produce the data schema for the table. Table 11-5 summarizes the most important
properties.
520 Chapter 11 ■ ADO.NET
Method Description
AllowDBNull Boolean value that indicates whether the column may contain null
values.
Unique Boolean value that indicates whether the column may contain dupli-
cate values.
DataTable columns are created automatically when the table is filled with the
results of a database query or from reading an XML file. However, for applications
that fill a table dynamically—such as from user input or real-time data acquisition—
it may be necessary to write the code that defines the table structure. It’s a worth-
while exercise in its own right that enhances a developer’s understanding of the
DataSet hierarchy.
The following segment creates a DataTable object, creates DataColumn objects,
assigns property values to the columns, and adds them to the DataTable. To make
things interesting, a calculated column is included.
Note that the ID column is defined to contain unique values. This constraint qual-
ifies the column to be used as a key field in establishing a parent-child relationship
with another table in a DataSet. To qualify, the key must be unique—as in this
case—or defined as a primary key for the table. You assign a primary key to a table
by setting its PrimaryKey field to the value of an array containing the column(s) to
be used as the key. Here is an example that specifies the ID field a primary key:
We’ll see how to use a primary key to create table relationships and merge data
later in this section.
Core Note
DataRows
Data is added to a table by creating a new DataRow object, filling it with column
data, and adding the row to the table’s DataRow collection. Here is an example that
places data in the table created in the preceding example.
DataRow row;
row = tb.NewRow(); // Create DataRow
row["Title"] = "Casablanca";
522 Chapter 11 ■ ADO.NET
row["Price"] = 22.95;
row["Quan"] = 2;
row["ID"] = 12001;
tb.Rows.Add(row); // Add row to Rows collection
Console.WriteLine(tb.Rows[0]["Total"].ToString()); // 45.90
A DataTable has methods that allow it to commit and roll back changes made to
the table. In order to do this, it keeps the status of each row in the DataRow.Row-
State property. This property is set to one of five DataRowState enumeration val-
ues: Added, Deleted, Detached, Modifed, or Unchanged. Let’s extend the
preceding example to demonstrate how these values are set:
tb.Rows.Add(row); // Added
tb.AcceptChanges(); // ...Commit changes
Console.Write(row.RowState); // Unchanged
tb.Rows[0].Delete(); // Deleted
// Undo deletion
tb.RejectChanges(); // ...Roll back
Console.Write(tb.Rows[0].RowState); // Unchanged
DataRow myRow;
MyRow = tb.NewRow(); // Detached
DataRow r = tb.Rows[0];
r["Price"]= 14.95;
r.AcceptChanges();
r["Price"]= 16.95;
Console.WriteLine("Current: {0} Original: {1} ",
r["Price",DataRowVersion.Current],
r["Price",DataRowVersion.Original]);
// output: Current: 16.95 Original: 14.95
11.4 DataSets, DataTables, and the Disconnected Model 523
Keeping track of table row changes takes on added importance when the purpose
is to update an underlying data source. We’ll see later in this section how the Data-
Adapter updates database tables with changes made to DataTable rows.
The DataReader is closed automatically after all of the rows have been loaded.
The CloseConnection parameter ensures that the connection is also closed.
If the table already contains data, the Load method merges the new data with the
existing rows of data. Merging occurs only if rows share a primary key. If no primary
key is defined, rows are appended. An overloaded version of Load takes a second
parameter that defines how rows are combined. This parameter is a LoadOption
enumeration type having one of three values: OverwriteRow, PreserveCurrent-
Values, or UpdateCurrentValues. These options specify whether the merge
operation overwrites the entire row, original values only, or current values only. This
code segment illustrates how data is merged with existing rows to overwrite the cur-
rent column values:
col[0] = dt.Columns["movie_ID"];
dt.PrimaryKey = col;
DataRow r = dt.Rows[0]; // Get first row of data
r["movie_Title"] = "new title"; // Change current column value
// Since reader is closed, must fill reader again
rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
// Merge data with current rows. Overwrites current values
dt.Load(rdr, LoadOption.UpdateCurrentValues );
// Updated value has been overwritten
Console.Write(dt.Rows[0]["movie_Title"]); // Casablanca
Of these, the first version is the simplest. It accepts two strings containing the
query and connection. From these, it constructs a SqlCommand object that is
assigned internally to its SelectCommand property. Unlike the other constructors,
there is no need to write code that explicitly creates a SqlCommand or SqlConnec-
tion object.
In the overloads that accept a connection object as a parameter, the opening and
closing of the connection is left to the DataAdapter. If you add a statement to
explicitly open the connection, you must also include code to close it. Otherwise, the
DataAdapter leaves it open, which locks the data in the database.
After the DataAdapter object is created, its Fill method is executed to load
data into a new or existing table. In this example, a new table is created and assigned
the default name Table:
11.4 DataSets, DataTables, and the Disconnected Model 525
For an existing table, the behavior of the Fill command depends on whether the
table has a primary key. If it does, those rows having a key that matches the key of the
incoming data are replaced. Incoming rows that do not match an existing row are
appended to the DataTable.
dt.Rows.Add(drow);
Core Note
Note that even though the delete occurs first, it does not affect the other
operations. The SQL statement that deletes or updates a row is based on a row’s
primary key value—not relative position. Also, be aware that updates on the same
row are combined and counted as a single row update by the Update method. In this
example, updates to row 30 count as one update.
Handling concurrency issues is not a simple task. After you identify the failures,
the next step—how to respond to the failures—is less clear, and depends on the
application. Often times, it is necessary to re-read the rows from the database and
compare them with the rows that failed in order to determine how to respond. The
ability to recognize RowState and the current and original values of rows is the key
to developing code that resolves update conflicts.
530 Chapter 11 ■ ADO.NET
public DataRelation(
string relationName,
DataColumn parentColumn,
DataColumn childColumn)
This code segment illustrates how constraints affect the capability to add a row to
a child table and delete or change a row in the parent table. The tables from the pre-
ceding example are used.
Note that setting the EnforceConstraints property to false turns off all con-
straints—which in database terms eliminates the check for referential integrity.3 This
allows a movie to be added even though its movie_DirectorID column (foreign
key) does not have a corresponding row in the directors table. It also permits a
director to be deleted even though a movie by that director exists in the movies
table. This clearly compromises the integrity of the database and should be used only
when testing or populating individual tables in a database.
3. The foreign key in any referencing table must always refer to a valid row in the referenced table.
11.5 XML and ADO.NET 533
Of course, the general rules have to be weighed against other factors. If the data
source contains a large number of records, a DataSet may require too many
resources; or if the data requires only a few updates, the combination of Data-
Reader and Command object to execute updates may make more sense. Despite the
gray areas, there are many situations where one is clearly preferable to the other.
A DataSet is a good choice when the following apply:
• Data need to be serialized and/or sent over the wire using HTTP.
• Multiple read-only controls on a Windows Form are bound to the data
source.
• A Windows Form control such as a GridView or DataView is bound
to an updatable data source.
• A desktop application must edit, add, and delete rows of data.
We’ll first look at examples that show how to write XML from a DataSet. This
XML output is then used as input in subsequent examples that create a DataSet
from XML.
The schema output shown in Listing 11-7 defines the permissible content of an
XML document (file). If you compare this with Figure 11-3 on page 502, you can get
a general feel for how it works. For example, each field in the movies table is repre-
sented by an element containing the permissible field name and type.
11.5 XML and ADO.NET 535
Listing 11-8 displays an abridged listing of the XML version of the relational data.
The name of the DataSet is the root element. Each row in the table is represented
by a child element (movies) containing elements that correspond to the columns in
the data table.
536 Chapter 11 ■ ADO.NET
/* output is:
movies: movie_ID movie_Title movie_Year movie_DirectorID
bestpicture AFIRank
*/
It is also possible to create a schema by inferring its structure from the XML data
or using a DataAdapter to configure the schema:
Core Note
Parameters:
XMLfilename Name of file (.xml) containing XML data.
mode One of the XmlReadMode enumeration values.
The XmlReadMode parameter merits special attention. Its value specifies how a
schema is derived for the table(s) in a DataSet. It can specify three sources for the
538 Chapter 11 ■ ADO.NET
schema: from a schema contained (inline) in the XML file, from the schema already
associated with the DataSet, or by inferring a schema from the contents of the XML
file. Table 11-6 summarizes how selected enumeration members specify the schema
source. The numbers in the table indicate the order in which a schema is selected.
For example, ReadSchema specifies that the inline schema is the first choice; if it
does not exist, the schema associated with the DataSet is used; if neither exists, a
data table is not built.
Table 11-6 XmlReadMode Values Determine How a Schema Is Derived for a DataSet
Schema Source
The code segment in Listing 11-9 loads an XML file into a DataSet and then calls
a method to display the contents of each row in the table created. Because the
DataSet does not have a predefined schema, and the file does not include an inline
schema, ReadXml infers it from the contents of the file.
Note the use of ExtendedProperties to store the name of the data source in
the data set. Because this collection of custom properties is implemented as a Hash-
table, it is accessed using that syntax:
<films>
<movies>
<movie_ID>5</movie_ID>
<movie_Title>Citizen Kane </movie_Title>
540 Chapter 11 ■ ADO.NET
<movie_Year>1941</movie_Year>
<director>
<first_name>Orson</first_name>
<last_name>Welles</last_name>
</director>
<bestPicture>Y</bestPicture>
<AFIRank>1</AFIRank>
</movies>
... more movies here
</films>
Next, run this code to display the contents of the table(s) created:
Figure 11-7 depicts the DataSet tables created from reading the XML file. It cre-
ates two tables, automatically generates a movies_ID key for each table, and assigns
values to this key, which link a row in the movies table to an associated row in the
director table.
DataTable
11.6 Summary
ADO.NET supports two database connectivity models: connected and disconnected.
The connected model remains connected to the underlying database while traversing
a resultset in a forward-only read-only manner; the disconnected model can retrieve
11.7 Test Your Understanding 541
a resultset into an in-memory cache and disconnect itself from the source data.
Two distinctly separate data storage objects are available for implementing these
models: the DataReader and the DataSet. The DataReader serves up data a row
at a time; the DataSet functions as an in-memory relational database. Changes to
the contents of the DataSet can be posted back to the original data source using
the DataAdapter object. This object includes properties and methods designed to
address synchronization issues that arise when disconnected data is used to update
a database.
Although XML classes are not part of the ADO.NET namespaces, a level of inter-
action between relational and XML data is provided through the ADO.NET
DataSet class. This class includes WriteXmlSchema and WriteXml methods that
are used to create an XML schema and document. The versatile DataSet.ReadXml
method has several overloads that are used to construct a DataSet from an XML
data file or schema.
7. You have an XML file and want to create a DataSet that has rows and
columns that match the layout of the XML file. The XML file does not
have a schema (.xsd) file associated with it. What DataSet method is
used to create the DataSet schema?
This page intentionally left blank
DATA BINDING WITH
WINDOWS FORMS
CONTROLS
Chapter 11, “ADO.NET.” discussed how to access data using ADO.NET. This chap-
ter extends the discussion to describe the techniques by which data is “bound” to the
Windows Forms controls that display data. Because all controls derive their data
binding capabilities from the base Control class, knowledge of its properties and
methods can be applied to all controls. Although many of the same concepts apply to
Web controls, data binding for them is discussed separately in Chapter 16,
“ASP.NET Web Forms and Controls.”
Data binding comes in two flavors: simple and complex. Controls that contain one
value, such as a label or Textbox, rely on simple binding. Controls populated with
rows of data, such as a ListBox, DataGrid, or DataGridView, require complex
binding. We’ll look at how both are implemented.
Of the Windows Forms controls that bind to data, the DataGridView is the most
complex and useful. Its layout maps directly to the rows and columns of a relational
database or similarly structured XML document. This chapter takes a detailed look at
the properties and methods of this control, and provides examples of how this control
can be used to implement common database applications.
545
546 Chapter 12 ■ Data Binding with Windows Forms Controls
Windows Form
binding
binding
CurrencyManager
image binding
DataTable
or Array
A control may have multiple bindings associated with it, but only one per prop-
erty. This means that the code used to create a binding can be executed only once; a
second attempt would generate an exception. To avoid this, each call to add a binding
should be preceded with code that checks to see if a binding already exists; if there is
a binding, it should be removed.
if (txtMovie.DataBindings["Text"] != null)
txtMovie.DataBindings.Remove(txtMovie.DataBindings["Text"]);
txtMovie.DataBindings.Add("Text", tp, "Tb_Text");
Binding to a List
The true value of data binding becomes obvious when the data source contains mul-
tiple items to be displayed. In the preceding example, the control was bound to a sin-
gle object. Let’s now create an array of these objects—each representing a different
movie. Instead of binding to a single object, the control is bound to the array (see
Figure 12-2). The control can still only display a single movie title at a time, but we
can scroll through the array and display a different title that corresponds to the cur-
rent array item selected. This scrolling is accomplished using a binding manager,
which is discussed shortly.
This example creates an ArrayList of objects that are used to set the TextBox
properties on the fly.
548 Chapter 12 ■ Data Binding with Windows Forms Controls
The one difference in the bindings from the preceding example is that the data
source now refers to the ArrayList. By default, the TextBox takes the values asso-
ciated with the first item in the array. When the index of the array points to the sec-
ond row, the displayed value changes to “Citizen Kane”.
TextBox
Casablanca
.Width 200
.Text Binding Casablanca
Manager
.BackColor Color.Beige
Bindings
200
Citizen Kane
Color.White
ArrayList
Figure 12-2 Binding TextBox properties to objects in a list
ds = new DataSet("films");
string sql = "select * from movies order by movie_Year";
da = new SqlDataAdapter(sql, conn);
da.Fill(ds,"movies"); // create datatable "movies"
// Bind text property to movie_Year column in movies table
txtYr.DataBindings.Add("Text", ds,"movies.movie_Year");
12.1 Overview of Data Binding 549
da.Fill(ds,"movies");
DataTable dt = ds.Tables[0];
// Minimum properties to bind listbox to a DataTable
listBox1.DataSource = ds;
listBox1.DisplayMember = "movies.movie_Title";
After these values are set, the list box is automatically filled. The DataSource
property can be changed programmatically to fill the control with a different set of
data, or it can be set to null to clear the control’s content. Note also that although no
Binding object is explicitly created, a DataBindings collection is created under-
neath and is accessible through code.
The bound list box control is often grouped with other controls, such as a text box
or label, in order to display multiple values from a row of data. When the controls are
bound to the same data source, scrolling through the list box causes each control to
display a value from the same data row. To illustrate, let’s add the following simple
bindings to the preceding code:
txtStudio.DataBindings.Add("Text", ds,"movies.studio");
txtYear.DataBindings.Add("Text", ds,"movies.movie_Year");
550 Chapter 12 ■ Data Binding with Windows Forms Controls
These text boxes display the studio name and year of the movie currently selected
in the list box (see Figure 12-3).
Note that changes in a control are not propagated to the data source until the user
moves to another item in the GUI control. Underneath, this changes the current
position within the binding manager—firing an event that causes the data to be
updated.
The other situation to handle is when a data item is deleted from or added to the
data source. Controls that are bound to the source using simple binding are updated
automatically; controls using complex binding are not. In the latter case, the update
can be forced by executing the Refresh method of a CurrencyManager object. As
we see next, the CurrencyManager is a binding manager especially designed for list
data sources.
552 Chapter 12 ■ Data Binding with Windows Forms Controls
1
Binding
Binding
4 Bindings Bindings
Binding Context 2 3
CurrencyManager PropertyManager
Figure 12-4 Binding managers synchronize the data source and controls
Binding requires the interaction of several objects to coordinate the two-way flow
of data between a data source and control. Let’s look at the four most important
objects, which are denoted by numbers in Figure 12-4.
// Bind to TextBox
txtStudio.DataBindings.Add("text", ds, "movies.studio");
// BindingManagerBase bmb has class-wide scope
bmb = this.BindingContext[ds, "movies"];
// Create delegate pointing to event handler
bmb.PositionChanged += new
EventHandler(bmb_PositionChanged);
The following method moves to the next item in the data source list when a button
is clicked. It would typically be paired with a method to move backward in the list.
The PositionChanged event is fired each time the binding manager moves to a
new position in the list. This could be triggered programmatically or by the user
clicking a row in the list box control.
Note that the Current property is used to return an object representing the cur-
rent item. The data source in this example is a data table, but the object returned is
not the expected DataRow—it is a DataRowView object. It is up to the code to pro-
vide the proper casting to access properties in the selected item.
12.2 Using Simple and Complex Data Binding in an Application 555
Binding to a DataTable
The code in Listing 12-1 is executed when the Bind to Table button is clicked. It
loads the necessary data from the Films database into a table and binds the controls
on the form to it. This populates the ListBox and ComboBox with a list of movie
titles. The value in the other controls is derived from the content of the current row
556 Chapter 12 ■ Data Binding with Windows Forms Controls
(highlighted in the list box). The most interesting of these is the PictureBox, which
has its BackgroundImage property bound to a column in the table containing
images. Because the database does not contain images, the program adds this column
to the data table and fills it with images for the movie in each row.
When designing a custom class to be used as a data source, the primary consider-
ation is whether the bindable properties provide read-only or read/write access. If
they are read-only, the only requirement is that they be public. For properties that
can be updated, the class must expose and fire an event to which the binding can
subscribe. Recall that the name of this event is propertynameChanged. This event
is fired in the Set block of the property (see Listing 12-3).
Identifying Updates
The rows in a table have a RowState property that can be used to determine if a
value in the row has been changed (discussed in Chapter 11). This method checks
the value of that property for each row in the data source table. If the value is
DataRowState.Modified, each column in the row is checked to determine which
values have changed (see Listing 12-5). This routine can be used to determine
whether an update to the original database is necessary. Observe that the method
checks only for data changes. You can easily extend it to check for deletions and
additions.
562 Chapter 12 ■ Data Binding with Windows Forms Controls
try
{
int updates = da.Update(ds, "movies");
MessageBox.Show("Updates: "+updates.ToString());
}
12.3 The DataGridView Class 563
Properties
Despite its myriad features, the DataGridView has an elegantly simple structure. As
shown in Figure 12-6, in its most elemental form, it consists of column headers, row
headers, and cells. To these, we can add the Columns and Rows collections that allow
an application to access the grid by indexing a row or column. That is the foundation.
Each property and event discussed in this section relates to one of these five classes.
The DataGridView class inherits many of its properties from the Control class;
to these, it adds properties required to support its own special characteristics and
behavior. The properties listed in Table 12-1 are primarily in this latter category. The
list is not meant to be exhaustive; instead, it presents those properties you’ll refer to
most frequently when implementing a grid.
Constructing a DataGridView
Listing 12-6 shows how to define columns for a DataGridView, set properties to
define its appearance and behavior, and add rows of data. (We’ll see in the succeeding
example how to use the more common approach of loading data from a database.)
Note that the column header cells and data cells have different styles. If a style is
not set for the header, it uses the same DefaultCellStyle as the data cells.
// Turn this off so column names do not come from data source
dataGridView1.AutoGenerateColumns = false;
// Specify table as data source
dataGridView1.DataSource = ds; // Dataset
dataGridView1.DataMember = "movies"; // Table in dataset
// Tie the columns in the grid to column names in the data table
dataGridView1.Columns[0].DataPropertyName = "Title";
dataGridView1.Columns[1].DataPropertyName = "Year";
dataGridView1.Columns[2].DataPropertyName = "director";
The DataGridView supports two-way data binding for ADO.NET data sources:
Changes made to the grid are reflected in the underlying table, and changes made to
the table are reflected in the grid. For example, this code responds to a button click
by adding a new row to the grid’s data source. The addition is immediately reflected
in the control. However, if we try to add a row directly to the DataGridView, an
exception occurs because adding directly to a bound control is not permitted.
r[1] = "1976";
r[2] = "Martin Scorsese";
dt.Rows.Add(r);
// Adding directly to DataGridView does not work
object[] row = {"The Third Man", "1949", "Orson Welles"};
DataRow r = dt.NewRow();
DataGridView1.Rows.Add(row4); // Fails!
}
Updating the original database from which a grid is loaded can be done by issuing
individual SQL commands or using a DataAdapter. The discussion in the previous
section applies.
Core Note
dataGridView1.AutoSizeRows(
DataGridViewAutoSizeRowsMode.ColumnsAllRows);
The AutoSizeRows method sets the row size when it is executed. If subsequent
updates cause the height of cells in a row to change, the row height does not adjust to
the changes. Also, if a row is sortable, clicking a column header to sort the grid causes
all rows to revert to the default row height. Fortunately, the DataGridView has an
AutoSizeRowsMode property that causes row heights to automatically adjust to
changes in grid content.
dataGridView1.AutoSizeRowsMode =
DataGridViewAutoSizeRowsMode.HeaderAndColumnsAllRows;
570 Chapter 12 ■ Data Binding with Windows Forms Controls
Note that this statement does not take effect until the AutoSizeRows method is
executed, and that it prevents users from manually resizing rows.
dataGridView1.Columns[0].SortMode =
DataGridViewColumnSortMode.NotSortable;
This code segment adds a column of buttons to a grid. The first step is
to create an instance of the column class. Its characteristics and data
values—if any—are then set. Finally, the Columns.Add method is
used to add the column to the grid’s column collection.
Any of the column types may be bound to a data source. Although a button is usu-
ally set manually, it can be bound to a property in the grid’s data source in two ways:
Buttons provide a convenient way for a user to select a grid row and trigger an
action such as a pop-up form that displays further information related to the row.
Buttons located in grid cells, however, have no direct event, such as a Click, associ-
ated with them. Instead, events are associated with an action on the overall grid or
specific cells on the grid. By identifying a cell for instance, an event handler can
determine which button is clicked.
Events
Just about every mouse and cursor movement that can occur over a DataGridView
can be detected by one of its events. In addition, events signify when data is changed,
added, or deleted. Table 12-2 provides a summary of the most useful events. Accom-
panying the table is a list of the delegate used to implement these events. (See
Appendix B for a complete list of events.)
Cell actions CellClick (1) Occurs when any part of the cell
(continued) is clicked. This includes cell bor-
ders and padding.
Cell Formatting
The CellFormatting event gives you the opportunity to format a cell before it is
rendered. This comes in handy if you want to distinguish a subset of cells by some
criteria. For example, the grid in Figure 12-7 contains a column indicating the year a
movie was released. Let’s change the background color of cells in that column to red
if the year is less than 1950.
e.CellStyle.SelectionForeColor = Color.Red;
// Indicate that event was handled
e.FormattingApplied = true;
}
}
}
Core Note
Although row and column header cells cannot become “current cells,”
they are assigned a column and row index value. Row headers always
have a column index of –1, and column headers have row index of –1.
The cell events can be used to recognize a single row and column selection. How-
ever, a grid may also permit multiple row, column, and cell selections. In these cases,
it is necessary to use the SelectedRows, SelectedColumns, and SelectedCells
collections to access the selected grid values.
12.3 The DataGridView Class 575
MessageBox.Show("Commit error");
}
// Occurs when selection cursor moves to another cell
if (dgError.Context ==
DataGridViewDataErrorContext.CurrentCellChange)
{
MessageBox.Show("Cell change");
}
if (dgError.Context ==
DataGridViewDataErrorContext.Parsing)
{
MessageBox.Show("parsing error");
}
// Could not format data coming from/going to data source
if (dgError.Context ==
DataGridViewDataErrorContext.Formatting)
{
MessageBox.Show("formatting error");
}
}
ds = new DataSet();
DataTable dt = new DataTable("movies"); // Master
DataTable da = new DataTable("actors"); // Detail
da.Columns.Add("movieID");
da.Columns.Add("firstname");
da.Columns.Add("lastname");
12.3 The DataGridView Class 577
//
dt.Columns.Add("movieID");
dt.Columns.Add("Title");
dt.Columns.Add("Year");
dt.Columns.Add("picture", typeof(Bitmap)); // To hold image
ds.Tables.Add(dt);
ds.Tables.Add(da);
// Define master-detail relationship
DataRelation rel = new DataRelation("movieactor",
dt.Columns["movieID"], da.Columns["movieID"]);
ds.Relations.Add(rel);
DataSet
Table: movies
Table: actors
After defining the table schemas, they are populated from the database using a
DataReader object. Because the database does not contain an image—although it
could—the image is inserted based on the value of the bestPicture field.
After the tables are created and populated, the final steps are to define the grids
and bind their columns to the tables. This segment adds three columns to the master
grid—one of which is an image type column.
Virtual Mode
When a DataGridView is bound to a data source, the entire data source must exist
in memory. This enables quick refreshing of the control’s cells as a user navigates
from row to row. The downside is that a large data store may have prohibitive mem-
ory requirements. To handle excessive memory requirements, a DataGridView can
be run in virtual mode by setting its VirtualMode property to true. In this mode,
the application takes responsibility for maintaining an underlying data cache to han-
dle the population, editing, and deletion of DataGridView cells based on actions of
the user. The cache contains data for a selected portion of the grid. If a row in the
grid cannot be satisfied from cache, the application must load the cache with the nec-
essary data from the original data source. Figure 12-10 compares virtual storage with
binding to a DataTable.
Virtual mode implementation requires that an application handle two special
virtual mode events: CellValueNeeded, which occurs when a cell value must be
displayed; and CellValuePushed, which occurs when a cell’s value is edited.
Other events are also required to manage the data cache. These are summarized in
Table 12-3.
580 Chapter 12 ■ Data Binding with Windows Forms Controls
10,000 1,000
Disk
Event Description
NewRowsNeeded Virtual mode event. Occurs when a row is appended to the Data-
GridView.
CellValueNeeded Virtual mode event. Occurs when cell in grid needs to be displayed.
CellValuePushed Virtual mode event. Occurs when a cell value is edited by the user.
RowValidated Occurs when another row is selected.
UserDeletingRow Occurs when a row is selected and the Delete key is pressed.
DataGridView dgv;
List<movie> movieList = new List<movie>(20); // cache
bool rowNeeded; // True when new row appended to grid
int storeRow = 0;
int currRow = -1; // Set to row being added
movie currMovie; // Holds movie object for current row
12.3 The DataGridView Class 581
Listing 12-7 shows the overhead code to initialize the DataGridView, register the
event handlers, and populate the data cache (this would usually come from a database).
The heart of the application is represented by the event handler methods shown
in Listing 12-8. To summarize them:
• RowNeeded. Is triggered when the user begins to add a new row at the
bottom of the grid. currRow is set to the row number of any row
being added.
• CellNeeded. Is triggered when a cell needs to be redrawn. This does
not require that a row be selected, but occurs as you move the cursor
over cells in the grid. This routine identifies the column the cell is in
and displays the data from the cache or the object that is created for
new rows. Note that the MapRow() is called to translate a row in the
grid to its corresponding row in the cache. In this simple example,
there is always a one-to-one relationship because the cache and grid
contain the same number of rows. In a production application, row
5000 in a grid might map to row 1 in the cache.
• CellPushed. Called when a cell value is edited. This routine updates
a movie object that represents the selected row with the new value.
• RowValidated. Signals that a different row has been selected and is
used to update the previous row. If the row exists in the cache, it is
updated; a new row is added to the cache.
• RowDeleting. Called when user selects a row to delete. If the row
exists in the cache, it is removed.
This example provides only the basic details for implementing a virtual Data-
GridView. The next step is to extend it to include a virtual memory manager that
reloads the cache when data must be fetched from disk to display a cell.
12.5 Test Your Understanding 585
12.4 Summary
Data binding is used to link the data displayed in a control with an underlying data
source. In many cases, it can eliminate the manual code required to populate con-
trols. There are two basic types of binding: simple and complex. Simple is used with
controls that display only one value; complex is used to display multiple data values in
selected controls such as a list box or data grid.
Each data source has a binding manager that keeps track of all connections to it.
This manager is responsible for synchronizing values in the data store and controls
bound to it. For list data sources such as an array or data table, the binding manager
is a CurrencyManager object; for a property on an object, the binding manager is a
PropertyManger object. Both of these objects expose methods that allow them to
be used to navigate through their data source.
Of the data bound controls, the DataGridView offers the richest interface. It
permits data to be displayed and manipulated in a grid format. Style classes and
appearance properties enable almost all of its features to be customized. Its event
members allow virtually any action involving the grid to be detected—from a click on
a cell to the addition of a row. It also permits control types such as a button, image,
or ComboBox to be inserted into any of its cells. Although data binding is typically
used to populate a DataGridView, the control also supports a virtual mode that
allows an application to manage the grid’s content using a custom data cache.
■ Chapter 13
Asynchronous Programming and
Multithreading 590
■ Chapter 14
Creating Distributed Applications
with Remoting 636
■ Chapter 15
Code Refinement, Security,
and Deployment 680
ASYNCHRONOUS
PROGRAMMING AND
MULTITHREADING
591
592 Chapter 13 ■ Asynchronous Programming and Multithreading
Process
App1.exe App2.exe
Multithreading
In a single CPU system, only one thread can execute at a time. The order in which
threads run is based on their priority. When a thread reaches the top of the priority
queue, its code stream is executed for a fixed amount of time known as a time slice. If
13.1 What Is a Thread? 593
the thread does not complete execution, its state information must be stored so that
the thread can later resume execution at the point it is interrupted. The state infor-
mation includes registers, stack pointers, and a program counter that tells the thread
which instruction is executed next. All of this information is stored in the area of
memory allocated to Thread Local Storage.
Core Note
Thread Priority
As mentioned, the order in which a thread runs is based strictly on its priority. If a
thread is running and a thread with a higher priority becomes available to run, the
running thread is preempted to allow the higher priority thread to run. If more than
one thread has the same priority, the operating system executes them in a
round-robin fashion.
In .NET, a thread’s Priority property is used to get or set its priority level. It
may have one of five values based on the ThreadPriority enum: Lowest,
BelowNormal, Normal, AboveNormal, and Highest. The default is ThreadPri-
ority.Normal.
You should override thread priorities only in situations where a task has a clearly
defined need to execute with a low or high priority. Using thread priorities to
fine-tune an algorithm can be self-defeating for several reasons:
• Even threads with the highest priority are subject to blocking by other
threads.
• Raising the priority of a thread can place it into competition with the
operating system’s threads, which can affect overall system perfor-
mance.
• An operating system keeps track of when a thread runs. If a thread has
not run for a while, its priority is increased to enable it to be executed.
Thread State
During its lifetime, a thread may exist in several states: It begins life in an
Unstarted state; after it is started and the CPU begins executing it, it is in Running
mode; when its slice of execution time ends, the operating system may suspend it; or
if it has completed running, it moves into Stopped mode. Running, Stopped, and
Suspended are somewhat deterministic states that occur naturally as the operating
system manages thread execution. Another state, known as WaitSleepJoin, occurs
when a thread must wait for resources or for another thread to complete its execu-
tion. After this blocking ends, the thread is then eligible to move into Running
mode.
Figure 13-2 illustrates the states that a thread may assume and the methods that
invoke these states. It is not a complete state diagram, because it does not depict the
events that can lead to a thread being placed in an inconsistent state. For example,
you cannot start a running thread nor can you abort a suspended thread. Such
attempts cause an interrupt to be thrown.
WaitSleepJoin Abo
rt
Abort
Interrupt Stopped
Sle oin
it/J
ep
Abort-
Wa
Requested
or t
Ab
Start
Unstarted Running
Su
sp
en Suspend-
d
Requested
Resume Suspended
If a program is not interested in a specific state, but does need to know if a thread
has been terminated, the Boolean Thread.IsAlive property should be used.
Synchronous A B End
Start C
Processing
Worker
B Thread
Asynchronous Main
Start A C End
Processing Thread
Before tackling these issues, let’s first look at the basics of how to write code that
provides asynchronous code execution. As we see in the next section, threads can be
explicitly created and used for parallel code execution. An easier approach is to use a
delegate to allocate a worker thread and call a method to execute on the thread—a
process referred to as asynchronous delegate invocation. Delegates can also be used
to specify the callback method that a worker thread calls when it finishes execution.
Although a discussion of creating threads is deferred until later in this chapter, it’s
worth noting now that the threads allocated for asynchronous methods come from a
pre-allocated thread pool. This eliminates the overhead of dynamically creating
threads and also means they can be reused. At the same time, indiscriminate use of
asynchronous calls can exhaust the thread pool—causing operations to wait until new
threads are available. We’ll discuss remedies for this in the section on threads.
Asynchronous Delegates
Delegates—which were introduced in Chapter 4, “Working with Objects in C#”—
provide a way to notify one or more subscribing methods when an event occurs. In
the earlier examples, all calls were synchronous (to methods on the same thread).
But delegates can also be used to make an asynchronous call that invokes a method
on a separate worker thread. Before looking at the details of this, let’s review what a
delegate is and how it’s used.
The following code segment illustrates the basic steps involved in declaring a del-
egate and using it to invoke a subscribing method. The key points to note are that the
callback method(s) must have the same signature as the delegate’s declaration, and
that multiple methods can be placed on the delegate’s invocation chain (list of meth-
ods to call). In this example, the delegate is defined to accept a string parameter
and return no value. ShowUpper and ShowMessage have the same signature.
A close look at the code reveals how delegates support both synchronous and
asynchronous calls.
598 Chapter 13 ■ Asynchronous Programming and Multithreading
Constructor
Takes two parameters. The important thing to note here is that when your program
creates an instance of the delegate, it passes a method name to the constructor—not
two parameters. The compiler takes care of the details of generating the parameters
from the method name.
Invoke
The compiler generates a call to this method by default when a delegate is invoked.
This causes all methods in the invocation list to be called synchronously. Execution
on the caller’s thread is blocked until all of the methods in the list have executed.
BeginInvoke
This is the method that enables a delegate to support asynchronous calls. Invoking it
causes the delegate to call its registered method on a separate worker thread.
BeginInvoke has two required parameters: the first is an AsyncCallback delegate
that specifies the method to be called when the asynchronous method has completed
its work; the second contains a value that is passed to the delegate when the method
finishes executing. Both of these values are set to null if no callback is required. Any
parameters defined in the delegate’s signature precede these required parameters.
Let’s look at the simplest form of BeginInvoke first, where no callback delegate
is provided. Here is the code to invoke the delegate defined in the preceding exam-
ple asynchronously:
IAsyncResult IAsync =
msgDelegate.BeginInvoke("Delegate Called.",null,null)
There is one small problem, however—this delegate has two methods registered
with it and delegates invoked asynchronously can have only one. An attempt to com-
pile this fails. The solution is to register only ShowMessage or ShowUpper with the
delegate.
Note that BeginInvoke returns an object that implements the IAsyncResult
interface. As we see later, this object has two important purposes: It is used to
retrieve the output generated by the asynchronous method; and its IsCompleted
property can be used to monitor the status of the asynchronous operation.
You can also pass an AsyncCallBack delegate as a parameter to BeginInvoke
that specifies a callback method the asynchronous method invokes when its execu-
tion ends. This enables the calling thread to continue its tasks without continually
polling the worker thread to determine if it has finished. In this code segment,
myCallBack is called when ShowMessage finishes.
EndInvoke
Is called to retrieve the results returned by the asynchronous method. The method is
called by passing it an object that implements the IAsyncResult interface—the
same object returned when BeginInvoke is called. These two statements illustrate
this approach:
End
Invoke WaitAll
Blocked Blocked
Callback
Method
Property Description
AsyncState The object that is passed as the last parameter to the Begin-
Invoke method.
AsyncWaitHandle Returns a WaitHandle type object that is used to wait for access
to resources. Access is indicated by a “signal” that the asynchro-
nous task has completed. Its methods allow for various synchroni-
zation schemes based on one or multiple active threads:
WaitOne. Blocks thread until WaitHandle receives signal.
WaitAny. Waits for any thread to send a signal (static).
WaitAll. Waits for all threads to send a signal (static).
13.2 Asynchronous Programming 601
Property Description
IsCompleted Boolean value that returns the status of the asynchronous call.
The WaitHandle and IsCompleted properties are often used together to imple-
ment polling logic that checks whether a method has finished running. Listing 13-1
illustrates this cooperation. A polling loop is set up that runs until IsCompleted is
true. Inside the loop, some work is performed and the WaitHandle.WaitOne
method is called to detect if the asynchronous method is done. WaitOne blocks pro-
cessing until it receives a signal or its specified wait time (20 milliseconds in this
example) expires.
To test performance, the method containing this code was executed multiple
times during a single session. The results showed that execution time was more than
700 milliseconds for the first execution and declined to 203 for the fourth and subse-
quent ones when three different threads were allocated.
Execution: 1 2 3 4 5
Thread: 75 75 80 75 75
Thread: 75 80 12 80 80
Thread: 80 75 80 12 12
Time(ms): 750 578 406 203 203
13.2 Asynchronous Programming 603
For comparison, the code was then run to execute the three tasks with each
BeginInvoke followed by an EndInvoke. It ran at a consistent 610 ms, which is
what would be expected given the 200 ms block by each EndInvoke—and is equiva-
lent to using synchronous code. The lesson to a developer is that asynchronous code
should be used when a method will be executed frequently; otherwise the overhead
to set up multithreading negates the benefits.
Core Note
Using Callbacks
Callbacks provide a way for a calling method to launch an asynchronous task and
have it call a specified method when it is done. This is not only an intuitively appeal-
ing model, but is usually the most efficient asynchronous model—permitting the
calling thread to focus on its own processing rather than waiting for an activity to end.
As a rule, the callback approach is preferred when the program is event driven; poll-
ing and waiting are better suited for applications that operate in a more algorithmic,
deterministic manner.
The next-to-last parameter passed to BeginInvoke is an optional delegate of type
AsyncCallback. The method name passed to this delegate is the callback method
that an asynchronous task calls when it finishes executing a method. The example in
Listing 13-2 should clarify these details.
Things to note:
• The callback method must have the signature defined by the Async-
Callback delegate.
public delegate void AsyncCallback(IAsyncResult
asyncResult);
• The callback method must cast its parameter to an AsyncResult type
in order to access the original delegate and call EndInvoke.
AsyncResult asyncObj = (AsyncResult)asResult;
// Get the original delegate
bmiDelegate bd= (bmiDelegate)asyncObj.AsyncDelegate;
decimal bmi = bd.EndInvoke(asResult);
• The call to EndInvoke should always be inside an exception handling
block. When an exception occurs on an asynchronous method, .NET
catches the exception and later rethrows it when EndInvoke is called.
• The BMICaller method is invoked from an instance of BMIExample
using the following code. Note that the main thread is put to sleep so
it does not end before the result is calculated.
BMIExample bmi = new BMIExample();
bmi.BMICaller(68,122, "Diana");
Thread.Sleep(500); // Give it time to complete
This may work temporarily, but should be avoided. As an alternative, .NET per-
mits a limited number of methods on the Control class to be called from other
threads: Invoke, BeginInvoke, EndInvoke, and CreateGraphics. Calling a
606 Chapter 13 ■ Asynchronous Programming and Multithreading
control’s Invoke or BeginInvoke method causes the method specified in the dele-
gate parameter to be executed on the UI thread of that control. The method can then
work directly with the control.
To illustrate, let’s replace the assignment to Label.Text with a call to a method
DisplayBMI that sets the label value:
DisplayBMI(bmi);
We also add a new delegate, which is passed to Invoke, that has a parameter to
hold the calculated value.
This code segment illustrates an important point about threads and code: The
same code can be run on multiple threads. The first time this method is called, it
runs on the same thread as OnCallBack. The InvokeRequired property is used to
determine if the current thread can access the form. If not, the Invoke method is
executed with a delegate that calls back DisplayBMI on the UI thread—permitting
it to now interact with the UI controls. To make this an asynchronous call, you only
need replace Invoke with BeginInvoke.
The advantage of using the built-in delegate is that you do not have to design your
own, and it runs more efficiently than an equivalent custom delegate.
As a rule, asynchronous techniques are not required for file I/O. In fact, for read
and write operations of less than 64KB, .NET uses synchronous I/O even if asynchro-
nous is specified. Also, note that if you specify asynchronous operation in the
FileStream constructor (by setting the useAsync parameter to true), and then
use synchronous methods, performance may slow dramatically. As we demonstrate in
later chapters, asynchronous techniques provide a greater performance boost to net-
working and Web Services applications than to file I/O.
13.3 Working Directly with Threads 609
Creating Threads
To create a thread, pass its constructor a delegate that references the method to be
called when the thread is started. The delegate parameter may be an instance of the
ThreadStart or ParameterizedTheadStart delegate. The difference in the two
is their signature: ThreadStart accepts no parameters and returns no value;
ParameterizedThreadStart accepts an object as a parameter, which provides a
convenient way to pass data to thread.
After the thread is created, its Start method is invoked to launch the thread. This
segment illustrates how the two delegates are used to create a thread:
To demonstrate thread usage, let’s modify the method to calculate a BMI value
(see Listing 13-2) to execute on a worker thread (Listing 13-4). The weight and
height values are passed in an array object and extracted using casting. The calcu-
lated value is exposed as a property of the BMI class.
In reality, the method GetBMI does not do enough work to justify running on a
separate thread; to simulate work, the Sleep method is called to block the thread for
a second before it performs the calculation. At the same time, the main thread con-
tinues executing. It displays the worker thread state and then displays the calculated
value. However, this logic creates a race condition in which the calling thread needs
the worker thread to complete the calculation before the result is displayed. Because
of the delay we’ve included in GetBMI, that is unlikely—and at best unpredictable.
One solution is to use the Thread.Join method, which allows one thread to wait
for another to finish. In the code shown here, the Join method blocks processing on
the main thread until the thread running the GetBMI code ends execution:
newThread.Start();
Console.WriteLine(newThread.ThreadState);
newThread.Join(); // Block until thread finishes
Console.WriteLine(b.bmi);
Note that the most common use of Join is as a safeguard to ensure that worker
threads have terminated before an application is shut down.
Aborting a Thread
Any started thread that is not in a suspended state can be requested to terminate
using the Thread.Abort method. Invoking this method causes a ThreadAbortEx-
ception to be raised on its associated thread; thus, the code running the thread
must implement the proper exception handling code. Listing 13-5 shows the code to
implement both the call and the exception handling.
The calling method creates a thread, sleeps for a second, and then issues an
Abort on the worker thread. The parameter to this command is a string that can be
displayed when the subsequent exception occurs. The Join command is then used
to wait for the return after the thread has terminated.
612 Chapter 13 ■ Asynchronous Programming and Multithreading
The method running on the worker thread loops until it is aborted. It is structured
to catch the ThreadAbortException raised by the Abort command and print the
message exposed by the exception’s ExceptionState property.
Multithreading in Action
To gain insight into thread scheduling and performance issues, let’s set up an applica-
tion to create multiple threads that request the same resources. Figure 13-5 illus-
trates our test model. The server is a class that loads images from its disk storage on
request and returns them as a stream of bytes to a client. The client spins seven
threads with each thread requesting five images. To make things interesting, the
threads are given one of two different priorities. Parenthetically, this client can be
used for stress testing because the number of threads and images requested can be
set to any value.
ImageServer
Client
Thread 1 GetMovieImage()
Thread 3
Main
Thread 4
Thread
Thread 5
Thread 6
Thread 7
The ImageServer class shown in Listing 13-6 uses the Stream class to input the
requested image file, write it into a memory stream, and convert this stream to an
array of bytes that is returned to the client. Note that any exceptions thrown in the
server are handled by the client code.
The code shown in Listing 13-7 uses the techniques described earlier to create
seven threads that call the static FetchImage method on the ImageServer class.
The threads are alternately assigned a priority of Lowest or AboveNormal, so that
we can observe how their scheduling is affected by priority. Each thread makes five
requests for an image from the server by calling its GetMovieImage method. These
calls are inside an exception handling block that displays any exception message orig-
inating at the server.
13.3 Working Directly with Threads 615
Because GetMovieImage prints the hash code associated with each image it
returns, we can determine the order in which thread requests are fulfilled. Figure
13-6 shows the results of running this application. The even-numbered threads have
the higher priority and are processed first in round-robin sequence. The lower prior-
ity threads are then processed with no interleaved execution among the threads.
The program was run several times to test the effects of varying the number of
images requested. In general, the same scheduling pattern shown here prevails,
although as more images are requested the lower priority threads tend to run in an
interleaved fashion.
This places a request on the thread pool queue for the next available thread. The
first time this runs, the pool must create a thread, which points out an important fact
about the thread pool. It contains no threads when it is created, and handles all
thread requests by either creating a thread or activating one already in the pool. The
pool has a limit (25) on the number of threads it can hold, and if these are all used, a
request must wait for a thread to be returned. You can get some information about
the status of the thread pool using the GetAvailableThreads method:
int workerThreads;
int asyncThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out
asyncThreads);
618 Chapter 13 ■ Asynchronous Programming and Multithreading
This method returns two values: the difference between the maximum number of
worker and asynchronous threads the pool supports, and the number of each cur-
rently active. Thus, if three worker threads are being used, the workerThreads
argument has a value of 22.
The thread pool is most useful for applications that repeatedly require threads for
a short duration. For an application that requires only a few threads that run simulta-
neously, the thread pool offers little advantage. In fact, the time required to create a
thread and place it in the thread pool exceeds that of explicitly creating a new thread.
Core Note
Timers
Many applications have a need to perform polling periodically to collect information
or check the status of devices attached to a port. Conceptually, this could be imple-
mented by coupling a timer with a delegate: The delegate handles the call to a speci-
fied method, while the timer invokes the delegate to place the calls at a specified
interval. In .NET, it is not necessary to write your own code to do this; instead, you
can use its prepackaged Timer classes. Let’s look at a couple of the most useful ones:
System.Timers.Timer and Windows.Forms.Timer. The former is for general
use, whereas the latter is designed for Windows Forms applications.
System.Timers.Timer Class
To use the Timer class, simply register an event handling method or methods with
the class’s Elapsed event. The signature of the method(s) must match that of the
ElapsedEventHandler delegate associated with the event:
using System;
using System.Timers;
13.3 Working Directly with Threads 619
System.Windows.Forms.Timer Class
We can dispense with a code example of this class, because its implementation paral-
lels that of the Timers.Timer class, with two differences: It uses a Tick exception
rather than Elapsed, and it uses the familiar EventHandler as its delegate. How-
ever, the feature that distinguishes it from the other Timer class is that it does not
use a thread from the thread pool to call a method. Instead, it places calls on a queue
to be handled by the main UI thread. Except for situations where the time required
by the invoked method may make the form unresponsive, a timer is preferable to
using threading. It eliminates the need to deal with concurrent threads and also
enables the event handler to directly update the form’s controls—something that
cannot be done by code on another thread.
620 Chapter 13 ■ Asynchronous Programming and Multithreading
Thread A Thread B
Because the first thread is suspended before it updates the log file, both threads
update the file with the same value. Because server applications may have hundreds
of active threads, there is clear need for a mechanism to control access to shared
resources.
13.4 Thread Synchronization 621
A solution is to ensure that after a thread invokes UpdateLog, no other thread can
access it until the method completes execution. That is essentially how synchroniza-
tion works: permitting only one thread to have ownership of a resource at a given
time. Only when the owner voluntarily relinquishes ownership of the code or
resource is it made available to another thread. Let’s examine the different synchro-
nization techniques available to implement this strategy.
.NET to give a thread exclusive access to an object’s code until the thread completes
execution. Here is the code that implements this type of synchronization in the log
update example:
[Synchronization]
public class WorkClass: ContextBoundObject
Encapsulating a Monitor
A problem with the preceding approach is that it relies on clients to use the monitor
for locking; however, there is nothing to prevent them from executing UpdateLog
without first applying the lock. To avoid this, a better design approach is to encapsu-
late the lock(s) in the code that accesses the shared resource(s). As shown here, by
placing Monitor.Enter inside UpdateLog, the thread that gains access to this lock
has exclusive control of the code within the scope of the monitor (to the point where
Monitor.Exit is executed).
{
Monitor.Exit(this); // Relinquish lock
}
Note the use of finally to ensure that Monitor.Exit executes. This is critical,
because if it does not execute, other threads calling this code are indefinitely
blocked. To make it easier to construct the monitor code, C# includes the lock state-
ment as a shortcut to the try/finally block. For example, the previous statements
can be replaced with the following:
lock(this)
{
// Code to be synchronized
}
Monitor and lock can also be used with static methods and properties. To do so,
pass the type of object as a command parameter rather than the object itself:
Monitor.Enter(typeof(WorkClass));
// Synchronized code ...
Monitor.Exit(typeof(WorkClass));
Core Recommendation
The Mutex
To understand the Mutex class, it is first necessary to have some familiarity with the
WaitHandle class from which it is derived. This abstract class defines “wait” meth-
ods that are used by a thread to gain ownership of a WaitHandle object, such as a
mutex. We saw earlier in the chapter (refer to Table 13-1) how asynchronous calls
use the WaitOne method to block a thread until the asynchronous operation is com-
pleted. There is also a WaitAll method that can be used to block a thread until a set
of WaitHandle objects—or the resources they protect—are available.
An application can create an instance of the Mutex class using one of several con-
structors. The most useful are
626 Chapter 13 ■ Asynchronous Programming and Multithreading
public Mutex();
public Mutex(bool initiallyOwned);
public Mutex(bool initiallyOwned, string name);
The Semaphore
The Semaphore class is another WaitHandle derived class. It functions as a shared
counter and—like a mutex—uses a wait call to control thread access to a code section
or resource. Unlike a mutex, it permits multiple threads to concurrently access a
resource. The number of threads is limited only by the specified maximum value of
the semaphore.
When a thread issues a semaphore wait call, the thread is not blocked if the sema-
phore value is greater than 0. It is given access to the code and the semaphore value
is decremented by 1. The semaphore value is incremented when the thread calls the
semaphore’s Release method. These characteristics make the semaphore a useful
tool for managing a limited number of resources such as connections or windows that
can be opened in an application.
The Semaphore class has several overloaded constructor formats, but all require
the two parameters shown in this version:
Avoiding Deadlock
When concurrent threads compete for resources, there is always the possibility that a
thread may be blocked from accessing a resource (starvation) or that a set of threads
may be blocked while waiting for a condition that cannot be resolved. This deadlock
situation most often arises when thread A, which owns a resource, also needs a
resource owned by thread B; meanwhile, thread B needs the resource owned by
thread A. When thread A makes its request, it is put in suspended mode until the
resource owned by B is available. This, of course, prevents thread B from accessing
A’s resource. Figure 13-8 depicts this situation.
Thread A Thread B
2
Waiting for Resource 2
Figure 13-8 Deadlock situation
As you would expect, the method locks both account objects so that it has exclu-
sive control before performing the transaction. Now, suppose two threads are run-
ning and simultaneously call this method to perform a funds transfer:
The problem is that the two threads are attempting to acquire the same resources
(accounts) in a different order and run the risk of creating a deadlock if one is pre-
empted before acquiring both locks. There are a couple of solutions. First, we could
lock the code segment being executed to prevent a thread from being preempted
until both resources are acquired:
lock(this)
{
... Monitor statements
}
13.5 Summary
5. What two delegates are used to create a thread directly, and how do
they differ?
7. How many times does the following code print the console message?
private static s;
public static void Main()
{
s = new Semaphore(0, 3);
// Create and start five numbered threads
for(int i = 1; i <= 5; i++)
{
Thread t = new Thread(new ThreadStart(Worker));
t.Start();
}
}
private static void Worker(object num)
{
s.WaitOne();
Console.WriteLine("Thread enters semaphore ");
Thread.Sleep(100);
s.Release();
}
13.6 Test Your Understanding 633
a. 0
b. 1
c. 3
d. 5
Five philosophers, who spend their lives alternately thinking and eat-
ing, are sitting around a table. In the center of the round table is an
infinite supply of food. Before each philosopher is a plate, and
between each pair of plates is a single chopstick. Once a philosopher
quits thinking, he or she attempts to eat. In order to eat, a philosopher
must have possession of the chopstick to the left and right of the plate.
634 Chapter 13 ■ Asynchronous Programming and Multithreading
P1
1 2
P5
P2
5 3
P4 P3
4
This chapter introduces the .NET way of developing distributed applications. The
emphasis is on how a technique known as remoting permits client computers to
access resources on other local or remote computers. Remoting is typically designed
for intranet applications, although it does support the HTTP protocol for communi-
cating over the Internet. In some cases, it may be regarded as an alternative to Web
Services and a Web browser. We’ll see in this chapter that remoting offers more flex-
ibility and a richer set of features than these standard Web-based solutions.
To fully appreciate remoting technology, it is necessary to understand the relation-
ship among processes, application domains, and assemblies. Toward that end, the
first section explains the role of the application domain in the .NET architecture.
We’ll see that the security and code isolation it offers requires that objects in separate
AppDomains agree upon the port number, protocol, and type of message formatting
before they can communicate.
The second section forms the heart of the chapter. It provides both a conceptual
and hands-on approach to remoting. Code examples illustrate how to select and
implement the remoting options that best fit a distributed application. The section
describes how to create client- and server-activated objects, select formatting and
protocol options, deploy assemblies for a distributed application, and use leases to
manage the lifetime of an object.
You may want to follow up this chapter by reading the chapter on Web Services,
which presents a second .NET technique for implementing a distributed application.
Although there are conceptual similarities between remoting and Web Services,
there are also distinct differences in performance, interoperability, and implementa-
tion complexity that an architect must understand. A reading of these chapters
should provide the know-how to select the approach that best meets the require-
ments of your distributed application.
637
638 Chapter 14 ■ Creating Distributed Applications with Remoting
Device 1 Device 2
Process 1 Process 1
AppDomain 1 AppDomain 1 AppDomain 2
Assembly B
Domain-Neutral Assemblies
Advantages of AppDomains
Aside from the need for a managed environment, the use of AppDomains provides
several advantages over the traditional process-based architecture:
// AppdTestClass.dll assembly
using System;
using System.Diagnostics;
namespace AppDomainTest
{
public class RemoteClass: MarshalByRefObject
{
public void ShowDomain()
{
AppDomain currentAppDomain = AppDomain.CurrentDomain;
Console.WriteLine("Domain:{0}",
currentAppDomain.FriendlyName);
}
}
}
(RemoteClass)currentAppDomain.CreateInstanceAndUnwrap(
"appdtestclass", "AppDomainTest.RemoteClass");
The first parameter is the name of the assembly, and the second is the name of the
type being instantiated. This statement returns an object that is cast to an instance of
RemoteClass. The object’s ShowDomain method is then executed to display the
domain it is running in. In this case, the console output is appdtestclient.exe,
the default name given to the AppDomain when the assembly is executed. The
remainder of the code displays the assemblies in the AppDomain, creates a new
AppDomain, and repeats these operations for it.
Figure 14-2 shows the three assemblies that exist in the default AppDomain: mscor-
lib, appdtestclient, and appdtestclass. Mscorlib is automatically loaded by
.NET because it contains the System namespace required by the application.
Note that the process also includes a second domain that holds two assemblies.
This AppDomain is created using the static CreateDomain method:
Process: appdtestclient.exe
mscorlib mscorlib
appdtestclient appdtestclass
appdtestclass
Figure 14-2 AppDomains and assemblies created from code in Listing 14-1
14.2 Remoting 643
The final step in the example is to unload the new domain from the process. Note
that .NET does not permit an individual assembly to be unloaded from an AppDomain.
14.2 Remoting
At its core, remoting is as a way to permit applications in separate AppDomains to
communicate and exchange data. This is usually characterized as a client-server rela-
tionship in which the client accesses resources or objects on a remote server that
agrees to provide access. The way in which this agreement between client and server
is implemented is what remoting is all about. The physical proximity of the App-
Domains does not matter: They may be in the same process, in different processes,
or on different machines on different continents.
Remoting is often portrayed as a concept that is difficult to understand and imple-
ment—particularly when compared to Web Services. This sentiment is misleading
and simply not true for many applications.
Consider the steps required to enable a client to access an object on a remote
server:
.NET takes care of all the details. You don’t have to understand the underlying
details of TCP, HTTP, or ports—just specify that you want a connection and what
port to use. If HTTP is selected, communications use a Simple Object Access Proto-
col (SOAP) format; for TCP, binary is used. The registration process occurs on both
the server and client. The client selects a registration method and passes it a couple
of parameters that specify the address of the server and the type (class) to be
accessed. The server registers the types and ports that it wants to make available to
clients, and how it will make them available. For example, it may implement the
object as a singleton that is created once and handles calls from all clients; or it may
choose to create a new object to handle each call.
Figure 14-3 depicts the learning curve that developers new to remoting can
expect to encounter. By hiding much of the underlying communications details,
.NET enables a developer to quickly develop functioning applications that can access
remote objects. The complexity often associated with remoting comes into play as
you move further up the learning curve to take advantage of advanced remoting
techniques such as creating sink providers and custom transport channels. Knowl-
edge of these advanced techniques enables one to customize the way distributed
644 Chapter 14 ■ Creating Distributed Applications with Remoting
Advanced Remoting
Difficulty
Sponsor
Leases
Client-Activated Object
Server-Activated Object
Customization
Figure 14-3 The learning curve for developing remoting applications
This chapter focuses on topics to the left of the advanced remoting line. You’ll
learn how to design applications that permit remote objects to be created by either
the server or the client, how to control the lifetime of these objects, and how to
design and deploy assemblies that best take advantage of the remoting architecture.
There are quite a few code examples whose purpose is to present prototypes that you
can use to implement a wide range of remoting applications. Included are examples
of a server that streams requested images to clients, and a message server that both
receives messages and sends them to the targeted recipient upon request.
Remoting Architecture
When a client attempts to invoke a method on a remote object, its call passes through
several layers on the client side. The first of these is a proxy—an abstract class that
has the same interface as the remote object it represents. It verifies that the number
and type of arguments in the call are correct, packages the request into a message,
and passes it to the client channel. The channel is responsible for transporting the
request to the remote object. At a minimum, the channel consists of a formatter sink
that serializes the request into a stream and a client transport sink that actually trans-
mits the request to a port on the server. The sinks within a channel are referred to as
a sink chain. Aside from the two standard sinks, the channel may also contain custom
sinks that operate on the request stream.
1. Advanced .NET Remoting, Second Edition, by Ingo Rammer and Mario Szpuszta; Apress, 2005.
14.2 Remoting 645
On the server side, the process is reversed, as the server transport sink receives
the message and sends it up the chain. After the formatter rebuilds the request from
the stream, .NET creates the object on the server and executes the requested
method.
Figure 14-4 illustrates the client-server roles in a remoting architecture. Let’s
examine its three key components: proxies, formatter classes, and channel classes.
Client Remote
Application Object
Proxy Dispatcher
FORMATTER FORMATTER
Binary SOAP Binary SOAP
Proxies
When a client attempts to communicate with a remote object, its reference to the
object is actually handled by an intermediary known as a proxy. For .NET remoting,
there are two types of proxies: a transparent proxy that the client communicates with
directly, and a real proxy that takes the client request and forwards it to the remote
object.
The transparent proxy is created by the CLR to present an interface to the client
that is identical to the remote class. This enables the CLR to verify that all client calls
match the signature of the target method—that is, the type and number of parame-
ters match. Although the CLR takes care of constructing the transparent proxy, the
developer is responsible for ensuring that the CLR has the metadata that defines the
remote class available at compile time and runtime. The easiest way is to provide the
client with a copy of the server assembly that contains the class. But, as we discuss
later, there are better alternatives.
646 Chapter 14 ■ Creating Distributed Applications with Remoting
After the transparent proxy verifies the call, it packages the request into a message
object—a class that implements the IMessage interface. The message object is
passed as a parameter to the real proxy’s Invoke method, which passes it into a chan-
nel. There, a formatter object serializes the message and passes it to a channel object
that physically sends the message to the remote object.
Core Note
Formatters
Two formatters are included as part of the .NET Remoting classes: a binary format-
ter and a SOAP formatter. SOAP, which is discussed in detail in the Web Services
chapter, serializes messages into an XML format. Binary produces a much smaller
message stream than SOAP, because it sends the message as a raw byte stream.
By default, SOAP is used when the HTTP protocol is selected and binary is used
with the TCP protocol. However, you can also choose to send SOAP over TCP and
binary over HTTP. Although not as efficient as the binary format, the combination of
SOAP and HTTP has become a de facto standard for transmitting data through fire-
walls whether using remoting or Web Services. The binary format is recommended
for cases where firewalls are not an issue.
Channels
Channel objects are created from classes that implement the IChannel interface.
.NET comes with two that handle most needs: HttpChannel and TcpChannel. It is
the responsibility of the remote host to register the channel over which it is willing to
provide access; similarly, the client registers the channel it wants to issue its calls on.
In its simplest form, registration on the host consists of creating an instance of the
channel object and registering it by passing it as a parameter to the static Register-
Channel method of the ChannelServices class. This example registers an HTTP
channel on port 3200:
// Channel Registration
HttpChannel c = new HttpChannel(3200); // Port 3200
ChannelServices.RegisterChannel(c);
14.2 Remoting 647
The only difference in registering on the client side is that the port number does
not have to be specified:
There are some rules to keep in mind when registering channels on the client and
server:
• Both the host and client can register multiple channels; however, the cli-
ent must register a channel that matches one the host has registered.
• Multiple channels cannot use the same port.
• By default, HTTP and TCP channels are given the names http and
tcp, respectively. If you attempt to register multiple HTTP or TCP
channels using the default name, you will receive an exception. The
way around this is to create the channels using a constructor
(described shortly) that accepts a channel name parameter.
<application>
<channels>
<channel ref="http" port="3200"/>
</channels>
</application>
A program uses this file by passing the file name to the static Configure method
of the RemotingConfiguration class:
RemotingConfiguration.Configure("MessageHost.exe.config");
The configuration file must be in the same directory as the assembly referencing it.
HttpChannel(IDictionary properties,
IClientChannelSinkProvider csp,
IserverChannelSinkProvicer ssp )
648 Chapter 14 ■ Creating Distributed Applications with Remoting
Only the first parameter is of interest for naming purposes. The second or third
can be used to specify the formatter used on the client or server side:
Types of Remoting
Recall that the parameters in a C# method may be passed by value or by reference.
Remoting uses the same concept to permit a client to access objects—although the ter-
minology is a bit different. When a client gets an actual copy of the object, it is referred
to as marshaling by value (MBV); when the client gets only a reference to the remote
object, it is referred to as marshaling by reference (MBR). The term marshaling simply
refers to the transfer of the object or request between the client and server.
Marshaling by Value
When an object is marshaled by value, the client receives a copy of the object in its
own application domain. It can then work with the object locally and has no need for
a proxy. This approach is much less popular than marshaling by reference where all
calls are made on a remote object. However, for objects that are designed to run on a
client as easily as on a server, and are called frequently, this can reduce the overhead
of calls to the server.
As an example, consider an object that calculates body mass index (BMI). Instead
of having the server implement the class and return BMI values, it can be designed
to return the BMI object itself. The client can then use the object locally and avoid
further calls to the server. Let’s see how to implement this.
For an object to be marshaled by value, it must be serializable. This means that the
class must either implement the ISerializable interface or—the easier approach—
have the [Serializable] attribute. Here is the code for the class on the server:
[Serializable]
public class BMICalculator
{
// Calculate body mass index
public decimal inches;
public decimal pounds;
public decimal GetBMI()
{
return ((pounds*703* 10/(inches*inches))/10);
}
}
14.2 Remoting 649
The client creates an instance of HealthTools and calls the GetBMIObj method
to return the calculator object:
Marshaling by Reference
Marshaling by reference (MBR) occurs when a client makes a call on an object run-
ning on a remote server. The call is marshaled to the server by the proxy, and the
results of the call are then marshaled back to the client.
Objects accessed using MBR must inherit from the MarshalbyRefObject class.
Its most important members, InitializeLiIfetimeServices and GetLife-
TimeServices, create and retrieve objects that are used to control how long a
remoting object is kept alive on the server. Managing the lifetime of an object is a key
feature of remoting and is discussed later in this section.
MarshalByRefObjects come in two flavors: client-activated objects (CAO) and
server-activated objects (SAO)—also commonly referred to as well-known objects
(WKO). Server-activated objects are further separated into single call and singleton
types. A server may implement both client-activated and server-activated objects. It’s
up to the client to choose which one to use. If the client selects SAO, the server
makes the determination as to whether to use server-activated single call or
server-activated singleton objects.
The choice of activation mode profoundly affects the overall design, performance,
and scalability of a remoting application. It determines when objects are created,
650 Chapter 14 ■ Creating Distributed Applications with Remoting
how many objects are created, how their lifecycle is managed, and whether objects
maintain state information. Let’s look at the details.
Client-Activated Objects
The use and behavior of a client-activated object (CAO) resembles that of a locally
created object. Both can be created using the new operator; both may have parame-
terized constructors in addition to their default constructor; and both maintain state
information in properties or fields. As shown in Figure 14-5, they differ in that the
CAO runs on a host in a separate application domain and is called by a proxy.
AppDomain 1
Object 1
AppDomain 2
Object 2
Client Proxy
The fact that the object resides in another AppDomain means that it is subject to
Garbage Collection there and can be destroyed even though the remote client is still
using it. .NET handles this potential problem by assigning a lease to each object that
can be used to keep it alive. Leases are discussed in detail later in this chapter.
Server-Activated Objects
A server-activated object (SAO) may be implemented as a singleton or single call
object. The former is best suited for sharing a single resource or collaborative opera-
tion among multiple users. Examples include a chat server and class factory. Single
call mode is used when clients need to execute a relatively short operation on the
server that does not require maintaining state information from one call to the next.
This approach is the most scalable solution and has the added advantage of working
well in an environment that uses load balancing to direct calls to multiple servers.
14.2 Remoting 651
Server-Activated Singleton
Figure 14-6 illustrates how a single object is used to handle all calls in single-
ton-based design. The server creates the object when the first client attempts to
access it—not when it tries to create it. Because the object is created only once,
efforts by other clients to create an instance of it are ignored; instead, they are all
given a reference to the same singleton object. Each time a client invokes the object,
the CLR allocates a new thread from the thread pool. For this reason, it is the
responsibility of the developer to ensure the server code is thread-safe. This also lim-
its scalability because there is usually only a finite number of threads available.
AppDomain 1
3
Singleton
AppDomain 2 Object
2
Client Proxy
Figure 14-6 Server-activated singleton object: one object handles all calls
AppDomain 1
3 (null)
Object 2
AppDomain 2
Object 3
2
Client Proxy
Figure 14-7 Server-activated single call: one object is created for each request
Type Registration
An application may support multiple activation modes and multiple objects. A client
indicates the object(s) it wants to access on a server and whether to use client- or
server-activation mode. The server, on the other hand, indicates which objects it
wants to make available to remote clients and the activation mode that is required to
access them. This is done using a mechanism known as type registration. As a com-
plement to channel registration, which tells .NET how to transport messages, type
registration specifies the objects that can be remotely accessed and the activation
mode to use. It’s the final part of the agreement that permits a client in one AppDo-
main to access objects on a host in another AppDomain.
Replace Singleton with SingleCall to register the object to run in single call
mode.
14.2 Remoting 653
When the client uses new to create an instance of the object, .NET recognizes
that the object is registered and uses its URL to locate it.
<wellknown
mode="Singleton"
type="SimpleServer.MessageManager, msgserver"
objectUri="MyObject"/>
</service>
</application>
The client also includes a url attribute to provide the address of the remote
object:
ge Client
essa
SetM
Host/Listener Server
msghost.exe msgserver.dll Client
FetchMessages
Client
msgclient.exe
Figure 14-8 Three assemblies are used in message server remoting example
Before examining the code, we need to clarify the terminology used to describe
the assemblies. In this chapter, server refers to an assembly that declares and imple-
ments the remote classes; host or listener refers to an assembly that contains the code
to perform type and channel registration. If the host and server functions are com-
bined, the assembly is referred to as a server or host/server. In the world of remoting
literature, you’ll find that some authors reverse this meaning of host and server,
whereas others refer to the host as general assembly.
Our message server application consists of three source files that are compiled
into the msgserver.dll, msghost.exe, and msgclient.exe assemblies:
Note that the server code is packaged as a library (DLL) and must be referenced
by both the host and client during compilation.
656 Chapter 14 ■ Creating Distributed Applications with Remoting
Server Assembly
Listing 14-2 contains the code for a MessageManager class that is made available to
clients as a server-activated singleton. Aside from the required MarshalByRefOb-
ject inheritance, the class is indistinguishable from a non-remoting class. It exposes
two methods, SetMessage and FetchMessages, which are used to post and
retrieve messages, respectively. A call to SetMessage contains the ID of the sender
and recipient along with the message. This information is packaged into an instance
of the Envelope class and stored in an array. Clients retrieve messages by invoking
FetchMessages with their client ID. The method searches the array of messages
and returns a string containing all messages for that ID.
public MessageManager()
{
Console.WriteLine("Message Object Created.");
}
// Concatenate all messages and return string to client
public string FetchMessages(string clientID)
{
string msgList= "";
for (int i=Messages.Count-1;i>=0;i--)
{
Envelope env= (Envelope)Messages[i];
if(env.RecipientID== clientID)
{
msgList+= env.SenderID+": "+env.Message+"\n";
Messages.RemoveAt(i); // Remove message
}
}
Console.WriteLine("Sending:\n {0}",msgList);
return(msgList);
}
// Accept message from client and store in memory
public void SetMessage(string msg, string sender,
string recipient)
14.2 Remoting 657
Host Assembly
The host assembly, shown in Listing 14-3, performs channel and type registration.
The channel is configured to use HTTP over port 3200; and the MessageManager
object is designated to run as a singleton. Keep in mind that the port number, which
is essentially an address associated with the application, should be greater than 1024
so as not to conflict with reserved port IDs.
Client Assembly
The code for the client class is shown in Listing 14-4. It is run from the command
line and takes an optional parameter that is used as the client ID:
The client first registers the type and channel. The latter must specify the same
port (3200) as registered by the host assembly. Following registration, an instance of
MessageManager is created using the new operator. When the user types an R, the
object retrieves messages; to send a message, an S is entered at one prompt and the
recipient ID and message at the next prompt.
Figure 14-9 shows the interactive dialog on the client screen and the correspond-
ing output on the server/host screen. Observe that Message Object Created, which is
inside the constructor, occurs when the first call is made to the remote object—not
when the host begins executing. Also, the constructor is only executed once because
this is a singleton object.
RemotingConfiguration.Configure("MsgHost.exe.config");
14.2 Remoting 661
//msghost.exe.config
<configuration>
<system.runtime.remoting>
<application name = "SimpleHost">
<service>
<wellknown
mode="Singleton"
type="SimpleServer.MessageManager, msgserver"
objectUri="MyObject"/>
</service>
<channels>
<channel ref="http" port="3200"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
If you compare this with the source code, it’s obvious that information in the
wellknown tag corresponds to the parameters in the RegisterWellKnownSer-
viceType method; and the channel tag provides information encapsulated in the
HttpChannel object. The client configuration file shows a similar correspondence
between these tags and source code:
//msgclient.exe.config
<configuration>
<system.runtime.remoting>
<application name = "SimpleClient">
<client >
<wellknown
type="SimpleServer.MessageManager, msgserver"
url="http://localhost:3200/MyObject" />
</client>
<channels>
<channel ref="http"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Note that the file contains no tag to specify the type of formatting to be used—so
the default applies. You can specify a different formatter by extending the channel
block:
662 Chapter 14 ■ Creating Distributed Applications with Remoting
<channel ref="http">
<clientProviders>
<formatter ref="Binary" />
</clientProviders>
</channel>
ge Client
essa
SetM
Interface Host/Server
msggeneral.dll msgserver.exe Client
FetchMessages
Client
msgclient.exe
Figure 14-10 MessageServer redesigned to use an
interface to provide remote object metadata
The simple interface shown here defines the signature of the two methods
exposed by the remote class.
// msggeneral.cs (DLL)
namespace SimpleServer
{
14.2 Remoting 663
The server assembly contains the same object implementation code as in the first
example. However, it now inherits from the newly defined IMessageManager inter-
face and includes the registration code from the host/listener assembly. This code is
contained in a new class StartServer that provides an entry point to the assembly
so that it can be compiled into an .exe file.
// msgserverv2.cs (exe)
public class MessageManager: MarshalByRefObject, IMessageManager
// Code for MessageManager and Envelope class goes here ...
// Class to provide entry point to assembly and perform
// registration
class StartServer
{
static void Main()
{
Console.WriteLine("Host Started.");
// Channel Registration
HttpChannel c = new HttpChannel(3200);
ChannelServices.RegisterChannel(c);
// Type Registration
Type ServerType = typeof(SimpleServer.MessageManager);
RemotingConfiguration.RegisterWellKnownServiceType(
ServerType, // Type of Object
"MyObject", // Arbitrary name
WellKnownObjectMode.Singleton);
Console.Read();
}
}
Changes are required in the client code to account for the fact that the descrip-
tion of the remote object now comes from an interface; and because it’s not possible
to directly instantiate an interface, another way must be found to gain a reference to
the remote object. The solution is to use Activator.GetObject to return an
instance of the interface. This is an important point: .NET returns interface infor-
mation to the client but creates the actual object on the server where it runs as a
664 Chapter 14 ■ Creating Distributed Applications with Remoting
2. For comparison, this image server is implemented in Chapter 18, “XML Web Services,”
as a Web Service.
14.2 Remoting 665
single call is preferable because it manages resources better by discarding the server
object as soon as the request is completed. If a client is expected to make several
image requests during a session, client activation is preferred. It allows a client to
create and reuse one object for several calls—obviating the need to build and tear
down objects with each request.
Host Assembly
The host/listener assembly has the familiar task of registering the channel(s) and
type. Because this application revolves around streaming raw bytes of data, binary
formatting is selected over SOAP. This does not have to be specified directly, because
the choice of the TCP protocol assigns binary formatting by default.
The other noteworthy change from the server-activated example is that Regis-
terActivatedServiceTypeCode is used for type registration instead of Regis-
terWellKnownServiceType.
using System.Runtime.Remoting.Channels.Tcp;
Server Assembly
Listing 14-5 provides the implementation of the ImageServer class, which exposes
two methods: GetFiles and GetMovieImage. The former returns an array contain-
ing the name of all available image files. GetMovieImage is the heart of the system.
It receives a string containing the name of a requested image, opens the correspond-
ing file as a memory stream, and converts it to an array of bytes that is returned to the
client. (See Chapter 5, “C# Text Manipulation and File I/O,” for a refresher on mem-
ory streams.)
Client Assembly
The registration steps in Listing 14-6 enable the client to communicate with the host
using TCP on port 3201. The call to RegisterActivatedClientType, which regis-
ters the ImageServer type, corresponds to the RegisterActivatedServiceType
call on the host. After registration, the new operator is used to give the client a refer-
ence to the remote object (via a proxy).
14.2 Remoting 667
// caoimageclient.cs (.exe)
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Collections;
using System.Drawing;
using System.IO;
namespace SimpleImageClient
{
class ImageClient
{
static void Main(string[] args)
{
// (1) Register channel for TCP/Binary
TcpChannel c = new TcpChannel();
ChannelServices.RegisterChannel(c);
// (2) Register remote type for CAO
Type ServerType = typeof(ImageServer);
RemotingConfiguration.RegisterActivatedClientType(
ServerType,
"tcp://localhost:3201");
ImageServer imgMgr=null;
bool serverOK=true; // Indicates whether server is up
// (3) Create instance of remote object
try{
imgMgr = new ImageServer();
} catch (Exception ex) {
Console.WriteLine(ex.Message);
serverOK=false;
}
if(serverOK)
{
string oper="";
while(oper !="Q")
{
Console.WriteLine(
"(L)ist files,(R)etrieve,(Q)uit");
oper= Console.ReadLine();
oper = oper.ToUpper();
if(oper=="R"){
668 Chapter 14 ■ Creating Distributed Applications with Remoting
Console.WriteLine(
"Enter image name to retrieve:");
string fname= Console.ReadLine();
// Exception is handled if image cannot be found
try
{
// Request image from server
byte[] image = imgMgr.GetMovieImage(fname);
MemoryStream memStream = new
MemoryStream(image);
Console.WriteLine("Image Size: {0}",
memStream.Length);
// Convert memory stream to a Bitmap object
Bitmap bm = new Bitmap(memStream);
// Save image on local system
bm.Save("c:\\cs\\"+fname,
System.Drawing.Imaging.ImageFormat.Jpeg);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
}
else
{
if (oper=="L") // List image file names
{
try
{
ArrayList images = imgMgr.GetFiles();
for (int i=0;i<images.Count;i++)
{
Console.WriteLine(images[i]);
}
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
}
}
} // while
} // serverok
} // Main
} // class
} // namespace
14.2 Remoting 669
You now have two approaches that can be used to avoid deploying an entire server
implementation assembly on the client’s machine: an interface assembly or an assem-
bly created from SoapSuds generated metadata. Which is better? In general, the
interface approach is recommended. SoapSuds works well, but doesn’t work for all
cases. For example, if an assembly contains a class that implements ISerializable
or has the [Serializable] attribute, it does not generate metadata for any of the
class’s properties. However, if you have a relatively simple server assembly as in the
preceding example, SoapSuds can be used effectively.
670 Chapter 14 ■ Creating Distributed Applications with Remoting
Design Considerations in
Creating a Distributed Application
One of the first decisions required in designing a remoting application is whether to
use server- or client-activated objects. Here are some general guidelines:
Configuring Assemblies
After the remoting model has been chosen, there remains the choice of how to
design the assemblies required on the client and server side of the application. As
discussed earlier, the client side of the application requires the client application,
plus an assembly, to provide the necessary information for .NET to construct a proxy.
The server implementation assembly is not a good choice because it includes code.
Alternatives are a metadata assembly provided by the SoapSuds utility or an assembly
that includes only an interface for the server class. Figure 14-11 summarizes the
most common design choices.
Assembly Definitions:
SoapSuds An assembly created by running SoapSuds against a server
Metadata assembly.
Interface An assembly containing an interface that defines the server class.
Server Code A DLL containing the implementation of the remote class.
Server/Host An .exe file containing code to implement the remote class and
perform type and channel registration.
Host An .exe file that performs type and channel registration.
Each row shown in Figure 14-11 indicates the type of assemblies to be used in an
application. For example, the second row shows a possible configuration for a
server-activated object design. A DLL containing an interface inherited by the
remote class is deployed on the client along with the client’s executable assembly; the
server side shares the same interface assembly and contains the interface implemen-
tation, as well as the registration code in a single server/host assembly.
This figure represents only a starting point in the design process, because many
remoting applications are hybrids that may combine both SAO and CAO. However,
an understanding of these core design techniques provides the foundation needed to
implement and deploy more complex applications.
Leasing
A lease is an object created internally by .NET that implements the ILease inter-
face. This interface, located in the System.Runtime.Remoting.Lifetime
namespace, defines the members that are used to govern a lease’s behavior. These
include properties to get or set the initial time of the lease (the default is 5 minutes),
obtain the time remaining on the lease, and specify the time a lease is renewed for
when its associated object is invoked. Table 14-1 summarizes the ILease members.
Member Description
InitialLeaseTime Gets or sets the initial amount of time-to-live that a lease assigns
to an object. Default is 5 minutes.
SponsorshipTimeout Gets or sets the amount of time available for a sponsor to pro-
vide a new lease renewal time. Default is 2 minutes.
<application >
<lifetime
leaseTime = "8M"
14.3 Leasing and Sponsorship 673
renewOnCallTime = "6M"
/>
...
</application>
In this example, the time is specified in minutes (M), but it may also be specified as
days (D), hours (H), seconds (S), or milliseconds (MS).
The same effect can be achieved inside a program by setting static properties on
the LifetimeServices class:
LifetimeServices.LeaseTime = System.TimeSpan.FromMinutes(8);
LifetimeServices.RenewOnCallTime =
System.TimeSpan.FromMinutes(6);
Note that implementation of this method overrides any values in the configuration
file. This means that a configuration file can be used to set default values for all
objects, and this method override approach can be used to set non-default values for
selected objects.
After a lease is in an active state, the only property on the lease that can be
changed is its CurrentLeaseTime. This value can be renewed by a sponsor
(described later) or by having the client or object explicitly invoke the lease’s Renew
method.
674 Chapter 14 ■ Creating Distributed Applications with Remoting
// Client
(ILease) lease=(ILease)RemotingServices.GetLifetimeServices(ob)
// Object on server
(ILease) lease=(ILease)RemotingServices.GetLifetimeSer-
vices(this)
if(lease.CurrentLeaseTime.TotalMinutes < 1.0)
lease.Renew(TimeSpan.FromMinutes(5));
If the current lease time is greater than this renewal time, the lease is not reset.
<application >
<lifetime
LeaseManagerPollTime"5S"
/>
The value can also be set programmatically using the LifetimeServices class:
LifetimeServices.LeaseManagerPollTime =
System.TimeSpan.FromSeconds(5);
on the object, the remaining lease time is compared with the renewOnCallTime
value. The lease time is set to the greater of the two values. For example, if 4 minutes
remain, and the renew time is 6 minutes, the value is set 6 minutes; if 7 minutes
remain, the value is not altered. When the TTL value goes to 0, the lease manager
checks to see if the lease has a sponsor that will assign a new lease time; if not, the
object is deactivated and made available for Garbage Collection. Calls made on an
object whose lease has expired throw an exception indicating that “no receiver could
be found” or the “object has been disconnected.” For this reason, it is important to
surround all calls on a remote object with exception handling code.
TTL Set
TTL >= RenewTime to CallTime
Sponsorship
A program’s final opportunity to renew a lease is when the lease’s CurrentLease-
Time value winds down to 0. At this point, the lease enters a LeaseState.Renew-
ing state (see Figure 14-12), and the lease manager checks to see if any sponsor has
been registered with the object (a lease may have sponsors created by multiple cli-
ents). If a sponsor is found, it is called and given the opportunity to renew the lease
by returning a TimeSpan value that becomes the new TTL for the object.
This sponsorship model, based on registration and callbacks, closely resembles the
.NET event handling model. In fact, a sponsor can be viewed as an event handler
that is called when the lease time approaches 0. Table 14-2 spells out the similarities.
676 Chapter 14 ■ Creating Distributed Applications with Remoting
Registers with a delegate to handle an Registers with an object’s lease when an object
object’s event. is created.
An event handler may be registered for A sponsor may register with multiple leases.
multiple events.
Signature of event handling method must Renewal must always have this signature:
match that of the delegate. Timespan Renewal(ILease lease)
2. Register the channel to be used for the callback from the lease man-
ager. Setting the port to 0 instructs .NET to automatically use any
available port:
// Register channel for callback from lease manager
HttpChannel c2 = new HttpChannel(0);
ChannelServices.RegisterChannel(c2);
This could also be done in a client configuration file:
<channels>
<channel ref="http" port="0">
</channels>
3. Register a sponsor for the lease. First, use GetLifetimeService to
get a reference to the lease. Then, pass an instance of the sponsor to
the ILease.Register method:
// Register sponsor
ISponsor currSponsor = new ImageSponsor();
ILease lease =
(ILease)RemotingServices.GetLifetimeService(imgMgr);
lease.Register(currSponsor);
4. Define the sponsor class. It must have the proper inheritance and
must provide an implementation of the ISponsor method Renewal.
// Implement Sponsor
public class ImageSponsor: MarshalByRefObject, ISponsor
{
public TimeSpan Renewal(ILease lease)
{
Console.WriteLine(lease.CurrentLeaseTime);
// Set object's time to live to 10 minutes
return TimeSpan.FromMinutes(10);
}
}
In this example, the sponsor renews the lease for 10 more minutes. Because this
sponsor is called each time the TTL approaches 0, the object associated with the
lease exists as long as the client is running. A more sophisticated implementation
would include logic that decides whether to renew the lease. To indicate no renewal,
the sponsor returns TimeSpan.Zero.
Core Note
A client-based sponsor works only if the server can access the client.
This technique cannot be used for a client situated behind a firewall that
blocks access.
678 Chapter 14 ■ Creating Distributed Applications with Remoting
14.4 Summary
To run code in a managed environment, .NET creates partitions called application
domains for the assemblies to execute in. The AppDomain, which runs inside of a
physical process, has the advantage of providing more secure code through code iso-
lation and security boundaries. The boundaries prevent an object in one AppDomain
from directly accessing an object in another AppDomain. For them to communicate,
.NET provides a set of classes that support remoting—a technique that enables
objects to communicate across AppDomain boundaries.
Remoting provides a way to implement client-server or peer-to-peer distributed
applications. It’s designed to conceal the underlying details of how messages are
transported and permit the developer to focus on higher level tasks such as selecting
a protocol or the way the transported message is formatted. A key component of the
remoting architecture is the use of a proxy on the client side that serves as a surrogate
for the remote object. It interacts with the client by presenting the same interface as
on the remote object and encapsulates the information required to translate client
calls into actual calls on the remote object.
The remote objects can be implemented in several ways: as a singleton that ser-
vices all requests, as a server-activated single call object that is created each time the
object is invoked, or as a client-activated object that persists as long as the client
keeps it alive. The three offer a variety of different characteristics that enable a
developer to select one most suited for an application.
2. What are the three types of object activation modes, and which proto-
cols can be used with each?
3. Which activation mode creates a new object each time a client invokes
a method on the object? Which mode creates a new object only the
first time a client invokes a method?
14.5 Test Your Understanding 679
5. What determines how often the lease manager checks for lease
expirations?
In the earliest days of programming, computers were used primarily to perform cal-
culations and tedious tabulations. The measure of a program’s correctness was
whether it produced accurate results for a given set of input values. Modern software
development now relies more on component-based solutions. The components often
come from multiple sources, and it’s not always possible to know the origin or trust-
worthiness of the components. As a result, code security and the ease of deploying
and updating an application are now important metrics against which an application’s
success is judged.
This chapter looks at the issues and steps involved in producing a deliverable
.NET software product. It breaks the process down into the three categories shown
in Figure 15-1: code refinement, which looks at how code is tested against best prac-
tice rules; code security, which ensures that code is accessed only by other code that
has permission to do so; and code deployment, which looks at how an application or
component is packaged and made available for deployment.
The first section shows how to use FxCop as a tool to analyze an assembly and
generate code change recommendations based on a predefined set of coding stan-
dards. The second section looks at the details of how to create a strongly named
assembly and the security benefits that accrue from doing so.
The next section—which forms the heart of the chapter—explores the topic of
Code Access Security (CAS). It explains how an administrator uses .NET tools to
define a multi-level security policy for a computing environment and how security
features are embedded in code. It also stresses understanding the interrelated secu-
rity roles of evidence, policy, and permissions.
681
682 Chapter 15 ■ Code Refinement, Security, and Deployment
Code
Coding Standards
Security
Deployment
Deliverable Code
1. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
cpgenref/html/cpconnetframeworkdesignguidelines.asp
15.1 Following .NET Code Design Guidelines 683
instance. To accommodate your own coding standards, the tool permits you to dis-
able rules and add custom ones. Figure 15-2 shows how rules are displayed with
check boxes that make it easy to enable or disable them.
Figure 15-2 FxCop allows rules to be enabled or disabled by clicking a check box
Using FxCop
The purpose of FxCop is to analyze an assembly and produce output that pinpoints
code features that violate the set of recommended best practices. To illustrate how it
works, let’s create an assembly from the code in Listing 15-1 and run it through
FxCop. The program is a simple console application that accepts an input string,
reverses it, and prints it. A simple menu permits the user to specify whether she
wants to reverse a string or quit.
To test with FxCop, compile the program and pass the assembly to FxCop:
Due to the length of the output, it’s better to direct it to a file rather than display it.
684 Chapter 15 ■ Code Refinement, Security, and Deployment
The output is serialized as XML that contains Message tags, describing each
occurrence where the code does not conform to the recommended practices. Here is
an example of the raw code comprising one message:
15.1 Following .NET Code Design Guidelines 685
<Message TypeName="AssembliesShouldHaveValidStrongNames"
Category="Microsoft.Design" CheckId="CA2210" Status="Active"
Created="2005-01-12 02:41:07Z" FixCategory="NonBreaking">
<Issue Name="NoStrongName" Certainty="95"
Level="CriticalError">Sign 'fxtest' with a strong name key.
</Issue>
</Message>
Let’s look at the analysis FxCop produces for the fxtest assembly. For brevity,
only the TypeName values from the XML are listed. Beneath each is a description of
the code changes that will eliminate the message:
(1) "AssembliesShouldDeclareMinimumSecurity"
Requires adding a permission attribute that specifies the permissions required
by this assembly. This is explained in Section 15.3 of this chapter.
(2) "AssembliesShouldHaveValidStrongNames"
Assembly should be assigned a strong name, which is a key that identifies the
assembly. Assigning strong names is described in Section 15.2 of this chapter.
(3) "MarkAssembliesWithAssemblyVersion"
Add version attribute: [assembly: AssemblyVersion("1.0.0.0")].
(4) "MarkAssembliesWithClsCompliant"
Add compliant attribute: [System.CLSCompliant(true)].
(5) "MarkAssembliesWithComVisible"
Add ComVisible attribute: [ComVisible(true)].
This exposes public managed assemblies and types to COM. By default, they
are not visible to COM.
(6) "StaticHolderTypesShouldNotHaveConstructors"
Because the class TestApp has only static members, it should not have a public
constructor. In this case, the public constructor is the default parameterless
constructor. To override this, add: private TestApp() {}.
(7) "AvoidUnnecessaryStringCreation"
To avoid allocating memory for strings, string operations should be avoided.
The solution is to eliminate oper = oper.ToUpper(); and to use case-
insensitive comparisons in the code, such as if(string.Compare(oper,
"R", true)==0).
(8) "AvoidTypeNamesInParameters"
Naming rules recommend that type names not be included as part of a param-
eter name. In this case, it objects to the parameter name stringParameter.
686 Chapter 15 ■ Code Refinement, Security, and Deployment
2. The manifest is a set of tables containing metadata that describes the files in the assembly.
15.2 Strongly Named Assemblies 687
5. When the runtime loads this assembly, it essentially reverses the sign-
ing process: It decrypts the signature using the public key found in the
manifest. Then, it performs a hash of the assembly’s contents and
compares this with the decrypted hash. If they do not match, the
assembly is not allowed to run.
Figure 15-3 summarizes the overall process. Note how decryption works. The
decrypted signature yields a hash that should match the output when a new hash is
performed on the assembly. If the two match, you can be sure that the private key
associated with the public key was used for the original signing, and that the assem-
bly has not been tampered with—changing even one bit will result in a different
hash.
Encrypt Digital
Assembly Hash Signature Decrypt
(Sign)
Figure 15-3 Using private and public keys to sign and verify a strong assembly
Core Note
Delayed Signing
It is imperative that the private key generated using Sn.exe (or some other process)
not be compromised, because it is how an organization uniquely signs its software. If
another party has access to the key, consumers cannot trust the ownership of the
assembly.
15.2 Strongly Named Assemblies 689
One measure to secure the key is to limit its availability to developers, or withhold
it altogether. During the development stage, developers are given only the public
key. Use of the private key is delayed until it is necessary to sign the final software
version. Delayed signing requires different steps than are used for creating a strongly
named assembly:
Because an assembly references another strongly named assembly using its public
key, there is no need to rebuild assemblies dependent on this one.
tral, well known location where they can be located and shared by multiple applica-
tions. A less obvious advantage is that the strong name signatures for assemblies in
the GAC are verified only when installed in the GAC. This improves the perfor-
mance of applications referencing these assemblies, because no verification is
required when loading the assembly.
Physically, the GAC is a Microsoft Windows directory located on the following
path:
C:\winnt\assembly
You can view its contents using a shell extension (ShFusion.dll) that is added to
Windows Explorer as part of the .NET Framework installation. Each entry displays
an assembly’s name, type, version number, and public key token. By clicking an
assembly entry, you can bring up a context menu that permits you to display the
assembly’s properties or delete it.
The easiest way to install a strongly named assembly into the GAC (or uninstall
one) is to use GACUtil.exe, a command-line utility that ships with .NET SDK.
Here is the syntax for performing selected operations:
There are a couple of drawbacks to storing an assembly in the GAC. First, it is dif-
ficult to reference during compilation due to the verbose GAC subdirectory naming
conventions. An alternative is to compile referencing a local copy of the assembly.
Then, remove the local assembly after compilation is completed.
Another possible drawback stems from the fact that an assembly cannot be copied
into the GAC. If your application requires an assembly in the GAC, it eliminates
deploying an application by simply copying files to a client’s machine. Deployment
issues are discussed in the last section of this chapter.
Versioning
A major benefit of using strongly named assemblies is that the CLR uses the assem-
bly’s version information to bind assemblies that are dependent on each other. When
such an assembly is loaded, the CLR checks the version number of referenced
assemblies to ensure they have the same version numbers as recorded in the calling
assembly’s manifest. If the version fails to match (usually because a new version has
been created), an exception is thrown.
15.2 Strongly Named Assemblies 691
[assembly: AssemblyVersion("1.0.0.0")]
You can specify all the values or you can accept the default build number, revision
number, or both by using an asterisk (*). For example:
When an asterisk (*) is specified for the build number, a default build number is
calculated by taking the number of days since January 1, 2000. The default revision
number is the number of seconds past midnight divided by two.
You can use reflection to view an assembly’s version along with the other parts of
its identity. To illustrate, add the following attributes to the code in Listing 15-1 to
create a custom version number and strong name:
[assembly: AssemblyKeyFile("Keylb.snk")]
[assembly: AssemblyVersion("1.0.*")]
Compile the code and use the Assembly.GetName method to display the assem-
bly’s identification.
Console.WriteLine(Assembly.GetExecutingAssembly().GetName());
This method returns an instance of the AssemblyName class that contains the
assembly’s simple name, culture, public key or public key token, and version.
15.3 Security
The centerpiece of .NET security is the Code Access Security model. As the name
implies, it is based on code access—not user access. Conceptually, the model is quite
simple. Before an assembly or component within an assembly may access system
resources (files, the registry, event log, and others), the CLR checks to ensure that it
has permission to do so. It does this by collecting evidence about the assembly—
where is it located and its content. Based on this evidence, it grants the assembly cer-
tain permissions to access resources and perform operations. Figure 15-4 illustrates
the key elements in this process and introduces the terms that you must know in
order to administer security.
Code Group
Evidence
Site
Site
evidence Permission
Assembly • Set
• Security
URL Policy
Zone
URL
evidence Permission
Set
Figure 15-4 An assembly is matched with code groups whose evidence it satisfies
When an assembly is loaded, the CLR gathers its evidence and attempts to match
it with code groups whose evidence it satisfies. A code group is a binding between a
set of permissions and a single type of evidence. For example, a code group may be
defined so that only assemblies from a particular application directory are allowed to
have Web access. If an assembly’s site evidence indicates it is from that directory, it is
part of the code group and has Web access. An assembly can be a member of multi-
ple code groups, and, consequently, can have multiple permissions.
.NET provides predefined evidence, permissions, code groups, and security poli-
cies—a collection of code groups. Although code can be used to hook into and mod-
ify some aspects of security, an administrator performs the bulk of security
configuration and management using .NET tools. In most cases, the predefined ele-
ments are all that an administrator needs. However, the security model is flexible,
and permits an administrator to create security policies from custom evidence, per-
missions, and code groups.
15.3 Security 693
Because both Assembly B and Assembly A possess the FileIO permission, the
write operation is permitted. An exception of type System.Security.Security-
Exception is thrown if either assembly does not have the requisite permission.
Permissions are often interrelated: for example, the permission to write to a file
requires an accompanying permission to access the directory containing the file. To
694 Chapter 15 ■ Code Refinement, Security, and Deployment
avoid having to grant and deny all permissions on an individual basis, .NET includes
a PermissionSet class that allows a collection of permissions to be treated as a sin-
gle entity for the purpose of denying or granting permissions.
As we shall see, permission sets can be created and applied programmatically by
creating and adding individual permission objects to a PermissionSet collection.
An alternate approach is to use the .NET Configuration tool to create permission sets
and assign them to code groups. To encourage this approach, .NET includes pre-
defined permission sets.
Figure 15-6 lists the most interesting permission sets along with the individual
permissions they contain. These sets cannot be modified; however, they can be cop-
ied and used as a base set for creating a custom permission set.
15.3 Security 695
[PermissionSet(SecurityAction.Demand,Name="LocalIntranet")]
public string GetTitle() // Requires LocalIntranet
{}
Note that all named permission sets except Everything can be applied as an
attribute.
All permission classes inherit and implement the interfaces shown in Figure 15-7.
Of these, IPermission and IStackWalk are the most useful. IPermission
defines a Demand method that triggers the stack walk mentioned earlier; IStack-
Walk contains methods that permit a program to modify how the stack walk is per-
formed. This proves to be a handy way to ensure that a called component does not
perform an action outside of those that are requested. We’ll look at these interfaces
in more detail in the discussion of programmatic security.
IPermission
CodeAccess
ISecurityEncodable Permission Class
Permission
IUnrestricted
IStackWalk
Permission
Figure 15-7 Interfaces inherited by permission classes
Identity Permissions
Recall that when the CLR loads an assembly, it matches the assembly’s evidence
against that required by code groups and grants permissions from the code groups
whose criteria it meets. These code group derived permissions are either custom per-
missions or built-in permissions as described in Table 15-1.
The CLR also grants another set of permissions that correspond directly to the
identity evidence provided by the assembly. For example, there are ZoneIdentity-
Permission and StrongNamedIdentityPermission classes that demand an
assembly originate from a specific zone or have a specific strong name identity. Table
15-2 lists the origin-based identity classes.
698 Chapter 15 ■ Code Refinement, Security, and Deployment
Unlike the built-in permissions described earlier, these classes cannot be adminis-
tered using configuration tools. Instead, a program creates an instance of an identity
permission class and uses its methods to demand that an assembly provide a specified
identity to perform some action. This programmatic use of permission classes is
referred to as imperative security.
Permission Attributes
All security permission classes have a corresponding attribute class that can be
applied as an attribute to an assembly, class, and method to specify security equiva-
lent to that provided by the permission class. This is referred to as declarative secu-
rity, and serves two useful purposes: When applied at the assembly level, a
permission attribute informs the runtime which permissions the assembly requires,
and enables the runtime to throw an exception if it cannot grant these permissions;
when applied to classes and methods within the code, the attribute specifies which
permissions any calling assemblies must have to use this assembly. Examples using
declarative security are provided later in the chapter.
Evidence
To qualify as a member of a code group and assume its privileges, an assembly must
provide evidence that matches the evidence membership requirements of the code
group. This evidence is based on either the assembly’s origin or its signature. The ori-
gin identification includes Site, Url, and Zone evidence; the signature refers to an
assembly’s strong name, its digitally signed certificate (such as X.509), or a hash of the
assembly’s content. The Common Language Runtime provides seven predefined
types of evidence. They are referred to by names used in the security administrative
tools:
15.3 Security 699
• Strong Name. An assembly with a Strong Name has a public key that
can be used to identify the assembly. A class or method can be config-
ured to accept calls only from an assembly having a specified public
key value. The most common use for this is to identify third-party
components that share the same public key. A Strong Name has two
other properties, Version and Name, that also can be required as evi-
dence by a host assembly.
• Publisher. This evidence indicates that an assembly has been digi-
tally signed with a certificate such as X.509. Certificates are provided
by a trusted certificate authority and are most commonly used for
secure Internet transactions. When a signed assembly is loaded, the
CLR recognizes the certificate and adds a Publisher object to the
assembly.
• Hash. By applying a computational algorithm to an assembly, a unique
identifier known as a hash is created. This hash evidence is automati-
cally added to each assembly and serves to identify particular builds of
the assembly. Any change in the compiled code yields a different hash
value—even if the version is unchanged.
• Application Directory. This evidence is used to grant a permis-
sion set to all assemblies that are located in a specified directory or in a
subdirectory of the running application.
• Site. Site evidence is the top-level portion of a URL that excludes
the format and any subdirectory identifiers. For example,
www.corecsharp.net is extracted as site evidence from
http://www.corecsharp.net/code.
• URL. This evidence consists of the entire URL identifying where an
assembly comes from. In the preceding example,
http://www.corecsharp.net/code is provided as URL evidence.
• Zone. The System.Security.SecurityZone enumeration defines
five security zones: MyComputer, Intranet, Internet, Trusted,
and Untrusted. An assembly’s zone evidence is the zone from which
it comes.
— MyComputer. Code coming from the local machine.
— Intranet. Code coming from computers on the same local area
network.
— Internet. Code coming from the Internet that is identified by an
HTTP or IP address. If the local machine is identified as
http://localhost/, it is part of the Internet zone.
— Trusted. Identifies Internet sites that are trusted. These sites are
specified using Microsoft Internet Explorer (IE).
— Untrusted. Sites specified in IE as being malicious or untrust-
worthy.
700 Chapter 15 ■ Code Refinement, Security, and Deployment
In addition to these, there is also a blank evidence known as All Code evidence
that is used by an administrator to create a code group that matches all assemblies.
The CLR maintains evidence in an instance of the Evidence collection class. This
object contains two evidence collections: one for the built-in host evidence and
another for user-defined evidence. This evidence is made available to security policy,
which then determines the permissions available to the assembly. You can use reflec-
tion to view evidence programmatically. In this example, we view the evidence for an
assembly, movieclient.exe, which is located on the local machine (the assembly’s
source code is presented later in this section):
using System;
using System.Reflection;
using System.Security.Policy;
class ClassEvidence
{
public static void Main()
{
Assembly ClientAssembly;
// (1)Load object to reference movieclient assembly
ClientAssembly = Assembly.Load("movieclient");
// (2) Evidence is available through Evidence property
Evidence ev = ClientAssembly.Evidence;
// (3) Display each evidence object
foreach (object ob in ev)
{
Console.WriteLine(ob.ToString());
}
}
}
Output from the program reveals the Zone, Url, Strong Name, and Hash evi-
dence associated with the assembly. No Site evidence is present because the assem-
bly’s Url origin is defined by a file:// rather than an http:// format.
Application Directory evidence is also missing because it comes from the host
application, not the assembly’s metadata.
<System.Security.Policy.Zone version="1">
<Zone>MyComputer</Zone>
</System.Security.Policy.Zone>
<System.Security.Policy.Url version="1">
<Url>file://C:/movieclient.EXE</Url>
</System.Security.Policy.Url>
<StrongName version="1"
15.3 Security 701
Key="002400... 8D2"
Name="movieclient"
Version="0.0.0.0"/>
<System.Security.Policy.Hash version="1">
<RawData>4D5A90000300000004000000FFFF0000B80
</RawData>
</System.Security.Policy.Hash>
Security Policies
A .NET security policy defines how assembly evidence is evaluated to determine the
permissions that are granted to the assembly. .NET recognizes four policy levels:
Enterprise, Machine, User, and Application Domain. The policy-level names
describe their recommended usage. Enterprise is intended to define security policy
across all machines in the enterprise; Machine defines security for a single machine;
User defines security policy for individual users; and Application Domain security
is applied to code running in a specific AppDomain. Enterprise, Machine, and User
policies are configured by an administrator. AppDomain policy, which is implemented
only programmatically and used for special cases, is not discussed.
Despite their names, policies can be configured in any way an administrator
chooses. The User policy could be set up to define enterprise security and the
Machine policy to define user security. However, an administrator should take
advantage of the names and use them to apply security to their intended target. As
you will see in the discussion of the .NET Framework Configuration Tool (see “The
.NET Framework Configuration Tool” on page 704), the security policy is granular
enough to allow custom security policies on individual machines and users.
Security Policies
Enterprise
P1 P2 P3 P4
P2 P3 P5 P6
Evidence P2 P3 P5 P6
Machine Permission
Assembly Set
P2 P5
P2 P5
P2 P5
User
P1 P2 P5 P6
P1 P2 P5 P6
Figure 15-8 A permission set is created from the intersection of policy level permissions
<Windows Directory>\Microsoft.NET\Framework\<Version>\config\
The User policy file is named security.config and is located on the path
Listing 15-2 contains an extract from the Machine policy file that illustrates the
file layout. It comprises four major sections:
3. Not to be confused with the machine.config file that holds machine-wide configuration
data.
15.3 Security 703
The code group section is structured as a multi-level hierarchy, with the condi-
tions for granting permissions becoming more restrictive at each lower level.
<SecurityClasses>
<SecurityClass Name="WebPermission"
Description="System.Net.WebPermission, System,
Version=2.0.3600.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"/>
<SecurityClass Name="EventLogPermission"
Description="System.Diagnostics.EventLogPermission,
System, Version=2.0.3600.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"/>
...
</SecurityClasses>
<NamedPermissionSets>
<PermissionSet class="NamedPermissionSet"
version="1"
Name="LocalIntranet"
Description="Default rights given to applications on
the local intranet">
<IPermission class="EnvironmentPermission"
version="1" Read="USERNAME"/>
<IPermission class="FileDialogPermission"
version="1" Unrestricted="true"/>
...
</PermissionSet>
...
</NamedPermissionSets>
<CodeGroup class="UnionCodeGroup"
version="1"
PermissionSetName="Nothing"
Name="All_Code"
Description="Code group grants no ...">
<IMembershipCondition class="AllMembershipCondition"
version="1"/>
<CodeGroup class="UnionCodeGroup"
version="1"
PermissionSetName="FullTrust"
Name="My_Computer_Zone"
Description="Code group grants ...">
...
</CodeGroup>
704 Chapter 15 ■ Code Refinement, Security, and Deployment
.NET provides two ways to work with these policy files: a command-line Code
Access Security Policy (caspol) and a graphical Configuration tool
(MSCorCfg.msc). Both can be used to modify the default configuration, create cus-
tom code groups, create custom permission sets, and export policies to be deployed
with an application. We’ll look at both tools in this section, with an emphasis on the
Configuration tool because its visual interface makes it more popular and easier to
learn than the command-line approach.
Microsoft_Strong_Name
ECMA_Strong_Name
Intranet_Same_Site_Access
Intranet_Same_Directory_Access
Internet_Same_Site_Access
Trusted_Same_Site_Access
Intranet_Same_Site_Access
Intranet_Same_Directory_Access
“Code group grants all code full trust and forms the root of the code group tree.”
Specifically, this code group binds All Code evidence with the FullTrust per-
mission set. Recall that all assemblies qualify as members of code groups that use
All Code evidence and that the FullTrust permission set offers unrestricted per-
missions. The net effect of binding these two is to create Enterprise and User pol-
icies that offer unlimited permissions to all assemblies. In other words, these two
policies offer no security at all by default.
Core Note
The code groups provided by .NET are named after the evidence they
represent. Because no two code groups may have the same name,
custom code groups must use a modified naming convention.
706 Chapter 15 ■ Code Refinement, Security, and Deployment
The Machine security policy is far more interesting and instructive. At its root is
the All_Code code group. Unlike the other two policies, it binds All Code evi-
dence to the Nothing permission set, which means the code group grants no permis-
sions. To find the permissions, you must look to the code groups nested beneath this
root. The first level contains six groups: My_Computer_Zone, LocalIntranet_
Zone, Internet_Zone, Restricted_Zone, Trusted_Zone, and Application_
Security_Manager. All except Restricted_Zone have one or more child code
groups. Let’s look at how default permissions are granted for two of these:
My_Computer_Zone and LocalIntranet_Zone. To view details about the other
code groups, simply right-click their name to bring up a properties window.
group that maps the new permission set to Url evidence that specifies a directory on
the local machine. The effect is to grant the Reflection permission to any assembly
that runs from this directory.
For testing, we’ll create a simple application that uses reflection to access a private
field in this assembly’s class:
Listing 15-3 contains the code that accesses the private Director field. This is
done by calling the Type.GetField method and passing as arguments the field
name and flags that request access to a private field in a class instance.
You now have a permission set that allows an assembly the rights associated with
the Reflection permission.
6. Click the new code group in the left pane and select Edit Code Group
Properties. Check the option This Policy Level Will Only Have the
Permissions from the Permission Set Associated with the Code
Group.
The final step is necessary to make this example work. Setting this Exclusive option
tells .NET to assign only the permissions of the new code group to any code found in
the specified directory path. If this option is not set, the code is evaluated against all
Machine level code groups and receives permissions from all whose evidence it
matches. Also, note that code not located in the specified subdirectory is unaffected
by the new code group and receives the default Machine policy permissions.
Caspol is run from the command line. Among its numerous options is –rsp,
which resolves permissions for an assembly. To determine permissions for the con-
figtest assembly, enter this command:
This output shows that a code group uses Url evidence to grant its permission set
(Reflection) exclusively to any assembly in the specified directory. Because our
assembly is in that directory, the Security and Reflection permissions must
come from this code group:
Level = Enterprise
Code Groups:
1. All code: FullTrust
Level = Machine
Code Groups:
1. All code: Nothing
1.1. Zone - MyComputer: FullTrust
1.6. Url - file://C:/cas/*: Reflection (Exclusive)
Level = User
Code Groups:
1. All code: FullTrust
Note that you can view the contents of the Reflection permission set by executing
C:\>caspol –m -lp
The serialized XML output lists the permissions associated with all permission
sets at the machine policy level—including the Reflection permission set.
15.3 Security 711
C:\>permcalc c:\cas\configtest.exe
Although the term request is broadly used to describe the use of permission
attributes, it’s better described as a way for an assembly to publish (in metadata) its
permission requirements. The effectiveness of including permission attributes in
code is governed by three rules:
Despite the fact that you do not have to include permission requests in your code,
there are important reasons for doing so:
[assembly : PermissionAttribute(
SecurityAction.membername,
PermissionAttribute property)
]
The final argument to the constructor sets a property of the permission attribute
class to a value that describes the specific permission requested. The following exam-
ple should clarify this.
Compile and run the code from the C:\ root directory. Because applications run
on the local machine have unrestricted permissions, the program runs successfully.
Let’s see what happens if the second parameter does not specify a permission that
enables access to a private field. To test this, change the enumeration value to
ReflectionEmit—an arbitrary property that only permits the assembly to emit
metacode. When run with this parameter, the assembly again succeeds because it
continues to receive unrestricted permissions on the local machine. As a final test,
change the first parameter to SecurityAction.RequestOptional. This causes
the assembly to fail, because only the requested ReflectionEmit permission is
granted to it. Table 15-3 summarizes how content of the permission attribute affects
the assembly’s operation.
Programmatic Security
The .NET Configuration tool provides a broad stroke approach to defining security
for a computing environment. In most cases, this is satisfactory. If two assemblies
come from the same security zone, it usually makes sense to grant them the same
permissions. However, suppose you are developing components for clients with an
unknown security policy, or you are using third-party components whose trustworthi-
ness is unknown. In both cases, programmatic security offers a way to enforce secu-
rity specifically for these components.
Programmatic security is implemented by using .NET permission classes to con-
trol and enforce security on a calling assembly or a called assembly. This is an impor-
tant point: The calling assembly can override permissions granted by the CAS and
prevent the assembly from accessing a resource; conversely, a called assembly can
refuse to perform an operation unless the calling assembly—or assemblies—has per-
missions it requires. The key to implementing programmatic security is a mechanism
known as a stack walk.
Stack Walk
As mentioned earlier in the chapter, a stack walk refers to steps the CLR follows to ver-
ify that all methods in a call stack have permission to perform an operation or access a
system resource. This ensures that an immediate client with the proper permissions is
not called by malicious code further up the stack that does not have permission.
As shown in Figure 15-10, a stack walk is triggered when code invokes a permis-
sion’s Demand method. This method is inherited from the IPermission interface
(refer to Figure 15-7), which includes other methods such as Intersect and Union
that allow permission objects to be logically combined.
Call Stack
Assembly
A
Assembly p.Demand
B
Assembly Write()
C
SqlClientPermission sqlPerm=
new SqlClientPermission(PermissionState.Unrestricted);
sqlPerm.Demand(); // Trigger stack walk
The code simply creates an instance of the desired permission class and calls its
Demand method. There is no limit on the number of permission objects that can be
created and used to demand a stack walk. However, it is an expensive process and
should be used judiciously.
This code can be tested from the command line. Compile both the component
and client. Then, run the client by passing it the movie name as a parameter:
[assembly : SqlClientPermission(SecurityAction.RequestRefuse)]
using System.Security;
using System.Data.SqlClient;
using System.Security.Permissions;
Then, the code is changed to create an IStackWalk object that is set to the per-
mission being checked by the stack walk—in this case, SqlClientPermission:
myMovie= args[0];
//
IStackWalk stackWalker;
stackWalker = new
SqlClientPermission(PermissionState.Unrestricted);
stackWalker.Deny(); // Deny use of SqlClient by GetMovie
// Call component to fetch movie data
try{
MovieProfile mp = MovieData.GetMovie(myMovie);
720 Chapter 15 ■ Code Refinement, Security, and Deployment
In our first example, we call the permission’s Deny() method that causes the stack
walk’s verification of the SqlClient permission to fail. Filmcomponent cannot
access the database and throws an exception.
Now, replace the call to Deny with a call to Assert. Assert prevents the stack
walk from continuing further up the code stack—unnecessary in this case because
there is no other object on the stack. The stack walk succeeds because movieclient
has the required permission, and database access is permitted.
Core Note
On the surface, the use of Assert seems to undermine the reason for a
stack walk—to ensure that all objects on the call stack have a required
permission. To make sure the method is not used to hide potentially
malicious code on the call chain, .NET requires that the asserting code
have a special security permission before it can assert; then, as added
protection, it triggers a stack walk when code makes an assertion that
verifies that all code above it has the asserted permission.
The final way to modify the stack walk is by using the PermitOnly method to
indicate specific permissions that the code is willing to let the called assembly use. In
the current example, the calling assembly has unrestricted permissions; the compo-
nent filmcomponent has need for only the SqlClient permission. By using Per-
mitOnly to specify this permission, movieclient thwarts any stack walks that
attempt to verify other permissions.
An assembly may be required to call multiple components, each with its own per-
mission requirements. You may need to permit SQL access for one and file I/O for
another. If you call PermitOnly a second time to override the first call, an exception
is thrown. Instead, you must clear the effects of the first call by calling CodeAc-
cessPermission’s static RevertPeritOnly method; then, you call PermitOnly
with a new permission. The following segment could be added to our code to remove
SqlClient as the only allowed permission, and replace it with the Reflection per-
mission.
// Imperative Security
SqlClientPermission sqlPerm= new
SqlClientPermission(PermissionState.Unrestricted);
sqlPerm.Demand();
// Declarative Security
[SqlClientPermission(SecurityAction.Demand)]
public static MovieProfile GetMovie(string movietitle){
The syntax for the attribute constructor is straightforward. It consists of the per-
mission attribute class name with a parameter specifying a SecurityAction enu-
meration member. Depending on the permission type, there may also be a second
parameter that specifies a property value for the permission class.
The most interesting feature of the attribute declaration is its SecurityAction
parameter that specifies the action caused by the attribute. Its three most important
members and their uses are the following:
The final two enumeration members result in checks that occur during loading
and have no comparable statements that can be executed at runtime. However, the
use of demand security to trigger a stack walk is common to both and leaves the
developer with the decision of which to use. In where the component knows which
permissions it needs at compile time, declarative security is recommended. Its syntax
is simpler, and it provides a form of self-documentation. Also, declarative security
information is placed in an assembly’s manifest where it is available to the CLR dur-
ing loading. As a rule, the sooner the CLR has information about code, the more effi-
ciently it can operate.
Imperative security is recommended when variables that affect security are
unknown at compile time. It’s also easier to use when more granular security is
required: attributes usually apply to a class or method, whereas demand statements
can be placed anywhere in code.
The installer is an installation service that processes files having a special format
(.msi) and performs install operations based on their content. An .msi file is
referred to as a Windows Installer Package, and an install can contain more than one
of these files.
The easiest way to create an MSI file for your application is using Visual Studio
.NET. It has a wizard that steps you through the process. If your application requires
special Code Access Security policies, use the Configuration tool to create an install
package. To do so, right-click the Runtime Security Policy node and select Create a
Deployment Package. The ensuing wizard steps you through the process. You can
also create a custom installer by creating a class that inherits from the System.Con-
figuration.Install.Installer class and overrides its Install and Unin-
stall methods.
Core Note
.NET 2.0 and Visual Studio 2005 introduce a ClickOnce technology that
is designed to simplify the installation and update of Windows
applications. The idea is to create the application and use VS.NET to
“publish” it to a File or Web Server. The location is provided as a URL to
users who link to a Web page that provides install instructions. Included
with the application is a manifest that describes the assembly and files
that comprise the application. This information is used to determine
whether an update is available for the application. The update process
can be set up to automatically check for updates each time the
application is run, or only run when requested.
ClickOnce works for straightforward installations that do not need to
access the registry or install assemblies in the GAC. It is also targeted for
the Microsoft Windows environment, as it contains shortcuts and an
uninstall feature used by Windows. Although VS.NET is the easiest way
to publish an application for installation, it can be done manually.
instead, a special tool is required to install or “register” it. During code development,
the GACUtil.exe utility is used for this purpose. However, this tool is not included
with the end-user version of the .NET Framework redistributable package, so there
is no assurance that it will exist on the user’s machine. Instead, use the Windows
Installer.
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="\graphics;\data" />
</assemblyBinding>
</runtime>
</configuration>
By default, the specified search paths are relative to the application’s base direc-
tory. You can also specify an absolute path, but the path must specify a folder below
the application’s base directory.
15.4 Application Deployment Considerations 725
The application configuration file in this example is rather simple, and you may
choose to build it manually. However, because the file often contains other configu-
ration elements, manual manipulation can quickly become an unwieldy task. As an
alternative, you can use the same Configuration tool described in the CAS discus-
sion. It has an Applications folder that can be opened and will lead you through
the steps to create a configuration file for a selected assembly. One of its more useful
and instructive features is an option to view a list of other assemblies that the
selected assembly depends on.
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="movieclass"
publicKeyToken="1F081C4BA0EEB6DB"
culture="neutral" />
<codeBase version="1.0.0.0"
href="http://localhost/scp/movieclass.dll" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
<codeBase version="1.0.0.0"
href="file:///e:\movieclass.dll" />
726 Chapter 15 ■ Code Refinement, Security, and Deployment
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="movieclass"
publicKeyToken="1F081C4BA0EEB6DB"
culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0"
newVersion="2.0.0.0"/>
<codeBase version="2.0.0.0"
href="http://localhost/scp/movieclass.dll" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
using System;
using System.Reflection;
[assembly:AssemblyVersion("2.0.0.0")]
[assembly:AssemblyCompany("Acme Software")]
[assembly:AssemblyCopyright("Copyright (c) 2005 Stephen Perry")]
[assembly:AssemblyTrademark("")]
// Set the version ProductName & ProductVersion fields
[assembly:AssemblyProduct("Core C# Examples")]
[assembly:AssemblyInformationalVersion("2.0.0.0")]
[assembly:AssemblyTitle("Core C# movieclass.dll")]
[assembly:AssemblyDescription("This is a sample C# class")]
//
public class Movies
{
// ... Remainder of code
15.5 Summary
There is more to developing an application or component than simply implementing
code to handle a specific task or set of tasks. Ancillary issues such as code correct-
ness, adherence to code standards, code efficiency, security, and code deployment
must be addressed. This chapter has presented an overview of tools and approaches
available to handle these issues in .NET.
A useful tool for determining how your code meets .NET coding standards is
FxCop. It’s included with the .NET SDK and evaluates an assembly against a set of
rules that define good coding practice. You can add custom rules and disable rules
that don’t apply.
To secure an application, .NET employs a concept known as Code Access Secu-
rity. Unlike role- or user-based security—which .NET also supports—CAS restricts
what resources code can access. When an assembly loads, .NET gathers information
about its identity known as evidence. It evaluates the evidence against security poli-
cies and maps it into a set of permissions that are granted to the assembly. A security
administrator typically configures the security policy using the .NET Configuration
tool, although a command-line utility is also available. Security can also be imple-
mented in code. Permissions are nothing more than classes, and can be used by
developers to request permissions for an assembly and demand that calling assem-
blies have the necessary permissions to access a secure resource.
The final step in developing an application is settling on a deployment strategy. In
.NET, this can be as easy as creating an install based on copying files to a machine
(XCOPY deployment); alternatively, MSI files can be created for use with the
Microsoft Windows Installer. One of the key install decisions is how to deploy assem-
blies. Private assemblies are placed together in a common directory path; shared
assemblies are stored in the Global Assembly Cache. In addition, a configuration file
can be set up to instruct the runtime to search for assemblies in directories on the
local computer or a remote Web server located across a network.
2. You are developing a component for a class library and want to follow
good coding practices. Evaluate the assembly with FxCop. The output
includes the following message. What is missing from your code?
TypeName= "AssembliesShouldDeclareMinimumSecurity"
5. What three methods are used to modify a stack walk? Describe the
role of each.
■ Chapter 16
ASP.NET Web Forms and Controls 732
■ Chapter 17
The ASP.NET Application Environment 806
■ Chapter 18
XML Web Services 868
ASP.NET WEB FORMS
AND CONTROLS
Developing applications to run on the Internet is a broad topic, and this book devotes
its last three chapters to the subject. This chapter introduces key features of
ASP.NET and focuses on using controls to create Web pages; Chapter 17, “The
ASP.NET Application Environment,” looks at application development issues such as
managing sessions and configuring ASP.NET control files; and the book’s final chap-
ter discusses Web Services.
ASP.NET is technically regarded as the next generation of ASP. There are syntac-
tic similarities and compatibilities, but the differences are even greater. Thus, this
chapter makes no attempt to explain ASP.NET in terms of ASP. There are some com-
parisons, but no prior knowledge of ASP is assumed. You will also find traces of Java-
Script sprinkled in a couple of applications, but the code is easily understood within
the context of the examples.
The first section provides a tour of client-server Web interaction. It begins with a
simple JavaScript application that demonstrates the fundamental techniques used to
transfer information between a client and Web server. It then shows how the
ASP.NET model encapsulates these principles and adds an object-oriented approach
to Web page design and implementation. Subsequent sections survey the array of
Web presentation and validation controls. Special attention is given to the DataList
and GridView controls.
One note: IIS (Microsoft Internet Information Server) is conspicuous by its
absence in our discussion. Although all of the applications were tested in an IIS envi-
ronment, and the preponderance of ASP.NET applications will run on this Web
server, they are not bound to it. Microsoft has created an open-source HTTP server
named Cassini that is written in C#. It’s fully HTTP/1.1 compliant, supports directory
browsing, as well as many of the standard MIME types, and most importantly, sup-
ports ASP.NET. It has been tested on Apache servers and is clearly geared toward
making ASP.NET the Web development tool of choice for multiple Web platforms.
733
734 Chapter 16 ■ ASP.NET Web Forms and Controls
Client
POST process.htm
Filled
Form 2
3
Receive survey.htm
Empty
Form
Realistically, the calculation could be done using JavaScript on the client’s page
without requiring a trip to the server. But let’s suppose that we also want to record
each time the calculator is used. For that, a trip to the server is necessary.
<HTML>
<HEAD><TITLE>BMI Calculator</TITLE>
<SCRIPT LANGUAGE="Javascript" TYPE="text/javascript">
<!—
// hrefstr is set to querystring values—if any
var hrefstr= location.search.substring(1,
location.search.length);
function showbmi(){
if (hrefstr ) // display values in form fields
{
var parms = hrefstr.split('&');
var f = self.document.forms[0];
f.bmi.value = eval(parms[0]);
f.hti.value = eval(parms[2]);
f.wt.value = eval(parms[3]);
f.htf.value = eval(parms[1]);
}
}
// -->Code for Verify goes here.
// Post Form to Web Host
function post() {
if (verify()) //Call function to verify values in form fields
16.1 Client-Server Interaction over the Internet 737
{
var f = self.document.forms[0];
f.bmi.value=0;
f.submit(); // Use HTTP GET to send form to server
}
}
//-->
</script>
</HEAD>
<BODY bgcolor=#ffffff>
<FORM NAME="bmi_input" method=GET action=bmicalculator.htm>
<table border=0 cellpadding=2 cellspacing=0 width=180
bgcolor=#cccccc>
<tr><td colspan=3 align=center><font size=2 color=#33333>
<b>BMI Calculator</b> </td></tr>
<tr><td><font size=2 ><b>BMI:</b></td>
<td colspan=2 ><input type=text size=5 name=bmi>
</td></tr>
<tr><td colspan=3><hr size=1></td></tr>
<tr><td><font size=2 >Height:</td>
<td><input type=text size=3 name=htf maxlength=1></td>
<td><input type=text size=3 name=hti maxlength=2></td>
</tr>
<tr><td> </td><td valign=top><font size=2>feet</td>
<td valign=top><font size=2>inches</td>
</tr>
<tr><td><font size=2 >Weight:</td>
<td colspan=2><input type=text size=3 name=wt
maxlength=3></td>
</tr>
<tr><td colspan=3 align=center>
<INPUT TYPE="button" VALUE="Submit Form" ONCLICK=
"self.post()";>
</td></tr>
<tr><td colspan=3> </td></tr>
</table>
</FORM>
<SCRIPT LANGUAGE="Javascript" TYPE="text/javascript">
<!--
showbmi(); // Fills form with values if they exist
//-->
</script>
</body>
</html>
738 Chapter 16 ■ ASP.NET Web Forms and Controls
Using the GET method causes the form’s data to be sent to the server as part of
the URL. This string of data, referred to as a query string, contains name-value pairs
that correspond to the name of the fields on the form, followed by their value. Figure
16-3 shows how form data is passed to the Web page bmicalculator.htm that cal-
culates the BMI.
//http://localhost/ideas/bmicalculator.htm?bmi=0&htf=6&hti=1&wt=168
To Server
//http://localhost/ideas/bmi.htm?bmi=22.2&htf=6&hti=1&wt=168
To Client
Data is returned to the client in the same manner—by appending it to the URL of
the page containing the original form. Note that bmi is now set to the calculated
value. Here is the server-side code that creates this response:
<html><head></head>
<body >
<script language="javascript">
<!—
// Use location.search to access the query string
var hrefstr = location.search.substring(1,
location.search.length);
var parms = hrefstr.split('&');
feet = parms[1];
inch = parms[2];
pounds = parms[3];
totinches = eval(feet)*12 + eval(inch);
// ..... Calculate BMI
var h2 = totinches * totinches;
bmi = Math.round(eval(pounds) * 703 * 10/ h2)/10;
// --> Place code here to maintain count of visits.
//... Return value and original parameters as a query string
ndx = hrefstr.indexOf('htf');
self.location = 'bmi.htm?bmi=
'+bmi+"&"+hrefstr.substring(ndx);
//-->
</script>
</body></html>
16.1 Client-Server Interaction over the Internet 739
This code grabs the values from the query string using location.search, parses
them, calculates the BMI, and then creates a URL for the page to be returned. The
final step is for the client browser to display the calculated BMI value.
At the bottom of the code in Listing 16-1 is a call to the JavaScript function
showbmi(). This function operates much like the preceding server code. It extracts
the data from the query string and displays it in the appropriate fields on the form.
Note that the function first confirms that the query string is not empty, as will be
the case the first time the form is loaded.
The use of a query string is popular for applications that transfer small amounts of
data. However, it becomes a problem with large forms because the query string is
limited by the 2K maximum string length imposed by some browsers (IE). In addi-
tion, it raises obvious security concerns by exposing data in a URL line that can be
altered by the user. Query string encryption can mitigate this problem and should be
considered where it makes sense. A more robust solution is to use the HTTP POST
method in place of GET.
The code for the ASP server file is quite simple. It accesses the data passed from
the client by referencing a request object. The data is passed to a VBScript function
that calculates the BMI. A URL is then constructed that contains the query string
with the BMI value and other parameters. The response.redirect method sends
the form to the client’s browser.
This solution illustrates the fundamental ASP approach of using VBScript to inter-
act with HTTP response and request objects. In addition, HTML and JavaScript can
be intermixed with the ASP code to create Web Forms. Although this offers a degree
of flexibility, it often results in a babel of code and inconsistent coding techniques.
page (.aspx file) results in the page being compiled into a .NET class.
Further requests are then handled by the assembly created by compi-
lation. For the user, it means faster Web access; for the developer, it
means applications are developed using the same .NET tools available
for desktop and component applications.
• Inline code. The HTML markup code and application code (C#)
coexist in a single .aspx file.
• Code-behind. The markup code and application code are placed in
separate files. The markup is in an .aspx file and the logic code
resides in a .cs or dll file.
• Partial classes. This is a variation of the code-behind model that
places the markup and code in separate files. The difference is that
the code-behind file is implemented using partial classes. It is stored
as a .cs file and is compiled along with the markup file. This model is
available only with ASP.NET versions 2.0 and later.
None of the models offers a performance advantage over the others. This leaves
the choice of model up to one’s preference—or need—for code separation. Let’s now
examine the models by using each to implement the BMI application.
To understand the differences between standard HTML and ASP.NET code, let’s
compare how controls are specified in Listing 16-1 versus Listing 16-2.
ASP.NET also adds three hidden fields to the HTML page it returns to the
browser:
The first field, __EVENTTARGET, specifies the control that invoked the request
(known as a postback) to the server; the second, __EVENTARGUMENT, contains any
parameters required by the event. The __VIEWSTATE field is by far the most
interesting.
View State
View state is the feature in ASP.NET that automatically maintains the state of
server-side controls (controls declared with runat=server) as a form makes the
round trip between the client and the server. In other words, it allows a page to place
data in a control such as a ListBox or GridView one time—usually when the page is
first loaded—and ensures the data is retained as subsequent postbacks occur. Here is
how it works.
When a page is posted back to the server, the data in the hidden __VIEWSTATE
field is deserialized and used to set the state of controls and the overall Web page.
Data received in the HTTP request as part of a POST operation is used to set the val-
ues of those related controls (note that for controls whose contents are posted—
TextBox, CheckBox, RadioButtons—the posted data overwrites the view state
data). The __VIEWSTATE field is then updated before it is passed back to the client.
The returned view state value plays no role on the client other than to represent a
snapshot of control values at the time the page is received by the browser.
Because the view state string can be viewed in a source listing, questions about
security become a legitimate issue. However, unlike the query string, the value is not
represented as clear text.
value="dDwxMzc5NjU4NTAwOzs+iIczTTLHA74jT/02tIwU9FRx5uc="
16.1 Client-Server Interaction over the Internet 745
Core Note
Maintaining view state data within a Web page makes the page
independent of the server. This means that a Web page request can be
sent to any server in a Web farm—rather than restricting it to a single
server.
Performance is another issue that must be considered when working with the view
state value. By default, it maintains data for all server-side controls on the form. The
control information is not limited to only the data value associated with the control.
For example, when a DataGrid is used, the view state includes not only the data in
each cell, but also column and row headers, and related style attributes. The view
state data can easily add several thousand bytes to a Web page and slow performance.
To improve performance, you may want to disable view state for the Web page
and apply it only to selected controls. Set the EnableViewState attribute in the
@Page directive to disable it at the page level:
Then, to enable view state for an individual control, apply the attribute as shown
in the following code:
Of course, you can also take the opposite tact and leave view state on for the page
and turn it off for selective controls.
The decision to enable or disable view state is one of the key decisions in design-
ing a Web page that displays large amounts of data in a server-side control. The easi-
est approach is to allow ViewState to take care of the details. However, this can
result in a large HTML payload being transferred repeatedly between browser and
server. An alternative is to design the code to reload the data into the controls on
each postback. The data may be fetched from the original source—usually a data-
base—or it may be stored on the server in session state or a cache. These last two
options are described in Chapter 17, “The ASP.NET Application Environment.”
746 Chapter 16 ■ ASP.NET Web Forms and Controls
Core Note
ViewState maintains not only a control’s data but also its state. For
example, it keeps track of the last item(s) selected in a ListBox and
permits the ListBox to be redisplayed in its most recent state. The
drawback of manually repopulating a control, rather than using
ViewState, is that this state information is lost.
Attribute/Value Description
EnableSessionState = value Specifies the type of access the page has to the
session state information. Sessions are discussed in
Chapter 17.
EnableViewState = bool Enables or disables view state for the page. Individual
controls can override this value.
EnableViewStateMac = bool Is used to make the view state more secure by adding
a validator hash string to the view state string that
enables the page to detect any possible attempt at
corrupting original data.
ErrorPage = url Specifies the URL of a Web page that is called when
an unhandled exception occurs.
16.1 Client-Server Interaction over the Internet 747
Attribute/Value Description
Culture = string A culture setting for the page based on the Culture-
Info class. This attribute affects how culture-depen-
dent functions, such as numbers and dates, are
displayed. The following setting causes a DateTime
object to be displayed in a European format using
German months and days: Culture="de-DE".
These settings can also be set in the Web.config file
as described in the next chapter.
Trace = bool Turns tracing on or off for the page. Default is false.
When tracing is on, diagnostic information about a
single request for an .aspx page is collected. The
results of the trace are available programmatically and
are appended as a series of tables at the bottom of the
browser output. Tracing is discussed in Chapter 17.
Inherits = class name Specifies the base class used to generate a class from
the .aspx file. The default is System.Web.UI.Page.
If code-behind is used, the class name from this code
is used.
MasterPageFile = master page Specifies the “master page” from which the current
page visually inherits its layout. Introduced with 2.0.
theme = theme name Specifies the subdirectory containing the .skin file
(specifies the appearance of controls) and any other
images and style sheets that define the look and style
(theme) of a page. The theme file is stored in the
/app_themes subdirectory. Introduced with 2.0.
The Codebehind, Codefile, and Src attributes specify the assembly or source
file containing the business logic code for the page. Instead of placing code between
<script></script> tags as we did in Listing 16-2, the code is placed in a separate
code-behind file that is referenced by these attributes. Before discussing
code-behind, let’s look at some additional directives that are frequently used in
.aspx pages.
Other Directives
@Import Directive
This directive is used to import a namespace in an .aspx page. It serves the same
purpose as the C# using statement.
• System, System.IO
• System.Web.UI, System.Web.UI.HtmlControls,
System.Web.UI.WebControls
• System.Web, System.Web.SessionState, System.Web.Caching
• System.Text, System.Text.RegularExpressions
@Assembly Directive
This directive links an assembly to the current page while the page is being compiled.
This provides the page with access to all types in the assembly. It takes two forms:
The first version references an assembly that may be private or deployed in the
Global Assembly Cache; the second statement causes the source to be dynamically
compiled into an assembly that is linked to the Web page. Note that assemblies in the
application’s \bin subdirectory are automatically linked to a page and do not need
to be referenced.
@Register Directive
This directive associates alias names with namespaces and classes. Its purpose is to
provide a convenient syntax for adding custom controls to a Web page. The directive
takes two forms:
16.1 Client-Server Interaction over the Internet 749
Syntax:
Attributes:
The first form of the directive is used to add an ASP.NET server control to a page;
the second form is used with a custom control contained in a source file. In the latter
case, the TagPrefix and TagName are always used together as a colon-separated
pair. Here is a code segment that places a user control defined in the file hdr.ascx
on a Web page. The @Register directive defines the alias pair that is used to declare
the control on the Web page.
We’ll make use of this directive in Section 16.4, which provides examples of how
to create and use custom controls. Note that @Register directive information also
can be stored in the Web.config file (see Chapter 17), eliminating the need to place
it in a Web page.
Page Request
Web Server Compile
Client
Software .aspx Page
HTML
.aspx Load
Page Compiled
Class
Render HTML
Let’s now look at how the code in Listing 16-2 can be changed to use a
code-behind file. We create a code-behind file named bmicb.cs (see Listing 16-3)
to replace the code currently between the <script/> tags. The @Page directive
links this file to the .aspx file:
The code-behind page is always structured as a class whose name must be speci-
fied by an Inherits attribute. This class is shown in Listing 16-3. Let’s take a close
look at it, because knowledge of how the code-behind file and the .aspx file interact
on the server is essential to understanding the ASP.NET Web page model.
using System;
using System.Web.UI.HtmlControls;
public class BMI : System.Web.UI.Page
{
// <input type=text id=htf runat=server>
protected HtmlInputText htf;
// <input type=text id=hti runat=server>
protected HtmlInputText hti;
// <input type=text id=wt runat=server>
protected HtmlInputText wt;
16.1 Client-Server Interaction over the Internet 751
The first thing to observe from this listing is that it consists of one class—BMI—
that derives from the System.Web.UI.Page class. The Page class is to Web Forms
what the Form class is to Windows Forms. Like the Form, it has a sequence of events
that are fired when it is initialized and loaded. It also has several properties that con-
trol its behavior—many of which correspond to the @Page directive attributes
already discussed. We’ll look at the Page class later in this section.
One of the trickiest aspects of learning ASP.NET is grasping how server-side con-
trols work—specifically, how the content and action of controls displayed on a
browser are managed on the server. Figure 16-5 illustrates the relationship. Each
server control is declared as a field in the BMI class. When the values of the controls
are posted to the server, they are assigned as field values. In this example, all of the
controls are HTML controls—that is, standard HTML controls with the
runat=server attribute.
The id value in the tag must match the field name identically. The field types are
defined in the System.Web.UI.HtmlControls namespace. Each HTML control
has a one-to-one mapping with an HTML tag, as shown in Table 16-2.
752 Chapter 16 ■ ASP.NET Web Forms and Controls
bmi.aspx bmicb.cs
HtmlAnchor <a>
HtmlSelect <select>
HtmlTextArea <textarea>
HtmlForm <form>
HtmlImage <img>
HtmlTable <table>
HtmlTableRow <tr>
HtmlTableCell <td>
Compare this with the tag defining the button in Listing 16-2:
This earlier code defines a method (getBMI) to be called when the click event
occurs. Because our current example has the method placed in a code-behind file,
there is no reference to it in the .aspx file. Instead, the server-side code handles the
event using a standard delegate-based event handling approach. An event handler
method (getBMI) is defined that matches the signature of the EventHandler dele-
gate. Then, using the button’s id value, we create a delegate instance that registers
the method for the ServerClick event of the bmiButton control:
When the button is clicked on the browser, the contents of the form are posted to
the server, which recognizes the button click event and calls the appropriate event
handler. This raises one obvious question: Because a form can contain any number of
buttons, how does the server determine the event that triggered the post-back? The
answer is that the name of the control causing the event is passed to the server in the
__EVENTTARGET hidden field that was discussed earlier. This is handled automati-
cally by ASP.NET; the developer’s responsibility is to create the delegate and
server-side control event handler.
The client markup page links to this code by specifying the file name and the par-
tial class name in its @Page declaration:
ASP.NET 2.0 continues to support the original code-behind model, but it should
be used only for preexisting code. New development should employ the inline or
partial class model.
Page Class
The first time an ASP.NET page is accessed, it is parsed and compiled into an assem-
bly. This is a relatively slow process that results in the delay one notices the first time
the .aspx page is called. Subsequent requests receive much faster responses
because the assembly handles them. This assembly consists of a single class that con-
tains all of the server-side code, as well as static HTML code.
The compiled class derives either directly from the System.Web.UI.Page class
or indirectly via an intermediate code-behind class. It is important to understand the
members of the Page class. Its methods and properties define much of the function-
ality the .aspx code relies on for handling requests, and its events define junctures
at which the code must perform initialization and housekeeping tasks.
16.1 Client-Server Interaction over the Internet 755
Table 16-3 summarizes some of the important properties of the Page class.
EnableViewState Boolean value that indicates whether controls retain their values
between requests. Default is true.
IsPostBack Boolean value indicating whether the page is being loaded and
accessed for the first time or in response to a postback.
PreviousPage Provides a reference to the page originating the call. Information on the
calling page is available when the page is reached by cross-page posting
or is invoked by HttpUtilityServer.Transfer().
Request Gets the HttpRequest object that provides access to data contained
in the current request.
The Application and Session properties provide state information for a Web
application and are discussed in the next chapter.
this judiciously. An .aspx file is much easier to read when the C# code is segregated
between <script> tags, as shown in Listing 16-2, or placed in a code-behind file.
Using IsPostBack
This useful property enables a Web application to distinguish between a postback
and the first time a Web page is invoked. It lets the program know when to initialize
values that only need to be set once. A typical example is a ListBox that contains
unchanging values, such as all of the states in the United States. When assigned,
these values are subsequently maintained in the __VIEWSTATE hidden field and do
not need to be re-initialized.
To demonstrate, let’s extend our BMI inline code example to display the date and
time when the user first requests the Web page. When displayed, this date and time
remain unchanged no matter how many times the BMI calculation is subsequently
requested.
The date and time are displayed using a <span/> tag, which is added beneath the
opening <FORM> tag (refer to Figure 16-2) in the bmi.aspx file. (This is equivalent
to a Web control Label.)
In the code section, we must assign the date and time to the inner contents of the
<span> tag the first time that the page is called. Here is the code to do this:
Recall that the code on the server is compiled into a class that inherits from the
Page class. This class includes several events—discussed in the following section—
that are triggered when a request is sent to the Web page. The most useful is the
Page_Load event that is raised each time the page is loaded. Applications typically
include an event hander for it that checks IsPostBack and performs initialization if
the call is not a postback. In this example, the InnerText field of the <span> tags is
set to the date and time the first time the page is loaded.
16.1 Client-Server Interaction over the Internet 757
Page Events
The preceding example demonstrates how the Page_Load event handler provides a
convenient place to initialize variables. The Load event is only one of four events
defined by the System.Web.UI.Page class. The others are Init, PreRender, and
UnLoad. These occur in a fixed sequence as shown in Figure 16-6.
To best understand the role of each, let’s look at what happens when a form is
posted to a server:
1. The temporary class for the page is generated, compiled, and loaded.
The ASP.NET runtime begins processing the page and fires the
Page.Init event.
2. The state of the page object and its controls are set from data in the
POST variables and the __VIEWSTATE field. The Page.Load event is
fired. Typically, the OnLoad event handler is overridden to initialize
control values and establish database connections.
3. Events related to all controls are fired. The last event that occurs is the
one that caused the postback. After all server-side control events have
been processed, the Page.PreRender event occurs.
4. The page enters its rendering phase. The class that represents the
page calls the Render method on all the individual controls included
in the page.
5. The HTTPResponse is issued, sending the rendered HTML to the cli-
ent. The Page.Unload event completes the process. This event should
be used to release resources by closing files and database connections.
758 Chapter 16 ■ ASP.NET Web Forms and Controls
Although Web Forms provide an event-based model that is similar to the Win-
dows Forms model, the key difference is that the events do not actually fire until
control returns to the server. There they must occur in the fixed order described in
this section. Your code interacts with these events by overriding or replacing the
event handlers for the events described in Figure 16-6.
Cross-Page Posting
A Web page in .NET 1.x could only post to itself. In 2.0, you can designate a target
page, other than the current page, by setting the PostBackUrl property of a But-
ton, ImageButton, or LinkButton to the address of the target page. Posting occurs
when the button is clicked. To demonstrate, the button defined in this code posts the
contents of the current form to nextstep.aspx when the button is clicked.
<asp:button id="postBtn"
text="redirect"
postbackurl="nextstep.aspx"
runat="Server">
</asp:button>
The page to which a form is posted can view the contents of the calling form using
the PreviousPage property. In this example, nextstep.aspx uses PreviousPage
to get the text value of the button that initiated the post. Of course, this technique
would be used more often to gather data from text boxes on the posted form.
if(!this.IsPostBack)
{
// Sets text to the value of the Text property of the button
// from the calling form having the id postBtn: "redirect"
string text =
((Button)PreviousPage.FindControl("postBtn")).Text;
}
attribute to the HTML tag. The main reason they are included in .NET is to ease the
transition from legacy HTML files to ASP.NET. However, except for incurring less
overhead, there is no real advantage in using HTML server controls. Web controls—
a much more powerful alternative—are nearly as easy to implement and unlock the
full capabilities of ASP.NET.
Web controls are native ASP.NET controls that extend the features of the HTML
server controls and add non-HTML controls, such as a calendar and data grid. They
are actual classes in the .NET Framework and expose properties that enable the
developer to exert much more control over their appearance and behavior than is
possible with server controls. With the exception of the Table—the HTML table is
easier to work with for general use—Web controls should be the control of choice for
ASP.NET pages.
Web Controls
Height 20 20 24
BorderWidth 1 1 1
Button
Displayed Upload File Upload File Upload File
Other useful properties include Enabled, Visible, and TabIndex. The latter
indicates the tab order of a control. Note that browsers may render these properties
differently or ignore some of them altogether. For example, Firefox and Netscape
tend to ignore the Width property.
Simple Controls
The simple controls are typically used in combination to create an interface for users
to enter data or make selections. The easiest way to present and manage controls that
are related by function and style is to place them in a common container. In tradi-
tional HTML, the <DIV> element is used for this purpose; in ASP.NET, the panel
control serves as a generic container (and is often rendered in browsers as a <DIV>
element). There are many advantages to using a panel to layout controls:
The screens in Figure 16-8 are created using a combination of button, label, text
box, and panel controls. The page consists of a form that accepts name and address
information. Controls to accept name fields are contained on one panel, whereas
address fields are on another. Clicking the Name and Address buttons toggles the
visibility property of the two panels. The effect is to reuse the same display space for
both types of data—obviating the need for a long scrolling form or multiple Web
pages.
A look at the underlying code reveals the syntax and mechanics of working with
Web controls. We’ll look at the three most interesting areas of the code: the
Page_Load event handler, the button declarations and event handlers, and the lay-
out of the panels.
<script runat="Server">
void Page_Load(object sender, EventArgs e)
{
if(!this.IsPostBack) {
16.2 Web Forms Controls 763
pnlName.Visible = true;
pnlAddress.Visible = false;
}
}
Buttons
The two menu buttons across the top of the form are declared as
The Clear and Submit buttons use the OnClick property—rather than OnCom-
mand—to specify their Click event handlers as separate methods:
<asp:Panel id="pnlBottom"
style="Z-INDEX:103; LEFT:20px;POSITION:absolute; TOP: 240px"
runat="server"
BackColor=#cccccc
Height="26px"
Width="278px">
764 Chapter 16 ■ ASP.NET Web Forms and Controls
<asp:Button id="btnClear" Text="Clear" OnClick="clear_Form"
runat="server" />
<asp:Button ID="btnSubmit" Text="Submit" Font-Bold="true"
OnClick="store_Form" runat="server" />
</asp:Panel>
The most important thing to note about the event handlers is their signature. The
Click event requires an EventArgs type as the second parameter; the Command
event requires a CommandEventArgs type.
Core Note
Using Panels
The panel declaration specifies its location, size, and appearance attributes such as
background color. Controls are placed on the panel by declaring them within the
<asp:Panel /> tags. In this example, the text box and label controls are members
of the panel’s control collection:
16.2 Web Forms Controls 765
The Firefox and Netscape browsers receive HTML in which the panel is ren-
dered as a table:
Core Note
Text Box
With the exception of the MaxLength property that limits the amount of text entered
in the control, the text box controls in this example rely on default property values.
However, they can be customized easily to present a more meaningful interface. The
most useful properties include Width and Column, which both specify the width of
the control (Column is the better choice since all browsers recognize it); ReadOnly,
which can be used to prevent the user from changing the content; Rows, which spec-
ifies the number of rows in a multi-line text box; and TextMode, which indicates
whether the text box is SingleLine, MultiLine, or contains a Password. In the
latter case, text entered into the text box is masked.
The text box also supports an OnTextChanged property that specifies an event
handler method to call when text in the box is changed. However, this is a delayed
event that is not processed on the server until a round-trip event occurs. You can pre-
vent the event from being delayed by adding the AutoPostBack = true property
to the control’s declaration. However, for best performance, the program should be
designed to process all control content in one trip.
List Controls
The four ASP.NET list controls—DropDownBox, ListBox, CheckBoxList, and
RadioButtonList—provide alternative ways of representing a collection of data.
All are data-bound controls that provide a visual interface for an underlying List-
ItemCollection; and all are derived from the System.Web.UI.WebCon-
trols.ListControl class that contributes the properties and methods used to
populate the controls with data and detect selected control items (see Figure 16-9).
Figure 16-9 List control display data from underlying ListItem collection
16.2 Web Forms Controls 767
The ListItem control also exposes a Value property that allows you to associate
a value with the item in the list control, in addition to the text displayed in the con-
trol. This is often used as a key when retrieving data related to the selected item from
a database table.
As mentioned, list controls are data bound, which means they expose a Data-
Source property that can be set to reference a collection of data. The control’s
DataBind method is called to load the data from the source into the control. It is
possible to bind to many kinds of data sources, such as database tables, hash tables,
XML files, and even other controls.
As an example, let’s declare a ListBox that will be bound to an array:
In the script section of the .aspx page, we define an array and bind its contents to
the ListBox. The DataBind method copies the data from the array into the control,
creating a ListItem collection (see Figure 16-9).
<script runat="Server">
void Page_Load(object sender, EventArgs e) {
string[] artists = new string[4] {"Rembrandt","Courbet",
"Manet","Degas"};
//
if( !this.IsPostBack) {
// Bind the first time the page is loaded
ListBox.DataSource = artists;
ListBox.DataBind();
}
}
768 Chapter 16 ■ ASP.NET Web Forms and Controls
Note that the binding occurs only when the page is loaded the first time. View-
State is used to populate the control on subsequent postbacks.
Selecting an Item
When an item in a list control is selected, the SelectedIndexChanged event
occurs. A postback to the server occurs only if the control specifies an event handler
to be called, and sets the AutoPostBack attribute to true.
The event handler declaration has the familiar parameters of the EventHandler
delegate declaration.
The event handler iterates through the items in the ListBox and displays those
that are selected. By default, the ListBox permits only one item to be selected, but
this can be overridden by setting SelectionMode to Multiple.
A single selected item is available through three properties: SelectedIndex
returns the index of a selected item in the list; SelectedItem returns the entire
item; and SelectedValue returns the value of the selected item. If there are multi-
ple selected items, these properties return information on the selected item having
the lowest index.
to a data source, such as a DataReader, each row of the data source is represented as
an item in the DataList. The DataList is processed much like a ListBox control:
Items.Count provides the number of items in the control, and the SelectedIndex
and SelectedItem properties reference a specific item.
To illustrate the use of this control, let’s create a Web page that lists DVDs for
sale. The items for sale are displayed in two-column rows as shown in Figure 16-10.
The row/column layout is specified in the DataList declaration:
Either construct is replaced by the corresponding data from the data source. It
can be displayed directly in the HTML stream or assigned to a control’s property.
770 Chapter 16 ■ ASP.NET Web Forms and Controls
The easiest part of working with a DataList is binding it to a data source. In this
example, we use a DataReader to load rows from a database. To display these rows
as items in the control, set the DataSource to the reader and then bind it:
rdr= cmd.ExecuteReader();
MyDataList.DataSource= rdr;
MyDataList.DataBind();
The contents of the DataList can be changed at any time by reassigning and
binding a new data source.
Core Note
Binding to a DataReader
The data reader provides a forward-only, read-only resultset that is the most efficient
way to present data. It is generated by issuing the ExecuteReader method of the
IDbCommand object. A control binds to the results by setting its DataSource prop-
erty to the data reader object and executing its DataBind method.
The example in Listing 16-6 illustrates how a data reader is used to populate a
ListBox with movie titles from the Films database (described in Chapter 10). The
values displayed in the ListBox are based on the value assigned to the DataText-
Field property—in this case, the Movie_Title column in the movies table. The
DataValueField property specifies an additional column value that is assigned to
each item in the ListBox. When an item is selected, this latter value is returned by
the SelectedValue property.
Binding to a DataSet
It is often preferable to bind a server control to a data reader, rather than to a
DataSet. To understand why, let’s compare the two (see Figure 16-11). The data
reader connects to a table and streams data into the control when the DataBind
method is called. A data set, on the other hand, is a cache in memory that is filled
with the resultset when the DataAdapter.Fill method is invoked. Its contents are
then copied into the control when the control’s DataBind method is called. In a
WinForms application, the data set remains available to the application until it is
closed; in a Web application, the data set disappears after a reply is sent to the
browser. As we discuss in the next chapter, it can be saved as a Session variable, but
this requires memory and can lead to scalability problems.
There are a couple of situations in which using a DataSet makes sense. One is
when multiple controls are bound to the contents of a DataSet. In this code seg-
ment, a data set is built that contains a list of all the movies in a table. Two ListBox
controls are then populated with different views of the data. One ListBox contains
movies produced prior to 1951, and the other contains those produced on or after
that year. The advantage of the DataSet in this case is that only one query is applied
against the database.
16.3 Data Binding and Data Source Controls 775
The code changes in the .aspx file are minimal. An @Import directive is added
so that the namespace in datalayer.dll can be accessed.
if(!this.IsPostBack) {
DataMethods dm = new DataMethods();
DataSet ds = dm.getMovies();
ListBoxMovie.DataSource = ds;
ListBoxMovie.DataBind();
}
The use of a data presentation layer promotes code reusability, hides the messy
ADO.NET connection details, and results in cleaner code.
DataSource Controls
As we have seen, ASP.NET makes it easy to bind a control to data by simply setting
the control’s DataSource property to the collection of data it is to display. However,
it is still up to the developer to assemble the collection of data. For example, using
the data reader as a data source requires the following pattern of operations:
First Name Last Name Sex First Name Last Name Sex
Gene Kelly M Gene Kelly M
Donald O'Connor M Donald O'Connor M
Debbie Reynolds F Debbie Reynolds F
Jean Hagen F Jean Hagen F
DataSource=rd DataSourceID=dsActors
Data
Source
SqlDataSource Control
This control represents a connection to a relational data store, such as SQL Server,
DB2, or Oracle. It requires a .NET managed data provider with the capability to
return a SQL resultset.
The SqlDataSource control is declared using standard Web control syntax.
Control Declaration:
Properties:
The four command properties are strings that contain either a SQL command or
the name of a stored procedure (if the database supports it) to be executed. Each
command can contain parameters whose values are defined by an associated collec-
tion of parameters. In an upcoming example, we’ll see how the SelectParameters
collection is used with SelectCommand. Before that, let’s look at how the SqlData-
Control is used to populate a Web control.
As in our earlier example, Listing 16-7 fills a ListBox with the name of movies
from the Films database. However, in place of raw ADO.NET coding, it defines a
data source control and assigns to its SelectCommand property a SQL select string
that retrieves a list of movies from the database. A ListBox control is declared with
its DataSourceID property set to the ID of the data source control. When the page
is loaded, the list control is populated with the resultset.
Let’s extend this example so that when a movie is selected from the list, its cast
members are displayed. To do this, we add a GridView, as shown in Figure 16-13.
The data for the GridView also comes from a new data source control. The pur-
pose of this control is to dynamically retrieve a list of actors for any movie selected in
the ListBox. The challenge is to identify the movie selected and to specify it in the
query. Here’s how it’s done.
780 Chapter 16 ■ ASP.NET Web Forms and Controls
The GridView control contains three columns that are bound to the data source
control:
Core Note
In addition to the ControlParameter that specifies the control from
which a query’s parameter value comes, ASP.NET recognizes five other
parameter sources: CookieParameter, FormParameter,
ProfileParameter, SessionParameter, and QueryStringParameter.
16.3 Data Binding and Data Source Controls 781
ObjectDataSource Control
The ObjectDataSource is used when data is retrieved through a data access layer,
rather than directly from the database. To demonstrate, recall the three-tier struc-
ture we created earlier by placing the getMovies method in a separate assembly. An
ObjectDataSource control can be declared to access this method by setting its
typename field to the class containing the method and its selectmethod field to
the name of the method.
<asp:objectdatasource
id="ObjectDataSource1"
runat="server"
typename="myUtil.DataMethods"
selectmethod="getMovies">
</asp:objectdatasource>
This could be used to populate the ListBox control in Listing 16-7 by replacing
the SqlDataSource control with the ObjectDataSource control and resetting
DataSourceID to ObjectDataSource1 in the ListBox declaration.
It is also worth noting that an ObjectDataSource can be used to fetch data from
a Web Service. Because Web Services (described in Chapter 18) are nothing more
than classes that expose remotely accessible methods, the typename and select-
method properties can be used to refer to the class and method as if they were in a
local assembly.
XmlDataSource Control
The XmlDataSource control is likely to be the most popular of the data source con-
trols. It reads XML data from a local file or a stream transmitted across a network. It’s
particularly useful for handling the XML formats that are becoming standards for
exchanging information across the Internet. To illustrate, we’ll create an example that
uses the control to read data in the increasingly popular RSS format.
RSS, which stands for Really Simple Syndication, is an XML formatting standard
designed originally for news feeds. However, it is now used as a generic way for Web
sites to periodically publish information feeds that can be picked up by RSS readers.
It not only provides an easy way to distribute data, but the simple format makes it
easy for the recipient to determine when any updates have occurred. Because the
data is sent as an XML stream, the XmlDataSource control can play the role of a
simple RSS reader. As an example, we pair it with a DataList control to capture and
display a sample RSS feed from the BBC news network (see Figure 16-14).
782 Chapter 16 ■ ASP.NET Web Forms and Controls
Figure 16-14 Display RSS feed using XmlDataSource and DataList controls
The underlying XML conforms to the RSS standard (several versions are now in
use). At the top level is the <rss> element that contains a required version attribute.
Subordinate to it is a single <channel> element that contains a description of the
channel along with its content. The content is supplied by <item> elements that
have three mandatory subelements: <title>, <link>, and <description>. Their
purpose should be clear from the portion of the XML feed shown here:
<rss version="0.91">
<channel>
<title>BBC News | Science/Nature | World Edition</title>
<link>
http://news.bbc.co.uk/go/click/rss/0.91/public/-
/2/hi/science/nature/default.stm
</link>
<description>Updated every minute of every day</description>
<item>
<title>Huge 'star-quake' rocks Milky Way</title>
<description>
Astronomers say they are stunned by the explosive
energy released by a super-dense star on the
far side of our galaxy.
</description>
<link>
http://news.bbc.co.uk/go/click/rss/0.91/public/-
/2/hi/science/nature/4278005.stm
</link>
</item>
<item>
... other items go here
</channel>
</rss>
16.3 Data Binding and Data Source Controls 783
Listing 16-8 shows how the XML is displayed using only a DataList and Xml-
DataSource control. The XmlDataSource control identifies the data source with
the DataFile property. As mentioned, this can be a file or a URL. The purpose of
the XPath property is to set a filter for the XML document so that only a subset of
the document is returned. In this case, we are interested in only the <item> data.
The DataList control identifies the data source component it is bound to by set-
ting the DataSourceID property to the component’s ID—XmlDataSource1. The
XPathBinder object is used to select items or nodes from the XML document. Its
XPath method returns a single node, whereas the XPathSelect method returns an
ArrayList of matching values. Both take an XPath expression (see Chapter 10) to
identify the desired item.
<asp:XmlDataSource
ID="XmlDataSource1"
Runat="server"
XPath="rss/channel/item"
DataFile=
"http://news.bbc.co.uk/rss/newsonline_world_edition/
science/nature/rss091.xml">
</asp:XmlDataSource>
CompareValidator Compares the value of an input control with a constant or other control.
RangeValidator Checks the value of the input control against a range of values.
RegularExpression Matches the value of the input control against a regular expression.
Validator
ValidationExpression Regex to match input control’s value against.
ValidationSummary Collects and lists all the error messages from the form validation process.
Only the final control in the table, ValidationSummary, does not perform a vali-
dation. Instead, it displays a summary of the errors generated by the other validation
controls.
<asp:RangeValidator id="htirangevalidator"
ControlToValidate="hti"
MaximumValue="12"
MinimumValue="0"
Type="Integer"
Display="dynamic "
ForeColor="Blue"
ErrorMessage="Invalid Height."
runat=server>
</asp:RangeValidator>
This example also illustrates useful properties that all validator controls inherit
from the BaseValidator class:
CustomValidator Control
There are many common validation patterns that the built-in validation controls do
not handle. For example, the contents of one control may be dependent on another,
such as when a credit card number format depends on the type of card selected.
Another common example is to require that a string entered in a field conform to a
minimum and maximum length. Cases such as these are handled with a CustomVal-
idator control that points to server-side and/or client-side routines that implement
custom validation logic. To demonstrate how this control works, let’s use it to validate
a field that accepts a password, which must be between 8 and 12 characters in length.
The declaration of the control is similar to that of the other validation controls.
The main difference is the ClientValidationFunction and OnServerValidate
fields that specify client and server routines to validate the associated password field.
The validation routines contain identical logic. The client side is written in Java-
Script and the server side in C#. They are contained in separate <script/> blocks in
the .aspx file.
788 Chapter 16 ■ ASP.NET Web Forms and Controls
<script language=javascript>
<!—
// Client side function to check field length
function checkPWClient(source, args)
{
var pw = args.Value;
var ln= pw.length;
args.IsValid=true;
if(ln <8 || ln > 12) args.IsValid=false
}
//-->
</script>
<script Language="C#" runat="Server">
private void checkPW(object source, ServerValidateEventArgs args){
if(args.Value.Length<8 || args.Value.Length>12)
{args.IsValid=false;
} else{
args.IsValid=true;
}
}
</script>
Two parameters are passed to the validation routines: source and args. Args is
the more important of the two. Its value property exposes the content of the form
field being validated. In this example, the length of the field value is checked. If it
falls within the bounds, IsValid is set to true; otherwise, it is set to false. A
false value triggers the error message defined by the CustomValidator control.
For consistency with the built-in validation controls, include both server- and cli-
ent-side routines for custom validation. If only one is to be implemented, server side
is always preferred.
ValidationSummary Control
This control collects all of the error messages generated by the validation controls
and displays them as a list or customizable paragraph. The messages are displayed
either within the control (as a <span> element within the HTML) or as a pop-up
window by setting the ShowMessageBox to true.
The most important factor to consider when deciding how to display validation
error messages is that the ValidationSummary control displays messages when a
16.5 Master and Content Pages 789
form is submitted, and individual validation controls display a message when their
associated control loses focus. Thus, displaying the message at a validation control
provides immediate feedback to the user. Also note that if the validation control has
its Display property set to static or dynamic, the error message is displayed by
both the validation control and the ValidationSummary control.
[home.aspx]
<%@ Page language="C#" masterpagefile=~/shell.master %>
<asp:content id="Header" runat="server"
contentplaceholderid="Header">
<font size=3 face=Verdana> <b>Introduction </b>
</asp:content>
The content may consist of any combination of standard HTML markup code,
images, managed code, and server controls. In this example, the MainBody place-
holder is replaced with literal text for the home.aspx page and a list of clients—
using the <UL> tag—for the clients.aspx content page.
[clients.aspx]
<%@ Page language="C#" masterpagefile=~/shell.master %>
<asp:content id="Header" runat="server"
contentplaceholderid="Header">
<font size=3 face=Verdana> <b>Our Clients </b>
</asp:content>
There are only a few commonsense rules to keep in mind when using master/con-
tent pages:
• A content page does not have to provide content for all placeholders.
When content is not mapped to a placeholder, its default value is used.
• Content may include ASP.NET server controls. However, controls
cannot be placed outside of the content tags.
• A placeholder’s ID is unique. You cannot, for example, map the same
content to two sections in a master page.
matter which page is loaded. As a rule, Web pages should distinguish the menu item
for the currently loaded page from the other items. One popular technique is to
highlight it. This requires adding only a few lines of code to our content files:
This class provides properties, methods, and events that the custom control requires.
The most important of these is the Render method that we describe next. Other
members include the Page events (Init, Load, PreRender, Unload) and members
for managing child controls.
If you are using Visual Studo.NET for control development, the base class will be
System.Web.UI.WebControls.WebControl. This class derives from the Control
class and adds several members, most of which affect appearance.
Each control must implement this method to generate the HTML that represents
the control to the browser. Note that the Render method is not called directly;
instead, a call is made to RenderControl, which then invokes Render. For controls
that contain child controls, the RenderChildren method is available. This is called
automatically by the Render method, and an implementation that overrides this
method should include a base.Render() call if the control contains child controls.
This example uses the HtmlTextWriter.Write method to generate HTML for the
control. This is the simplest approach, but HtmlTextWriter offers several other
796 Chapter 16 ■ ASP.NET Web Forms and Controls
methods that you may prefer. One alternative is to use a set of helper methods that
eliminate writing full literal strings.
output.AddAttribute(HtmlTextWriterAttribute.Href,Link);
output.RenderBeginTag(HtmlTextWriterTag.A); // <a
output.AddAttribute(HtmlTextWriterAttribute.Src,bgImg);
output.AddAttribute(HtmlTextWriterAttribute.Align,"middle");
output.AddAttribute(HtmlTextWriterAttribute.Border,"0");
output.RenderBeginTag(HtmlTextWriterTag.Img);
output.RenderEndTag();
output.RenderEndTag(); // </a>
output.Write (" ");
output.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"24");
output.AddStyleAttribute(HtmlTextWriterStyle.FontFamily,"arial");
output.AddStyleAttribute(HtmlTextWriterStyle.Color,"#333333");
output.RenderBeginTag(HtmlTextWriterTag.B);
output.Write("Shell Design Studio");
output.RenderEndTag();
<script runat="server">
protected void SendPage(object src, EventArgs e)
{
// Process page here.
}
</script>
<html>
<body>
<logo:CompanyLogo runat="server" id="lgc"
Link="products.aspx"
LogoType="large"/>
<hr>
<font size=2 face=arial color=black><center>
This page contains ways to contact us
<br>
<asp:Button runat="server" text="submit"
OnClick="SendPage" />
</body>
</html>
The control that we have created behaves similarly to the built-in Web controls,
but lacks one important feature that they all have: the capability to maintain its state
during a postback operation.
On the initial request, LogoType is set and the page is returned with a large image.
However, subsequent postbacks result in the small image being displayed because the
value is not retained, and the code defaults to the small image. Recall that state is main-
tained between postbacks in the hidden _VIEWSTATE field. This field contains the val-
ues of the ViewState collection, so the secret to state control is to place values in this
collection. It operates like a hash table—accepting name/value pairs—and is accessible
by any control. The following code demonstrates how property values are placed in
ViewState as a replacement for the simple fields used in Figure 16-7.
798 Chapter 16 ■ ASP.NET Web Forms and Controls
The property values are now maintained in the _VIEWSTATE field and persist
between postbacks.
Composite Controls
At the beginning of the chapter, we created a Web page (refer to Figure 16-2) that
calculates the Body Mass Index. This calculator consists of text boxes, labels, and a
button. To turn this into a custom control, we could take our previous approach and
override the Render method with a lengthy list of statements to generate the appro-
priate HTML. In addition, special code would have to be added to maintain the state
of the control during postback operations. A better solution is to create a custom
composite control.
A composite control is created from existing controls. Its advantage is that these
controls, referred to as child controls, are very low maintenance. They render them-
selves—eliminating the need to override Render—and they maintain their state dur-
ing postbacks. In addition, they let you program with familiar objects and their
members, rather than output statements.
There are two major differences in the code used for the “from scratch” custom
class in our preceding example and that of a composite control:
Listing 16-11 contains the code to implement the BMI calculator as a composite
control. The calculator comprises three text boxes, a label to display the result, and a
button to invoke a method to perform the calculation. Most of the code of interest is
in the overridden CreateChildControls method. It adds the standard controls to
the collection, and uses LiteralControl to add HTML and descriptive informa-
tion that helps format the control. To simplify the listing, code validation is included
on only one control.
Note that getBMI performs the BMI calculation only if the Page.IsValid prop-
erty is true. Include this check when validation controls are used, because
server-side validation sets the IsValid flag to false if validation tests fail, but does
not prevent code execution.
16.7 Selecting a Web Control to Display Data 801
To make the control available, compile it and place it in the \bin directory of the
Web page.
Display multiple data records in a GridView. Provides sorting, pagination, and edit-
spreadsheet or grid format. ing. Permits controls to be embedded in cells. Per-
formance can be slow, so use only when these
features are required. If displaying data is the only
objective, use the DataList or Repeater.
Display multiple data records in a cus- DataList or Repeater. Use the DataList if edit-
tom format using simple controls. ing and event handling is required. Use the
Repeater strictly to display data in a repeated for-
mat.
Web page with multiple sections or Use multiple panels as containers for simple con-
one that steps through a process in trols. Panels in ASP.NET 2.0 include a scrolling
multiple steps. capability for IE browsers.
16.8 Summary
ASP.NET is a development platform that offers a variety of techniques to overcome
the inherent problems that plague client-server interaction over the Internet. These
problems include browser incompatibility, the difficulty of retaining an application’s
state between requests, and a reliance on interpreted script rather than compiled
code to implement program logic.
The ASP.NET answer to these problems is server-side based model whose code is
written in C# or VB.NET. A Web page is a class deriving from the Page class. Con-
trols are all classes that implement a Render method to generate the HTML code
that represents them in the browser. An important design feature of this model is the
ability to separate presentation logic from the business logic by placing the latter in a
code-behind file.
Controls fall into two categories: HTML server controls and Web controls. The
former correspond to traditional HTML tags, but include a runat=server attribute
that indicates they are run on the server. Web controls are much richer than HTML
16.9 Test Your Understanding 803
controls. They include list controls; data display controls—DataList and Grid-
View—that are usually bound to data sources; and validation controls, which are
helper controls that validate the content of other controls. If none of these controls
meet an application’s need, custom controls can be designed that extend existing con-
trols or present a new interface.
8. You have a form containing a text box field that is used to input a
phone number. Which control would you use to ensure that the phone
number includes an area code?
10. What role does the HtmlTextWriter class play in the implementa-
tion of a custom control?
Chapter 16, “ASP.NET Web Forms and Controls,” dealt with the most visible aspect of
ASP.NET—the Web page. It described a Web application in terms of the controls,
HTML, and compilable C# code that comprise it. Much of the emphasis was on how to
construct the interface presented to a client. This chapter shifts the focus to the under-
lying details of ASP.NET support for the Hypertext Transfer Protocol (HTTP), which
defines how Web requests and responses are transported between client and host.
You’ll find that a discussion of the ASP.NET environment is heavily tilted toward
those issues that fall under the responsibility of the Web server—as opposed to a
Web client. This is only natural because one of the purposes of ASP.NET is to enable
developers and architects to manage the difficult side of Web performance, while
allowing Web clients to remain blissfully thin and unaffected.
The chapter begins with a little background information on the structure of a
response and request—as defined by the HTTP standard. It shows how the HttpRe-
quest and HttpResponse objects serve as proxies that expose and extend the infor-
mation you’ll find in the standard HTTP message structure. Next, the role of
configuration files is discussed—a significant role that allows XML elements to be used
to manage the behavior and performance of Web applications at an application,
domain, and machine level. Controlling who can gain access to a Web page or resource
is the next topic. Included is an overview of the .NET options for user authentication
and authorization, as well as a detailed example of forms authentication.
One of the challenges of designing a Web application is determining how to main-
tain and manage state information. A discussion of how to persist data between Web
requests includes examples of using both Session and Application objects to
store information. ASP.NET also offers data and output caching as a way to store
807
808 Chapter 17 ■ The ASP.NET Application Environment
state information or to buffer frequently used data or Web pages. When used cor-
rectly, caching can greatly improve Web performance. Its advantages and disadvan-
tages are considered.
The client side is not totally abandoned. The penultimate section demonstrates
how to access Web resources using the WebRequest and WebResponse classes. An
example uses an HttpWebRequest object to retrieve a Web page and glean informa-
tion about the server.
The final section discusses the HTTP pipeline—a metaphor for the series of inter-
nal events that occur along the roundtrip journey that a request and response travel.
HttpRequest Object
An HttpRequest object is available to a Web application via the Page.Request or
Context.Request property. The object’s properties represent the way that .NET
chooses to expose the content to an underlying HTTP request message. Conse-
quently, the best way to understand HttpRequest is to examine the layout of the
message it represents.
Unless you are writing a browser, it is not necessary to understand the full details
of the standard. However, a general understanding is useful to a developer who
needs to extract information from the HttpRequest object. A few observations:
this.Request.SaveAs("c:\\myrequest.txt",true);
Posting a form containing two text boxes to the Web server generates this sample
output. Of most interest are the browser description, referring Web page, and text
box content values.
__VIEWSTATE=%2FwEPDwULLTEwODExMTgwOTAPZBYCA ...
&txtFirstName=joanna&txtLastName=larson&btnSubmit=Submit
HttpRequest Request
Property Message Field Description
HttpRequest Request
Property Message Field Description
HttpRequest Request
Property Message Field Description
The only cookie in this collection is the ID used by ASP.NET to identify sessions.
ASP.NET’s use of cookies is discussed further in the next section.
.NET_SessionId = avsqkn5501m3u2a41e3o4z55
Expires: 1/1/0001 12:00:00 AM
17.1 HTTP Request and Response Classes 813
HttpResponse Object
The HttpResponse class contains properties that encapsulate the information
returned in a response message. It also provides methods that construct the response
and send it to the requestor. As we did with the Request object, let’s begin by taking
a quick look at the underlying HTTP response message represented by the
Response object (see Figure 17-2).
General Header
Response Header Content-Length:133
Accept-Language:en-us
. . .
Entity Header Server: Apache/1.3.27 (Unix)
Allow: GET, HEAD, PUT
. . .
Body
Property Description
Filter A developer-written Stream object that filters all data being sent to
the client.
Output TextWriter object that sends text output to the client software.
Example: Response.Output.Write("{0} Years Old", age);
Particularly noteworthy is the Cache property, which can greatly affect how pages
are displayed to a browser. It is used to define a caching policy that dictates if and
how long pages are cached. We’ll look at this property in Section 17.5, “Caching.”
An example that displayed the contents of a cookie was presented in the discus-
sion of the HttpRequest class. Let’s extend that example by demonstrating how the
response object is used to create and return a cookie to the client.
// Create a cookie
HttpCookie myCookie = new HttpCookie("userid","007");
// Cookie will live for 10 minutes
// Timespan(days, hours, minutes, seconds)
816 Chapter 17 ■ The ASP.NET Application Environment
A simple, but useful, example (see Listing 17-2) illustrates how these methods can
be used to download a file. The client request for this page includes a query string
with the name of a requested file. The page locates the file (or returns an error mes-
sage) and uses WriteFile to send it to the user. The AppendHeader, ClearCon-
tent, and End methods prepare and manage the response stream.
Virtual Directory
web.config
c:\calculators\
Subdirectory web.config
c:\calculators\bmi
Core Note
Values are retrieved from the appSettings section using the static AppSet-
tings property of the ConfigurationSettings class:
myFont = ConfigurationSettings.AppSettings["mainFont"];
820 Chapter 17 ■ The ASP.NET Application Environment
<location path="test">
<system.web>
<trace enabled="true" pageOutput="true" />
</system.web>
</location>
This has the same effect as placing a web.config file with this trace setting in the
physical directory associated with the “test” virtual directory.
Element Use
Element Use
<membership> Defines how the Membership class manages user credentials and
authentication.
The mode attribute takes the value On, Off, or RemoteOnly. The latter value,
which is also the default, specifies that the default page is called only if the request
822 Chapter 17 ■ The ASP.NET Application Environment
comes from a machine other than the Web server. This permits local developers to
see details of the actual error, whereas clients see only the custom page. Off should
be used when a developer is testing pages on a remote machine. Note that multiple
<error> elements can be included in the section.
<trace
enabled="true"
pageOutput="false"
traceMode="SortByTime"
requestLimit="5",
localOnly="false"
</trace>
Aside from the enabled attribute that turns tracing on or off, the most interesting
attributes are pageOutput and localOnly.
These options are described in detail in Section 17.4. Our interest in this section is
to understand the elements and attributes that define how ASP.NET manages session
state. To illustrate, here are the default settings for the <sessionState> element:
<sessionState
mode="InProc"
stateConnectString="tcpip=127.0.0.1:42424"
sqlConnectionString="data
source=127.0.0.1;Trusted_Connection=yes"
cookieless="false"
timeout="15"
/>
The timeout attribute specifies the number of minutes the session can be idle
before it is abandoned. The cookieless attribute indicates whether the identifica-
tion of the client session is maintained using a cookie or by mangling the URL (plac-
ing encoded session information in the URL line). The other two attributes are
dependent upon the mode setting: stateConnectString specifies the IP address of
the machine running the Session State Server process, and sqlConnectionString
contains the connection string value used to access SQL Server if it is used to main-
tain state information.
<connectionStrings>
<add name="movies"
connectionString=
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=/movies.mdb;"
providerName="System.Data.OleDb"/>
</connectionStrings>
string cs=
ConfigurationSettings.ConnectionStrings["movies"].ConnectionString;
824 Chapter 17 ■ The ASP.NET Application Environment
Core Note
You can view the section handlers for the standard predefined sections,
such as <appSections> in the machine.config file.
To demonstrate how section handlers work, let’s create one for the <Rewriter-
Config> section shown in the code that follows. The first thing to note is the <con-
figSections> element that contains a definition of the new section. Its type
attribute specifies the handler (class) that parses the <RewriterConfig> section.
The data section represents URLs that have been changed and need to be replaced
with a new URL.
<configSections>
<sectionGroup name="RewriterConfig">
<section name="RewriterRules"
type="ConfigRewriter.RulesHandler,RulesConfigHandler" />
</sectionGroup>
</configSections>
<OldPath>/ideas/bmi.aspx</OldPath>
<NewPath>/utilities/bmi.aspx</NewPath>
</Rule>
</RewriterRules>
</RewriterConfig>
After the XML data structure is determined, the next step in developing a section
handler is to create a class that is populated with the contents of the section and con-
tains members that make the data available to an application. In this example, data is
defined by any number of <Rule> elements. Consequently, the class must provide
some sort of collection to return multiple values. We’ll use a Hashtable for the pur-
pose. The GetNewPath method accepts a string value that it uses as a key to retrieve
and return its associated value from the hash table.
// File: SectionRules.cs
using System.Collections;
namespace ConfigRewriter
{
// Class to contain content of configuration section
public class RulesData
{
private Hashtable paths;
// Constructor accepts hash table as parameter
public RulesData (Hashtable pathCollection){
paths = pathCollection;
}
// Use old path as key and return hash table value
public string GetNewPath(string OldUrl)
{
return ((string)paths[OldUrl]);
}
}
}
The final step, shown in Listing 17-4, is to create the section handler code. Its pri-
mary function is to implement the Create method. This method is passed an XML-
Node parameter that corresponds to the <RewriterRules> node in our section. It is
used to parse its child nodes and fill the hash table with its contents. An instance of
the RulesData class is instantiated by passing the hash table to its constructor. This
object is then available to a Web application.
826 Chapter 17 ■ The ASP.NET Application Environment
After the section has been constructed, accessing its content is trivial: use the
ConfigurationSettings.GetConfig method to return an instance of the section
object, and use its properties to retrieve values.
Forms Authentication
The general framework for implementing Forms Authentication is based on a mix-
ture of configuration files, authentication modules, ASP.NET security classes, and
the script (C# code) to work with the methods and properties of these classes. Figure
17-4 illustrates how it works.
A web.config file defines a login page to which a user’s browser is redirected
when it first attempts to access a Web resource. This login page accepts the client’s
credentials—usually name and password—and determines if they are valid. If so, the
828 Chapter 17 ■ The ASP.NET Application Environment
Web.config applogin.aspx
1. Redirect to Login page. 2. Authenticate user and direct
to requested page.
<authentication mode ="Forms">
<forms name="FORMSURL" FormsAuthentication.
loginUrl="applogin.aspx" RedirectFromLoginPage(
timeout="1" path="/"> txtName.Text, false);
</forms>
</authentication>
app.aspx Global.asax
4. Control is given to page originally 3. Handle AuthenticateRequest
requested. An authentication ticket event and add role information
(cookie) has been created that to current user.
permits future access to the
Context.User = new GenericPrincipal(
application during the session.
Context.User.Identity, appRoles);
Figure 17-4 Forms Authentication steps that occur when a page is first requested
The first thing to note is the mode attribute in the <authentication> element,
which specifies the type of authentication being used. Contained in the <authenti-
cation> section is a <forms> tag or <passport> tag (not shown) that corresponds
to the type of authentication chosen. The <forms> tag, which is used in a later exam-
ple, contains several key attributes. The loginurl specifies the login page that a
user is directed to when she has not been authenticated. The timeout attribute
specifies how long a user may wait between requests before the authentication ticket
expires. Thus, if the value is set to 5 minutes and the user has not requested a page in
that interval, she is forced to log in again at her next request. The protection
attribute specifies how, or if, ticket authentication information is processed before it
is written to a cookie. You’ll normally set this to All.
The <forms> element may contain a <credentials> element that can be used
as a mini-database to list each user and his password. The advantage of placing them
here, as we see in a later example, is that .NET authentication classes provide meth-
ods to automatically perform authentication against these names. To add a measure
of security for storing the passwords, the passwordFormat attribute is provided to
specify how the passwords are encrypted.
Web.config may also contain an <authorization> section that explicitly allows
or denies access to users based on their name or role (think of role as a group the
user belongs to). The <allow> and <deny> tags are processed in the order they
occur in the file. When the tag’s policy can be applied to the user, processing stops. If
no allow or deny policy matches the user, the user is authorized to access the
resource.
<system.web>
<authentication mode="Forms">
<forms name="AppCookie"
loginUrl="applogin.aspx" protection="All"
timeout="10" path="/" >
<credentials passwordFormat="Clear">
<user name="joanna" password="chicago" />
17.3 ASP.NET Application Security 831
This denies access to all users (the asterisk (*) wildcard selects all users) except for
those that are explicitly allowed. When both <allow> and <deny> are present, the
order in which they are placed can be important. In this example, placing the <deny>
rule first would deny access to all users, and the <allow> rule would be ignored.
<<interface>> <<interface>>
IPrincipal IIdentity
Identity AuthenticationType
IsInRole() IsAuthenticated
Name
GenericPrincipal Page.User
defined in the HTTP protocol: hidden fields that could be sent back and forth using
the POST method, and a string full of values that could be placed after the ? character
in an HTTP URL path. The former technique is used to maintain .NET ViewState
information; the latter constitutes a “query string” consisting of name/value pairs.
A third client-side approach to managing state is the use of cookies, a small (4K)
text-only string that is stored in browser memory during a session and may be written
to disk. Many applications maintain session-related state information, such as a shop-
ping cart’s identification, in cookies. ASP.NET identifies each session by creating a
cookie containing a unique session ID.
These approaches are plagued by a common weakness: They can be compromised
by the client. Query strings and hidden fields can be altered; cookies can be altered
or simply turned off on the browser.
To overcome these shortcomings, ASP.NET offers two general types of server-side
state management: session state, which maintains information for the life of the ses-
sion; and application state, which maintains information across multiple sessions.
Although these techniques maintain information on a server—rather than passing it
back and forth between requests—they still rely on a cookie, where possible, to
uniquely identify a user’s session. However, the server-based solution presents
another problem: All session activity must be handled by that server possessing the
state information. This is incompatible with large Web sites where a server-selection
algorithm assigns requests to any machine in a Web farm. An ASP.NET solution to
this is the use of a central database to store session state information.
Table 17-4 summarizes the client- and server-side state management approaches. Of
those listed, this section focuses on the application and session management techniques.
Web Farm
Technique Compatible Description
Application State
Application state data is maintained in an instance of the HttpApplicationState
class—an in-memory dictionary holding key-value objects. The contents of this dic-
tionary object are available to all sessions using that application.
The data is usually loaded within the global.asax file when the Application_
Start event fires.
Users of the application then have read and write access to the data without hav-
ing to regenerate it. This is particularly useful when working with data from a data-
base. It can also be a convenient way to initialize variables used for collecting usage
statistics. (See Section 17.2 for background on global.asax file.)
Not illustrated are the Lock and UnLock methods that allow application variables
to be updated in a thread-safe manner.
updated to keep statistics regarding the application’s use. Both of these uses can be
handled more efficiently using a different approach.
The ASP.NET data cache provides a more flexible approach for making read-only
data available on an application-wide basis. Its contents are as accessible as those in the
application object, and it offers the advantage of having its contents automatically
refreshed at a specified time interval. (Its use is described in Section 17.5.) As a
rule-of-thumb, use application state to preload small amounts of read-only data required
by an application; use the data cache when working with large amounts of data.
The problem with maintaining usage data in application state is that the data is
available only during the life of the application. When it ends, the data is gone. In
addition, if there are multiple servers, the data will apply to only one server. A better
approach is store the information in a central database.
Core Note
Session State
When a new client requests an ASP.NET application, ASP.NET creates a cookie con-
taining a unique session ID and returns it to the client’s browser. This ID, or session
key, is used to identify subsequent requests from the client. State information unique
to each session is maintained in an instance of the HttpSessionState object. The
contents of this object are available through the Session properties of both the
Page and HttpContext class. Note that Session can be used in place of the fully
qualified HttpContext.Session. Working with session state is analogous to work-
ing with application state. Many applications perform state session initialization in a
Session_Start event handler in the global.asax file. This segment initializes
variables for an online survey:
// global.asax file
protected void Session_Start(object sender, EventArgs e)
{
Session["Start"]= DateTime.Now;
Session["QuestionsAnswered"] = 0;
}
17.4 Maintaining State 839
The variables can be updated in the application with the same syntax used for the
Application object:
Server
1
aspnet_wp.exe
InProc
Server 1
2
aspnet_state.exe
Server 2
StateServer
Server 1
3
Server 2
SqlServer
Figure 17-6 Session state data store options
840 Chapter 17 ■ The ASP.NET Application Environment
<configuration>
<system.web>
<sessionState mode="InProc" timeout="30"/>
</system.web>
</configuration>
<configuration>
<system.web>
<sessionState mode="StateServer"
stateConnectionString="192.168.1.109:42424"
/>
</system.web>
</configuration>
17.5 Caching 841
<configuration>
<system.web>
<sessionState mode="SQLServer"
sqlConnectionString="datasource=x; user id=sa; password="/>
</system.web>
</configuration>
SQL Server should be considered when there is a need to maintain data beyond
the life of the session. For example, sites often persist the content of a shopping cart
so it is available when the user logs in again.
Because the use of SQL Server slows Web page performance, it should be used
only on those pages in an application that require state data. The @Page directive has
an EnableSessionState attribute that dictates how state is used on the page. It
can be set to false to disable session state, true to enable read and write session
state, or readOnly.
17.5 Caching
Consider a Web page with a list box containing the names of a thousand movie actors
whose movies are displayed when an actor is selected. The first time the page is
requested, a query is sent to the database, the list box is populated with the results,
and the rendered HTML is returned to the user’s browser. This is a relatively slow
and expensive process. Multiply it by a thousand user requests and Web server
response time will slow measurably.
842 Chapter 17 ■ The ASP.NET Application Environment
One solution is to use caching to bypass most, or all, of this reprocessing by keep-
ing an HTML page or data in memory. Subsequent requests for the page are handled
using the cached information—obviating the need to fetch data from the database. A
sound caching strategy can improve Web server response more than any other single
factor. This section presents the factors to be weighed in designing a caching strategy
and the two broad categories of caching available in ASP.NET: output caching and
data (or request) caching.
Its attributes declare the specific caching policies in effect for that Web page.
Let’s look at how these attributes are used.
Core Note
Response.Cache.SetExpires(DateTime.Now.AddSeconds(20));
Figure 17-7 shows four requests that result in three unique pages being cached
with their calculated BMI value. Note that we could have also assigned an asterisk
(*) to VaryByParam to indicate that each parameter’s value affects the cache.
Output Cache
Page Requests
POST /BMI.aspx wt=168 hti=1 htf=6
22.16 23.17
POST /BMI.aspx wt=135 hti=4 htf=5
POST /BMI.aspx wt=184 hti=10 htf=5
26.4
POST /BMI.aspx wt=168 hti=1 htf=6
Be aware that you can create some unexpected results if you set VaryByParam
incorrectly. If only the wt parameter were specified in this example, requests with a
weight of 168 and heights of 5'1" and 6'1" would return the same Web page. The
page returned to both would be the one requested earliest.
VaryByHeader caches a different version of a page, based on the value of the
Request Header fields (refer to Figure 17-1). This is the most commonly used with
the Accept-Language field to ensure that different language versions of a Web
page are cached.
The final conditional attribute to be familiar with is VaryByCustom. This is most
useful when you have a Web page that is rendered differently depending on the cli-
ent’s browser type and version. To create different page versions by browser type, set
the VaryByCustom attribute to "Browser".
This attribute also can be set to recognize custom values that your program gener-
ates. Please refer to caching documentation for details on this.
The application Web page to hold this control displays a title and date. Note that
its caching is turned off by setting Location to None. The result is that only the con-
trol is cached.
Figure 17-8 Example of custom control with its own caching policy
Data Caching
Data caching, sometimes referred to as request caching, is often an alternative to
using application state or ViewState to store read-only data. It is as easy to use as
the Application object, and adds the flexibility of assigning expiration and priority
values to the stored data.
Cache["userid"] = "rigoletto";
The Cache.Insert method is often a better approach since it takes full advan-
tage of the fact that each cache entry is actually an instance of the CacheEntry class.
This class contains many useful properties that are set by passing parameters to the
Insert method:
Syntax: Example:
The priority parameter makes sense when you understand that data in a cache
is not guaranteed to remain there. Because there is no limit on what can be placed in
the cache, ASP.NET periodically invokes a resource scavenging process to remove
less important data from the cache. The determination of what is important is based
on the priority of the resource. By setting this priority to a CacheItemPriority
enum value, you can reduce, or increase, its likelihood of being removed through
scavenging. Enum values may be set to AboveNormal, BelowNormal, Default,
High, Low, Normal, and NotRemovable.
Because the data is stored as an object, it is necessary to cast the result. If no data
exists for the key, a null value is returned.
Suppose the user selects an item in the list box on page A that requires loading
page B to show more details about the item. The server constructs the description as
page B and returns it to the browser, discarding page A. After viewing this page, the
user now wants to return to A. In the absence of caching, page A must be recon-
structed by retrieving data from the database. To avoid this, the contents of the list
box can be cached prior to navigating to page B (step 3):
Figure 17-9 Use a data cache to preserve information between separate pages
In step 6, page A is constructed using data from the cache to restore the list box.
The cache is cleared after the page is restored; otherwise, both the cache and view
state will contain the contents of the ListBox. It is worth noting that another option
is to turn view state off for the ListBox (EnableViewState=false) and rely on
caching to maintain its content.
848 Chapter 17 ■ The ASP.NET Application Environment
Core Note
The basic steps for communicating synchronously with the HTTP server are quite
simple:
Note that request and response are cast to HttpWebRequest and HttpWebRe-
sponse types, respectively. As mentioned, these subclasses deal specifically with the
HTTP protocol (see Listing 17-9).
850 Chapter 17 ■ The ASP.NET Application Environment
You may encounter Web pages that check the UserAgent property of the request
object and do not allow their pages to be downloaded unless they can identify the
browser. You can assign a legitimate browser value to overcome this.
Request (HTTP://www.mysite.com/bmi.aspx)
HttpApplication
1
HTTP Modules
2
HttpRuntime 3
GetApplication 4 Handler-
Instance() Factory
Events 6 5
HttpContext
BMI.aspx
aspnet_wp.exe
that maps to this request type. Different handlers are needed for the
different types of resources that may be requested. For example, a
request for a Web page is handled differently than one for a text or
XML file.
5. Handler processes request. In our example, the handler class is the
requested .aspx page. It may seem strange that the page is a handler,
but the main requirement for a handler is that it implement the
IHttpHandler interface—which the .aspx page does via its Page
class.
6. Response is constructed and returned. The ProcessRequest
method of the handler is called to generate the response. This method
takes an HttpContext object parameter that gives it access to the
requested information needed to complete the processing.
As requests and responses move through the HTTP pipeline, an ordered chain of
deterministic events—starting with the HttpApplication.BeginRequest event
and concluding with the HttpApplication.EndRequest event—defines each
stage of the processing. In addition, random error events can arise at any time from
unhandled exceptions.
A developer can greatly improve the robustness and effectiveness of an applica-
tion by selectively developing handlers for these events. Handlers are typically used
to trap error events and direct them to a custom error page, log session lengths and
number of users, authenticate users, and adorn pages with common header and
footer information. These events can be handled using either a custom application
class or custom HTTP modules. We’ll look at both, and see how they compare.
A third place to customize the pipeline is at the endpoint, where an HTTP han-
dler processes a resource request. As we will see, different resources, such as .aspx
or .soap files, use their own handler components. ASP.NET makes it easy to write
your own handler to manage special resources
HttpApplication Class
The HttpApplication object handles most of the duties required to process a
request. Its properties expose caching and application/session state information, as
well as a collection of predefined HttpModules. Equally important are the set of
events it exposes. We can customize an application by including code in a glo-
bal.asax file to handle these events.
Table 17-5 lists the HttpApplication events in the order that they occur in the
HTTP pipeline. The Error event is an exception because it can occur at any time
during the process.
854 Chapter 17 ■ The ASP.NET Application Environment
Event Description
ReleaseRequestState Fires after all request handlers have been executed and
ASP.NET is ready to store session state information.
UpdateRequestCache Data being sent to client is stored in the cache for future
requests.
Your code can provide a handler for these events by creating a component that
uses a delegate to subscribe to the event or define event handlers in the glo-
bal.asax file. We’ll demonstrate the former approach when discussing HTTP mod-
ules, but first let’s look at how to use the global.asax file.
global.asax File
This file is stored in the root of the virtual directory containing the application(s) it is
to be associated with. Its primary purpose is to hold the handlers that respond to the
Application object’s events. It is similar to an .aspx file in that it is compiled into
an assembly when the Web application is first accessed. Unlike the Web page, how-
ever, it inherits from the HttpApplication class.
Here is a simple global.asax file that displays copyright information at the bot-
tom of each page returned by an application in its virtual directory. It does this by
implementing a custom handler for the EndRequest event. Notice that the format of
17.7 HTTP Pipeline 855
the event handler for this, and all Application events in the global.asax file, is
Application_eventname.
Global.asax supports four other important events that are not members of the
HttpApplication class: Application_Start, Application_End, Session_
Start, and Session_End. The purpose of these events, which should be clear from
their name, is to mark the beginning and end of an application and the sessions using
it. They are the most frequently implemented event handlers in the global.asax
file—performing the bookend operations of state initialization and cleanup. To illus-
trate this, let’s extend the preceding global.asax example to keep track of the
number of sessions that request an application and display this session number below
the copyright.
The session counters are kept in an HttpApplicationState object represented
by the Application property of the HttpApplication base class. It acts like a dic-
tionary to hold information during the lifetime of an application (refer to Section
17.4, “Maintaining State”).
As shown in Listing 17-10, counters that track the number of sessions and sessions
cancelled are initialized to zero when the Application_Start event fires. The ses-
sion counter is incremented when a Session_Start occurs, and the counter for ter-
minated sessions is incremented when a Session_Ends event occurs.
The code-behind file must be implemented as a class that inherits from Http-
Application. Here is a segment of the code:
// file: sessionctr.cs
using System;
using System.Web;
17.7 HTTP Pipeline 857
HTTP Modules
An HttpModule is a component that implements the IHttpModule interface. The
role of the module is similar to that of the global.asax file: It processes events
(refer to Table 17-2) associated with response and request messages in the HTTP
pipeline. In many cases, you can implement the same functionality using either the
module or global.asax file. We discuss the factors that affect your choice later in
this section.
Listing 17-11 shows the predefined modules that ship with ASP.NET. The physi-
cal modules (assemblies) are stored in the Global Assembly Cache (GAC) and are
made available by listing them in the <httpModules> section of the machine.con-
fig file. They perform such diverse tasks as user authentication, caching, and state
management. Although you do not work with the modules directly, they should give
you an idea of the type of pre- and post-request processing that can be performed.
<configuration>
<system.web>
<httpModules>
<add name="copyright"
type="CustomModules.FooterModule, footmodule" />
</httpModules>
</system.web>
</configuration>
The key part is the <add> element that takes the form:
Listing 17-13 contains code for a simple rewriter module with the rewriting logic
included in the AuthorizeRequest event handler. Look closely at this code. It first
calls a static method Rewrites.getRewrites() that returns a hash table in which
the keys are old URLs and the associated value is the replacement URL. The hash
table is checked to see if it contains the currently requested path and if it’s in the
table, a call is made to Context.RewritePath. The new URL is passed to it, and
the method automatically takes care of details such as including any query string with
the new URL.
The module is registered by placing the following entry in the web.config file:
<add name="redirector"
type="CustomModules.ReWriteModule, rewritemodule" />
This information could also be stored in the web.config file, although some
overhead is required to do this. For details, refer to “Adding a Custom Configuration
Section” on page 824.
HTTP Handlers
When a request is made for a resource, ASP.NET directs control to the appropriate
handler for that resource. For example, if the request is for an .aspx file, control
passes to the PageHandlerFactory component, which returns the requested
.aspx page. The default ASP.NET handlers are registered in the machine.config
file within the <httpHandlers> section. An extract of that file shows how the
resource type is mapped to the class capable of creating a handler for it:
<httpHandlers>
<add verb="*" path="trace.axd"
type="System.Web.Handlers.TraceHandler" />
<add verb="*" path="*.aspx"
type="System.Web.UI.PageHandlerFactory" />
<add verb="*" path="*.ashx"
type="System.Web.UI.SimpleHandlerFactory" />
<add verb="GET,HEAD" path="*"
type="System.Web.StaticFileHandler" />
...
</httpHandlers>
processing features. For example, when a text file (.txt) is requested, ASP.NET sim-
ply returns the contents of the file. As an exercise, let’s improve this with a handler
that accepts a URL with the name of the text file and a query string parameter that
specifies a keyword to search for in the file. The output from the handler is a listing
of the text file with all instances of the keyword highlighted, as well as a count of the
number of times the keyword occurs in the document.
The IHttpHandler interface contains two members that must be implemented
in our custom handler class: the ProcessRequest method and the Reuseable
property. As shown in Listing 17-14, ProcessRequest contains the code that pro-
cesses the HTTP request; IsReusable indicates whether the instance of the handler
can be reused for other requests. This is applicable only when a handler factory is
providing handlers and pools them for reuse.
This handler responds to URL requests that contain a text file and query string
containing a keyword to search for in the file:
HTTP://www.scilibrary.com/films.txt?kwd=Bogart
The context object provides access to the file name through the
Request.PhysicalPath property. An attempt is made to open the text file and
read it line by line. Each line is searched for the keyword using Regex.Match. If a
match is found, the method BoldWord is called to place HTML bold (<b>) tags
around the text. This method also increments the word counter.
The final code is compiled and placed in the \bin subdirectory of the application.
17.7 HTTP Pipeline 865
<httpHandlers>
<add verb="*" path="*.txt" type="TextHandler,txthandler"/>
</httpHandlers>
The final step in deployment is to make IIS (Internet Information Service) aware
that requests for the .txt extension are to be handled by ASP.NET. This is done by
using Internet Services Manager to map the file extension to the ISAPI extension
DLL (aspnet_isapi.dll). Follow these steps:
With these steps completed, TextHandler is now called to process all requests
for .txt files. Figure 17-12 shows output from a sample request.
Figure 17-12 Output from HTTP handler that displays text files
866 Chapter 17 ■ The ASP.NET Application Environment
Core Note
17.8 Summary
The ASP.NET environment offers a variety of types and configuration files that
enable a developer or software architect to customize how Web requests are handled
by a server. Configuration files such as web.config provide a flexible and easy way
to customize the ASP.NET settings. These files contain sections that enable/disable
tracing, specify the default culture of a page, define the way session state information
is stored, and define an authentication/authorization security model. The security
model enables an application to limit page access to users based on credentials such
as their name, password, and role. Credential information may be stored in the
web.config file, an XML file, or an application’s proprietary database.
ASP.NET provides several mechanisms for handling both Application and Session
state data. The application data is available to all sessions using that application; ses-
sion data is limited to the individual session and can be configured to support Web
farms and permanent storage in a database. An alternative to using an application
state object is data caching. ASP.NET provides an area in memory where CacheEn-
try objects are stored and made available to all sessions. Output caching is also sup-
ported to enable an entire Web page or Web page fragment to be stored in a cache
on the server, a proxy server, or the browser. This reduces the load on a server by
allowing a Web page to be retrieved from memory rather than being recreated on the
server.
Advanced Web application development can benefit from an understanding of the
underlying ASP.NET architecture that supports HTTP communications. Central to
this is the HTTP pipeline through which requests and responses are transported.
After a request or response enters this pipeline, it can be parsed, modified, rerouted,
or rejected. Both the global.asax file and HTTP module can be designed to
respond to pipeline events. The event handlers they supply are used to do such
things as append standard information to all responses, maintain Web statistics, and
transparently reroute requests when necessary. At the end of the pipeline, a handler
is responsible for providing the requested resource. Custom handlers can be written
to handle specially defined resources or enhance the existing handlers.
17.9 Test Your Understanding 867
5. You have a reasonably static Web page that is rendered differently for
different browsers and you want to refresh the cached page every
three hours. What would be included in your OutputCache directive
for this page?
7. What is resource scavenging, and how can a data cache prevent it from
occurring?
XML Web Services provide a relatively simple technique for accessing a method on
an object that is running on a local or remote computer. Many embrace this light-
weight approach to making Remote Procedure Calls (RPC) as technology that will
spell the end to much heavier and complex solutions for distributed communications
such as DCOM and CORBA. At the root of its appeal is the fact that Web Services
are based on standardized technologies such as HTTP and XML that are designed to
promote seamless interoperability among different operating systems. Not everyone
is sold on them. Critics point out the lack of security standards, the heavy bandwidth
requirements of XML, and the lack of notification and transaction services.
The chapter takes a practical look at the pluses and minuses of implementing and
consuming Web Services in a .NET environment. It presents Web Services from the
perspective of both the server and client. On the server side, the chapter explores
how to define a Web Services class, make its methods available to HTTP clients, and
access the ASP.NET Application and Session objects. The emphasis on the client
side is how to use the Web Services Description Language (WSDL) contract to cre-
ate a proxy class that implement calls to the Web Service.
HTTP and SOAP—a protocol that codifies how XML is used to package the
request and response data that comprise a Web Service operation—are the two cor-
nerstones of Web Services. Other protocols such as GET and POST are available,
but the Simple Object Access Protocol (SOAP) has fewer limitations and is used in
the majority of real-world applications. This chapter takes a look at the SOAP format
as well as several issues related to SOAP, including the handling of complex data
types, exception handling, and security.
869
870 Chapter 18 ■ XML Web Services
Core Note
Data
SOAP/XML
HTTP
TCP/IP
be called, the address of the service (usually a URL), and binding information that
describes how the transport operation will occur. In practical terms, the WSDL
information contains the methods that actually call a Web Service. We implement
these methods in our client code (as source or a DLL reference) and use them as a
proxy to access the service. Section 18.2, “Building an XML Web Service,” provides a
concrete example of using .NET to retrieve WSDL information and incorporate it
into a client application.
Introduction to UDDI
Web sites are identified by a domain name and IP address that are maintained by a
distributed network service known as the Domain Name System (DNS). Servers in
this network are responsible for controlling e-mail delivery and translating domain
names into IP addresses. The combination of DNS and Web search engines enables
users to quickly locate Web content. However, neither DNS servers nor search
engines provide a formal way of identifying Web Services.
To organize Web Services into a publicly searchable directory, a public consortium
(www.uddi.com) comprising hundreds of companies has defined a standard known
as Universal Description Discovery and Integration (UDDI). This standard defines a
SOAP-based interface that can be used to publish a service or inquire about services
in a UDDI-compliant registry. The registry has a business-to-business flavor about
it—containing information about a company, its services, and interface specifications
for any Web Services it offers. Importantly, there is no single UDDI registry. IBM,
SAP, and Microsoft maintain the most prominent registries. Users may query each
separately by entering a business name or service as a search term. Figure 18-2 pro-
vides an overview of the inquiry process.
The dialog between the client and UDDI registry server is conducted using SOAP
messages. Overall, UDDI defines approximately 40 SOAP messages for inquiry and
publishing.
Publish
UDDI 1. Send Discovery query.
Web
Service 2. Receive list of matching services.
3 1 2
3. Retrieve WSDL for Web Service.
WSDL
4. Send SOAP query to Web Service.
Proxy Client
4 Bind to Service
http://uddi.microsoft.com/inquire
<Envelope>
<Body>
<find_tModel generic="1.0" maxRows="100">
<findQualifiers/>
<name>stock quote</name>
</find_tModel>
</Body>
</Envelope>
<soap:Envelope>
<soap:Body>
<tModelList generic="1.0" operator="Microsoft Corporation" >
<tModelInfos>
<tModelInfo
tModelKey="uuid:7aa6f610-5e3c-11d7-bece-000629dc0a53">
<name>Stock Quote</name>
</tModelInfo>
<tModelInfo
tModelKey="uuid:265973ab-31cb-4890-83e0-34d9c1b385e5">
<name>Stock Quotes and Information</name>
</tModelInfo>
</tModelInfos>
874 Chapter 18 ■ XML Web Services
</tModelList>
</soap:Body>
</soap:Envelope>
<Envelope>
<Body>
<get_tModelDetail generic="1.0">
<tModelKey>uuid:7aa6f610-5e3c-11d7-bece-000629dc0a53
</tModelKey>
</get_tModelDetail>
</Body>
</Envelope>
The response message includes the OverviewURL element that points to a WSDL
document. This document contains the information needed to create an application
to access the service.
<overviewDoc>
<description xml:lang="en">
Get Stock quote for a company symbol
</description>
<overviewURL>
http://www.webservicex.net/stockquote.asmx?WSDL
</overviewURL>
</overviewDoc>
You can display the WSDL by pointing your browser to this URL. To invoke the
Web Service, remove the query string (?WSDL) from the URL, and navigate to it with
your browser.
http://www.soapclient.com/UDDISearch.html
18.2 Building an XML Web Service 875
} else {
DateTime dt = new DateTime(yr,mo,day);
dob = dt.ToString("dddd"); // Get day
}
return(dob);
}
}
}
The code consists of a single class and a method that is invoked by a client to
return the day-of-week value. In addition to the C# code that implements the logic of
the service, two other elements are required: a WebService directive and a Web-
Method attribute.
WebService Directive
The WebService directive identifies the file as defining a Web Service:
The directive specifies the class implementing the XML Web Service and the pro-
gramming language used in the implementation. In this example, the directive and
the code for the class are present in the BirthDayWS.asmx file. Note, however, that
the class can be in a separate assembly. In that case, the separate assembly is placed
in a \bin directory below the Web application where the Web Service resides. If the
class were in bdAssembly.dll, the WebService directive would look like this:
This statement would be the only line needed in the .asmx file.
WebMethod Attribute
The WebMethod attribute identifies a method as being accessible to clients making
HTTP requests—that is, as an XML Web Service. Although not required, it is a good
practice to include the Description property to describe the purpose of the
method:
[System.Web.Services.WebMethod
(Description="Return day of week for a date")]
18.2 Building an XML Web Service 877
The description is added to the WSDL for the service and—as we will see next—
is displayed when a Web Service is accessed via a browser. The WebMethod attribute
has other optional parameters, which are described later in this chapter.
http://localhost/ws/BirthDayWS.asmx
This brings up a Web page that lists all the services (methods) available through
this .asmx file as well as a Service Description link that displays WSDL information.
For our example, there is only one method, GetDayBorn. Clicking it yields the page
shown in Figure 18-3. This page contains the name of the class implementing the
Web Service, the name of the method to be invoked, a description of the method,
and text boxes for entering values to be passed to the Web Service method.
To use the Web Service, fill in the parameter values and select the Invoke button.
This causes the parameters to be sent to the Web Service using the HTTP POST pro-
tocol. The output received from the service is an XML document shown in Figure
18-4.
878 Chapter 18 ■ XML Web Services
The output from the method is included in the string element of the XML
wrapper. Fortunately, we do not have to parse the XML to retrieve this value when
writing our own SOAP client. The WSDL contract provides information that allows
our client to treat the remote Web Service as a method that returns data conforming
to the method’s type—not as XML.
Core Note
To view the WSDL associated with this Web Service, open your browser and
append ?WSDL to the URL of the .asmx file:
http://localhost/ws/BirthDayWS.asmx?WSDL
namespace BirthDayWS
{
18.2 Building an XML Web Service 879
To implement the service, rename the class to Birthday and add the code for the
GetDayBorn method. Use the browser to test the service by either compiling the
code, which automatically invokes the browser, or directly pointing the browser to
http://localhost/BirthDayWS/BirthDayWS.asmx
The only significant difference between this code and our handcrafted version is
that the Web class now inherits from WebService rather than the default Sys-
tem.Object. The service works either way, but by inheriting from WebService it
gains the functionality of an ASP.NET application.
System.Web.Services.WebService Class
The WebService base class exposes properties to a Web Service that enable it to
access the intrinsic ASP.NET objects introduced in Chapter 17, “The ASP.NET
Application Environment.”
• Context. Exposes the HttpContext class. Recall that this class pro-
vides a wealth of information about the current request.
• User. Returns the IPrincipal object that provides user authentica-
tion information and can be used to determine whether the request is
authorized (see Chapter 17).
To demonstrate the use of state information, let’s extend our example to use the
Application object to keep track of the number of times the Web Service is called.
Our first step is to add the statement in bold to the GetDayBorn method:
Next, add a method to the class that internally returns the “count” value of the
Application object. Finally, add a method, GetVisitors, which allows clients to
view the number of calls to the Web Service as a Web Service call:
[WebService(Namespace="http://www.dateservices.com/ws/",
Description="<b>Web Service that Provides Date
Functions.</b>")]
public class BirthDay : System.Web.Services.WebService
Be aware that Windows Forms clients do not provide the same support for session
state variables that a browser does. Sessions rely on cookies to preserve state data.
Unlike browsers, Windows Forms applications do not store cookies. Thus, the ser-
vice thinks that each request is the first request. There is a work-around, however,
which is described in the following section on building a Web Services client.
The only rule to follow when (if) setting up caching is to do it judiciously. Caching
greatly improves performance for methods that are requested frequently and imple-
ment logic that requires lengthy processing. On the other hand, methods such as
GetDayBorn in our example can actually hurt performance because most requests to
it will yield unique results.
The test form is only available for requests from the local
machine
<system.web>
<webServices>
<protocols>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>
</system.web>
Recall that navigating to a Web Services page with no parameters brings up a help
page that describes how to use the service, and provides links to invoke the Web Ser-
vice methods or display the WSDL. If you do not want to expose this information,
you can disable the display of help pages by removing the Documentation protocol.
To do so, place the following statement in the <protocols> element of the
web.config file:
Web Class
Call to Web Method 1
Figure 18-1). The client passes three arguments to the service and prints the string it
returns. Recall that the service consists of the Web class Birthday and a single
method GetDayBorn:
Although the Web Service method can reside on a remote machine anywhere on
the Internet, we can approach the client design as if we were within the same assem-
bly. Here is the client code contained in the file bdClient.cs:
using System;
using System.Web.Services;
public class BirthDayClient
{
static void Main(string[] args)
{
BirthDay bd = new BirthDay();
string dayOfWeek = bd.GetDayBorn(12,20,1963);
Console.WriteLine(dayOfWeek);
}
}
Compiling this, of course, results in an error stating that BirthDay and bd can-
not be found. We resolve this by creating a proxy class that performs the remote
call, yet can be accessed locally by the client. The code for the proxy class is
obtained by feeding the WSDL information that defines the Web Service into the
.NET wsdl.exe utility.
Option Description
/appsettingurlkey Specifies a key within the client’s *.config file that contains the
/urlkey: URL of the Web Service. The default is to hardcode the URL
within the proxy class.
/language Specifies the language to use for generating the proxy class.
/l: Choices are:
CS (C#), VB (Visual Basic), JS (JScript), and VJS (Visual J#).
C# is the default.
/out Specifies file in which the generated proxy code is placed. The
default is to use the XML Web Service name with an extension
reflecting the language used for the code.
/server Generates an abstract class for the Web Service. This is used when
the WSDL document is used to create a Web Service.
The following statement creates a proxy class from the BirthDayWS.asmx Web
Service and places it in c:\BDProxy.cs:
This proxy source code can be used in two ways: include it in the client’s source
code, or compile it into a DLL and add a reference to this DLL when compiling the
client code. Let’s look first at the DLL approach. This command line compiles the
source code and links two DLL files containing classes required by the proxy:
We are now ready to compile the client source code into an executable file,
bdClient.exe:
18.3 Building an XML Web Service Client 887
If we add the proxy code directly to the bdClient.cs file and compile it, the
result is a module that produces the same output as a version that links the proxy as a
separate DLL file.
Core Note
Listing 18-2 examines the proxy source code to understand how it converts a cli-
ent’s call into a remote Web Service invocation.
Observe that the proxy class has the same name as the Web Service class (Birth-
day) it represents, and implements a method (GetDayBorn) having the same name
as the Web Service method. The supporting code is quite different, however. It con-
tains transport logic rather than application logic. This code uses methods provided
by the System.Web.Services.Protocols.SoapHttpClientProtocol class from
which the proxy class derives. Table 18-2 summarizes of the more useful members of
the class.
Member Description
Proxy Gets or sets proxy information needed to make a Web Service call
through a firewall.
Timeout Gets or sets the timeout (in milliseconds) a client waits for a
synchronous call to a Web Service to be completed.
Url Gets or sets the URL used to access the Web server.
This is the most intuitive way to access a service because a client can be totally
unaware of the proxy and execute a call to the Web Service method, using its actual
name. However, because a synchronous call by definition does not require a
response, the client code should include a timeout interval and handle any exception
that occurs if the interval is exceeded. The following code sets a timeout of four sec-
onds and handles any System.Net.WebException that is thrown.
Core Note
This “polling” approach can be used to periodically check the status of the
request. Another approach is to have the Web Service call a method in the client
code when the response is ready. The AsyncCallback delegate is used to specify
the method that receives the callback.
These enable client code to treat the Web Service invocation as an event that can
be handled by a local event handler. Only a few lines of code are required to hook up
an event handler to the event defined in the proxy:
2. Web Services Descriptive Language (WSDL) 1.1—W3C Note, March 15, 2002.
896 Chapter 18 ■ XML Web Services
<Definitions>
This is the root element of the WSDL document. It declares multiple namespaces
used throughout the document, and contains all of the other elements:
<definitions xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:s0="http://tempuri.org/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
targetNamespace="http://tempuri.org/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<Types>
This element contains an XSD (XML Schema Definition Language) schema that
describes the data types publicly exposed by the service: the parameters passed in
the Web Service request, and the response:
<types>
<s:schema elementFormDefault="qualified"
targetNamespace="http://tempuri.org/">
<s:element name="GetDayBorn">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="mo"
type="s:int" />
<s:element minOccurs="1" maxOccurs="1" name="day"
type="s:int" />
<s:element minOccurs="1" maxOccurs="1" name="yr"
type="s:int" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetDayBornResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="GetDayBornResult"
type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
</s:schema>
</types>
18.4 Understanding WSDL and SOAP 897
<Message>
Defines the data that is exchanged between the Web Service provider and consumer.
Each message is assigned a unique name and defines its parameters—if any—in
terms of names provided by the types element:
<message name="GetDayBornSoapIn">
<part name="parameters" element="s0:GetDayBorn" />
</message>
<message name="GetDayBornSoapOut">
<part name="parameters" element="s0:GetDayBornResponse" />
</message>
<PortType>
Each <portType> element defines the <Message> elements that belong to a com-
munications transport. The name attribute specifies the name for the transport. The
<portType> element contains <operation> elements that correspond to the meth-
ods in the Web Service. The <input> and <output> elements define the messages
associated with the operation. Four types of operations are supported: one-way, in
which the service receives a message; request-response, in which the client sends a
request; solicit-response, in which the service first sends a message to the client; and
notification, where the service sends a message to clients.
<portType name="BirthDaySoap">
<operation name="GetDayBorn">
<documentation>Return day of week for any date</documentation>
<input message="s0:GetDayBornSoapIn" />
<output message="s0:GetDayBornSoapOut" />
</operation>
</portType>
<Binding>
A set of rules that describe how the <portType> operation is transmitted over the
wire. Wire protocols available are HTTP GET, HTTP POST, and SOAP. This example
demonstrates how SOAP is specified.
As acknowledgement of the importance of SOAP as a transport protocol, the
WSDL 1.1 specification includes extensions for SOAP 1.1. These extension elements
include <binding>, <operation>, and <body>.
<input>
<soap:body use="literal" />
</input>
<output>
<soap:body use="literal" />
</output>
</operation>
</binding>
Note that the <operation> element specifies the entry point for the Web
method that is called on the server. One other thing to be aware of is the style
attribute in the binding element. This value, which may be document or rpc, speci-
fies how an operation is formatted. By default, .NET sets this value to document. To
specify rpc, you must apply the SoapRpcMethodAttribute to the Web method:
[SoapRpcMethod][WebMethod]
public string GetDayBorn(string month, int day, int yr)
Although there is a rather spirited debate among WSDL purists as to which is bet-
ter, you can safely ignore the histrionics and use the .NET default. However, know-
ing your options will enable you to easily work with third parties that may have a
preference.
<Service>
Identifies the location of the Web Service. Specifically, it lists the name of the Web
Service class, the URL, and references the binding for this endpoint.
<service name="BirthDay">
<port name="BirthDaySoap" binding="s0:BirthDaySoap">
<soap:address location=
"http://localhost/ws/BirthDayWs.asmx" />
</port>
</service>
the BirthDayWS Web Service example. The format of these messages is described
on the Web page containing the desired method(s) of the Web Service.
Listing 18-3 shows the XML template for the SOAP message that is sent to the
server.
A SOAP envelope, as the name implies, is conceptually a container for the mes-
sage. The SOAP header represents a way to extend the basic message. It may contain
additional information about the message and—as we will see later—can be used to
add a measure of security. The SOAP body contains what one would regard as the
actual data: the arguments sent to the service and the response. The contents of the
<Body> element in this example consist of the method name and its three parame-
ters that correspond to the call made within the client code:
Core Note
A question that arises when a Web Service is being invoked from a Web
page is whether Forms Authentication (Chapter 17) can be used. The
short answer is yes, but it requires a special implementation. Unlike a
regular Web page, a Web Service does not recognize the authentication
cookie created by the separate login screen. The solution is to add a
login method to the Web Services that creates the authentication cookie.
In addition, each service must check user authentication before
performing its operation. The coding requirements are comparable to
using SOAP header authentication.
Listing 18-5 demonstrates a Web Service that checks the header for an ID and
password before making the service available to the requestor. It consists of the code
taken from Listing 18-1 and updated to include features required to access a SOAP
header.
This example illustrates the four steps that are followed to receive and access any
SOAP header data:
1. Create a class to represent the data in the header. This class must
inherit from the SoapHeader class. SOAPHeaderContent serves the
purpose in this code.
2. Add a member to the Web Service class that is the same type as the
class created in Step 1. The example uses headerInfo.
18.4 Understanding WSDL and SOAP 903
The proxy for this Web Service includes the class that represents the header con-
tents. The client creates an instance of this class and assigns a password and user
name. The class instance is assigned to a field that is now a member of the proxy class
representing the Web Service class. In this example, Birthday now has a field
named SOAPHeaderContentValue. As you have probably guessed, .NET creates
this field name by appending Value to the name of the class that accesses the header
info (SOAPHeader).
using System;
using System.Web.Services;
using system.Web.Services.Protocols;
using System.Text.RegularExpressions;
public class BirthDayClient
{
static void Main(string[] args)
{
SOAPHeaderContent acctInfo = new SOAPHeaderContent();
acctInfo.UserName = "Vincent";
acctInfo.PassWord = "arles";
BirthDay bd = new BirthDay();
bd.SOAPHeaderContentValue = acctInfo;
try {
string dayOfWeek = bd.GetDayBorn(12,20,1963);
Console.WriteLine(dayOfWeek);
} catch (SoapException ex)
{
// Extract Soap error message
// Be sure to add:
// using System.Text.RegularExperssions
Match errMatch = Regex.Match(ex.Message,":(.*)");
Console.WriteLine(errMatch.Groups[1]);
}
}
}
904 Chapter 18 ■ XML Web Services
• Message. The error message that explains the reason for the
exception.
• Actor. The URL of the Web Service that threw the exception.
• Code. An XMLQualifiedName object that specifies one of four SOAP
fault codes that categorize the exception. These fault codes are repre-
sented as static fields of the SoapException class.
• Detail. An XMLNode object containing application-specific informa-
tion about the error.
The Message and Code properties are used most frequently in processing a
SOAP exception. The message is verbose: It consists of the full namespace qualifica-
tion of the SoapException class followed by the actual message and the name of
the Web method where the exception occurred. A regex was used in the preceding
client code to extract the actual message.
The SoapException class contains static fields that can be compared with the
Code value to broadly classify the exception. These fields include the following:
Here is a code sample that demonstrates using the Code property. It first extracts
a message embedded in the Message property and prints it. Then it checks the Code
property to classify the excepton.
try {
string dayOfWeek = bd.GetDayBorn(12,20,1963);
Console.WriteLine(dayOfWeek);
}
catch (SoapException ex)
{
Match errMatch = Regex.Match(ex.Message,":(.*)");
Console.WriteLine(errMatch.Groups[1]);
// Check various fault codes here
if(ex.Code == SoapException.ClientFaultCode)
{
Console.WriteLine("Problem with Client message.");
}
if(ex.Code == SoapException.ServerFaultCode)
{
Console.WriteLine("Problem with Server. Try again.");
}
}
SOAP Security
The preceding example illustrates a lightweight technique for using SOAP headers to
authenticate a user. Its main drawback is that the contents of the SOAP headers are
passed as cleartext. To overcome this, you can add a layer of encryption by using this
technique in conjunction with Secure Sockets Layer (SSL).
As an alternative to this and other ad hoc approaches to SOAP security, there is
now a Web Services Security (WS-Security) specification3 that defines enhancements
to SOAP messaging. Its objectives are to ensure the following:
3. http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-soap-message-security-1.0.pdf
906 Chapter 18 ■ XML Web Services
• XML serialization can only be used with classes that contain a public
parameterless constructor. For example, you may have a Web Service
that returns a hash table because it has the constructor public
Hashtable(). On the other hand, the Bitmap class does not have a
parameterless constructor and cannot be used as a return type.
• Read-only properties in a class cannot be serialized. The property
must have a get and set accessor and be public.
• Fields must be public to be serialized; private ones are ignored.
In this section, we work with two Web Service examples that illustrate the use of
complex data. Our first example creates a Web Service that accepts the name of an
image and returns it as a byte stream. The second example creates a client to use the
Amazon Web Services provided by Amazon.com, Inc. These services offer a rich—
but practical—sampling of accessing multiple Web methods and processing a wide
variety of custom classes.
18.5 Using Web Services with Complex Data Types 907
tempStream.ToArray()
This byte array is then sent to the Web client (see Listing 18-6).
Our client code receives the byte stream representing an image and reassembles it
into a Bitmap object. Because the Bitmap constructor accepts a stream type, we
convert the byte array to a MemoryStream and pass it to the constructor. It can now
be manipulated as an image.
Method Description
Method Description
Each call to a Web method passes an object that describes the search request.
This object is different for each method—for example, AuthorSearchRequest
requires an AuthorRequest object, whereas KeyWordSearchRequest requires
an instance of the KeywordRequest class. These classes expose almost identical
fields. Each contains a unique string field that represents the search query, five
other required fields common to each class, and some optional fields for sorting or
specifying a locale. Table 18-4 lists unique and shared fields for each method listed
in Table 18-3.
Table 18-4 Selected Fields for Classes That Define a Search Request
Field Description
KeywordRequest.Keyword These are string values containing the search value or query.
AsinRequest.Asin For example:
ActorRequest.Actor
AuthorRequest.Author PowerRequest pr = new PowerRequest();
PowerRequest.Power pr.Power = "author:Nabokov and
keyword:butterfly";
Field Description
Reviews[] Reviews The Reviews class contains several fields relating to reviews
of the book:
string AvgCustomerRating
string TotalCustomerReviews
CustomerReview CustomerReviews
string Comment
string Rating
This is only a small sample of the fields available in the Details class. There is a
particularly rich set of fields worth exploring that define video products.
wsdl.exe /out:c:\client\AZProxy.cs
http://soap.amazon.com/schema3/AmazonWebServices.wsdl
Next, we create an assembly, AZProxy.dll, containing the proxy that will be used
by client code. It is linked to assemblies containing .NET Web classes required by
the application.
You can make a quick test of the service using this barebones application,
azclient.cs:
using System;
using System.Web.Services;
namespace webclient.example {
public class AmazonClient
{
static void Main(string[] args)
{
// Search for books matching keyword "butterflies"
AmazonSearchService amazon = new AmazonSearchService();
KeywordRequest kwRequest = new KeywordRequest();
kwRequest.keyword = "butterflies";
kwRequest.type = "heavy";
kwRequest.devtag= "*************"; // your developer token
kwRequest.mode = "books"; // search books only
kwRequest.tag = "webservices-20";
kwRequest.page = "1"; // return first page
ProductInfo products =
amazon.KeywordSearchRequest(kwRequest);
Console.WriteLine(products.TotalResults); // Results count
}
}
}
Each Search button has a Click event handler that calls a method to create an
appropriate request object and send it to the Amazon Web Service. A successful call
returns a ProductInfo object containing information about up to 10 books meeting
the search criteria. Listing 18-7 displays code that creates an AuthorRequest
object, sends it to the Web Service, and calls FillListView to display the results in
the ListView control.
{
MaxPages = Convert.ToInt32(products.TotalPages);
ListViewItem rowItem;
string auth,rev;
for (int i=0; i< products.Details.Length; i++)
{
rowItem = new
ListViewItem(products.Details[i].ProductName);
// Add Author. Make sure author exists.
object ob = products.Details[i].Authors;
if (ob != null) auth =
products.Details[i].Authors[0]; else auth="None";
rowItem.SubItems.Add(auth);
// Add Price
rowItem.SubItems.Add(products.Details[i].OurPrice);
// Add Average Rating
ob = products.Details[i].Reviews;
if (ob != null) rev =
products.Details[i].Reviews.AvgCustomerRating;
else rev="None";
rowItem.SubItems.Add(rev);
// Add Date Published
rowItem.SubItems.Add(
products.Details[i].ReleaseDate);
listView1.Items.Add(rowItem);
}
}
The keyword, title, and power searches use an identical approach: Each has a rou-
tine comparable to AuthorReq that creates its own request object. The only significant
difference pertains to the power search that creates a Boolean query from the search
field values. The format for this type query is field:value AND field2:value
AND field3:value. For example:
Expect100Continue
This property determines whether a POST request should expect to receive a
100-Continue response from the server to indicate that the data can be posted. If this
value is set to true, only the request header portion of a request is sent to the server.
If the server finds no problem with the header, it returns a 100-Continue response,
and the data is then sent. Two trips are required. If the property is set to false, the
initial request includes the headers and data. If it is rejected by the server, the data
has been sent unnecessarily; if it is accepted, a second trip is not necessary.
Because Web Service calls tend to pass small amounts of data, it can be beneficial
to turn this property off. Even if a request is rejected, only a small amount of data
will have been sent.
The crux of the algorithm is that small amounts of data should continue to be col-
lected by TCP until it receives acknowledgment to send the data. .NET institutes a
delay of up to 200 milliseconds to collect additional data for a packet. For a typically
small Web Service request, there may be no reason to include this delay. It’s an
option you can experiment with.
To set the Expect100Continue and UseNagleAlgorithm properties, it is nec-
essary to get a reference to the ServicePoint object being used to handle the Web
request. This is done in the proxy code on the client side. Refer to Listing 18-2, and
you’ll see that the proxy code consists of a class derived from the base SoapHttp-
ClientProtocol class. By overriding the inherited GetWebRequest method, you
can customize the WebRequest object before the request is sent to the Web Service.
Add the following code to override the GetWebRequest method. Inside the
method, you use the Uri object to get the ServicePoint. Its properties can then be
turned off or on to test performance:
18.7 Summary
.NET supports the development of both Web Service provider and Web Service con-
sumer applications. On the server side, a .NET Web Service is easily constructed
using a WebService directive to define the Web Service class and a WebMethod
attribute to identify methods accessible to HTTP requests. For the Web Service to be
used by clients, a description of the service(s) must be made available. This is the pur-
pose of the Web Service Description Language (WSDL) contract. This XML file
describes the service, the methods available, and a description of the arguments each
method accepts. You can generate the contract by using a browser to navigate to a
URL address that consists of the URL of the Web Service with ?WSDL appended to it.
The WSDL information is used on the client side to create a Proxy class that is
used to actually communicate with the Web Service. This proxy is created using the
wsdl.exe utility or within a Visual Studio.NET project as part of the Add Web Ser-
vice option. The proxy defines methods that permit the Web Service methods to be
accessed synchronously or asynchronously. The latter technique returns control to
the application while the request is being processed.
SOAP (Simple Object Access Protocol) describes the XML format that is used to
transport information between a Web Service provider and consumer. Its structure
consists of an envelope, optional header, and body. The body contains the actual data
or message; the header may contain annotation about the message. One such use is
to include user authentication information. The main advantage of SOAP over the
other two wire protocols—HTTP GET and HTTP POST—is that it supports the trans-
mission of non-text data such as images and objects. We demonstrated this by build-
ing a client to access the Amazon Web Services.
3. How can we create a Web Service that exposes two methods having
the same name?
5. You are using a Web Service that has heavy traffic or is subject to
downtime. How can you ensure that your client program waits no
longer than 10 seconds for a request?
6. Which WSDL element contains the URL of the target Web Service?
a. <Definitions>
b. <Message>
c. <Service>
d. <Binding>
In This Appendix
Chapter.
Section Feature Description
3.4 Property access The get and set accessors can now have modifiers:
modifier protected set { name= value; }
3.7 Anonymous methods Eliminates the need for a separate event handler
for delegates.
921
922 Appendix A ■ Features Specific to .NET 2.0 and C# 2.0
Chapter.
Section Feature Description
4.4 System.Collection. Holds a generic version of the collection classes. New classes
Generic namespace include List<>, Dictionary<>, and Stack<>.
10.2 XmlReaderSettings class New class used to define the behavior of the XmlReader object.
Notably, it allows XML validation to be performed automatically
while reading nodes.
11.1 ProviderFactory class Used to return connection and command objects from a
specified data provider:
string pvdr="System.Data.SqlClient";
DBProviderFactory factory;
factory = DBProviderFactories.GetFactory(pvdr);
DbConnection conn = factory.CreateConnection();
12.3 DataGridView class WinForms class that supersedes the DataGrid control. Includes
a virtual mode that permits the application to dynamically
control the contents of the grid
13.4 Semaphore class A new synchronization class that is used to control the number of
threads that can access a resource.
16.1 Partial classes A code-behind page can now contain a partial class that extends
the class in the main Web page.
Appendix A ■ Features Specific to .NET 2.0 and C# 2.0 923
Chapter.
Section Feature Description
16.1 PostBackUrl property Permits a Web page to easily post its content to a Web page other
than itself.
16.3 GridView control ASP.NET’s new control that supersedes the DataGrid control.
16.3 AppendDataBoundItems Indicates whether data bound to a list control should overwrite
property existing data in the control.
16.3 DataSource controls The logic for accessing a data source can now be encapsulated in
controls designed to serve as a bridge to a variety of data sources
including SQL and XML.
16.5 Master pages Allow a Web page to be created as a template that provides a
consistent interface to users. New content pages are created by
replacing the placeholders in the template.
17.2 <connectionStrings> New section in the web.config file reserved for connection
section strings.
18.3 Web Service response .NET now includes in a Web proxy a delegate and event that fire
handled as event when a Web Service returns a response. An application can pro-
cess the results in its custom event handler.
DATAGRIDVIEW
EVENTS AND
DELEGATES
In This Appendix
This section contains two tables that describe the events and delegates
associated with the System.Windows.Forms.DataGridView
control. Table B-1 contains a list of the events (first column) and
corresponding delegates (second column). Table B-2 contains the
parameters for the delegate.
B
The contents of both tables are generated using reflection to extract all the events in
the namespace associated with the DataGridView control and write them to a text
file. The output files can be sorted in a text editor and imported into word processor’s
table format. Alternatively, you can extend the program to create XML or HTML
formatted output. Here is the code to create Table B-1. The code used to create
Table B-2 follows Table B-1.
925
926 Appendix B ■ DataGridView Events and Delegates
AllowUserToAddRowsChanged System.EventHandler
AllowUserToDeleteRowsChanged System.EventHandler
AllowUserToOrderColumnsChanged System.EventHandler
AlternatingRowsDefaultCellStyle System.EventHandler
Changed
AutoGenerateColumnsChanged System.EventHandler
AutoSizeChanged System.EventHandler
AutoSizeColumnCriteriaChanged Forms.DataGridViewAutoSizeColumnCriteria
EventHandler
AutoSizeColumnHeadersEnabledChanged System.EventHandler
AutoSizeRowHeadersModeChanged Forms.DataGridViewAutoSizeModeEventHandler
AutoSizeRowsModeChanged Forms.DataGridViewAutoSizeModeEventHandler
BackColorChanged System.EventHandler
BackgroundColorChanged System.EventHandler
BackgroundImageChanged System.EventHandler
BackgroundImageLayoutChanged System.EventHandler
BindingContextChanged System.EventHandler
BorderStyleChanged System.EventHandler
CancelRowEdit Forms.QuestionEventHandler
CausesValidationChanged System.EventHandler
Appendix B ■ DataGridView Events and Delegates 927
CellBeginEdit Forms.DataGridViewCellCancelEventHandler
CellBorderStyleChanged System.EventHandler
CellClick Forms.DataGridViewCellEventHandler
CellContentClick Forms.DataGridViewCellEventHandler
CellContextMenuStripChanged Forms.DataGridViewCellEventHandler
CellContextMenuStripNeeded Forms.DataGridViewCellContextMenuStripNeeded
EventHandler
CellEndEdit Forms.DataGridViewCellEventHandler
CellEnter Forms.DataGridViewCellEventHandler
CellErrorTextChanged Forms.DataGridViewCellEventHandler
CellErrorTextNeeded Forms.DataGridViewCellErrorTextNeededEvent
Handler
CellFormatting Forms.DataGridViewCellFormattingEventHandler
CellLeave Forms.DataGridViewCellEventHandler
CellMouseClick Forms.DataGridViewCellMouseEventHandler
CellMouseDoubleClick Forms.DataGridViewCellMouseEventHandler
CellMouseDown Forms.DataGridViewCellMouseEventHandler
CellMouseEnter Forms.DataGridViewCellEventHandler
CellMouseLeave Forms.DataGridViewCellEventHandler
CellMouseMove Forms.DataGridViewCellMouseEventHandler
CellMouseUp Forms.DataGridViewCellMouseEventHandler
CellPainting Forms.DataGridViewCellPaintingEventHandler
CellParsing Forms.DataGridViewCellParsingEventHandler
CellStateChanged Forms.DataGridViewCellStateChangedEventHandler
CellStyleChanged Forms.DataGridViewCellEventHandler
CellStyleContentChanged Forms.DataGridViewCellStyleContentChangedEvent
Handler
928 Appendix B ■ DataGridView Events and Delegates
CellToolTipTextChanged Forms.DataGridViewCellEventHandler
CellToolTipTextNeeded Forms.DataGridViewCellToolTipTextNeededEvent
Handler
CellValidated Forms.DataGridViewCellEventHandler
CellValidating Forms.DataGridViewCellValidatingEventHandler
CellValueChanged Forms.DataGridViewCellEventHandler
CellValueNeeded Forms.DataGridViewCellValueEventHandler
CellValuePushed Forms.DataGridViewCellValueEventHandler
ChangeUICues Forms.UICuesEventHandler
Click System.EventHandler
ColumnContextMenuStripChanged Forms.DataGridViewColumnEventHandler
ColumnDataPropertyNameChanged Forms.DataGridViewColumnEventHandler
ColumnDefaultCellStyleChanged Forms.DataGridViewColumnEventHandler
ColumnDisplayIndexChanged Forms.DataGridViewColumnEventHandler
ColumnDividerWidthChanged Forms.DataGridViewColumnEventHandler
ColumnHeaderCellChanged Forms.DataGridViewColumnEventHandler
ColumnHeaderMouseClick Forms.DataGridViewCellMouseEventHandler
ColumnHeaderMouseDoubleClick Forms.DataGridViewCellMouseEventHandler
ColumnHeadersBorderStyleChanged System.EventHandler
ColumnHeadersDefaultCellStyleChanged System.EventHandler
ColumnHeadersHeightChanged System.EventHandler
ColumnMinimumWidthChanged Forms.DataGridViewColumnEventHandler
ColumnNameChanged Forms.DataGridViewColumnEventHandler
ColumnSortModeChanged Forms.DataGridViewColumnEventHandler
ColumnStateChanged Forms.DataGridViewColumnStateChangedEvent
Handler
ColumnToolTipTextChanged Forms.DataGridViewColumnEventHandler
Appendix B ■ DataGridView Events and Delegates 929
ColumnWidthChanged Forms.DataGridViewColumnEventHandler
ContextMenuChanged System.EventHandler
ContextMenuStripChanged System.EventHandler
ControlAdded Forms.ControlEventHandler
ControlRemoved Forms.ControlEventHandler
CurrentCellChanged System.EventHandler
CurrentCellDirtyStateChanged System.EventHandler
CursorChanged System.EventHandler
DataBindingComplete Forms.DataGridViewBindingCompleteEventHandler
DataError Forms.DataGridViewDataErrorEventHandler
DataMemberChanged System.EventHandler
DataSourceChanged System.EventHandler
DefaultCellStyleChanged System.EventHandler
DefaultValuesNeeded Forms.DataGridViewRowEventHandler
Disposed System.EventHandler
DockChanged System.EventHandler
DoubleClick System.EventHandler
DragDrop Forms.DragEventHandler
DragEnter Forms.DragEventHandler
DragLeave System.EventHandler
DragOver Forms.DragEventHandler
EditingControlShowing Forms.DataGridViewEditingControlShowingEvent
Handler
EditModeChanged System.EventHandler
EnabledChanged System.EventHandler
Enter System.EventHandler
930 Appendix B ■ DataGridView Events and Delegates
FontChanged System.EventHandler
ForeColorChanged System.EventHandler
GiveFeedback Forms.GiveFeedbackEventHandler
GotFocus System.EventHandler
GridColorChanged System.EventHandler
HandleCreated System.EventHandler
HandleDestroyed System.EventHandler
HelpRequested Forms.HelpEventHandler
ImeModeChanged System.EventHandler
Invalidated Forms.InvalidateEventHandler
KeyDown Forms.KeyEventHandler
KeyPress Forms.KeyPressEventHandler
KeyUp Forms.KeyEventHandler
Layout Forms.LayoutEventHandler
Leave System.EventHandler
LocationChanged System.EventHandler
LostFocus System.EventHandler
MarginChanged System.EventHandler
MouseCaptureChanged System.EventHandler
MouseClick Forms.MouseEventHandler
MouseDoubleClick Forms.MouseEventHandler
MouseDown Forms.MouseEventHandler
MouseEnter System.EventHandler
MouseHover System.EventHandler
MouseLeave System.EventHandler
MouseMove Forms.MouseEventHandler
Appendix B ■ DataGridView Events and Delegates 931
MouseUp Forms.MouseEventHandler
MouseWheel Forms.MouseEventHandler
Move System.EventHandler
MultiSelectChanged System.EventHandler
NewRowNeeded Forms.DataGridViewRowEventHandler
PaddingChanged System.EventHandler
Paint Forms.PaintEventHandler
ParentChanged System.EventHandler
QueryAccessibilityHelp Forms.QueryAccessibilityHelpEventHandler
QueryContinueDrag Forms.QueryContinueDragEventHandler
ReadOnlyChanged System.EventHandler
RegionChanged System.EventHandler
Resize System.EventHandler
ResizeBegin System.EventHandler
ResizeEnd System.EventHandler
RightToLeftChanged System.EventHandler
RowContextMenuStripChanged Forms.DataGridViewRowEventHandler
RowContextMenuStripNeeded Forms.DataGridViewRowContextMenuStripNeeded
EventHandler
RowDefaultCellStyleChanged Forms.DataGridViewRowEventHandler
RowDirtyStateNeeded Forms.QuestionEventHandler
RowDividerHeightChanged Forms.DataGridViewRowEventHandler
RowEnter Forms.DataGridViewCellEventHandler
RowErrorTextChanged Forms.DataGridViewRowEventHandler
RowErrorTextNeeded Forms.DataGridViewRowErrorTextNeededEvent
Handler
932 Appendix B ■ DataGridView Events and Delegates
RowHeaderCellChanged Forms.DataGridViewRowEventHandler
RowHeaderMouseClick Forms.DataGridViewCellMouseEventHandler
RowHeaderMouseDoubleClick Forms.DataGridViewCellMouseEventHandler
RowHeadersBorderStyleChanged System.EventHandler
RowHeadersDefaultCellStyleChanged System.EventHandler
RowHeadersWidthChanged System.EventHandler
RowHeightChanged Forms.DataGridViewRowEventHandler
RowHeightInfoNeeded Forms.DataGridViewRowHeightInfoNeededEvent
Handler
RowHeightInfoPushed Forms.DataGridViewRowHeightInfoPushedEvent
Handler
RowLeave Forms.DataGridViewCellEventHandler
RowMinimumHeightChanged Forms.DataGridViewRowEventHandler
RowPostPaint Forms.DataGridViewRowPostPaintEventHandler
RowPrePaint Forms.DataGridViewRowPrePaintEventHandler
RowStateChanged Forms.DataGridViewRowStateChangedEventHandler
RowUnshared Forms.DataGridViewRowEventHandler
RowValidated Forms.DataGridViewCellEventHandler
RowValidating Forms.DataGridViewCellCancelEventHandler
RowsAdded Forms.DataGridViewRowsAddedEventHandler
RowsDefaultCellStyleChanged System.EventHandler
RowsDeleted Forms.DataGridViewRowsDeletedEventHandler
Scroll Forms.ScrollEventHandler
SelectionChanged System.EventHandler
SizeChanged System.EventHandler
SortCompare Forms.DataGridViewSortCompareEventHandler
Sorted System.EventHandler
Appendix B ■ DataGridView Events and Delegates 933
StyleChanged System.EventHandler
SystemColorsChanged System.EventHandler
TabIndexChanged System.EventHandler
TabStopChanged System.EventHandler
TextChanged System.EventHandler
UserAddedRow Forms.DataGridViewRowEventHandler
UserDeletedRow Forms.DataGridViewRowEventHandler
UserDeletingRow Forms.DataGridViewRowCancelEventHandler
Validated System.EventHandler
Validating System.ComponentModel.CancelEventHandler
VisibleChanged System.EventHandler
This code is added to the code for the first table in order to gather information about the dele-
gates’ signature. The signatures are stored in a hash table and written to a text file of your choice.
The content of the output file can then be loaded into a table with three columns.
events.Add(gev.Name, gev.EventHandlerType);
Type deleg = gev.EventHandlerType;
if(!signature.Contains(deleg))
{
// Get parameters
MethodInfo invoke = deleg.GetMethod("Invoke");
ParameterInfo[] pars = invoke.GetParameters();
string sig = "";
foreach (ParameterInfo p in pars)
{
Console.WriteLine(p.ParameterType);
sig += mytab +p.ParameterType ;
}
signature.Add(deleg, sig);
}
934 Appendix B ■ DataGridView Events and Delegates
Note: The first parameter is always System.Object, which is not shown in the table.
Delegate Parameter 2
ControlEventHandler Forms.ControlEventArgs
DataGridViewAutoSizeColumnCriteria Forms.DataGridViewAutoSizeColumn
EventHandler CriteriaEventArgs
DataGridViewAutoSizeModeEventHandler Forms.DataGridViewAutoSizeModeEvent
Args
DataGridViewBindingCompleteEventHandler Forms.DataGridViewBindingCompleteEvent
Args
DataGridViewCellCancelEventHandler Forms.DataGridViewCellCancelEventArgs
DataGridViewCellContextMenuStripNeeded Forms.DataGridViewCellContextMenuStrip
EventHandler NeededEventArgs
DataGridViewCellErrorTextNeededEvent Forms.DataGridViewCellErrorTextNeeded
Handler EventArgs
DataGridViewCellEventHandler Forms.DataGridViewCellEventArgs
DataGridViewCellFormattingEventHandler Forms.DataGridViewCellFormattingEvent
Args
DataGridViewCellMouseEventHandler Forms.DataGridViewCellMouseEventArgs
DataGridViewCellPaintingEventHandler Forms.DataGridViewCellPaintingEvent
Args
DataGridViewCellParsingEventHandler Forms.DataGridViewCellParsingEventArgs
DataGridViewCellStateChangedEventHandler Forms.DataGridViewCellStateChanged
EventArgs
DataGridViewCellStyleContentChangedEvent Forms.DataGridViewCellStyleContent
Handler ChangedEventArgs
DataGridViewCellToolTipTextNeededEvent Forms.DataGridViewCellToolTipText
Handler NeededEventArgs
DataGridViewCellValidatingEventHandler Forms.DataGridViewCellValidatingEvent
Args
DataGridViewCellValueEventHandler Forms.DataGridViewCellValueEventArgs
DataGridViewColumnEventHandler Forms.DataGridViewColumnEventArgs
DataGridViewColumnStateChangedEvent Forms.DataGridViewColumnStateChanged
Handler EventArgs
Appendix B ■ DataGridView Events and Delegates 935
Delegate Parameter 2
DataGridViewDataErrorEventHandler Forms.DataGridViewDataErrorEventArgs
DataGridViewEditingControlShowingEvent Forms.DataGridViewEditingControl
Handler ShowingEventArgs
DataGridViewRowCancelEventHandler Forms.DataGridViewRowCancelEventArgs
DataGridViewRowContextMenuStripNeeded Forms.DataGridViewRowContextMenuStrip
EventHandler NeededEventArgs
DataGridViewRowErrorTextNeededEvent Forms.DataGridViewRowErrorTextNeeded
Handler EventArgs
DataGridViewRowEventHandler Forms.DataGridViewRowEventArgs
DataGridViewRowHeightInfoNeededEvent Forms.DataGridViewRowHeightInfoNeeded
Handler EventArgs
DataGridViewRowHeightInfoPushedEvent Forms.DataGridViewRowHeightInfoPushed
Handler EventArgs
DataGridViewRowPostPaintEventHandler Forms.DataGridViewRowPostPaintEvent
Args
DataGridViewRowPrePaintEventHandler Forms.DataGridViewRowPrePaintEventArgs
DataGridViewRowStateChangedEventHandler Forms.DataGridViewRowStateChangedEvent
Args
DataGridViewRowsAddedEventHandler Forms.DataGridViewRowsAddedEventArgs
DataGridViewRowsDeletedEventHandler Forms.DataGridViewRowsDeletedEventArgs
DataGridViewSortCompareEventHandler Forms.DataGridViewSortCompareEventArgs
DragEventHandler Forms.DragEventArgs
GiveFeedbackEventHandler Forms.GiveFeedbackEventArgs
HelpEventHandler Forms.HelpEventArgs
InvalidateEventHandler Forms.InvalidateEventArgs
KeyEventHandler Forms.KeyEventArgs
KeyPressEventHandler Forms.KeyPressEventArgs
LayoutEventHandler Forms.LayoutEventArgs
MouseEventHandler Forms.MouseEventArgs
936 Appendix B ■ DataGridView Events and Delegates
Delegate Parameter 2
PaintEventHandler Forms.PaintEventArgs
QueryAccessibilityHelpEventHandler Forms.QueryAccessibilityHelpEventArgs
QueryContinueDragEventHandler Forms.QueryContinueDragEventArgs
QuestionEventHandler Forms.QuestionEventArgs
ScrollEventHandler Forms.ScrollEventArgs
System.ComponentModel.CancelEventHandler System.ComponentModel.CancelEventArgs
System.EventHandler System.EventArgs
UICuesEventHandler Forms.UICuesEventArgs
This page intentionally left blank
ANSWERS TO
CHAPTER
EXERCISES
Chapter 1
Chapter 2
1. A C# program must have an entry point defined by the static Main() method. It
accepts a string array args as input.
2. // is used for single line comments; /* */ is used to enclose multi-line com-
ments; and /// is used to create XML comments that can be exported to a text
file.
3. A primitive refers to a simple value type such as an int or byte.
4. true
5. A do loop is evaluated at the end of the iteration and thus must execute at least
once.
6. A break statement causes control to exit the enclosing loop immediately; a
continue statement continues the same loop by skipping to the beginning of
the loop.
7. a does not compile because an int cannot be converted to a char.
c does not compile because a string cannot be converted to a char.
8. Each time concatenation occurs, a new string is created in memory. For a large
number of concatenations, this wastes memory and also degrades performance.
The StringBuilder class can be used as an alternative—but only where there
are many concatenations. Otherwise, it will not outperform concatenation.
9. All classes inherit from System.Object. In addition, value types inherit from
System.ValueType.
10. 7
Chapter 3
1. A sealed class cannot be inherited. A sealed class is used primarily when the
class contains static members. Note that a struct is implicitly sealed.
2. A class can inherit from one class explicitly and inherits from System.Object
implicitly. It can inherit from any number of interfaces.
940 Answers to Chapter Exercises
Chapter 4
1. The advantages of a class factory pattern are that it can control the number of
objects created, encapsulates the logic required to create an object, and makes it
easier to add new products by isolating the code in the factory.
2. Custom exceptions should inherit from the ApplicationException class.
Three constructors should be included: a parameterless one, a constructor
accepting a string parameter, and a constructor that accepts a string and
Exception object parameter.
3. System.Object.Equals bases equality on objects having the same memory
location.
4. A class must implement the IEnumerable and IEnumerator interfaces to sup-
port the foreach statement.
5. Generics permit a collection to be type-safe. This means the collection class is
restricted to holding and processing objects of one type. Non-generic collections
can hold any mixture of objects and require casting to detect their type.
Answers to Chapter Exercises 941
Chapter 5
Chapter 6
Chapter 7
1. Only one radio button in a group may be selected at a time. A GroupBox pro-
vides a way to group radio buttons logically.
2. SizeMode = PictureBoxSizeMode.StretchImage
3. To display selected fields of an object in a ListBox, override the object’s
ToString() method to display the desired fields.
4. The SelectedIndexChanged event is fired. Use the SelectedIndex to get
the index of the selected item, or SelectedItem to get the object selected.
5. Set View property to View.Details.
6. The Tag property can be used to store objects.
Answers to Chapter Exercises 943
Chapter 8
1. The Graphics object encapsulates the drawing surface and is used to draw; the
ClipRectangle represents the area that needs to be redrawn.
2. Control.Invalidate().
3. C
4. b fails because Brush is an abstract class.
5. b is more transparent because its alpha value (200) is less than the value (255) of
color a.
6. 100%. The image is scaled to fit the rectangle.
7. Here is one solution:
Graphics g = panel1.CreateGraphics();
g.SmoothingMode = SmoothingMode.AntiAlias;
GraphicsPath gp = new GraphicsPath();
gp.AddLine(10, 170, 30, 170);
gp.AddLine(16, 100, 100, 100);
gp.AddLine(100, 100, 190, 180);
gp.AddLine(40, 50, 50, 20);
gp.StartFigure();
gp.AddLine(50, 20, 145, 100);
gp.StartFigure();
gp.AddArc(65, 10, 120, 180, 180, 80);
gp.StartFigure();
gp.AddArc(65, 5, 120, 100, 200, 70);
g.DrawPath(new Pen(Color.Black, 2), gp);
8. D
Chapter 9
1. Typeface
2. Point is the default measurement for a font. It is 1/72nd of an inch.
3. Graphics.DrawString method is used. It is passed a StringFormat object
that has its Alignment property set to StringAlignment.Far.
944 Answers to Chapter Exercises
Chapter 10
Chapter 11
an internal data store. It typically loads data from a data source into a DataSet.
It can also update the data source.
5. A DataSet object contains one or more DataTables.
6. Rejected and Changed are not valid DataRowState values. Detached,
Unchanged, and Modified are the other values.
7. A DataSet schema can be created (without loading actual XML data) by
DataInferXmlSchema(xml file);
Chapter 12
1. a. False
b. True
c. True
d. False
e. False
f. False
g. True
h. False
i. True
2. Simple binding occurs on a control that displays a single value; complex binding
associates a control with a collection of data in a data source. In one-way data
binding, the control is bound to the source for read-only purposes. Changes to
the control’s value are not reflected in the data source. Two-way binding permits
the data source to be updated by changing the control’s value(s).
3. The properties on a custom data source that expose the bound data must be
writable, so the object can be updated.
4. DataGridView.SelectionMode =
DataGridViewSelectionMode.FullRowSelect;
5. A ListBox cannot be included in a DataGridView cell. Other controls that can
be included are a Link, CheckBox, and Image.
6. To freeze a column, set the column’s Frozen property to true. The column and
all to its left remain visible during scrolling.
dgv.Columns[1].Frozen=true;
Chapter 13
chopStick[left] = true;
// Signal threads in queue that chopsticks are available
Monitor.PulseAll(this);
}
}
}
class Philosopher
{
int n; // Philosopher number
int eatDelay;
int thinkDelay;
int left, right;
Stick chopSticks;
public Philosopher (int n, int thinkTime,int eatTime,
Stick sticks)
{
this.n = n;
this.eatDelay = eatTime;
this.thinkDelay = thinkTime;
this.chopSticks = sticks;
// Fifth philosopher has chopstick 1 on left
left = n == 5 ? 1 : n+1;
right = n;
new Thread(new ThreadStart(Run)).Start();
}
{
Stick sticks = new Stick();
// Create thread for each philosopher
// Eat time is random
Random r = new Random(DateTime.Now.Millisecond);
new Philosopher(1, 100, r.Next(500), sticks);
new Philosopher(2, 200, r.Next(500), sticks);
new Philosopher(3, 300, r.Next(500), sticks);
new Philosopher(4, 400, r.Next(500), sticks);
new Philosopher(5, 500, r.Next(500), sticks);
}
}
Chapter 14
Chapter 15
Chapter 16
1. GET and PUT are used to transfer data from a client to a server. PUT is default for
ASP.NET.
2. a. True.
b. True.
c. False. ASP.NET returns HTML, therefore a client does not require the
.NET runtime.
d. True.
e. True.
3. The ListItem class contains a collection for List controls.
4. If the TextBox.AutoPostBack property is set to true, the event handler is
called immediately on the server; otherwise, it waits until the next round trip to
the server occurs.
5. The .Master property allows access to the MasterPage object.
6. The DataSource property must be set to the data source, and DataBind must
be executed to load the data into the control.
7. a. False. You cannot bind directly to a DataAdapter.
b. True. A control can bind to a DataReader.
c. False. It is populated when DataBind is executed.
d. True. A DataSet or data source control can bind to a control.
8. Use a validating control to manage input to a text box.
9. The @Register directive is required to specify a custom control.
10. The HtmlTextWriter class emits HTML that renders a control.
11. The controls in a composite control render themselves and offer the standard
properties to work with.
Chapter 17
Chapter 18
1. System.Web.Services.WebService
2. WebService attribute is applied to a class providing a Web Service. It describes
the Web Service. WebService directive identifies a file as containing a Web
Service.
3. Use the Web Service attribute to specify a namespace to uniquely identify a
service.
4. The Invoke method is used for a synchronous call; BeginInvoke is used for
asynchronous.
5. Use the Timeout property on the Web Service object to set the timeout in
milliseconds.
6. The <Service> element contains the target URL for a Web Service.
7. A Web method requires the SoapHeader attribute to access the SOAP header
info.
8. BeginInvoke with polling using the IsCompleted property to check for a
response. BeginInvoke with EndInvoke to retrieve results. Call <web service
name>Async and implement event handler to process response.
A anchoring controls, 275–276
annotations, ToolTip controls, 310
aborting threads, 611–613 anonymous methods, delegates, 117–118
abstract classes, comparing to interfaces, 130 AppDomain class, 640–643
abstract modifiers, 86, 102–103 applications
accessor methods, 93 configuration files, 23
activating forms, 294 data binding, 555–563
adding deploying, 722–727
data bindings, 547 state, 837–838
Help to forms, 308–313 Windows, 268–271
items <appSettings> configuration file section, 819
to data caches, 845–846 architecture
data sources, 560–561 ADO.NET. See ADO.NET
to list boxes, 336–338 CLI, 7. See also CLI
nodes, 351 remoting, 644–648
on trees, 491–493 Web services, 870. See also Web services
radio buttons into groups, 326–327 ARGB (AlphaRGB), 400
role-based authorization, 833–834 arithmetic operators, 50–51
rows (DataGridView class), 566–568 ArrayList class, 179–180
ADO.NET binding, 558–560
binding, 548–549 objects, 69
connected models, 502–504, 506–518 arrays
connection classes, 506–510 C#, 69–73
data access models, 502–506 controls (binding), 558–559
disconnected models, 504–506, 518–533 declaring, 70
overview of, 497–502 System.Array class, 71
XML, 533–540 ASCII, 204, 219
algorithms .asmx files, 877
DES, 253 ASP.NET
Nagle, 916–917 binding, 772–884
aligning buttons, 763–764
strings, 438–439 caching, 841–848
tab stops, 436–437 client-server Internet interaction, 734–758
allocating code-behind models, 749–754
memory, 74–75, 77 configuration files, 817–826
StringBuilder class, 220–223 content pages, 789–793
AlphaRGB (ARGB), 400 DataList controls, 768–772
alpha values, 400 HTTP
AmazonSearchService class, 909–910 pipelines, 851–866
Amazon Web services, 909–912 requests/responses, 808–817
analysis, FxCop tool, 683–686 inline code models, 741–749
952
master pages, 789–793 FormsAuthentication class, 831–833
Page class, 754–758 SOAP, 900–903
panels, 764–765 viewing, 835
security, 827–835 authorization
state maintenance, 835–841 role-based, 833–834
text boxes, 766 web.config file, 830–831
validation controls, 784–789 AutoSizeRows method, 569
Web clients, 848–851 availability of interface members, 129–130
Web controls avoiding deadlock, 628–630
customizing, 793–801
selecting, 801–802 B
Web Forms controls, 758–772
assemblies, 10 background threads, 593–594
application domains, 639–640 backing store, 245
clients, 658–660 base classes, 87
configuration file management, 726 BeginInvoke method, 598–599
configuring, 29 BeginPrint event, 446–447
deploying, 724–725 BinaryFormatter object, 188
FCL, 19 binary serialization, 188–192
FxCop tool, 683–686 binding
GAC, 16, 723–724 ADO.NET, 548–549
hosts, 657–658, 665 ArrayList class, 558–560
interfaces (SAO), 662–664 ASP.NET, 772–784
managing, 29 DataReader class, 772–774
multiple files, 15, 34–35 DataSet class, 556–557, 774–776
.NET, 13–18 DataTables class, 555–557
permissions lists, 547–548
granting, 709–710 managers, 547, 552–554
requesting, 711–714 policies, 29
precompiling, 17–18 <Binding>, 897
private, 16–17 BindingManagerBase class, 553–554
satellite, 369–376 Bit Block Transfer, 421–422
servers, 656–657, 665–666 bit flags and enumerated types, 69
shared, 16–17 bitmaps (BMP), 408
strongly named, 14, 686–691 blocks
versioning, 690–691, 727 catch, 151–153
@Assembly directive, 748 finally, 153
asynchronous programming, 595–608 try, 151
calls BMI (Body Mass Index), 648
event-based, 892–893 calculating, 735–741
implementing, 599–608 BMP (bitmaps), 408
I/O (Input/Output), 607–608 bool type, 47
to Web service methods, 891–892 boxing, 75–77
delegate invocation, 596 BreakBar property, 306
attributes Break property, 306
C# classes, 83–85 brightness, 400
conditional, 84–85 brushes, 395–400
@Page directives, 746–748 BufferedStream class, 248–249
permissions, 695, 698 buffering images, 413–414
applying, 712–713 built-in events, 115–116
declarative security, 721–722 built-in named permission sets, 694–695
testing, 713–714 built-in security permissions, 695–697
Synchronization, 622–623 Button class, 323–324
VaryByCustom, 843–844 buttons
VaryByHeader, 843–844 ASP.NET, 763–764
VaryByParam, 843–844 click events, 271
WebMethod, 876–877, 881–883 controls, 270
WebService, 881 formatting, 323–324
XML serialization, 465–466 Help, 311–312
authentication, 827 byte type, 48
forms, 827–835
953
954 Index
C single type, 49
TryParse method, 49–50
C# statements
applications control flow, 52–53
case sensitivity, 42 if-else, 53–54
embedding comments, 43–45 switch, 54–55
naming conventions, 42–43 strings, 61–66
arrays, 69–73 text
classes comparing strings, 212–215
constants, 89–97 formatting DateTime/numeric values, 223–231
constructors, 106–112 modifying strings, 216–220
defining, 82–87 regular expressions, 232–243
delegates, 112–123 StringBuilder class, 220–223
fields, 89–97 System.IO namespace, 244–263
generics, 130–133 System.String class, 209–212
interfaces, 126–130 Unicode, 204–209
members, 88–89 CacheDuration property, 882
methods, 97–106 caching
operator overloading, 123–126 ASP.NET, 841–848
properties, 89–97 data, 845–848
structures, 134–139 deploying, 723–724
code-behind models (ASP.NET), 749–754 duration of, 842
compiling, 31–35 fragments, 844–845
enumerated types, 66–69 GAC, 16, 689–690
features specific to, 920–923 location of, 842
layouts, 40–45 output from Web operations, 882
loops, 55–59 callbacks, 113, 595
do, 56 asynchronous calls, 603–605
for, 56–57 calls
foreach, 57 asynchronous
transferring control within, 58–59 event-based, 892–893
while, 55 to Web service methods, 891–892
objects synchronous calls to Web service methods, 890–891
creating, 145–149 CAO (client-activated objects), 649, 650
exception handling, 149–160 carriage returns, 334–335
implementing System.Object methods, 160– case sensitivity of C# applications, 42
167 casting, 46
life cycle management, 192–196 catch block, 151–153
.NET collection classes and interfaces, 167–187 catching exceptions, 149
serialization, 187–192 cells
operators, 50 formatting, 573–574
arithmetic, 50–51 recognizing, 574–575
conditional, 51–52 channels
equality (==), 65 naming, 647–648
relational, 51–52 remoting, 646–647
preprocessing directives, 59–61 characters, 204–208
primitives, 45–50 carriage returns, 334–335
bool type, 47 dates, 227–228
byte type, 48 encoding schemes, 219–220
char type, 48 matching, 237
decimal type, 47 positional, 237
double type, 49 repetition, 237
int type, 48 standards, 205–209
long type, 48 strings
Parse method, 49–50 escape, 62, 216
sbyte type, 48 tab stops, 436–437
short type, 48 XML, 482–484
Index 955
long type, 48 Q
Parse method, 49–50
sbyte type, 48 queries (XPath), 486–489
short type, 48 QueryPageSettingsEvent event, 447–448
single type, 49 Queue class, 177–179
TryParse method, 49–50
PrintDocument class, 439–456 R
customizing, 454–456
events, 440, 446–448 RadioButton class, 325–327
printers RadioCheck property, 306
resolution, 444 RAD (Rapid Application Development), 15
selecting, 443 reading
PrinterSettings object, 442–444 resource files, 372–373
printing text, 249–251
GDI+, 439–456 XML, 472–482, 537–540
pages, 443–444 ReadOnly columns, 570
previewing, 449–454 read-only fields, static, 92
PrintPage event, 448–449 rectangles
priority threads, 592 drawing, 417–419
private assemblies, 16–17 DrawRectangle method, 382
deploying, 724–725 red/green/blue (RGB), 400
private constructors, 110–111 references
private keys, 253 instances, 92
delayed signing, 688 MBR, 649–650
processing HTTP pipeline requests, 851–853 objects, 165
ProductInfo class, 911 support for CLR, 73–77
profiles types, 11, 138
Compact Profile, 8 reflection, 8
Kernel Profile, 8 TreeView class, 352–355
programmatic security, 715–722 Regex class, 232–237
ProgressBar class, 355–358 IsMatch method, 233
properties Replace method, 233
C# classes, 93–95 Split method, 234
controls, 272–274, 546 regions, code, 61
DataGridView class, 564–571 @Register directive, 748–749
DataSource controls, 776–784 registering
DataTable.Columns, 519–521 methods, 113
forms, 285 types, 652–654
HttpRequest class, 810–812 registries (UDDI), 874
HttpResponse class, 814–816 regular expressions, 232–243
MenuItem class, 306 backreferencing groups, 241–242
Page class, 755 creating regular expressions, 237-238
structures, 136–137 repetition characters, 237
System.Array class, 71 relational operators (C#), 51–52
System.Exception class, 150–151 relationships
XmlReaderSettings class, 489–490 constraints, 531–532
propertyChanged event, 551 tables
ProviderFactory class, 500 creating, 521
providers. See also data providers defining, 530–532
proxy classes releasing memory, 77
Amazon Web services, 911–912 Remote Procedure Call. See RPC
remoting, 645–646 remoting applications, 643–671
Visual Studio.NET, 894 architecture, 644–648
wsdl.exe utility, 885–890 CAO, 650, 664–669
public keys, 253 design, 670–671
tokens, 17, 687 real proxies, 645
registration, 652–654
968 Index
XCOPY, 722–723
XmlDataDocument class (XPath), 491–493
Wouldn’t it be great
if the world’s leading technical
publishers joined forces to deliver
their best tech books in a common
digital reference platform?
informit.com/onlinebooks
relevance-ranked results in a matter of seconds.
■ Immediate results.
With InformIT Online Books, you can select the book
you want and view the chapter or section you need
immediately.
PH PTR Online
We strive to stay on the cutting edge of what’s happening in
professional computer science and engineering. Here’s a bit of
what you’ll find when you stop by www.phptr.com:
Articles
Online Books
Catalog