Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
106 views

C# - Net

Uploaded by

shanmugasun489
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
106 views

C# - Net

Uploaded by

shanmugasun489
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1006

CORE C#

AND .NET
PRENTICE HALL
CORE SERIES

Core J2EE Patterns, Second Edition, Alur/Malks/Crupi


Core PHP Programming,Third Edition, Atkinson/Suraski
Core Lego Mindstorms, Bagnall
Core JSTL, Geary
Core JavaServer Faces, Geary/Horstmann
Core Web Programming, Second Edition, Hall/Brown
Core Servlets and JavaServer Pages, Second Edition,
Hall/Brown
Core Java™ 2, Volume I—Fundamentals,
Horstmann/Cornell
Core Java™ 2, Volume II—Advanced Features,
Horstmann/Cornell
Core C# and .NET, Perry
Core CSS, Second Edition, Schengili-Roberts
Core Security Patterns, Steel/Nagappan/Lai
Core Java Data Objects, Tyagi/Vorburger/
McCammon/Bobzin
Core Web Application Development with PHP and
MySQL, Wandschneider
CORE C#
AND .NET

Stephen C. Perry

Prentice Hall Professional Technical Reference


Upper Saddle River, NJ • Boston • Indianapolis • San Francisco
New York • Toronto • Montreal • London • Munich • Paris • Madrid
Capetown • Sydney • Tokyo • Singapore • Mexico City
Many of the designations used by manufacturers and sellers to distinguish their products are
claimed as trademarks. Where those designations appear in this book, and the publisher was
aware of a trademark claim, the designations have been printed with initial capital letters or in
all capitals.
The author and publisher have taken care in the preparation of this book, but make no
expressed or implied warranty of any kind and assume no responsibility for errors or omissions.
No liability is assumed for incidental or consequential damages in connection with or arising out
of the use of the information or programs contained herein.
The publisher offers excellent discounts on this book when ordered in quantity for bulk
purchases or special sales, which may include electronic versions and/or custom covers and
content particular to your business, training goals, marketing focus, and branding interests. For
more information, please contact:
U. S. Corporate and Government Sales
(800) 382-3419
corpsales@pearsontechgroup.com
For sales outside the U. S., please contact:
International Sales
international@pearsoned.com
Visit us on the Web: www.phptr.com

Library of Congress Cataloging-in-Publication Data


Perry, Stephen (Stephen C.)
Core C# and .NET / Stephen Perry.
p. cm.
ISBN 0-13-147227-5
1. C++ (Computer program language) 2. Microsoft .NET. I. Title.
QA76.73.C153P468 2005
005.13'3--dc22
2005021301

Copyright © 2006 Pearson Education, Inc.


All rights reserved. Printed in the United States of America. This publication is protected by
copyright, and permission must be obtained from the publisher prior to any prohibited
reproduction, storage in a retrieval system, or transmission in any form or by any means,
electronic, mechanical, photocopying, recording, or likewise. For information regarding
permissions, write to:
Pearson Education, Inc.
Rights and Contracts Department
One Lake Street
Upper Saddle River, NJ 07458
ISBN 0-13-147227-5
Text printed in the United States on recycled paper at R. R. Donnelley in Crawfordsville,
Indiana.
First printing, September 2005
Chapter

ABOUT THE AUTHOR XXIII


FOREWORD XXV
PREFACE XXVII
ACKNOWLEDGMENTS XXX

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

1.4 Working with the .NET Framework and SDK 22


Updating the .NET Framework 23
.NET Framework Tools 23
Ildasm.exe 25
wincv.exe 28
Framework Configuration Tool 29
1.5 Understanding the C# Compiler 31
Locating the Compiler 31
Compiling from the Command Line 32
1.6 Summary 35
1.7 Test Your Understanding 36

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

Abstract, Sealed, and Static Modifiers 86


Class Identifier 86
Base Classes, Interfaces, and Inheritance 87
3.3 Overview of Class Members 88
Member Access Modifiers 89
3.4 Constants, Fields, and Properties 89
Constants 89
Fields 91
Properties 93
Indexers 95
3.5 Methods 97
Method Modifiers 98
Passing Parameters 103
3.6 Constructors 106
Instance Constructor 106
Private Constructor 110
Static Constructor 111
3.7 Delegates and Events 112
Delegates 113
Delegate-Based Event Handling 115
3.8 Operator Overloading 123
3.9 Interfaces 126
Creating and Using a Custom Interface 127
Working with Interfaces 129
3.10 Generics 131
3.11 Structures 134
Defining Structures 134
Using Methods and Properties with a Structure 136
3.12 Structure Versus Class 137
Structures Are Value Types and Classes Are Reference Types 138
Unlike a Class, a Structure Cannot Be Inherited 138
General Rules for Choosing Between a Structure and a Class 139
Contents ix

3.13 Summary 139


3.14 Test Your Understanding 140

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

Working with Groups 240


Examples of Using Regular Expressions 242
5.8 System.IO: Classes to Read and Write Streams of Data 244
The Stream Class 244
FileStreams 245
MemoryStreams 247
BufferedStreams 248
Using StreamReader and StreamWriter
to Read and Write Lines of Text 249
StringWriter and StringReader 251
Encryption with the CryptoStream Class 252
5.9 System.IO: Directories and Files 255
FileSystemInfo 256
Working with Directories Using the DirectoryInfo,
Directory, and Path Classes 256
Working with Files Using the FileInfo and File Classes 261
5.10 Summary 263
5.11 Test Your Understanding 264

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

Forms Interaction—A Sample Application 294


Owner and Owned Forms 298
Message and Dialog Boxes 299
Multiple Document Interface Forms 301
6.4 Working with Menus 306
MenuItem Properties 306
Context Menus 307
6.5 Adding Help to a Form 308
ToolTips 309
Responding to F1 and the Help Button 311
The HelpProvider Component 312
6.6 Forms Inheritance 313
Building and Using a Forms Library 313
Using the Inherited Form 314
6.7 Summary 315
6.8 Test Your Understanding 316

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

Other List Controls: the ComboBox and the CheckedListBox 341


7.5 The ListView and TreeView Classes 342
The ListView Class 342
The TreeView Class 349
7.6 The ProgressBar, Timer, and StatusStrip Classes 355
Building a StatusStrip 355
7.7 Building Custom Controls 358
Extending a Control 358
Building a Custom UserControl 359
A UserControl Example 359
Using the Custom User Control 361
Working with the User Control at Design Time 362
7.8 Using Drag and Drop with Controls 363
Overview of Drag and Drop 363
7.9 Using Resources 369
Working with Resource Files 369
Using Resource Files to Create Localized Forms 373
7.10 Summary 376
7.11 Test Your Understanding 376

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

8.3 Images 407


Loading and Storing Images 408
Manipulating Images 411
Sample Project: Working with Images 414
A Note on GDI and BitBlt for
the Microsoft Windows Platform 421
8.4 Summary 423
8.5 Test Your Understanding 423

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

The Command Object 511


DataReader Object 516
11.4 DataSets, DataTables, and the Disconnected Model 518
The DataSet Class 518
DataTables 519
Loading Data into a DataSet 523
Using the DataAdapter to Update a Database 525
Defining Relationships Between Tables in a DataSet 530
Choosing Between the Connected and Disconnected Model 532
11.5 XML and ADO.NET 533
Using a DataSet to Create XML Data and Schema Files 534
Creating a DataSet Schema from XML 536
Reading XML Data into a DataSet 537
11.6 Summary 540
11.7 Test Your Understanding 541

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

15.3 Security 692


Permissions and Permission Sets 693
Evidence 698
Security Policies 701
Configuring Security Policy 702
The .NET Framework Configuration Tool 704
Configuring Code Access Security with the
Configuration Tool—An Example 706
Requesting Permissions for an Assembly 711
Programmatic Security 715
15.4 Application Deployment Considerations 722
Microsoft Windows Deployment: XCOPY
Deployment Versus the Windows Installer 722
Deploying Assemblies in the Global Assembly Cache 723
Deploying Private Assemblies 724
Using CodeBase Configuration 725
Using a Configuration File to Manage
Multiple Versions of an Assembly 726
Assembly Version and Product Information 727
15.5 Summary 728
15.6 Test Your Understanding 728

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

The Code-Behind Model 749


Code-Behind with Partial Classes 753
Page Class 754
16.2 Web Forms Controls 758
Web Controls Overview 759
Specifying the Appearance of a Web Control 760
Simple Controls 761
List Controls 766
The DataList Control 768
16.3 Data Binding and Data Source Controls 772
Binding to a DataReader 772
Binding to a DataSet 774
DataSource Controls 776
16.4 Validation Controls 784
Using Validation Controls 786
16.5 Master and Content Pages 789
Creating a Master Page 790
Creating a Content Page 791
Accessing the Master Page from a Content Page 792
16.6 Building and Using Custom Web Controls 793
A Custom Control Example 794
Using a Custom Control 796
Control State Management 797
Composite Controls 798
16.7 Selecting a Web Control to Display Data 801
16.8 Summary 802
16.9 Test Your Understanding 803

Chapter 17
THE ASP.NET APPLICATION ENVIRONMENT 806
17.1 HTTP Request and Response Classes 808
HttpRequest Object 808
Contents xxi

HttpResponse Object 813


17.2 ASP.NET and Configuration Files 817
A Look Inside web.config 818
Adding a Custom Configuration Section 824
17.3 ASP.NET Application Security 827
Forms Authentication 827
An Example of Forms Authentication 830
17.4 Maintaining State 835
Application State 837
Session State 838
17.5 Caching 841
Page Output Caching 842
Data Caching 845
17.6 Creating a Web Client with WebRequest and WebResponse 848
WebRequest and WebResponse Classes 848
Web Client Example 848
17.7 HTTP Pipeline 851
Processing a Request in the Pipeline 851
HttpApplication Class 853
HTTP Modules 857
HTTP Handlers 862
17.8 Summary 866
17.9 Test Your Understanding 867

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

Extending the Web Service with the


WebService and WebMethod Attributes 880
18.3 Building an XML Web Service Client 884
Creating a Simple Client to Access the Web Service Class 884
Creating a Proxy with Visual Studio.NET 894
18.4 Understanding WSDL and SOAP 895
Web Services Description Language (WSDL) 895
Simple Object Access Protocol (SOAP) 898
18.5 Using Web Services with Complex Data Types 906
A Web Service to Return Images 907
Using Amazon Web Services 909
Creating a Proxy for the Amazon Web Services 911
Building a WinForms Web Service Client 913
18.6 Web Services Performance 916
Configuring the HTTP Connection 916
Working with Large Amounts of Data 917
18.7 Summary 918
18.8 Test Your Understanding 918

Appendix A
FEATURES SPECIFIC TO .NET 2.0 AND C# 2.0 920

Appendix B
DATAGRIDVIEW EVENTS AND DELEGATES 924

ANSWERS TO CHAPTER EXERCISES 938

INDEX 952
Chapter

Stephen Perry is a software architect specializing in the design and implementation


of .NET applications. For the past three years he has designed and developed signif-
icant .NET-based solutions for clients in the textile, furniture, legal, and medical pro-
fessions. Prior to that, he worked for more than 20 years in all phases of software
development. With the extra 25 hours a week now available from the completion of
the book, he’ll have more time for triathlon training and watching “Seinfeld” reruns.

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

“The process of preparing programs for a digital computer is especially attractive


because it not only can be economically and scientifically rewarding, it can
also be an aesthetic experience much like composing poetry or music.”
— Donald Knuth, Preface to Fundamental Algorithms (1968)

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

ming (OOP)—although it will re-enforce the principles of encapsulation, polymor-


phism, and inheritance through numerous examples. Finally, this book is not an
introduction to using Visual Studio.NET to develop C# programs. VS.NET is men-
tioned, but the emphasis is on developing and understanding C# and the .NET
classes—independent of any IDE.
This book is intended for the experienced programmer who is moving to .NET
and wants to get an overall feel for its capabilities. You may be a VB6 or C++ pro-
grammer seeking exposure to .NET; a VB.NET programmer expanding your reper-
toire into C#; or—and yes it does happen occasionally—a Java programmer
investigating life on the far side. Here’s what you’ll find if you choose to journey
through this book.

• 18 Chapters. The first four chapters should be read in order. They


provide an introduction to C# and a familiarity with using the .NET
class libraries. The remaining chapters can be read selectively based
on your interests. Chapters 6 and 7 describe how to develop Windows
Forms applications. Chapters 8 and 9 deal with GDI+—the .NET
graphics classes. Chapters 10 through 12 are about working with data.
Both XML and ADO.NET are discussed. Chapters 13, 14, and 15
tackle the more advanced topics of threading, remoting, and code
security, respectively. The final chapters form a Web trilogy: Chapter
16 discusses ASP.NET Web page development; Chapter 17 looks
behind the scenes at how to manage state information and manage
HTTP requests; the book closes with a look at Web Services in
Chapter 18.
• .NET 2.0. The manuscript went to publication after the release of
Beta 2.0. As such, it contains information based on that release. The
2.0 topics are integrated within the chapters, rather than placing them
in a special 2.0 section. However, as a convenience, Appendix A con-
tains a summary and separate index to the .NET 2.0 topics.
• Coding examples. Most of the code examples are short segments
that emphasize a single construct or technique. The objective is to
avoid filler code that does nothing but waste paper. Only when it is
essential does a code example flow beyond a page in length. Note that
all significant code examples are available as a download from
www.corecsharp.net or indirectly at www.phptr.com/title/
0131472275. To access the download area, enter the keyword
parsifal.
• Questions and answers. Each chapter ends with a section of ques-
tions to test your knowledge. The answers are available in a single sec-
tion at the end of the book.
Preface xxix

• Fact rather than opinion. This book is not based on my opinion; it is


based on the features inherent in .NET and C#. Core recommenda-
tions and notes are included with the intent of providing insight rather
than opinion.

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#

Topics in This Chapter

• Overview of the .NET Framework: Architecture and features.


• Common Language Runtime: An overview of the tasks performed
by the runtime portion of the .NET Framework: Just-in-Time (JIT)
compiler, loading assemblies, and code verification.
• Common Type System and Common Language Specifications:
Rules that govern Common Language Runtime (CLR)
compatibility and language interoperability.
• Assemblies: A look at the structure of an assembly, the philosophy
behind it, and the difference between private and shared
assemblies.
• Framework Class Library: The Framework Library supplies
hundreds of base classes grouped into logical namespaces.
• Development Tools: Several tools are provided with .NET to aid
code development. These include Ildasm for disassembling code,
WinCV to view the properties of a class, and the Framework
Configuration tool.
• Compiling and Running C# Programs: Using the C# compiler
from the command line and options for structuring an application.
1

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#

1.1 Overview of the .NET Framework


The .NET Framework is designed as an integrated environment for seamlessly
developing and running applications on the Internet, on the desktop as Windows
Forms, and even on mobile devices (with the Compact Framework). Its primary
objectives are as follows:

• To provide a consistent object-oriented environment across the range


of applications.
• To provide an environment that minimizes the versioning conflicts
(“DLL Hell”) that has bedeviled Windows (COM) programmers, and
to simplify the code distribution/installation process.
• To provide a portable environment, based on certified standards, that
can be hosted by any operating system. Already, C# and a major part
of the .NET runtime, the Common Language Infrastructure (CLI),
have been standardized by the ECMA.1
• To provide a managed environment in which code is easily verified for
safe execution.

To achieve these broad objectives, the .NET Framework designers settled on an


architecture that separates the framework into two parts: the Common Language
Runtime (CLR) and the Framework Class Library (FCL). Figure 1-1 provides a styl-
ized representation of this.

FRAMEWORK CLASS LIBRARY


Web Applications
Windows Forms
ASP.NET, Web Services

Data Classes
ADO.NET, XML, SQL
Base Classes
System.IO, System.Drawing, System.Threading

Common Language Runtime


CTS, Just-in-Time Compiler, Memory Management

Operating System

Figure 1-1 .NET Framework

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

The CLR—which is Microsoft’s implementation of the CLI standard—handles


code execution and all of the tasks associated with it: compilation, memory manage-
ment, security, thread management, and enforcement of type safety and use. Code
that runs under the CLR is referred to as managed code. This is to distinguish it from
unmanaged code that does not implement the requirements to run in the CLR—
such as COM or Windows API based components.
The other major component, the Framework Class Library, is a reusable code
library of types (classes, structures, and so on) available to applications running under
.NET. As the figure shows, these include classes for database access, graphics, inter-
operating with unmanaged code, security, and both Web and Windows forms. All
languages that target the .NET Framework use this common class library. Thus, after
you gain experience using these types, you can apply that knowledge to any .NET
language you may choose to program in.

Microsoft .NET and the CLI Standards


A natural concern for a developer that chooses to invest the time in learning C# and
.NET is whether the acquired skill set can be transferred to other platforms. Specifi-
cally, is .NET a Microsoft product tethered only to the Windows operating system?
Or is it a portable runtime and development platform that will be implemented on
multiple operating systems? To answer the question, it is necessary to understand the
relationship among Microsoft .NET, C#, and the Common Language Infrastructure
(CLI) standards.
The CLI defines a platform-independent virtual code execution environment. It
specifies no operating system, so it could just as easily be Linux as Windows. The
centerpiece of the standard is the definition for a Common Intermediate Language
(CIL) that must be produced by CLI compliant compilers and a type system that
defines the data types supported by any compliant language. As described in the next
section, this intermediate code is compiled into the native language of its host oper-
ating system.
The CLI also includes the standards for the C# language, which was developed
and promoted by Microsoft. As such, it is the de facto standard language for .NET.
However, other vendors have quickly adopted the CIL standard and produced—just
to name a few—Python, Pascal, Fortran, Cobol, and Eiffel .NET compilers.
The .NET Framework, as depicted in Figure 1-1, is Microsoft’s implementation of
the CLI standards. The most important thing to note about this implementation is
that it contains a great deal more features than are specified by the CLI architecture.
To illustrate this, compare it to the CLI standards architecture shown in Figure 1-2.
8 Chapter 1 ■ Introduction to .NET and C#

Network XML Reflection


Library Library Library

Runtime Infrastructure
Library

Base Class
Library

Kernel Profile

Compact Profile

Figure 1-2 Architecture defined by CLI specifications

Briefly, the CLI defines two implementations: a minimal implementation known


as a Kernel Profile and a more feature rich Compact Profile. The kernel contains the
types and classes required by a compiler that is CLI compliant. The Base Class
Library holds the basic data type classes, as well as classes that provide simple file
access, define security attributes, and implement one-dimensional arrays. The Com-
pact Profile adds three class libraries: an XML library that defines simple XML pars-
ing, a Network library that provides HTTP support and access to ports, and a
Reflection library that supports reflection (a way for a program to examine itself
through metacode).
This book, which describes the Microsoft implementation, would be considerably
shorter if it described only the CLI recommendations. There would be no chapters
on ADO.NET (database classes), ASP.NET (Web classes), or Windows Forms—and
the XML chapters would be greatly reduced. As you may guess, these libraries
depend on the underlying Windows API for functionality. In addition, .NET permits
a program to invoke the Win32 API using an Interop feature. This means that a
.NET developer has access not only to the Win32 API but also legacy applications
and components (COM).
By keeping this rather wide bridge to Windows, Microsoft’s .NET implementation
becomes more of a transparent than virtual environment—not that there’s anything
wrong with that. It gives developers making the transition to .NET the ability to cre-
ate hybrid applications that combine .NET components with preexisting code. It also
means that the .NET implementation code is not going to be ported to another oper-
ating system.
1.2 Common Language Runtime 9

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.

1.2 Common Language Runtime


The Common Language Runtime manages the entire life cycle of an application: it
locates code, compiles it, loads associated classes, manages its execution, and ensures
automatic memory management. Moreover, it supports cross-language integration to
permit code generated by different languages to interact seamlessly. This section
peers into the inner workings of the Common Language Runtime to see how it
accomplishes this. It is not an in-depth discussion, but is intended to make you com-
fortable with the terminology, appreciate the language-neutral architecture, and
understand what’s actually happening when you create and execute a program.

Pascal VB.NET C# J# C++ Perl

Base Class
IL + Metadata Libraries

Common Language Runtime


Execution Class Loader
Support
Just-in-Time
Security Compiler
Memory
Management Native Code

CPU

Figure 1-3 Common Language Runtime functions

2. http://www.mono-project.com/Main_Page
10 Chapter 1 ■ Introduction to .NET and C#

Compiling .NET Code


Compilers that are compliant with the CLR generate code that is targeted for the
runtime, as opposed to a specific CPU. This code, known variously as Common
Intermediate Language (CIL), Intermediate Language (IL), or Microsoft Intermedi-
ate Language (MSIL), is an assembler-type language that is packaged in an EXE or
DLL file. Note that these are not standard executable files and require that the run-
time’s Just-in-Time (JIT) compiler convert the IL in them to a machine-specific code
when an application actually runs. Because the Common Language Runtime is
responsible for managing this IL, the code is known as managed code.
This intermediate code is one of the keys to meeting the .NET Framework’s for-
mal objective of language compatibility. As Figure 1-3 illustrates, the Common Lan-
guage Runtime neither knows—nor needs to know—which language an application
is created in. Its interaction is with the language-independent IL. Because applica-
tions communicate through their IL, output from one compiler can be integrated
with code produced by a different compiler.
Another .NET goal, platform portability, is addressed by localizing the creation of
machine code in the JIT compiler. This means that IL produced on one platform can
be run on any other platform that has its own framework and a JIT compiler that
emits its own machine code.
In addition to producing IL, compilers that target the CLR must emit metadata
into every code module. The metadata is a set of tables that allows each code module
to be self-descriptive. The tables contain information about the assembly containing
the code, as well as a full description of the code itself. This information includes
what types are available, the name of each type, type members, the scope or visibility
of the type, and any other type features. Metadata has many uses:

• 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

IL and metadata are crucial to providing language interoperability, but its


real-world success hinges on all .NET compilers supporting a common set of data
types and language specifications. For example, two languages cannot be compatible
at the IL level if one language supports a 32-bit signed integer and the other does not.
They may differ syntactically (for example, C# int versus a Visual Basic Integer),
but there must be agreement of what base types each will support.
As discussed earlier, the CLI defines a formal specification, called the Common
Type System (CTS), which is an integral part of the Common Language Runtime. It
describes how types are defined and how they must behave in order to be supported
by the Common Language Runtime.

Common Type System


The CTS provides a base set of data types for each language that runs on the .NET
platform. In addition, it specifies how to declare and create custom types, and how to
manage the lifetime of instances of these types. Figure 1-4 shows how .NET orga-
nizes the Common Type System.

Object

Class Primitives

Interface Structures

Array Enums

Reference Types Value Types

Figure 1-4 Base types defined by Common Type System

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

In .NET, “type” is a generic term that refers to a class, structure,


enumeration, delegate, or interface.

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

Table 1-1 Selected Common Language Specification Features and Rules

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

Enumerations The underlying type of an enumeration (enum) must be of the


type Byte, Int16, Int32, or Int64.

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

public class Conversion


{
public double Metric( double inches)
{ return (2.54 * inches); }
public double metric( double miles)
{ return (miles / 0.62); }
}

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

Figure 1-5 Single file assembly

An assembly is created when a .NET compatible compiler converts a file contain-


ing source code into a DLL or EXE file. As shown in Figure 1-5, an assembly con-
tains a manifest, metadata, and the compiler-generated Intermediate Language (IL).
Let’s take a closer look at these:
14 Chapter 1 ■ Introduction to .NET and C#

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.

Metadata. In addition to the manifest tables just described, the C# compiler


produces definition and reference tables. The definition tables provide a com-
plete description of the types contained in the IL. For instance, there are tables
defining types, methods, fields, parameters, and properties. The reference
tables contain information on all references to types and other assemblies. The
JIT compiler relies on these tables to convert the IL to native machine code.

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:

• All managed code, whether it is a stand-alone program, a control, or a


DLL library containing reusable types, is packaged in an assembly. It
is the most atomic unit that can be deployed on a system. When an
application begins, only those assemblies required for initialization
must be present. Other assemblies are loaded on demand. A judicious
developer can take advantage of this to partition an application into
assemblies based on their frequency of use.
• In .NET jargon, an assembly forms a version boundary. The version
field in the manifest applies to all types and resources in the assembly.
Thus, all the files comprising the assembly are treated as a single unit
with the same version. By decoupling the physical package from the
logical, .NET can share a logical attribute among several physical files.
This is the fundamental characteristic that separates an assembly from
a system based on the traditional DLLs.
• An assembly also forms a security boundary on which access permis-
sions are based. C# uses access modifiers to control how types and
type members in an assembly can be accessed. Two of these use the
assembly as a boundary: public permits unrestricted access from any
assembly; internal restricts access to types and members within the
assembly.
1.2 Common Language Runtime 15

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

Figure 1-6 Multi-file assembly

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:

• They allow you to combine modules created in different programming


languages. A programming shop may rely on Visual Basic.NET for its
Rapid Application Development (RAD) and C# for component or
enterprise development. Code from both can coexist and interact in
the .NET assembly.
• Code modules can be partitioned to optimize how code is loaded into
the CLR. Related and frequently used code should be placed in one
module; infrequently used code in another. The CLR does not load
the modules until they are needed. If creating a class library, go a step
further and group components with common life cycle, version, and
security needs into separate assemblies.
• Resource files can be placed in their own module separate from IL
modules. This makes it easier for multiple applications to share
common resources.
16 Chapter 1 ■ Introduction to .NET and C#

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.

Private and Shared Assemblies


Assemblies may be deployed in two ways: privately or globally. Assemblies that are
located in an application’s base directory or a subdirectory are called privately
deployed assemblies. The installation and updating of a private assembly could not be
simpler. It only requires copying the assembly into the directory, called the AppBase,
where the application is located. No registry settings are needed. In addition, an
application configuration file can be added to override settings in an application’s
manifest and permit an assembly’s files to be moved within the AppBase.
A shared assembly is one installed in a global location, called the Global Assembly
Cache (GAC), where it is accessible by multiple applications. The most significant
feature of the GAC is that it permits multiple versions of an assembly to execute
side-by-side. To support this, .NET overcomes the name conflict problem that
plagues DLLs by using four attributes to identify an assembly: the file name, a cul-
ture identity, a version number, and a public key token.

Figure 1-7 Partial listing of Global Assembly Directory

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.

Culture Setting. The contents of an assembly may be associated with a particu-


lar culture or language. This is designated by a two-letter code such as “en” for
English or “fr” for French, and can be assigned with an AssemblyCulture
attribute placed in source code:
[assembly: AssemblyCulture ("fr-CA")]

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

An assembly that is signed with a public/private key is referred to as a


strongly named assembly. All shared assemblies must have a strong
name.

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

As a rule, a dynamically compiled assembly provides performance


equal to, or better than, that of a precompiled executable created using
Ngen.

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

By default, code produced by the C# compiler is verifiably type-safe.


However, there is an unsafe keyword that can be used to relax memory
access restrictions within a C# program (such as referencing beyond an
array boundary).

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.

1.3 Framework Class Library


The Framework Class Library (FCL) is a collection of classes and other types (enu-
merations, structures, and interfaces) that are available to managed code written in
1.3 Framework Class Library 19

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.

Figure 1-8 Output from Ildasm shows the namespaces


and types that comprise an assembly

Figure 1-8 displays a portion of the output generated by using Ildasm.exe to


examine the contents of the mscorlib assembly. Although this only a partial listing,
you can see that mscorlib contains System, the preeminent namespace in .NET,
which serves as a repository for the types that give .NET its basic functionality. The
assembly is also home to the System.Collections namespace, which includes
classes and interfaces used for manipulating collections of data.
20 Chapter 1 ■ Introduction to .NET and C#

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.

Table 1-2 Selected FCL Namespaces

Namespace Use Chapter

System Contains the basic data types used 3, 18


by all applications. It also contains
exception classes, predefined
attributes, a Math library, and
classes for managing the applica-
tion environment.

System.Collections Interfaces and classes used to man- 4


System.Collections.Specialized age collections of objects. These
System.Collections.Generic collections include the ArrayList,
Hashtable, and Stack.

System.Data Classes used for database opera- 11, 12


System.Data.OracleClient tions (ADO.NET). The client
System.Data.SqlClient namespaces support Oracle and
System.Data.OleDb SQL Server, respectively; OledDb
System.Data.Odbc
and Odbc define the data connec-
tion used.

System.Diagnostics Contains classes that can be used to 13


trace program execution, debug,
and work with system logs and per-
formance counters.

System.Drawing Provides graphics functionality for 8, 9


System.Drawing.Drawing2D GDI+. These namespaces contain a
System.Drawing.Printing class used for drawing as well as
System.Drawing.Text pens, brushes, geometric shapes,
and fonts.

System.Globalization Contains classes that define 5


culture-related information that
affects the way dates, currency,
and symbols are represented.

System.IO Provides file and data stream I/O. 5


These classes provide a way to
access the underlying file systems
of the host operating system.
1.3 Framework Class Library 21

Table 1-2 Selected FCL Namespaces (continued)

Namespace Use Chapter

System.Net Classes that support network proto- 17


cols and operations. Examples
include WebRequest and Web-
Response that request and fetch a
Web page.

System.Reflection Contains types that permit the 7, 15,


System.Reflection.Emit runtime inspection of metadata. App. B
The Emit namespace allows a com-
piler or tool to generate metadata
and IL dynamically.

System.Runtime.InterOpServices Provides interoperability between 8


managed and unmanaged code
such as legacy DLLs or COM.

System.Security Classes used to manage .NET secu- 5, 15


System.Security.Permissions rity. Defines classes that control
System.Security.Cryptography access to operations and resources.

System.Text.RegularExpressions Classes that support .NET’s regular 5


expression engine.

System.Threading Manages threading activites: thread 13


System.Threading.Thread creation, synchronization, and
thread pool access.

System.Web The Internet-related classes 16, 17, 18


System.Web.Services referred to as ASP.NET. They man-
System.Web.UI age browser-server communication
System.Web.UI.WebControls requirements, manipulate cookies,
System.Web.Security
and contain the controls that adorn
a Web page.
Web.Services includes those
classes required for SOAP-based
XML messaging.
Web.UI includes classes and inter-
faces used for creating controls and
pages that comprise Web forms.
22 Chapter 1 ■ Introduction to .NET and C#

Table 1-2 Selected FCL Namespaces (continued)

Namespace Use Chapter

System.Windows.Forms Classes used to build Windows 6, 7


desktop GUI applications. Controls
including the ListBox, TextBox,
DataGrid, and buttons are found
here.

System.Xml Types for processing XML. 10

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.

1.4 Working with the .NET


Framework and SDK
The .NET Framework Software Development Kit (SDK) contains the tools, compil-
ers, and documentation required to create software that will run on any machine that
has the .NET Framework installed. It is available as a free download (100 megabytes)
from Microsoft that can be installed on Windows XP, Windows 2000, Windows
Server 2003, and subsequent Windows operating systems. If you have Visual Stu-
dio.NET installed, there is no need to download it because VS.NET automatically
does it for you.
Clients using software developed with the SDK do not require the SDK on their
machine; however, they do require a compatible version of the .NET Framework.
This .NET Framework Redistributable is available as a free download3 (20+ mega-
bytes) and should be distributed to clients along with applications that require it.
This redistributable can be installed on Windows 98 and ME, in addition to the ones
listed for the SDK. With minor exceptions, .NET applications will run identically on
all operating system platforms, because they are targeted for the Common Language
Runtime and not the operating system. There are some system requirements such as
a minimum Internet Explorer version of 5.01. These are listed at the download site.

3. http://msdn.microsoft.com/netframework/downloads/updates/
default.aspx
1.4 Working with the .NET Framework and SDK 23

Updating the .NET Framework


Unlike many development environments, installing a new version of the framework
is almost effortless. The installation process places the updated version in a new
directory having the name of the version. Most importantly, there is no file depen-
dency between the new and older versions. Thus, all versions are functional on your
system. Although it varies by operating system, the versions are usually in the path

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

.NET Framework Tools


The .NET Framework automates as many tasks as possible and usually hides the
details from the developer. However, there are times when manual intervention is
required. These may be a need to better understand the details of an assembly or
perform the housekeeping required to prepare an application for deployment. We
have encountered several examples of such tasks throughout the chapter. These
include the need to

• Add a file to an assembly


• View the contents of an assembly
• View the details of a specific class
• Generate a public/private key pair in order to create a strongly named
assembly
• Edit configuration files

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.

Table 1-3 Selected .NET Framework Tools

Tool Description

Al.exe Can be used for creating an assembly composed


Assembly Linker of modules from different compilers. It is also
used to build resource-only (satellite) assemblies.

Fuslogvw.exe Used to troubleshoot the assembly loading pro-


Assembly Binding Log Viewer cess. It traces the steps followed while attempt-
ing to load an assembly.

Gacutil.exe Is used to install or delete an assembly in the


Global Assembly Cache tool Global Assembly Cache. It can also be used for
listing the GAC’s contents.

Ildasm.exe A tool for exploring an assembly, its IL, and


MSIL Disassembler metadata.

Mscorcfg.msc A Microsoft Management Console (MMC)


.NET Framework Configuration tool snap-in used to configure an assembly while
avoiding direct manual changes to an applica-
tion’s configuration file. Designed primarily for
administrators, a subset, Framework Wizards.
Available for individual programmers.

Ngen.exe Compiles an assembly’s IL into native machine


Native Image Generator code. This image is then placed in the native
image cache.

Sn.exe Generates the keys that are used to create a


Strong Name tool strong—or signed—assembly.

wincv.exe A visual interface to display searchable informa-


Windows Forms Class Viewer tion about a class.

Wsdl.exe Generates descriptive information about a Web


Web Services Description Language tool Service that is used by a client to access the
service.
1.4 Working with the .NET Framework and SDK 25

Many of these tools are located in an SDK subdirectory:

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:

1. Right click on the My Computer icon and select Properties.


2. Select Advanced – Environment Variables.
3. Choose the Path variable and add the SDK subdirectory path to it.

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

at a command-line prompt (the optional /adv switch makes advanced viewing


options available). This invokes the GUI that provides a File menu you use to select
the assembly to view. Note that it does not open files in the Global Assembly Cache.
Figure 1-9 shows an example of the output created when an assembly is opened in
Ildasm. The contents are displayed in a readable, hierarchical format that contains
the assembly name, corecsharp1, and all of its members.
This hierarchy can then be used to drill down to the underlying IL (or CIL)
instructions for a specific member. As an example, let’s consider the Conversion
class. The figure shows that it consists of three methods: Metric, conversion, and
metric. The original source code confirms this:

public class Conversion


{
public double Metric( double inches)
{ return (2.54 * inches); }
[CLSCompliantAttribute(false)]
public double metric( double miles)
26 Chapter 1 ■ Introduction to .NET and C#

{ return (miles / 0.62); }


public double conversion( double pounds)
{ return (pounds * 454);}
}

Figure 1-9 View assembly contents with Ildasm.exe

Double clicking on the Metric method brings up a screen that displays its IL
(Figure 1-10).

Figure 1-10 View of the IL


1.4 Working with the .NET Framework and SDK 27

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.

Ildasm and Obfuscation


One of the natural concerns facing .NET developers is how to protect their code
when a tool such as Ildasm—and other commercially available disassemblers—can
be used to expose it. One solution is to use obfuscation—a technique that uses
renaming and code manipulation sleight of hand to make the contents of an assembly
unreadable by humans.
It is important to understand that obfuscation is not encryption. Encryption
requires a decryption step so that the JIT compiler can process the code. Obfusca-
tion transforms the IL code into a form that can be compiled using the tools of your
development environment (see Figure 1-11).

Source
Compile MSIL CLR
Code

Obfuscate Obfuscated CLR


MSIL

Figure 1-11 Obfuscation conceals the original Intermediate Language

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

1. System.Array is the class that is being explored.


2. This class is located in the mscorlib.dll assembly. We have already men-
tioned that this assembly contains the .NET managed types.
3. This list contains the class, object, and interfaces that the Array class
inherits from.
4. The definition of each method in the class is included. This definition,
which includes accessibility, type, and parameters, is called the
method’s signature.

Framework Configuration Tool


This tool provides an easy way to manage and configure assemblies as well as set
security policies for accessing code. This tool is packaged as a Microsoft Manage-
ment Console (MMC) snap-in. To access it, select Administrative Tools from the
Control Panel; then select the Microsoft .NET Framework Configuration tool. This
tool is designed for administrators who need to do the following:

• Manage assemblies. Assemblies can be added to the GAC or


deleted.
• Configure assemblies. When an assembly is updated, the publisher
of the assembly is responsible for updating the binding policy of the
assembly. This policy tells the CLR which version of an assembly to
load when an application references an assembly. For example, if
assembly version 1.1 replaces 1.0, the policy redirects version 1.0 to
1.1 so that it is loaded. This redirection information is contained in a
configuration file.
• View .NET Framework security and modify an assembly’s
security. .NET security allows an assembly to be assigned certain
permissions or rights. In addition, an assembly can require that other
assemblies accessing it have certain permissions.
• Manage how individual applications interact with an assembly
or set of assemblies. You can view a list of all assemblies an
application uses and set the version that your application uses.

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.

Figure 1-13 Using application configuration tool to select assembly version

This configuration tool is clearly targeted for administrators. Individual develop-


ers should rely on a subset of this tool that is packaged as three wizards: Adjust .NET
Security, Trust An Assembly, and Fix An Application. Access these by selecting
Framework Wizards from Administrative Tools.
1.5 Understanding the C# Compiler 31

1.5 Understanding the C# Compiler


Many developers writing nontrivial .NET applications rely on Visual Studio or some
other Integrated Development Environment (IDE) to enter source code, link exter-
nal assemblies, perform debugging, and create the final compiled output. If you fall
into this category, it is not essential that you understand how to use the .NET SDK
and raw C# compiler; however, it will increase your understanding of the .NET com-
pilation process and give you a better feel for working with assemblies. As a byprod-
uct, it will also acquaint you with the command line as a way to work with SDK
programs. Many of the utilities presented in the previous section are invoked from
the command line, and you will occasionally find it useful to perform compilation in
that environment rather than firing up your IDE.

Visual Studio
IL + Metacode
SRC1
Third-Party IDE
SharpDevelop CSC.EXE APP.EXE
SRC2
Text Editor .cs
External
LIB.DLL Assembly

Figure 1-14 Compilation process

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.

Locating the Compiler


The C# compiler, csc.exe, is located in the path where the .NET Framework is
installed:

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

Compiling from the Command Line


To compile the C# console application client.cs into the executable client.exe,
enter either of the following statements at the command prompt:

C:\> csc client.cs


C:\> csc /t:exe client.cs

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.

Table 1-4 Selected Options for the C# Command-Line Compiler

Option Description

/addmodule Specifies a module that is to be included in the assembly created.


This is an easy way to create a multi-file assembly.

/debug Causes debug information to be produced.

/define Preprocessor directive can be passed to compiler:


/define:DEBUG.

/delaysign Builds an assembly using delayed signing of the strong name. This
is discussed in Chapter 15.

/doc Used to specify that an output file containing XML documenta-


tion is to be produced.

/keyfile Specifies the path to the .snk file containing the key pair used for
strong signing (see Chapter 15).

/lib Specifies where assemblies included in the /reference option


are located.

/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

Table 1-4 Selected Options for the C# Command-Line Compiler (continued)

Option Description

/reference (/r) References an external assembly.

/resource Used to embed resource files into the assembly that is created.

/target (/t) Specifies the type of output file created:


/t:exe builds a *.exe console application. This is the default
output.
/t:library builds a *.dll assembly.
/t:module builds a module (Portable Executable file) that
does not contain a manifest.
/t:winexe builds a *.exe Windows Forms assembly.

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#

Example 1: Compiling Multiple Files


The C# compiler accepts any number of input source files. It combines their output
into a single file assembly:

csc /out:client.exe client.cs clientlib.cs

Example 2: Creating and Using a Code Library


The code in clientlib can be placed in a separate library that can be accessed by
any client:

csc /t:library clientlib.cs

The output is an assembly named clientlib.dll. Now, compile the client code
and reference this external assembly:

csc /r:clientlib.dll client.cs

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.

Example 3: Creating an Assembly with Multiple Files


Rather than existing as a separate assembly, clientlib can also be packaged as a
separate file inside the client.exe assembly. Because only one file in an assembly
may contain a manifest, it is first necessary to complile clientlib.cs into a Porta-
ble Executable4 (PE) module. This is done by selecting module as the target output:

csc /t:module clientlib.cs

The output file is clientfile.netmodule. Now, it can be placed in the cli-


ent.exe assembly by using the compiler’s addmodule switch:

csc /addmodule:clientlib.netmodule client.cs

The resultant assembly consists of two files: client.exe and clientlib.net-


module.

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

Manifest Manifest Manifest Manifest

Metadata Metadata Metadata Metadata Metadata

IL
IL IL IL IL
clientlib.
client.exe client.exe clientlib.dll client.exe netmodule

Figure 1-15 Options for deploying an application

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#

1.7 Test Your Understanding


1. What portable environment must be installed on a client’s machine to
enable it to run a .NET application?

2. What is managed code? What is unmanaged code?

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?

5. What is the role of the Global Assembly Cache?

6. What four components make up the identity of a strongly named


assembly?

7. What is the relationship between a namespace and an assembly?

8. Describe what these commonly used acronyms stand for: CLR, GAC,
FCL, IL.
This page intentionally left blank
C# LANGUAGE
FUNDAMENTALS

Topics in This Chapter

• Overview of a C# Program: In addition to the basic elements that


comprise a C# program, a developer needs to be aware of other
.NET features such as commenting options and recommended
naming conventions.
• Primitives: Primitives are the basic data types defined by the FCL
to represent numbers, characters, and dates.
• Operators: C# uses traditional operator syntax to perform
arithmetic and conditional operations.
• Program Flow Statements: Program flow can be controlled using
if and switch statements for selection; and while, do, for,
and foreach clauses for iteration.
• String: The string class supports the expected string operations:
concatenation, extracting substrings, searching for instances of a
character pattern, and both case sensitive and insensitive
comparisons.
• Enums: An enumeration is a convenient way to assign
descriptions that can be used to reference an underlying set of
values.
• Using Arrays: Single- or multi-dimensional arrays of any type can
be created in C#. After an array is created, the System.Array
class can be used to sort and copy the array.
• Reference and Value Types: All types in .NET are either a value or
reference type. It is important to understand the differences and
how they can affect a program’s performance.
2

In September 2000, an ECMA1 (international standardization group for information


and communication systems) task group was established to define a Microsoft pro-
posed standard for the C# programming language. Its stated design goal was to pro-
duce “a simple, modern, general-purpose, object-oriented programming language.”
The result, defined in a standard known as ECMA-334, is a satisfyingly clean lan-
guage with a syntax that resembles Java, and clearly borrows from C++ and C. It’s a
language designed to promote software robustness with array bounds checking,
strong type checking, and the prohibition of uninitialized variables.
This chapter introduces you to the fundamentals of the language: It illustrates the
basic parts of a C# program; compares value and reference types; and describes the
syntax for operators and statements used for looping and controlling program flow.
As an experienced programmer, this should be familiar terrain through which you
can move quickly. However, the section on value and reference types may demand a
bit more attention. Understanding the differences in how .NET handles value and
reference types can influence program design choices.

1. ECMA International was formerly known as European Computer Manufacturers Association


and is referred to herein simply as ECMA.

39
40 Chapter 2 ■ C# Language Fundamentals

2.1 The Layout of a C# Program


Figure 2-1 illustrates some of the basic features of a C# program.

// (1) using simplifies references to namespaces


using using System;
// (2) A Class Declaration
class Apparel
Class {
///
public double Price = 250.0;
///
public string FabType = "Synthetic";
}
XML /// <remarks> Entry point to program </remarks>
Comment public class MyApp
{
// (3) Main() is required in each C# program
static void Main()
{
Main() Apparel myApparel = new Apparel();
string myType = myApparel.FabType;
Console.WriteLine(myApparel.Price, myType);
}
}

Figure 2-1 Basic elements of a C# program

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:

1. The using statement specifies the namespace System. Recall


from Chapter 1, “Introduction to .NET and C#,” that the .NET class
libraries are organized into namespaces and that the System namespace
contains all of the simple data types. The using statement tells the com-
piler to search this namespace when resolving references, making it
unnecessary to use fully qualified names. For example, you can refer to
label rather than System.Web.UI.WebControls.Label.
2. All programming logic and data must be contained within a
type definition. All program logic and data must be embedded in a
class, structure, enum, interface, or delegate. Unlike Visual Basic, for
2.1 The Layout of a C# Program 41

instance, C# has no global variable that exists outside the scope of a


type. Access to types and type members is strictly controlled by access
modifiers. In this example, the access modifier public permits exter-
nal classes—such as MyApp—to access the two members of the
Apparel class.
3. A Main() method is required for every executable C#
application. This method serves as the entry point to the application;
it must always have the static modifier and the M must be capital-
ized. Overloaded forms of Main()define a return type and accept a
parameter list as input.

Return an integer value:

static int Main()


{
return 0; // must return an integer value
}

Receive a list of command-line arguments as a parameter and return


an integer value:

static int Main(string[] args)


{
// loop through arguments
foreach(string myArg in args)
Console.WriteLine(myArg);
return 0;
}

The parameter is a string array containing the contents of the com-


mand line used to invoke the program. For example, this command
line executes the program MyApparel and passes it two parameter
values:

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

General C# Programming Notes


Case Sensitivity
All variable and keywords are distinguished by case sensitivity. Replace class with
Class in Figure 2-1 and the code will not compile.

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:

1. Pascal. The first character of each word is capitalized (for example,


MyClassAdder).
2. Camel. The first character of each word, except the first, is capital-
ized (for example, myClassAdder).

Table 2-1 C# Naming Conventions

Type Case Notes and Examples

Class Pascal • Use noun or noun phrases.


• Try to avoid starting with I because this is reserved for
interfaces.
• Do not use underscores.

Constant Pascal public const double GramToPound = 454.0 ;

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.

Exception Pascal • Has suffix Exception.

Interface Pascal • Has prefix of I.


IDisposable

Local Variable Camel • Variables with public access modifier use Pascal.
int myIndex
2.1 The Layout of a C# Program 43

Table 2-1 C# Naming Conventions (continued)

Type Case Notes and Examples

Method Pascal • Use verb or verb phrases for name.

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

Property Pascal • Use noun or noun phrase.

Parameter Camel • Use meaningful names that describe the parameter’s


purpose.

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:

// for a single line


/* for one or more lines
*/
/// <remarks> XML comment describing a class </remarks>

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

Table 2-2 XML Documentation Tags

Tag Description

<example> Text illustrating an example of using a particular program


feature goes between the beginning and ending tags.

<exception cref="Excep"> cref attribute contains name of exception.


/// <exception cref="NoParmException">
</exception>

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

<param name="parm1"> name attribute contains the name of the parameter.

<permission cref= ""> Most of the time this is set to the following:
///<permission cref="System.Security.Permis-
sionSet"> </permission>

<remarks> Provides additional information about a type not found in


the <summary> section.

<returns> Place a textual description of what is returned from a


method or property between the beginning and ending
tags.

<seealso cref="price"> The cref attribute is set to the name of an associated


type, field, method, or other type member.

<summary> Contains a class description; is used by IntelliSense in


VisualStudio.NET.

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:

C:\> csc consoleapp.cs /doc:consoleXML.xml

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

To suppress this, add the /nowarn:1591 option to the compile-line command.


The option accepts multiple warning codes separated with a comma.

Core Note

Many documentation tools are available to transform and extend the C#


XML documentation output. One of the most advanced is NDoc
(ndoc.sourceforge.net), an open source tool that not only formats
the XML but uses reflection to glean further information about an
assembly.

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.

Table 2-3 C# Primitive Data Types

C# Primitive Type FCL Data Type Description

object System.Object Ultimate base type of all other types.

string System.String A sequence of Unicode characters.

decimal System.Decimal Precise decimal with 28 significant digits.

bool System.Boolean A value represented as true or false.

char System.Char A 16-bit Unicode character.

byte System.Byte 8-bit unsigned integral type.

sbyte System.SByte 8-bit signed integral type.

short System.Int16 16-bit signed integral type.

int System.Int32 32-bit signed integral type.


46 Chapter 2 ■ C# Language Fundamentals

Table 2-3 C# Primitive Data Types (continued)

C# Primitive Type FCL Data Type Description

long System.Int64 64-bit signed integral type.

ushort System.UInt16 16-bit unsigned integral type.

uint System.UInt32 32-bit unsigned integral type.

ulong System.UIint64 64-bit unsigned integral type.

single (float) System.Single Single-precision floating-point type.

double System.Double Double-precision floating-point type.

As the table shows, primitives map directly to types in the base class library and
can be used interchangeably. Consider these statements:

System.Int32 age = new System.Int32(17);


int age = 17;
System.Int32 age = 17;

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:

int iMax = int.MaxValue; // Return largest integer


int pVal = int.Parse("100"); // converts string to int

The C# compiler supports implicit conversions if the conversion is a


“safe” conversion that results in no loss of data. This occurs when the
target of the conversion has a greater precision than the object being
converted, and is called a widening conversion. In the case of a
narrowing conversion, where the target has less precision, the
conversion must have explicit casting. Casting is used to coerce, or
convert, a value of one type into that of another. This is done
2.2 Primitives 47

syntactically by placing the target data type in parentheses in front of


the value being converted: int i = (int)y;.

short i16 = 50; // 16-bit integer


int i32 = i16; // Okay: int has greater precision
i16 = i32; // Fails: short is 16 bit, int is 32
i16 = (short) i32; // Okay since casting used

• Literal values assigned to the types float, double, and decimal


require that their value include a trailing letter: float requires F or f;
double has an optional D or d; and decimal requires M or m.

decimal pct = .15M; // M is required for literal 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.

decimal iRate = 3.9834M; // decimal requires M


iRate = decimal.Round(iRate,2); // Returns 3.98
decimal dividend = 512.0M;
decimal divisor = 51.0M;
decimal p = decimal.Parse("100.05");
// Next statement returns remainder = 2
decimal rem = decimal.Remainder(dividend,divisor);

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:

myChar = 'B'; // 'B' has an ASCII value of 66


myChar = (char) 66; // Equivalent to 'B'
myChar = '\u0042'; // Unicode escape sequence
myChar = '\x0042'; // Hex escape sequence
myChar = '\t'; // Simple esc sequence:horizontal tab
bool bt;
string pattern = "123abcd?";
myChar = pattern[0]; // '1'
bt = char.IsLetter(pattern,3); // true ('a')
bt = char.IsNumber(pattern,3); // false
bt = char.IsLower(pattern,0); // false ('1')
bt = char.IsPunctuation(pattern,7); // true ('?')
bt = char.IsLetterOrDigit(pattern,1); // true
bt = char.IsNumber(pattern,2); // true ('3')
string kstr="K";
char k = char.Parse(kstr);

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.

byte[] b = {0x00, 0x12, 0x34, 0x56, 0xAA, 0x55, 0xFF};


string s = b[4].ToString(); // returns 170
char myChar = (char) b[3];

short, int, long


These represent 16-, 32-, and 64-bit signed integer values, respectively. The
unsigned versions are also available (ushort, uint, ulong).

short i16 = 200;


i16 = 0xC8 ; // hex value for 200
int i32 = i16; // no casting required
2.2 Primitives 49

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.

float xFloat = 24567.66F;


int xInt = Convert.ToInt32(xFloat); // returns 24567
int xInt2 = (int) xFloat;
if(xInt == xInt2) { } // False
string xStr = Convert.ToString(xFloat);
single zero = 0;
if (Single.IsNaN(0 / zero)) { } // True
double xDouble = 124.56D;

Note that the F suffix is used when assigning a literal value to a single type, and
D is optional for a double type.

Using Parse and TryParse to


Convert a Numeric String
The primitive numeric types include Parse and TryParse methods that are used to
convert a string of numbers to the specified numeric type. This code illustrates:

short shParse = Int16.Parse("100");


int iParse = Int32.Parse("100");
long lparse = Int64.Parse("100");
decimal dParse = decimal.Parse("99.99");
float sParse = float.Parse("99.99");
double dbParse = double.Parse("99.99");

TryParse, introduced in .NET 2.0, provides conditional parsing. It returns a


boolean value indicating whether the parse is successful, which provides a way to
50 Chapter 2 ■ C# Language Fundamentals

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.

2.3 Operators: Arithmetic, Logical,


and Conditional
The C# operators used for arithmetic operations, bit manipulation, and conditional
program flow should be familiar to all programmers. This section presents an over-
view of these operators that is meant to serve as a syntactical reference.

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.

Table 2-4 Numerical Operators

Operator Description Example

+ (3) Addition int x = y + 10;


- Subtraction

* (2) Multiplication int x = 60;


/ Division, int y = 15;
% Modulo int z = x * y / 2; // 450
y = x % 29 ; // remainder is 2

++ (1) Prefix/postfix x = 5;
-- Increment/decrement Console.WriteLine(x++) // x = 5
Console.WriteLine(++x) // x = 6

~ (1) Bitwise complement int x = ~127; // returns -128


2.3 Operators: Arithmetic, Logical, and Conditional 51

Table 2-4 Numerical Operators (continued)

Operator Description Example

>> (4) Shift right byte x = 10; // binary 10 is 01010


<< Shift left int result = x << 1; // 20 = 10100
result = x >> 2; // 5 = 00101
Works with byte, char, short, int, and
long

& (5-6-7) Bitwise AND byte x = 12; // 001100


| Bitwise OR byte y = 11; // 001011
^ Bitwise XOR int result = x & y; //8 = 001000
result = x ^ y; //7 = 000111

Core Note

C# does not provide an exponentiation operator. Instead, use the


Math.Pow() method to raise a number to a power, and Math.Exp() to
raise e to a power.

Conditional and Relational Operators


Relational operators are used to compare two values and determine their relation-
ship. They are generally used in conjunction with conditional operators to form more
complex decision constructs. Table 2-5 provides a summary of C# relational and con-
ditional operators.

Table 2-5 Relational and Conditional Boolean Operators

Statement Description Example

== Equality if (x == y) {...}
!= Inequality

< Numeric less than if (x <= y) {...}


<= Less than or equal to
> Greater than
>= Greater than or equal to

&& Logical AND if (x == y && y < 30) {...}


|| Logical OR If first expression is false, second is not evaluated
52 Chapter 2 ■ C# Language Fundamentals

Table 2-5 Relational and Conditional Boolean Operators (continued)

Statement Description Example

& Logical AND if (x== y | y < 30) {...}


| Logical OR Always evaluates second expression

! Logical negation if !(x ==y && y < 30) {...}

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.

Control Flow Statements


The C# language provides if and switch conditional constructs that should be
quite familiar to C++ and Java programmers. Table 2-6 provides a summary of these
statements.

Table 2-6 Control Flow Statements

Conditional Statement Example

if (boolean expression) { if (bmi < 24.9) {


// statements weight = "normal";
} else { riskFactor = 2;
// statements } else {
} weight = "over";
riskFactor=6;
}
2.3 Operators: Arithmetic, Logical, and Conditional 53

Table 2-6 Control Flow Statements (continued)

Conditional Statement Example

switch (expression) switch (ndx)


{ {
case constant expression: case 1:
// statements; fabric = "cotton";
// break/goto/return() blend = "100%";
case constant expression: break;
// statements; case 2: // combine 2 & 3
// break/goto/return() case 3:
default: fabric = "cotton";
// statements; blend = "60%";
// break/goto/return() break;
} default: // optional
fabric = "cotton";
• Constant expression may be an integer, enum blend = "50%";
value, or string. break;
• No “fall through” is permitted. Each case }
block must end with a statement that transfers
control.

if-else
Syntax:

if ( boolean expression ) statement


if ( boolean expression ) statement1 else statement2

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:

switch( expression ) {switch block}

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:

// switch with string expression


using System;
public class MyApp
{
static void Main(String[] args)
{
switch (args[0])
{
case "COTTON": // is case sensitive
case "cotton":
Console.WriteLine("A good natural fiber.");
goto case "natural";
case "polyester":
Console.WriteLine("A no-iron synthetic fiber.");
break;
case "natural":
Console.WriteLine("A Natural Fiber. ");
break;
default:
Console.WriteLine("Fiber is unknown.");
break;
}
}
}
2.4 Loops 55

The most important things to observe in this example are as follows:

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

while ( boolean expression ) { body }

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:

byte[] r = {0x00, 0x12, 0x34, 0x56, 0xAA, 0x55, 0xFF};


int ndx=0;
int totVal = 0;
while (ndx <=6)
{
totVal += r[ndx];
ndx += 1;
}
56 Chapter 2 ■ C# Language Fundamentals

do loop
Syntax:

do { do-body } while ( boolean expression );

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:

byte[] r = {0x00, 0x12, 0x34, 0x56, 0xAA, 0x55, 0xFF};


int ndx=0;
int totVal = 0;
do
{
totVal += r[ndx];
ndx += 1;
}
while (ndx <= 6);

for loop
Syntax:

for ( [initialization]; [termination condition]; [iteration] )


{ for-body }

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:

int[] r = {80, 88, 90, 72, 68, 94, 83};


int totVal = 0;
for (int ndx = 0; ndx <= 6; ndx++) {
totVal += r[ndx];
}
2.4 Loops 57

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:

for (ndx = 0; ndx < 6; )


{
totVal += r[ndx];
ndx++; // increment here
}

You can also leave out all of the for clauses:

for (;;) { body } // equivalent to while(true) { body }

A return, goto, or break statement is required to exit this loop.

foreach loop
Syntax:

foreach ( type identifier in collection ) { body }

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

In a one-dimensional array, iteration begins with index 0 and moves in ascending


order. In a multi-dimensional array, iteration occurs through the rightmost index
first. For example, in a two-dimensional array, iteration begins in the first column
and moves across the row. When it reaches the end, it moves to the next row of the
first column and iterates that row.
58 Chapter 2 ■ C# Language Fundamentals

Transferring Control Within a Loop


It is often necessary to terminate a loop, or redirect the flow of statements within the
loop body, based on conditions that arise during an iteration. For example, a while
(true) loop obviously requires that the loop termination logic exists in the body.
Table 2-7 summarizes the principal statements used to redirect the program flow.

Table 2-7 Statements to Exit a Loop or Redirect the Iteration

Statement Description Example

break Redirects program while (true) {


control to the end ndx+=1;
point of a containing if (ndx >10) break;
loop construct. }

continue Starts a new iteration while (ndx <10) {


of enclosing loop ndx +=1;
without executing if(ndx %2 =1) continue;
remaining state- totVal += ndx;
}
ments in loop.

goto Directs program con- public int FindMatch(string myColor)


identifier; trol to a label, a case {
statement within a string[] colorsAvail("blueaqua",
goto case exp; switch block, or the "red", "green","navyblue");
int loc;
default statement
goto default; int matches=0;
within a switch foreach (colorType in colorsAvail)
block. {
loc = colortype.IndexOf(myColor);
The goto may not if (loc >=0) goto Found;
transfer control into a continue;
nested scope—for Found:
example, a loop. matches += 1;
}
return(matches);
}

return Returns program public double Area(double w, double l)


[expression] ; control to the method {
that called the cur- return w * l;
rent method. Returns }
no argument if the
enclosing method has
a void return type.
2.5 C# Preprocessing Directives 59

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.

2.5 C# Preprocessing Directives


Preprocessing directives are statements read by the C# compiler during its lexical
analysis phase. They can instruct the compiler to include/exclude code or even abort
compilation based on the value of preprocessing directives.
A preprocessor directive is identified by the # character that must be the first non-
blank character in the line. Blank spaces are permitted before and after the # symbol.
Table 2-8 lists the directives that C# recognizes.

Table 2-8 Preprocessing Directives

C# Preprocessing
Symbol Description

#define Used to define and undefine a symbol. Defining a symbol makes it


#undef evaluate to true when used in a #if directive.

#if Analogues to the C# if, else if, and else statements.


#elif
#else
#endif

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

#error #error causes the compiler to report a fatal error.


#warning #warning causes the compiler to report a warning and continue
processing.

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:

csc /Define:DEBUG myproject.cs

Compiling code with this statement is equivalent to including a #Define DEBUG


statement in the source code.

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.

Table 2-9 String Escape Characters

Escape Character Description

\' Inserts a single quote into a string

\" Inserts a double quote

\\ Inserts a backslash; useful for file paths

\a System alert

\b Backspace

\f Form feed

\n Inserts a new line

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

string myQuote, path;


myQuote = @"The solution is in the problem.";
myQuote = "The solution\nis in the problem.";
myQuote = "The Unicode representation of f is \u0066";
// The next two statements assign the same value to myQuote.
myQuote = @"""The solution is in the problem. """;
myQuote = "\"The solution is in the problem. "";
// The next two statements assign the same value to path.
path = @"c:\my documents\notes.txt";
path = "c:\\my documents\\notes.txt";
path = "c:\my documents\notes.txt"; // Fails
2.6 Strings 63

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.

Indexing Individual Characters in a String


The foreach and while loops offer the easiest way to iterate through the characters
in a string. In both cases, the operations are read-only.

// Example 1 - using foreach statement


string myQuote = "The solution is in the problem.";
foreach (char cc in myQuote)
{
Console.Write(cc.ToString());
}

// Example 2 - using while loop


int ndx = 0;
while (ndx < myQuote.Length)
{
Console.Write(myQuote[ndx].ToString());
ndx += 1;
}

Note that before an individual character can be displayed or assigned to a string, it


must be converted to a string type.

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.

string s1 = "My age = ";


int myAge = 28;
string cat = s1 + myAge; // My age = 28
MyClass clStr = new MyClass;
Cat = "Class Name = " + clStr; // Class Name = MyClass
64 Chapter 2 ■ C# Language Fundamentals

The concatenation operation is simple to use, but it is important to understand


what is going on behind the scenes: During concatenation, the strings being joined
are copied and a new combined string is allocated space. Each concatenation results
in the allocation of more memory equal to the length of the new string. This is an
acceptable use of resources as long as the number of concatenations is minimal.
However, if concatenation occurs inside a long loop, an application’s performance
can suffer.
Consider an example where an HTML document is constructed by inserting the
<br> tag between names in a list.

// assume names is an array containing 1000 names


string nameList = "";
foreach (string custName in names)
{
// This is inefficient and should be avoided.
nameList = nameList + custName+"<br>";
}

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.

Extracting and Locating Substrings


The Substring method extracts selected portions of a string. Its two overloads are
illustrated here:

string poem = "In Xanadu did Kubla Khan";


string poemSeg;
poemSeg = poem.Substring(10); // did Kubla Khan
// second argument specifies length
poemSeg = poem.Substring(0,9); // In Xanadu

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

Listing 2-1 Locating Text Occurrences in a String


// Method to count the occurrences of text in a given string
public static int CharCount(String strSource,String strToFind)
{
int iCount=0; // string type has index of 0
int iPos=strSource.IndexOf(strToFind);
while(iPos!=-1)
{
iCount++;
iPos=strSource.IndexOf(strToFind, iPos+1);
}
return iCount;
}
public class MyApp
{
static void Main()
{
string txt = "In Xanadu did Kubla Khan";
int ct = CharCount(txt, "a"); // ct = 4
}
}

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

isMatch = (title.ToUpper() == "ANCIENT MARINER"); // true


isMatch = (title == "Ancient"+" Mariner"); // true
isMatch = title.Equals("Ancient Mariner"); // true

Note that the == operator is just a syntactical shortcut for calling the Equals
method; it is actually faster to call Equals()directly.

2.7 Enumerated Types


An enumerated type, or enum as it’s called in C#, offers a convenient way to create a
structured set of symbols to represent constant values.

Syntax:

[access modifiers]enum <identifier> [:enum-base]{enum body}

Example:

enum Fabric :short {


Cotton = 1,
Silk = 2,
Wool = 4,
Rayon = 8,
Other = 128
}

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.

Working with Enumerations


Enumerated types not only improve program readability, but also minimize code
changes when the underlying value changes. In such cases, all references to the value
2.7 Enumerated Types 67

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.

Listing 2-2 Using an Enumerated Type


static double GetPrice(Fabric fab)
{
switch(fab)
{
case Fabric.Cotton: return(3.55);
case Fabric.Silk: return(5.65);
case Fabric.Wool: return(4.05);
case Fabric.Rayon: return(3.20);
case Fabric.Other: return(2.50);
default: return(0.0);
}
}
static void Main()
{
Fabric fab = Fabric.Cotton;
int fabNum = (int) fab; // 1
string fabType = fab.ToString(); // "Cotton"
string fabVal = fab.ToString("D"); // "1"
double cost = GetPrice(fab); // 3.55
}

Things to note:

• Casting is required to set the value of an enum to an integer variable:


fabNum = (int) fab;
• The character value of the underlying constant value can be obtained
using the ToString() method with the parameter "D". "D" is a
format character that converts a value to its decimal form.
• Passing an instance of the Fabric enum to GetPrice requires that
the corresponding parameter in the GetPrice method is declared as
the same type.
68 Chapter 2 ■ C# Language Fundamentals

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.

string fabStr = "Cotton";


// Determine if symbol Cotton exists in Fabric enum
if (Enum.IsDefined(typeof(Fabric),fabStr))
{
// Create enum instance
Fabric fab = (Fabric)Enum.Parse(
typeof(Fabric) , fabStr);
// Output from the following statement is: "Silk"
Console.WriteLine("Second value of Fabric Enum is: " +
Enum.GetName(typeof(Fabric), 2));
}

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

Enums and Bit Flags


It was not by accident that the values of the Fabric enum were set to powers of 2.
Enum members are often used in logical operations where such values have obvious
advantages in mapping to unique bit values. You may have code to identify a combi-
nation of values:

Fabric cotWool = Fabric.Cotton | Fabric.Wool;


Console.WriteLine(cotWool.ToString()); // Output: 5

It would be more meaningful if the output identified the variable as a combination


of wool and cotton. This can be accomplished by adding the [Flags] attribute to the
enum declaration:

[Flags]
enum Fabric :short {

The ToString() method checks an enum declaration to see if this attribute is


present. If so, it treats the enum as a set of bitmapped flag elements. In this example,
it cannot find a symbolic value equal to 5, so it uses the bit pattern “101” and prints
the symbols having the bit patterns “001” and “100”. The new output is a comma-
delimited list: “Cotton, Wool”.

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:

• Each element in the array is of the type specified in the array


declaration. Besides the usual primitive types, an array may contain
structures, objects, enums, and even other arrays.
70 Chapter 2 ■ C# Language Fundamentals

• Common Language Specification (CLS) compliance requires that all


arrays be zero-based to ensure language interoperability (for example,
an array reference in C# can be passed to code written in VB.NET).
Although it is possible to create a non-zero–based array in C#, it is
discouraged because array operations are optimized for zero-based
dimensions.
• When an array is created, it is allocated space for a fixed number of
elements. Its capacity cannot be dynamically increased. The
ArrayList/List is usually a better choice when the number of
elements the array must hold is unknown.

Declaring and Creating an Array


Syntax:

<type> identifier [ ] = new <type> [n] [{ initializer list}]

Example:

int[] myRating; // declares an array


myRating = new int[5]; // creates array and allocates memory
int[] myRating = new int[5] {3,4,7,2,8};
int[] myRating = {3,4,7,2,8}; // shorthand version

// Create array containing instances of an Apparel class.


Apparel myApparel = {new Apparel(), new Apparel(),
new Apparel());

// Set to an enum
Fabric[] enumArray = new Fabric[2];
enumArray[0] = Fabric.Cotton;

// Create a 2-dimensional array with 3 rows and 2 columns


int[ , ] myRatings = {{3 , 7}, {4 , 9}, {2, 6}};

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

Using System.Array Methods and Properties


The System.Array class defines a number of properties and methods that are used
to query an array and manipulate its contents. Array operations include sorting, copy-
ing, searching for specific values, and clearing the contents. Table 2-10 summarizes
some of the more useful members of the System.Array class.

Table 2-10 Selected Members of System.Array

Member Type Description

Length Instance Total number of elements in the array.


property

Rank Instance Number of dimensions of the array.


property

CreateInstance Static Creates an Array object.


method To create a single-dimensional array:
int[] rank1 =
(int[]) Array.CreateInstance(typeof(int),4);

GetUpperBound(n) Instance The upper bound of a specified dimension n. Returned value is


method Length – 1.
d0 = myArray.GetUpperBound(0);
d1= myArray.GetUpperBound(1);

Sort Static Sorts the elements in an array or a section of an array.


method

Reverse Static Reverses the elements in a one-dimensional array.


method

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.

Copy Static Copies a selected portion of one array to another.


method

Clear Static Sets a specified range of elements in an array to zero or null.


method
72 Chapter 2 ■ C# Language Fundamentals

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.

Listing 2-3 Working with Arrays Using System.Array Members


class MyApp
{
static void Main()
{
string[] artists = {"Rembrandt", "Velazquez",
"Botticelli", "Goya", "Manet","El Greco"};
// ..Sort array in ascending order
Array.Sort(artists);
// ..Invert the array
Array.Reverse(artists);
PrintArray(artists); // call method to list array
int ndx = Array.IndexOf(artists,"Goya"); // ndx = 3
// ..Clone the array
string[] artClone = (string[]) artists.Clone();
// Do arrays point to same address?
bool eq = Object.ReferenceEquals(
artClone[0],artists[0]); // true
Array.Clear(artClone,0,artClone.Length);
// ..Copy selected members of artists to artClone
Array.Copy(artists,1,artClone,0,4);
eq = Object.ReferenceEquals(
artClone[0],artists[1]); // true
}
// List contents of Array
public static void PrintArray(string[] strArray)
{
for ( int i = 0; i<= strArray.GetUpperBound(0); i++ )
{
Console.WriteLine(strArray[i]);
}
}
}
2.9 Reference and Value Types 73

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:

(source, source index, target, target index, # to copy)

2.9 Reference and Value Types


The Common Language Runtime (CLR) supports two kinds of types: reference types
and value types (see Figure 2-2 on the following page). Reference types include
classes, arrays, interfaces, and delegates. Value types include the primitive data types
such as int, char, and byte as well as struct and enum types. Value and reference
types are distinguished by their location in the .NET class hierarchy and the way in
which .NET allocates memory for each. We’ll look at both, beginning with the class
inheritance hierarchy.

System.Object and System.ValueType


Both reference and value types inherit from the System.Object class. The differ-
ence is that almost all reference types inherit directly from it, whereas value types
inherit further down the hierarchy—directly from the System.ValueType class.
As the base for all types, System.Object provides a set of methods that you can
expect to find on all types. This set includes the ToString method used throughout
this chapter, as well as methods to clone a type, create a unique hash code for a type,
and compare type instances for equality. Chapter 4 discusses these methods in detail
and describes how to implement them on custom classes.
74 Chapter 2 ■ C# Language Fundamentals

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

Figure 2-2 Hierarchy of common reference and value types

System.ValueType inherits from System.Object. It does not add any mem-


bers, but does override some of the inherited methods to make them more suitable
for value types. For example, Equals() is overridden to return true if the value of
two objects’ fields match. By definition, all value types implicitly inherit from the
ValueType class.

Memory Allocation for Reference


and Value Types
The primary difference between value and reference types is the way the CLR han-
dles their memory requirements. Value types are allocated on a runtime stack, and
reference types are placed on a managed heap that is referenced from the stack.
Figure 2-3 illustrates how the value and reference types from our example (refer
to Figure 2-1) are represented in memory. Let’s step through what happens when an
instance of a reference type is created and is then assigned to a second variable:

Apparel myApparel = new Apparel();


Apparel myApparel2 = myApparel;
2.9 Reference and Value Types 75

appStatus 0

myApparel •
myApparel2 •
fabType • "Synthetic"

price 250
Overhead

Thread Stack Managed Heap

Figure 2-3 Memory layout for value and reference types

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:

int age = 17;


object refAge = age;
76 Chapter 2 ■ C# Language Fundamentals

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:

int newAge = (int) refAge;


string newAge = (string) refAge; // Fails. InvalidCastException

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.

// Create array with four values


Int[] ages = {1,2,3,4};

// Place four values in ArrayList


ArrayList ages = new ArrayList();
For (int i=0; i<4; i++) {
ages.add(i); // expects object parameter
}

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

Managed Heap Managed Heap

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

Summary of Value and Reference


Type Differences
Memory Allocation
We have seen that memory allocation is the most significant difference between
value and reference types. Reference types are allocated on the heap and value types
on the thread or call stack. When a reference type is created, it is initialized to null,
indicating it does not point to anything. A value type is initialized to zero (0).

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.11 Test Your Understanding


1. Which method is required in each C# program, and what parameters
does it take?

2. What are the three types of inline comments available in C#? Which
can be exported?

3. What is a primitive?

4. What is printed by the following statements?


int grade=78;
bool pass = (grade >=70) ? true : false;
Console.WriteLine(pass);

5. Which loop construct is executed at least once?

6. How do a break and a continue statement differ?

7. Given the following variables


char c= 'c';
double d= 120.0D;
int i=10;
string s="Rodin";

which of the following fails to compile?


2.11 Test Your Understanding 79

a. c = c+ i;
b. s += i;
c. c += s;
d. d += i;

8. Describe a potential drawback to using the + operator for concatena-


tion. What is the recommended alternative?

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#

Topics in This Chapter

• Defining a Class: The attributes and modifiers included in a class


definition influence the behavior and accessibility of the class.
• Constants: A const type defines fixed values at compilation time.
• Fields: A field is typically used to maintain data inside a class.
• Properties: A property is the recommended way to expose a
class’s data.
• Methods: The functionality of a class is defined by its methods.
• Inheritance: By using inheritance, a class can take advantage of
preexisting types.
• Constructors: This special purpose method is used to initialize a
class.
• Events and Delegates: A program’s actions are often triggered by
events; delegates have the role of invoking methods to handle an
event.
• Operator Overloading: Operators can be used to manipulate
classes.
• Interfaces: An inherited interface defines the methods and
properties that a struct or class must implement.
• Generics: By creating a generic class to store data, it is possible to
eliminate casting, boxing, and ensure type safety.
• Structures: In some situations, a struct is a better choice than a
class.
3

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#

3.1 Introduction to a C# Class


Figure 3-1 displays a class declaration followed by a body containing typical class
members: a constant, fields, a constructor containing initialization code, a property,
and a method. Its purpose is to familiarize you with the syntax common to most C#
classes and serve as a reference for later examples.

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

Figure 3-1 Class declaration and body

3.2 Defining a Class


A class definition consists of an optional attributes list, optional modifiers, the word
class followed by the class identifier (name), and an optional list containing a base
class or interfaces to be used for inheritance. Following this class declaration is the
class body, consisting of the code and class members such as methods and properties.

Syntax for class definition:

[attributes] [modifiers] class identifier [:baselist]


{class body} [;]

Classes—as do all .NET types—inherit from the System.Object class. This


inheritance is implicit and is thus not specified as part of the class definition. As we’ll
3.2 Defining a Class 83

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]

Single attribute with named argument and positional argument (0):

[ClassDesc(Author="Knuth", 0)]

Multiple attributes can be defined within brackets.:

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

• An attribute is an instance of a public class. As such, it has fields and


properties that can be used to provide rich descriptive information
about a target or targets.
• All compilers that target the Common Language Runtime (CLR) rec-
ognize attributes and store information about them in the module’s
metadata. This is an elegant way to attach information to a program
entity that can affect its behavior without modifying its implementa-
tion code. An application can then use reflection (a set of types for
reading metadata) to read the metadata at runtime and make deci-
sions based on its value.
• Hundreds of predefined attributes are included in the .NET Frame-
work Class Library (FCL). They are used heavily in dealing with
84 Chapter 3 ■ Class Design in C#

interoperability issues such as accessing the Win32API or allowing


.NET applications and COM objects to communicate. They also are
used to control compiler operations. The[assembly:CLSCompliant-
true)] attribute in Figure 3-1 tells the C# compiler to check the code
for CLS compliance.

Core Note

Attributes provide a way to extend the metadata generated by the C#


compiler with custom descriptive information about a class or class
member.

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

File: attribs.cs (attribs.dll)

#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

File: attribclient.cs (attribclient.exe)

#define TRACE
using System;
public class MyApp {
static void Main()
{
Console.WriteLine("Testing Method Calls");
AttributeTest.ListTrace();
AttributeTest.ListDebug();
}
}

Executing attribclient yields the following output:

Testing Method Calls


Trace is On

When attribclient is compiled, the compiler detects the existence of the


TRACE symbol, so the call to ListTrace is included. Because DEBUG is not defined,
the call to ListDebug is excluded. The compiler ignores the fact that DEBUG is
defined in attribs; its action is based on the symbols defined in the file containing
the method calls. Note that a conditional attribute can be used only with methods
having a return type of void.

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.

public A class can be accessed from any assembly.


protected Applies only to a nested class (class defined within another
class). Access is limited to the container class or classes derived
from the container class.
internal Access is limited to classes in the same assembly. This is the
default access.
private Applies only to a nested class. Access is limited to the container
class.
protected The only case where multiple modifiers may be used.
internal Access is limited to the current assembly or types derived from
the containing class.
86 Chapter 3 ■ Class Design in C#

Core Note

A base class must be at least as accessible as its derived class. The


following raises an error:

class Furniture { } // default access is internal


public class Sofa : Furniture { } // error

The error occurs because the Furniture class (internal by default) is


less accessible than the derived Sofa class. Errors such as this occur
most frequently when a developer relies on a default modifer. This is
one reason that modifiers should be included in a declaration.

Abstract, Sealed, and Static Modifiers


In addition to the access modifiers, C# provides a dozen or so other modifiers for use
with types and type members. Of these, three can be used with classes: abstract,
sealed, and static.
abstract Indicates that a class is to be used only as a base class for other
classes. This means that you cannot create an instance of the
class directly. Any class derived from it must implement all of its
abstract methods and accessors. Despite its name, an abstract
class can possess nonabstract methods and properties.
sealed Specifies that a class cannot be inherited (used as a base class).
Note that .NET does not permit a class to be both abstract and
sealed.
static Specifies that a class contains only static members (.NET 2.0).

Class Identifier
This is the name assigned to the class. The ECMA standard recommends the follow-
ing guidelines for naming the identifier:

• Use a noun or noun phrase.


• Use the Pascal case capitalization style: The first letter in the name
and the first letter of each subsequent concatenated word are capital-
ized—for example, BinaryTree.
• Use abbreviations sparingly.
• Do not use a type prefix, such as C, to designate all classes—for exam-
ple, BinaryTree, not CBinaryTree.
• Do not use the underscore character.
3.2 Defining a Class 87

• By convention, interface names always begin with I; therefore, do not


use I as the first character of a class name unless I is the first letter in
an entire word—for example, IntegralCalculator.

Base Classes, Interfaces, and Inheritance


This optional list contains a previously defined class or interface(s) from which a class
may derive its behavior and capabilities. The new class is referred to as the derived
class, and the class or interface from which it inherits is the base class or interface. A
base class must be listed before any interface(s).

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

• Inheritance from a base class is referred to as implementation


inheritance. The derived class inherits all of the members of the base
class. However, the base class can prevent access to a member by
defining it with the private modifier.
• Inheritance from an interface is referred to as interface inheritance
because the interface does not provide implementation code. The
derived class must provide the logic to implement any functions
defined in the base interface(s).
88 Chapter 3 ■ Class Design in C#

3.3 Overview of Class Members


Table 3-1 provides a summary of the types that comprise a .NET class. They can be
classified broadly as members that hold data—constants, fields, and properties—and
members that provide functionality—the constructor, method, and event. We’ll look
at each individually.

Table 3-1 Class Members

Member Type Valid In Description

Constant Class, Structure A symbol that represents an unchanging value.


The compiler associates it with the class—not an
instance of the class.

Field Class, Structure A variable that holds a data value. It may be


read-only or read/write.

Property Class, Structure Provides access to a value in a class. It uses an


accessor that specifies the code to be executed in
order to read or write the value. The code to read
or write to a property is implemented implicitly
by .NET as two separate methods.

Constructor Class, Structure C# has three types of constructors:


Instance. Initializes fields when an instance of
a class is created.
Private. Commonly used to prevent instances
of a class from being created.
Static. Initializes class before any instance is
created.

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.

Types Class, Structure, Classes, interfaces, structs, delegates.


Interface
3.4 Constants, Fields, and Properties 89

Member Access Modifiers


The access modifiers used for a class declaration can also be applied to class mem-
bers. They determine the classes and assemblies that have access to the class. Table
3-2 summarizes the scope of accessibility.

Table 3-2 Summary of Accessibility Provided by Access Modifiers

Access Modifiers

Class can be accessed


public protected Internal private
by classes in:

Another assembly Yes * No *

Same assembly Yes * Yes *

Containing class Yes Yes Yes Yes

Class derived from containing class Yes Yes Yes No

* Not applicable

3.4 Constants, Fields, and Properties


Constants, fields, and properties are the members of a class that maintain the con-
tent or state of the class. As a rule of thumb, use constants for values that will
never change; use fields to maintain private data within a class; and use properties
to control access to data in a class. Let’s now look at the details of these three class
members.

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#

Listing 3-1 Constants


using System;
class Conversions
{
public const double Cm = 2.54;
public const double Grams = 454.0 , km = .62 ;
public const string ProjectName = "Metrics";
}
class ShowConversions
{
static void Main()
{
double pounds, gramWeight;
gramWeight = 1362;
pounds = gramWeight / Conversions.Grams;
Console.WriteLine(
"{0} Grams= {1} Pounds", gramWeight,pounds);
Console.WriteLine("Cm per inch {0}", Conversions.Cm);
Conversions c= new Conversions(); // Create class
// instance
// This fails to compile. Cannot access const from object
Console.WriteLine("Cm per inch {0}", c.Cm);
}
}

• The const keyword can be used to declare multiple constants in one


statement.
• A constant must be defined as a primitive type, such as string or
double shown here (see Chapter 2 for a list of all C# primitives).
• Constants cannot be accessed from an instance of the class. Note that
the ShowConversion class accesses the constants without instantiat-
ing the class.

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

It is often desirable to store a group of constants in a special utility or


helper class. Declare them public to make them available to all classes.
Because there is no need to create instances of the class, declare the
class as abstract.

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

Table 3-3 Field Modifiers

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:

const double salesTax = .065;

can be replaced with a field

public static readonly double SalesTax = .065;

A method then references this field as Furniture.SalesTax, and the readonly


modifier ensures the value cannot be changed. Note that if you create an instance of
this class, you cannot access salesTax as a member of that instance. An attempt to
do so results in the compile error “static member cannot be accessed with an
instance reference”.

Furniture chair = new Furniture("Broyhill","12422",225.00);


double itemTax = chair.SalesTax; // Raises an error

Using Static Read-Only Fields


to Reference Class Instances
Static readonly fields can be used to represent groups of related constant data by
declaring them a reference type such as a class instance or array. This is of use when
the data can be represented by a limited number of objects with unchanging values.
This example presents the interesting concept of fields defined as instances of their
containing class. The static modifier makes this possible, because it designates the
field as part of the class and not its instances. Note that the private constructor pre-
vents clients outside the scope of the class from creating new class instances. Thus,
only those objects exposed by the fields are available outside the class.
The class also contains two instance fields, yardPrice and deliveryWeeks, that
are declared as private. Access to them is controlled though a public method and
property:

Upholstery.silk.FabCost(10); // Value from method


Upholstery.silk.DeliveryTime; // Value from property
3.4 Constants, Fields, and Properties 93

Listing 3-2 Using Static Read-Only Fields as Reference Types


public class Upholstery
{
// fields to contain price and delivery time for fabrics
public static readonly Upholstery silk =
new Upholstery(15.00, 8);
public static readonly Upholstery wool =
new Upholstery(12.00, 6);
public static readonly Upholstery cotton =
new Upholstery(9.00, 6);
private double yardPrice;
private int deliveryWeeks;
// constructor - set price per yard and delivery time
// private modifier prevents external class instantiation
private Upholstery ( double yrPrice, int delWeeks)
{
yardPrice = yrPrice;
deliveryWeeks = delWeeks;
}
// method to return total cost of fabric
public double FabCost(double yards)
{
return yards * this.yardPrice;
}
// property to return delivery time
public int DeliveryTime
{get { return deliveryWeeks;}}
// property to return price per yard
public double PricePerYard
{get {return yardPrice;}}
}

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:

1. In addition to the four access modifiers, the property modifier may be


static, abstract, new, virtual, or override. Abstract is used
only in an abstract class; virtual is used in a base class and per-
mits a subclass to override the property.
2. value is an implicit parameter containing the value passed when a
property is called.
3. The get and set accessors may have different access modifiers.

Listing 3-3 Creating and Accessing a Property


public class Upholstery
{
private double yardPrice;
// Property to return or set the price
public double PricePerYard
{
get {return yardPrice;} // Returns a property value
set { // Sets a property value
if ( value <= 0 )
throw new ArgumentOutOfRangeException(
"Price must be greater than 0.");
yardPrice = value
}
}
...
}

The syntax for accessing the property of a class instance is the same as for a field:

// fabObj is instance of Upholstery class


double fabricPrice = fabObj.PricePerYard;
fabObj.PricePerYard = 12.50D;
3.4 Constants, Fields, and Properties 95

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:

[attributes] <modifier><return type> this[parameter(s)] {

Example:

public int this [int ndx] {

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#

Listing 3-4 Using Indexer to Expose an Array of Objects


using System;
using System.Collections; // Namespace containing ArrayList
public class Upholstery
{
// Class to represent upholstery fabric
private double yardPrice;
private int deliveryWeeks;
private string fabName;
// Constructor
public Upholstery (double price, int delivery, string fabric)
{
this.yardPrice = price;
this.deliveryWeeks = delivery;
this.fabName = fabric;
}
// Three readonly properties to return Fabric information
public int DeliveryTime
{get {return deliveryWeeks;}}
public double PricePerYard
{get {return yardPrice;}}
public string FabricName
{get {return fabName;}}
}
public class Fabrics
{
// Array to hold list of objects
private ArrayList fabricArray = new ArrayList();
// Indexer to add or return an object from the array
public Upholstery this[int ndx]
{
get {
if(!(ndx<0 || ndx > fabricArray.Count-1))
return (Upholstery)fabricArray[ndx];
// Return empty object
else return(new Upholstery(0,0,""));
}
set { fabricArray.Insert(ndx, value);}
}
}
public class IndexerApp
{
3.5 Methods 97

Listing 3-4 Using Indexer to Expose an Array of Objects (continued)


public static void Main()
{
Fabrics sofaFabric = new Fabrics();
// Use Indexer to create array of Objects
sofaFabric[0] = new Upholstery(15.00, 8, "Silk");
sofaFabric[1] = new Upholstery(12.00, 6, "Wool");
sofaFabric[2] = new Upholstery(9.00, 6, "Cotton");
// Next statement prints "Fabric: Silk"
Console.WriteLine("Fabric: {0} ",
sofaFabric[0].FabricName);
}
}

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:

Fabrics sofaFabric = new Fabrics();


// Use Indexer to create array of Objects
sofaFabric[0] = new Upholstery(15.00, 8, "Silk");
sofaFabric[1] = new Upholstery(12.00, 6, "Wool");

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:

Fabrics curtainFabric = new Fabrics();


curtainFabric[0] = new Upholstery(11.00, 4, "Cotton");
curtainFabric[1] = new Upholstery(7.00, 5, "Rayon");

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.

Table 3-4 Method Modifiers

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.

new Permits a method in an inherited class to “hide” a non-virtual method with


a same name in the base class. It replaces the original method rather than
overriding it.
3.5 Methods 99

Table 3-4 Method Modifiers (continued)

Modifier Description

sealed Prevents a derived class from overriding this method.


• Is used in a derived class that will serve as the base for its own subclasses.
• Must be used with the override modifier.

abstract The method contains no implementation details and must be implemented


by any subclass. Can only be used as a member of an abstract class.

extern Indicates that the method is implemented externally. It is generally used


with the DLLImport attribute that specifies a DLL to provide the imple-
mentation.

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.

Listing 3-5 Static Method


using System;
class Conversions
{
// class contains functions to provide metric conversions
private static double cmPerInch = 2.54;
private static double gmPerPound = 455;
public static double inchesToMetric(double inches) {
return(inches * cmPerInch);
}
public static double poundsToGrams(double pounds) {
return(pounds * gmPerPound);
}
}
class Test
{
static void Main() {
double cm, grams;
cm = Conversions.inchesToMetric(28.5);
grams = Conversions.poundsToGrams(984.4);
}
}
100 Chapter 3 ■ Class Design in C#

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

Method Inheritance with Virtual and Override Modifiers


Inheritance enables a program to create a new class that takes the form and function-
ality of an existing (base) class. The new class then adds code to distinguish its behav-
ior from that of its base class. The capability of the subclass and base class to respond
differently to the same message is classical polymorphism. In practical terms, this
most often means that a base and derived class(es) provide different code for meth-
ods having the same signature.
By default, methods in the base class cannot be changed in the derived class. To
overcome this, .NET provides the virtual modifier as a cue to the compiler that a
method can be redefined in any class that inherits it. Similarly, the compiler requires
that any derived class that alters a virtual method preface the method with the over-
ride modifier. Figure 3-2 and Listing 3-6 provide a simple illustration of this.

Class Fiber

Class Natural

Class Cotton

Figure 3-2 Relationship between base class and subclasses for Listing 3-6

Listing 3-6 Virtual Methods


using System;
class Fiber
{
public virtual string ShowMe() { return("Base");}
}
class Natural:Fiber
{
public override string ShowMe() { return("Natural");}
}
class Cotton:Natural
{
public override string ShowMe() { return("Cotton");}
}
3.5 Methods 101

Listing 3-6 Virtual Methods (continued)


class Test
{
static void Main ()
{
Fiber fib1 = new Natural(); // Instance of Natural
Fiber fib2 = new Cotton(); // Instance of Cotton
string fibVal;
fibVal = fib1.ShowMe(); // Returns "Natural"
fibVal = fib2.ShowMe(); // Returns "Cotton"
}
}

In this example, Cotton is a subclass of Natural, which is itself a subclass of


Fiber. Each subclass implements its own overriding code for the virtual method
ShowMe.

fib1.ShowMe(); //returns "Natural"


fib2.ShowMe(); //returns "Cotton"

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

New Modifier and Versioning


There are situations where it is useful to hide inherited members of a base class. For
example, your class may contain a method with the same signature as one in its base
class. To notify the compiler that your subclass is creating its own version of the
method, it should use the new modifier in the declaration.
ShowMe() is no longer virtual and cannot be overridden. For Natural to create
its own version, it must use the new modifier in the method declaration to hide the
inherited version. Observe the following:

• An instance of the Natural class that calls ShowMe()invokes the new


method:
Natural myFiber = new Natural();
string fibTtype = myFiber.ShowMe(); // returns "Natural"
• Cotton inherits the new method from Natural:
Cotton myFiber = new Cotton();
string fibType = myFiber.ShowMe(); // returns "Natural"
102 Chapter 3 ■ Class Design in C#

• If ShowMe were declared as private rather than public in Natural,


Cotton would inherit ShowMe from Fiber, because it cannot inherit a
method that is out of scope.

Listing 3-7 Using the New Modifier for Versioning


public class Fiber
{
public string ShowMe() {return("Base");}
public virtual string GetID() {return("BaseID");}
}
public class Natural:Fiber
{
// Hide Inherited version of ShowMe
new public string ShowMe() {return("Natural");}
public override string GetID() {return("NaturalID");}
}
public class Cotton:Natural
{ // Inherits two methods: ShowMe() and GetID()
}

Sealed and Abstract Modifiers


A sealed modifier indicates that a method cannot be overridden in an inheriting
class; an abstract modifier requires that the inheriting class implement it. In the
latter case, the base class provides the method declaration but no implementation.
This code sample illustrates how an inheriting class uses the sealed modifier to
prevent a method from being overridden further down the inheritance chain. Note
that sealed is always paired with the override modifier.

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

An abstract method represents a function with a signature—but no implementa-


tion code—that must be defined by any non-abstract class inheriting it. This differs
from a virtual method, which has implementation code, but may be redefined by an
inheriting class. The following rules govern the use abstract methods:

• Abstract methods can only be declared in abstract classes; however,


abstract classes may have non-abstract methods.
• The method body consists of a semicolon:
public abstract void myMethod();
• Although implicitly virtual, abstract methods cannot have the
virtual modifier.
• A virtual method can be overridden by an abstract method.

When facing the decision of whether to create an abstract class, a developer


should also consider using an interface. The two are similar in that both create a
blueprint for methods and properties without providing the implementation details.
There are differences between the two, and a developer needs to be aware of these
in order to make the better choice. The section on interfaces in this chapter offers a
critical comparison.

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#

Listing 3-8 Using the ref and out Parameter Modifiers


class TestParms
{
public static void FillArray(out double[] prices)
{
prices = new double[4] {50.00,80.00,120.00,200.00};
}
public static void UpdateArray(ref double[] prices)
{
prices[0] = prices[0] * 1.50;
prices[1] = prices[1] * 2.0;
}
public static double TaxVal(double ourPrice,
out double taxAmt)
{
double totVal = 1.10 * ourPrice;
taxAmt = totVal – ourPrice;
ourPrice = 0.0; // Does not affect calling parameter
return totVal;
}
}
class MyApp
{
public static void Main()
{
double[] priceArray;
double taxAmt;
// (1) Call method to initialize array
TestParms.FillArray(out priceArray);
Console.WriteLine( priceArray[1].ToString()); // 80
// (2) Call method to update array
TestParms.UpdateArray(ref priceArray);
Console.WriteLine( priceArray[1].ToString()); // 160
// (3) Call method to calculate amount of tax.
double ourPrice = 150.00;
double newtax = TestParms.TaxVal(ourPrice, out taxAmt);
Console.WriteLine( taxAmt.ToString()); // 15
Console.WriteLine( ourPrice); // 150.00
}
}
3.5 Methods 105

In this example, the class MyApp is used to invoke three methods in the
TestParms class:

1. FillArray is invoked to initialize the array. This requires passing the


array as a parameter with the out modifier.
2. The returned array is now passed to a second method that modifies
two elements in the array. The ref modifier indicates the array can be
modified.
3. ourPrice is passed to a method that calculates the amount of tax and
assigns it to the parameter taxAmt. Although ourPrice is set to 0
within the method, its value remains unchanged in the calling method
because it is passed by value.

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:

// Calculate average of variable number of arguments


public static double GetAvg(params double[] list)
{
double tot = 0.0;
for (int i = 0 ; i < list.Length; i++)
tot += list[i];
return tot / list.Length;
}

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:

[attributes] [modifiers] <identifier> ( [parameter-list])


[:initializer] { constructor-body}

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:

• The identifier (constructor name) must be the same as the class


name.
• The initializer provides a way to invoke code prior to entering the
constructor-body. It takes two forms:

base(argument list)—Calls a base class


this(argument list)—Calls another constructor in the same class
• If no explicit constructor is provided for a class, the compiler creates a
default parameterless constructor.

The instance constructor is called as part of creating a class instance. Because a


class may contain multiple constructors, the compiler calls the constructor whose sig-
nature matches that of the call:

Fiber fib1 = new Cotton();


Fiber fib1 = new Cotton("Egyptian");
3.6 Constructors 107

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

Inheritance and Constructors


Constructors, unlike methods, are not inherited. It is up to each class to define its
own constructor(s) or use the default parameterless constructor. Each constructor
must call a constructor in its base class before the first line of the calling constructor
is executed. Because C# generates a call to the base class’s default constructor auto-
matically, the programmer typically does not bother with this. But there are excep-
tions. Consider this code in which the base class Apparel defines a constructor:

public class Apparel


{
private string color;
private decimal price;
// constructor
public Apparel(string c, decimal p, string b)
{
color = c;
price = p;
}
}
// class inheriting from Apparel
class Coat: Apparel
{
private string length;
public Coat(string c, decimal p, string b, string l)
{
length = l;
// ... other code
}
}

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.

Listing 3-9 A Constructor with a Base Initializer


using System;
public class Apparel
{
private string color;
private decimal price;
private string brand;
// Constructor
public Apparel(string c,decimal p, string b)
{
color = c;
price = p;
brand = b;
}

public string ItemColor


{
get {return color;}
}
// other properties and members go here
}

public class Shirt: Apparel


{
private decimal mySleeve;
private decimal myCollar;
public Shirt (string c, decimal p, string b,
decimal sleeve, decimal collar) : base(c,p,b)
{
mySleeve = sleeve;
myCollar = collar;
}
}
3.6 Constructors 109

Listing 3-9 A Constructor with a Base Initializer (continued)


public class TestClass
{
static void Main()
{
Shirt shirtClass = new Shirt("white", 15.00m, "Arrow",
32.0m, 15.5m);
Console.WriteLine(shirtClass.ItemColor); // "white"
}
}

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.

public class Natural


{
string fiberType = "Generic";
string color = "white";
// Constructors
public Natural() { ... }
public Natural(string cotton_type) { ... }
public Natural(string cotton_type, string color) { ... }
}

For more efficient code, perform the field initialization in a single constructor and
have the other constructors invoke it using the this initializer.

public Natural() { // constructor initializes fields


fiberType="Generic";
color = "white";
}
// Following constructors use this() to call default
// constructor before constructor body is executed.
110 Chapter 3 ■ Class Design in C#

public Natural(string cotton_type): this() { ... }


public Natural(string cotton_type, string color):
this() { ... }

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.

Private Constructor Used with Class


Listing 3-10
Containing Static Methods
using System;
class Conversions
{
// class contains functions to provide metric conversions
// static method can only work with static field.
static cmPerInch = 2.54;
private static double gmPerPound = 455;
public static double inchesToMetric(double inches) {
return(inches * cmPerInch);
}
public static double poundsToGrams(double pounds) {
return(pounds*gmPerPound);
}
// Private constructor prevents creating class instance
private Conversions()
{ ... }
}
3.6 Constructors 111

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:

• It cannot have parameters and cannot be overloaded. Consequently, a


class may have only one static constructor.
• It must have private access, which C# assigns automatically.
• It cannot call other constructors.
• It can only access static members.

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
}

This class contains a static initializer, a static constructor, and an instance


constructor. Let’s look at the sequence of events that occur when the class is instanti-
ated:

BaseClass myClass1 = new BaseClass();


BaseClass myClass2 = new BaseClass();
BaseClass myClass2 = new BaseClass();

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.

3.7 Delegates and Events


Clicking a submit button, moving the mouse across a Form, pushing an Enter key, a
character being received on an I/O port—each of these is an event that usually trig-
gers a call to one or more special event handling routines within a program.
In the .NET world, events are bona fide class members—equal in status to prop-
erties and methods. Just about every class in the Framework Class Library has event
members. A prime example is the Control class, which serves as a base class for all
GUI components. Its events—including Click, DoubleClick, KeyUp, and Got-
Focus—are designed to recognize the most common actions that occur when a user
interacts with a program. But an event is only one side of the coin. On the other side
is a method that responds to, or handles, the event. Thus, if you look at the Control
class methods, you’ll find OnClick, OnDoubleClick, OnKeyUp, and the other meth-
ods that correspond to their events.
3.7 Delegates and Events 113

object1
OnClick
Click
object2
OnClick
delegate object

Figure 3-3 Event handling relationships

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:

public delegate void MyString (string msg);

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.

Listing 3-11 Multicasting Delegate


// file: delegate.cs
using System;
using System.Threading;
class DelegateSample
{
public delegate void MyString(string s);
public static void PrintLower(string s){
Console.WriteLine(s.ToLower());
}

public static void PrintUpper(string s){


Console.WriteLine(s.ToUpper());
}

public static void Main()


{
MyString myDel;
// register method to be called by delegate
myDel = new MyString(PrintLower);
// register second method
myDel += new MyString(PrintUpper);
// call delegate
myDel("My Name is Violetta.");
// Output: my name is violetta.
// MY NAME IS VIOLETTA.
}
}

Note that the += operator is used to add a method to the invocation list. Con-
versely, a method can be removed using the -= operator:

myDel += new MyString(PrintUpper); // register for callback


myDel -= new MyString(PrintUpper); // remove method from list

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

Delegate-Based Event Handling


In abstract terms, the .NET event model is based on the Observer Design Pattern.
This pattern is defined as “a one-to-many dependency between objects so that when
one object changes state, all its dependents are notified and updated automatically.”1
We can modify this definition to describe the .NET event handling model depicted
in Figure 3-3: “when an event occurs, all the delegate’s registered methods are noti-
fied and executed automatically.” An understanding of how events and delegates
work together is the key to handling events properly in .NET.
To illustrate, let’s look at two examples. We’ll begin with built-in events that have a
predefined delegate. Then, we’ll examine how to create events and delegates for a
custom class.

Working with Built-In Events


The example in Listing 3-12 displays a form and permits a user to draw a line on the
form by pushing a mouse key down, dragging the mouse, and then raising the mouse
key. To get the endpoints of the line, it is necessary to recognize the MouseDown and
MouseUp events. When a MouseUp occurs, the line is drawn.
The delegate, MouseEventHandler, and the event, MouseDown, are predefined
in the Framework Class Library. The developer’s task is reduced to implementing the
event handler code and registering it with the delegate. The += operator is used to
register methods associated with an event.

this.MouseDown += new MouseEventHandler(OnMouseDown);

The underlying construct of this statement is

this.event += new delegate(event handler method);

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:

public delegate void MouseEventHandler(


object sender,
MouseEventArgs e)

Listing 3-12 Event Handler Example


using System;
using System.Windows.Forms;
using System.Drawing;
class DrawingForm:Form
{
private int lastX;
private int lastY;
private Pen myPen= Pens.Black; // defines color of drawn line
public DrawingForm() {
this.Text = "Drawing Pad";
// Create delegates to call MouseUp and MouseDown
this.MouseDown += new MouseEventHandler(OnMouseDown);
this.MouseUp += new MouseEventHandler(OnMouseUp);
}
private void OnMouseDown(object sender, MouseEventArgs e)
{
lastX = e.X;
lastY = e.Y;
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
// The next two statements draw a line on the form
Graphics g = this.CreateGraphics();
if (lastX >0 ){
g.DrawLine(myPen, lastX,lastY,e.X,e.Y);
}
lastX = e.X;
lastY = e.Y;
}
static void Main() {
Application.Run(new DrawingForm());
}
}
3.7 Delegates and Events 117

Using Anonymous Methods with Delegates


.NET 2.0 introduced a language construct known as anonymous methods that elimi-
nates the need for a separate event handler method; instead, the event handling code
is encapsulated within the delegate. For example, we can replace the following state-
ment from Listing 3-12:

this.MouseDown += new MouseEventHandler(OnMouseDown);

with this code that creates a delegate and includes the code to be executed when the
delegate is invoked:

this.MouseDown += delegate(object sender, EventArgs e)


{
lastX = e.X;
lastY = e.Y;
}

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:

delegate [(parameter-list)] {anonymous-method-block}

• The delegate keyword is placed in front of the code that is executed


when the delegate is invoked.
• An optional parameter list may be used to pass data to the code block.
These parameters should match those declared by the delegate. In
this example, the parameters correspond to those required by the pre-
defined delegate MouseEventHandler.
• When the C# compiler encounters the anonymous code block, it cre-
ates a new class and constructs a method inside it to contain the code
block. This method is called when the delegate is invoked.

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

// Register two anonymous methods with the delegate


MyString myDel;
myDel = delegate(string s) { Console.WriteLine(s.ToLower()); };
118 Chapter 3 ■ Class Design in C#

myDel += delegate(string s) { Console.WriteLine(s.ToUpper()); };


// invoke delegate
myDel("My name is Violetta");

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.

Defining Custom Events


When writing your own classes, it is often necessary to define custom events that sig-
nal when some change of state has occurred. For example, you may have a compo-
nent running that monitors an I/O port and notifies another program about the status
of data being received. You could use raw delegates to manage the event notification;
but allowing direct access to a delegate means that any method can fire the event by
simply invoking the delegate. A better approach—and that used by classes in the
Framework Class Library—is to use the event keyword to specify a delegate that
will be called when the event occurs.
The syntax for declaring an event is

public event <delegate name> <event name>

Let’s look at a simple example that illustrates the interaction of an event and
delegate:

public class IOMonitor


{
// Declare delegate
public delegate void IODelegate(String s);
// Define event variable
public event IODelegate DataReceived ;
// Fire the event
public void FireReceivedEvent (string msg)
{
if (DataReceived != null) // Always check for null
{
DataReceived(msg); // Invoke callbacks
}
}
}

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:

IOMonitor monitor = new IOMonitor();


// You must provide a method that handles the callback
monitor.DataReceived += new IODelegate(callback method);
monitor.FireReceivedEvent("Buffer Full"); // Fire event

Defining a Delegate to Work with Events


In the preceding example, the event delegate defines a method signature that takes a
single string parameter. This helps simplify the example, but in practice, the signa-
ture should conform to that used by all built-in .NET delegates. The EventHandler
delegate provides an example of the signature that should be used:

public delegate void EventHandler(object sender,


EventArgs eventArgs);

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.

public class IOEventArgs: EventArgs


{
public IOEventArgs(string msg){
this.eventMsg = msg;
}
public string Msg{
get {return eventMsg;}
}
private string eventMsg;
}
120 Chapter 3 ■ Class Design in C#

IOEventArgs illustrates the guidelines to follow when defining an EventArgs


class:

• It must inherit from the EventArgs class.


• Its name should end with EventArgs.
• Define the arguments as readonly fields or properties.
• Use a constructor to initialize the values.

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

If your delegate uses the EventHandler signature, you can use


EventHandler as your delegate instead of creating your own. Because it
is part of the .NET Framework Class Library, there is no need to declare it.

An Event Handling Example


Let’s bring these aforementioned ideas into play with an event-based stock trading
example. For brevity, the code in Listing 3-13 includes only an event to indicate when
shares of a stock are sold. A stock purchase event can be added using similar logic.

Implementing a Custom Event-Based


Listing 3-13
Application
//File: stocktrader.cs
using System;
// (1) Declare delegate
public delegate void TraderDelegate(object sender,
EventArgs e);
// (2) A class to define the arguments passed to the delegate
public class TraderEventArgs: EventArgs
{
public TraderEventArgs(int shs, decimal prc, string msg,
string sym){
this.tradeMsg = msg;
this.tradeprice = prc;
this.tradeshs = shs;
this.tradesym = sym;
}
3.7 Delegates and Events 121

Implementing a Custom Event-Based


Listing 3-13
Application (continued)
public string Desc{
get {return tradeMsg;}
}
public decimal SalesPrice{
get {return tradeprice;}
}
public int Shares{
get {return tradeshs;}
}
public string Symbol{
get {return tradesym;}
}

private string tradeMsg;


private decimal tradeprice;
private int tradeshs;
private string tradesym;
}
// (3) class defining event handling methods
public class EventHandlerClass
{
public void HandleStockSale(object sender,EventArgs e)
{
// do housekeeping for stock purchase
TraderEventArgs ev = (TraderEventArgs) e;
decimal totSale = (decimal)(ev.Shares * ev.SalesPrice);
Console.WriteLine(ev.Desc);
}
public void LogTransaction(object sender,EventArgs e)
{
TraderEventArgs ev = (TraderEventArgs) e;
Console.WriteLine(ev.Symbol+" "+ev.Shares.ToString()
+" "+ev.SalesPrice.ToString("###.##"));
}
}
// (4) Class to sell stock and publish stock sold event
public class Seller
{
// Define event indicating a stock sale
public event TraderDelegate StockSold;
public void StartUp(string sym, int shs, decimal curr)
122 Chapter 3 ■ Class Design in C#

Implementing a Custom Event-Based


Listing 3-13
Application (continued)
{
decimal salePrice= GetSalePrice(curr);
TraderEventArgs t = new TraderEventArgs(shs,salePrice,
sym+" Sold at "+salePrice.ToString("###.##"), sym);
FireSellEvent(t); // Fire event
}
// method to return price at which stock is sold
// this simulates a random price movement from current price
private decimal GetSalePrice(decimal curr)
{
Random rNum = new Random();
// returns random number between 0 and 1
decimal rndSale = (decimal)rNum.NextDouble() * 4;
decimal salePrice= curr - 2 + rndSale;
return salePrice;
}
private void FireSellEvent(EventArgs e)
{
if (StockSold != null) // Publish defensively
{
StockSold(this, e); // Invoke callbacks by delegate
}
}
}

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.

3.8 Operator Overloading


Built-in operators such as + and - are used so instinctively that one rarely thinks of
them as a predefined implementation for manipulating intrinsic types. In C#, for
example, the + operator is used for addition or concatenation depending on the data
types involved. Clearly, each must be supported by different underlying code.
Because the compiler knows what a numeric and string type represent, it is quite
capable of doing this. It makes sense then that these operators cannot be applied to
custom classes or structures of which the compiler has no knowledge.
It turns out that C# provides a mechanism referred to as operator overloading that
enables a class to implement code that determines how the class responds to the
operator. The code for overloading an operator is syntactically similar to that of a
method:

public static <return type> operator <op> ( parameter list)


{ implementation code}

Several rules govern its usage:

• The public and static modifiers are required.


• The return type is the class type when working with classes. It can
never be void.
• op is a binary, unary, or relational operator. Both equals (==) and not
equals (!=) must be implemented in a relational pair.
• Binary operators require two arguments; unary operators require one
argument.

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#

Listing 3-14 Operator Overloading for Classes


using System;
class Portfolio
{
public decimal risk;
public decimal totValue;
// Overloaded operator to add stock to Portfolio
public static Portfolio operator + (Portfolio p,Stock s)
{
decimal currVal = p.totValue;
decimal currRisk = p.risk;
p.totValue = p.totValue + s.StockVal;
p.risk = (currVal/p.totValue)*p.risk +
(s.StockVal/p.totValue)* s.BetaVal;
return p;
}
// Overloaded operator to remove stock from Portfolio
public static Portfolio operator - (Portfolio p,Stock s)
{
p.totValue = p.totValue - s.StockVal;
p.risk = p.risk - ((s.BetaVal-p.risk)
*(s.StockVal/p.totValue));
return p;
}
}
class Stock
{
private decimal value;
private decimal beta; // risk increases with value
public Stock(decimal myBeta, decimal myValue,
int shares)
{
value = (decimal) myValue * shares;
beta = myBeta;
}
public decimal StockVal
{ get {return value; } }

public decimal BetaVal


{ get {return beta; } }
}
class MyApp
{
3.8 Operator Overloading 125

Listing 3-14 Operator Overloading for Classes (continued)


public static void Main()
{
Portfolio p = new Portfolio();
// 200 shs of HPQ at $25, 100 shs of IBM @ $95
Stock hpq = new Stock(1.1M, 25M, 200);
Stock ibm = new Stock(1.05M, 95.0M, 100);
p += hpq; // Add hpq
p += ibm; // Add ibm
Console.Write("value:{0} ",p.totValue.ToString());
Console.WriteLine(" risk: {0}",
p.risk.ToString("#.00"));
// value = 14,500 and risk = 1.07
p -= ibm; // Remove ibm from portfolio
Console.Write("value:{0} ",p.totValue.ToString());
Console.Write(" risk: {0}",p.risk.ToString("#.00"));
// value = 5000 and risk = 1.10
}
}

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:

public static Portfolio AddStocks ( Portfolio p, Stock s)


{ return p + s; }

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:

[attributes] [modifiers] interface identifier [:baselist]


{interface body} [;]

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:

• An interface cannot inherit from a class.


• An interface can inherit from multiple interfaces.
• A class can inherit from multiple interfaces, but only one class.
• Interface members must be methods, properties, events, or indexers.
• All interface members must have public access (the default).
• By convention, an interface name should begin with an uppercase I.
3.9 Interfaces 127

Creating and Using a Custom Interface


Listing 3-15 demonstrates how to define, implement, and program against a simple
custom interface:

Listing 3-15 Interface Basics


public interface IShapeFunction
{
double GetArea(); // public abstract method
}

class Circle : IShapeFunction // Inherit Interface


{
private double radius;
public circle( double rad)
{
radius = rad;
}
public double GetArea()
{
return ( Math.PI*radius * radius);
}
public string ShowMe()
{
return ("Circle");
}
}

class Rectangle: IShapeFunction // Inherit interface


{
private double width, height;
public rectangle( double myWidth, double myHeight)
{
width= myWidth;
height= myHeight;
}
public double GetArea()
{
return (width * height);
}
}
128 Chapter 3 ■ Class Design in C#

Listing 3-15 Interface Basics (continued)


class MyApp
{
public static void Main()
{
Circle myCircle = new Circle(4);
// Interface variable that references a circle object.
IShapeFunction myICircle = myCircle;
Rectangle myRectangle = new Rectangle(4,8);
// Place shape instances in an array
IShapeFunction[] myShapes = {myCircle, myRectangle};
// The next two statements print the same results
MessageBox.Show(myCircle.GetArea().ToString());
MessageBox.Show(myShapes[0].GetArea().ToString());
MessageBox.Show(myCircle.ShowMe()); // class method
MessageBox.Show(myICircle.GetArea().ToString());
// The following will not compile because myICircle can
// access only members of the IShapeFunction interface.
MessageBox.Show(myICircle.ShowMe());
}
}

The declaration of the interface and implementation of the classes is straightfor-


ward: A Circle and Rectangle class inherit the IShapeFunction interface and
implement its GetArea method.
Conceptually, it is useful to think of a class that implements one or more inter-
faces as having multiple types. It has its own innate type, but it also can be viewed as
having the type of each interface it implements. Consider this code in the MyApp
class:

Circle myCircle = new Circle(4);


IShapeFunction myICircle = myCircle;

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

IShapeFunction[] myShapes = {myCircle, myRectangle};


MessageBox.Show(myShapes[0].GetArea().ToString());

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.

public class ShapeUtil


{
public static double SumAreas
(IShapeFunction[] funArray)
{
double tot = 0.0;
for (int i = 0; i < funArray.Length; i++)
{ tot += funArray[i].GetArea(); }
return tot;
}
}
// Access this method with ShapeUtil.SumAreas(myShapes);

The code in this class can be used with any concrete class that implements the
IShapeFunction interface.

Core Approach

For maximum program flexibility, consider using interface types, rather


than class types, when defining method parameters. This ensures there is
no limit on the types of classes that can be passed to the method, as long
as they implement the required interface.

Working with Interfaces


Determining Which Interface Members Are Available
You may not always know at compile time whether a class implements a specific
interface. This is often the case when working with a collection that contains a num-
ber of types. To perform this check at runtime, use the as or is keyword in your
code.
130 Chapter 3 ■ Class Design in C#

// (1) as keyword to determine if interface is implemented


Circle myCircle = new Circle(5.0);
IShapeFunction myICircle;
myICircle = myCircle as IShapeFunction;
If (myICircle !=null) //interface is implemented

// (2) is keyword to determine if interface is implemented


Circle myCircle = new Circle(5.0);
If (myCircle is IShapeFunction) // True if interface implemented

Accessing Interface Methods


Because a class may inherit methods from a base class and/or multiple interfaces,
there is the possibility that inherited methods will have the same name. To avoid this
ambiguity, specify an interface method declaration in the derived class with the inter-
face and method name:

double IShapeFunction.GetArea() { // <interface>.<method>

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:

Circle myCircle = new Circle(5.0);


// cannot reference explicit method
double myArea = myCircle.GetArea();

Interfaces Versus Abstract Classes


In the overall design of a system, the real issue is not whether to use a class or inter-
face, but how to best mix the two. The C# restriction on multiple implementation
inheritance assures an expanded role for interfaces. It is just too burdensome to try to
load all the needed functionality into one base class or a hierarchy of base classes.
A better solution is to define a base class and then expand its capabilities by add-
ing corresponding interfaces. This permits developers to use the class directly with-
out regard to whether the methods are interface implementations. Yet, it also permits
the developer to make use of the interface and ignore the other class members.
Of course, you must consider the drawbacks to using interfaces. A well-designed
base class reduces the burden on the inherited class to implement everything by pro-
moting code reuse. Also, a non-abstract class can add a member without breaking
any existing derived class; adding a member to an interface would break any class
implementing the interface.
3.10 Generics 131

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.

Object[] myStack = new object[50];


myStack[0] = new Circle(5.0); // place Circle object in array
myStack[1] = "Circle"; // place string in array
Circle c1 = myStack[0] as Circle;
if( c1!=null) { // circle object obtained
Circle c2 = (Circle) myStack[1]; // invalid case exception

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:

public class myCollection<T>


{
T[] myStack = new T[50];
}

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:

myCollection <string> = new myCollection<string>;

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:

public class myCollection<T> where


T:ISerializable,
T:IComparable

A parameter may have multiple interface constraints and a single class restraint.
In addition, there are three special constraints to be aware of:

class—Parameter must be reference type.


struct—Parameter must be value type.
new()—Type parameter must have a parameterless constructor.

An example of a generic class is provided in Listing 3-16. The class implements an


array that manages objects of the type specified in the type parameter. It includes
methods to add items to the array, compare items, and return an item count. It
includes one constraint that restricts the type parameter to a reference type.

Listing 3-16 A Generic Class to Hold Data


using System.Collections.Generic
public class GenStack<T>
where T:class // constraint restricts access to ref types
{
private T[] stackCollection;
private int count = 0;
// Constructor
public GenStack(int size)
{
stackCollection = new T[size];
}
public void Add(T item)
{
stackCollection[count] = item;
count += 1;
}
3.10 Generics 133

Listing 3-16 A Generic Class to Hold Data (continued)


// Indexer to expose elements in internal array
public T this[int ndx]
{
get
{
if (!(ndx < 0 || ndx > count - 1))
return stackCollection[ndx];
// Return empty object
else return (default(T));
}
}
public int ItemCount
{
get {return count;}
}
public int Compare<C>(T value1, T value2)
{
// Case-sensitive comparison: -1, 0(match), 1
return Comparer<T>.Default.Compare(value1, value2);
}
}

The following code demonstrates how a client could access the generic class
described in Listing 3-16:

// Create instance to hold 10 items of type string


GenStack<string> myStack = new GenStack<string>(10);
myStack.Add("Leslie");
myStack.Add("Joanna");
Console.WriteLine(myStack.ItemCount); // 2
Console.WriteLine(myStack[1]); // Joanna
int rel = myStack.Compare<string>("Joanna", "joanna"); // -1
Console.WriteLine(rel.ToString());

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:

• A struct is a value type. It implicitly inherits from the


System.ValueType.
• A struct cannot inherit from classes, nor can it be inherited.
• An explicitly declared constructor must have at least one parameter.
• struct members cannot have initializers. The field members must be
initialized by the constructor or the client code that creates the con-
structor.

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:

[attribute][modifier] struct identifier [:interfaces]


{struct-body}

Example:

public struct DressShirt


{
public float CollarSz;
public int SleeveLn;
3.11 Structures 135

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

It is possible to specify the layout of a struct’s fields in memory using


the StructLayout attribute. This is most commonly used when the
struct is to be accessed by unmanaged code that expects a specific
physical layout of the data. By default, the fields in a struct are stored
in sequential memory locations.

An instance of the structure can be created in two ways:

DressShirt dShirt = new DressShirt(16, 33);


DressShirt myShirt = new DressShirt();
myShirt.CollarSz = 16.5F;
myShirt.SleeveLn = 33;

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#

Using Methods and Properties with a Structure


Methods and properties are usually associated with classes, but they play an equally
important role in the use of structures. In fact, a client accessing a method has no
syntactical clue as to whether the method or property is associated with a class or
struct. Listing 3-17 extends the original example to add two properties and a
method.

Listing 3-17 Basic Elements of a Struct


public struct DressShirt
{
private float CollarSz;
private int SleeveLn;
public DressShirt(float collar, int sleeve)
{
this.CollarSz = collar;
this.SleeveLn = sleeve;
}

// Properties to return sleeve and collar values


public int Sleeve
{
get {return (SleeveLn);}
set {SleeveLn = value;}
}
public float Collar
{
get {return (CollarSz); }
// "value" is an implicit parameter
set {CollarSz = value; }
}

// Method to convert size to different scale


public string ShirtSize()
{
string mySize = "S";
if (CollarSz > 14.5) mySize = "M";
if (CollarSz > 15.5) mySize = "L";
if (CollarSz > 16.5) mySize = "XL";
return (mySize);
}
}
3.12 Structure Versus Class 137

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.

3.12 Structure Versus Class


Many developers instinctively select a class to represent data and the operations per-
formed on it. However, there are cases where a struct is a better choice, as evi-
denced by its use in the Framework Class Library to represent simple data types.
This section compares the two and offers general guidelines to consider when choos-
ing between the two.

Table 3-5 Comparison of Structure and Class

Structure Class

Default access level of the type Internal Internal

Default access level for data members Public Private

Default access level for properties and Private Private


methods

Value or reference type Value Reference

Can be a base for new types No Yes

Implement interfaces Yes Yes

Raise and handle events Yes Yes

Scope of members Structure Class

Instance initialization Constructor—with or Constructor—with or


without parameters. A without parameters.
struct cannot contain
a custom parameterless
constructor.

Can be nested Yes Yes

Has a destructor No Yes (Finalizer)


138 Chapter 3 ■ Class Design in C#

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.

Structures Are Value Types


and Classes Are Reference Types
As mentioned, classes are allocated space from the managed heap when the object or
class instance is created. The address of the object (on the heap) is returned to the
variable representing the object. In contrast, a variable set to a struct type contains
the structure’s actual data—not a pointer. The ramifications of this are most pro-
nounced when passing arguments to functions. Reference types simply require that a
pointer be passed, whereas structures require that a copy of all fields be made and
passed to the function.
The structure does have some advantages with regard to memory allocation and
Garbage Collection. Structure instances are allocated in a thread’s stack rather than
the managed heap, thus avoiding the associated overhead of managing pointers.
Memory management is also simpler. When a copy of a structure is no longer reach-
able, its memory is collected and made available. In contrast, classes often require
special code to handle unmanaged resources or invoke the system’s garbage collec-
tion routine. Garbage Collection is covered in the next chapter.

Unlike a Class, a Structure


Cannot Be Inherited
This is a departure from C++ conventions that permit a class to use an existing struc-
ture as a base class and permit a structure to use a class as a base. Is the .NET lack of
support for this a step backward? Not really. It better delineates the role of the struc-
ture versus the class—making the structure less of a pseudo-class and more of a data
structure. In addition, it provides for a more efficient implementation of the struc-
ture. The restriction enables the compiler to minimize the amount of administrative
code that would be required to support inheritance. For example, because the com-
piler knows that a structure’s methods cannot be overridden by subclasses, it can
optimize the method invocation by expanding the method code inline rather than
executing a method call.
3.13 Summary 139

General Rules for Choosing


Between a Structure and a Class
The easiest way to make the choice is to compare the features of the type that you are
designing with the following checklist. If any of these are true, you should use a class.

• The type needs to serve as a base for deriving other types.


• The type needs to inherit from another class. Note that although
a structure cannot explicitly specify a base class, it does implicitly
inherit from the System.ValueType and may override the inherited
methods.
• The type is frequently passed as a method parameter. Performance
degrades as copies of the structure are created with each call. An
exception to this is when the structure exists inside an array. Because
an array is a reference type, only a pointer to the array is passed.
• The type is used as the return type of methods. In the case where the
return type is a structure, the system must copy the structure from the
called function to the calling program.

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#

3.14 Test Your Understanding


1. What type of class cannot be inherited?

2. How many classes can a class directly inherit? How many interfaces?

3. Referring to the following class, answer the questions below:


public class ShowName {
public static void ShowMe(string MyName)
{ Console.WriteLine(MyName); }
}
a. Can the method ShowName be referenced from s?
ShowName s = new ShowName();
s.ShowMe("Giacomo");
b. Write a code sample that will print “My Name is Ishmael”.

4. Can an abstract class have non-abstract methods?

5. What keyword must a derived class use to replace a non-virtual inher-


ited method?

6. What are the results from running the following code?


public class ParmTest
{
public static void GetCoordinates(ref int x, int y)
{
x= x+y;
y= y+x;
}
}
// calling method
int x=20;
int y=40;
ParmTest.GetCoordinates(ref x, y);
Console.WriteLine("x="+x+" y="+y);

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.

9. Your application contains a class StoreSales that fires an event when


an item is sold. The event provides the saleprice (decimal), date
(DateTime), and the itemnum (int) to its subscribers. Create an
event handler that processes the ItemSold event by extracting and
printing the sales data.
public delegate void SaleDelegate(object sender,
SaleEvArgs e);
public event SaleDelegate ItemSold;
StoreSales mysale= new StoreSale();
mySale.ItemSold += new SaleDelegate(PrintSale);

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");
}
}

public class Child: Base


{
string parm="Hello";
public static void Main(String[] argv)
{
Child c = new Child();
c.aMethod();
}
void aMethod(int i, string Parm)
{
Console.WriteLine(Parm);
}
public void aMethod()
{ }
}
142 Chapter 3 ■ Class Design in C#

a. Error during compilation.


b. “Base Constructor” is printed.
c. “Base Constructor” and “Base Method” are printed.
d. “Base Constructor” and “Hello” are printed.
This page intentionally left blank
WORKING WITH
OBJECTS IN C#

Topics in This Chapter

• Creating Objects: Learn how to use a factory design pattern to


create objects.
• Exception Handling: Effective exception handling requires an
understanding of how exceptions are thrown and caught in .NET.
Along with an overview of exception handling, this section looks
at how to create custom exception objects.
• Using System.Object Methods: Familiarity with the
System.Object methods is necessary if you want to create
custom collection classes that implement the standard features
found in classes contained in the Framework Class Library.
• Collection Classes and Interfaces: .NET offers a variety of
collection classes for managing multiple objects. This section
looks at arrays, hash tables, and stacks, among others. Of
particular interest are the 2.0 classes that support generics.
• Object Serialization: Objects can be converted (serialized) into a
binary stream or an XML formatted stream. An example using the
binary serializer is presented.
• Object Life Cycle Management: .NET Garbage Collection
automatically removes unreferenced objects. This can produce
unwanted results unless measures are taken to ensure that objects
are destroyed properly.
4

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.

4.1 Object Creation


The subject of creating objects typically receives less attention than that of designing
classes; but it is important enough that a formal body of techniques known as cre-
ational patterns has been developed to provide object creation models. A popular
approach—used throughout the Framework Class Library (FCL)—is to implement
the factory creational pattern. As the name implies, the factory is an object whose

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

Using a Class Factory to Create Objects—


Listing 4-1
Single Factory
public interface IApparel // Interface representing product
{
string ShowMe();
bool Knit // Property to indicate if Knit
{ get; }
}
public class SportsShirt : IApparel
{
public string ShowMe()
{
return("Sports Shirt");
}
public bool Knit
{ get {return true;} }
4.1 Object Creation 147

Using a Class Factory to Create Objects—


Listing 4-1
Single Factory (continued)
}
public class DressShirt : IApparel
{
public string ShowMe()
{
return("Dress Shirt");
}
public bool Knit
{ get {return false;} }
}
// Factory to return instances of apparel classes
public class ApparelFactory
{
public IApparel CreateApparel( string apptype)
{
switch (apptype)
{
case "MDRSHIRT":
return new DressShirt();
case "MSPSHIRT":
return new SportsShirt();
}
return null;
}
}

In this example, the class factory implements a method named CreateApparel


that returns objects. The most important thing to note is that this method is declared
with IApparel as its return type, which enables it to return an instance of any class
that implements that interface.

public IApparel CreateApparel( string apptype)

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#

ApparelFactory factory= new ApparelFactory();


IApparel ob1 = factory.CreateApparel("MDRSHIRT");
IApparel ob2 = factory.CreateApparel("MSPSHIRT");
string shirtType = ob1.ShowMe(); // Returns "Dress Shirt"

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.

Example: Creating Objects with


Multiple Factories
This solution adds an abstract class (we could use an interface) for the factory and
two concrete subclasses that implement it to produce specific apparel objects:

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

public class ApparelCollector


{
public void CollectApparel(AppFactory factory)
{
IApparel apparel = factory.CreateApparel();
}
}
4.2 Exception Handling 149

The code to use the new class is analogous to that in the first example:

AppFactory factory = new DressShirtFactory();


IApparel obj2 = new ApparelCollector().CollectApparel(factory);

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.

4.2 Exception Handling


One of the most important aspects of managing an object is to ensure that its behav-
ior and interaction with the system does not result in a program terminating in error.
This means that an application must deal gracefully with any runtime errors that
occur, whether they originate from faulty application code, the Framework Class
Library, or hardware faults.
.NET provides developers with a technique called structured exception handling
(SEH) to deal with error conditions. The basic idea is that when an exception occurs,
an exception object is created and passed along with program control to a specially
designated section of code. In .NET terms, the exception object is thrown from one
section of code to another section that catches it.
Compared to error handling techniques that rely on error codes and setting bit
values, SEH offers significant advantages:

• The exception is passed to the application as an object whose proper-


ties include a description of the exception, the assembly that threw the
exception, and a stack trace that shows the sequence of calls leading to
the exception.
• If an exception is thrown and an application does not catch it, the
Common Language Runtime (CLR) terminates the application. This
forces the developer to take error handling seriously.
• The exception handling and detection code does not have to be
located where the errors occur. This means, for example, that excep-
tion handling code could be placed in a special class devoted to that
purpose.
• Exceptions are used exclusively and consistently at both the applica-
tion and system level. All methods in the .NET Framework throw
exceptions when an error occurs.
150 Chapter 4 ■ Working with Objects in C#

Before looking at the actual mechanics of implementing exception handling, let’s


examine the exception itself. As previously mentioned, an exception is a class
instance. All .NET exceptions derive from the System.Exception class. So, an
understanding of this class is essential to working with exceptions.

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

Custom Application Exceptions

System.SystemException

.NET FCL Exceptions

Figure 4-3 .NET exception classes hierarchy

The System.Exception class contains relatively few members. Table 4-1 sum-
marizes the members discussed in this section.

Table 4-1 System.Exception Class Properties

Property Type Description

HelpLink string Contains a URL that points to help documentation.

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.

Message string The text describing the exception.


4.2 Exception Handling 151

Table 4-1 System.Exception Class Properties (continued)

Property Type Description

Source string Name of the assembly that generated the exception.

StackTrace string Contains the sequence of method names and signa-


tures that were called prior to the exception. It is
invaluable for debugging.

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.

HResult Int32 This is a protected property used when interoperating


with COM code. When an exception is thrown to a
COM client, this value is converted to an HRESULT in
the COM world of unmanaged code.

Writing Code to Handle Exceptions


C# uses a try/catch/finally construct to implement exception handling (see Fig-
ure 4-4). When an exception occurs, the system searches for a catch block that can
handle the current type of exception. It begins its search in the current method,
working down the list of catch blocks sequentially. If none is found, it then searches
the catch blocks in the calling method associated with the relevant try block. If the
search yields no matching catch block, an unhandled exception occurs. As discussed
later, the application is responsible for defining a policy to deal with this. Let’s look at
the details of using these three blocks.

The try Block


The code inside the try block is referred to as a guarded region because it has asso-
ciated catch or finally blocks to handle possible exceptions or cleanup duties.
Each try block must have at least one accompanying catch or finally block.

The catch Block


A catch block consists of the keyword catch followed by an expression in parenthe-
ses called the exception filter that indicates the type of exception to which it
responds. Following this is the code body that implements the response.
152 Chapter 4 ■ Working with Objects in C#

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

Figure 4-4 Code blocks used for exception handling

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:

catch (DivideByZeroException ex) { ... }

The filter will be invoked if a System.DivideByZeroException occurs. The


variable ex references the exception and provides access to its properties, such as
ex.Message and ex.StackTrace .
When using multiple catch blocks, ordering is important. They should be listed
hierarchically, beginning with the most specific exceptions and ending with the more
general ones. In fact, the compiler generates an error if you do not order them cor-
rectly.

catch (DivideByZeroException ex) { ... }


catch (IndexOutOfRangeException ex) { ... }
catch (Exception ex) { ... }

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.

The finally Block


Regarded as the “cleanup” block, the finally block is executed whether or not an
exception occurs and is a convenient place to perform any cleanup operations such as
closing files or database connections. This block must be included if there are no
catch blocks; otherwise, it is optional.

Example: Handling Common


SystemException Exceptions
Listing 4-2 illustrates the use of the try/catch/finally blocks in dealing with an
exception generated by the CLR.

Listing 4-2 Handling Exceptions Generated by the CLR


using System;
// Class to illustrate results of division by zero
public class TestExcep
{
public static int Calc(int j)
{
return (100 / j);
}
}
class MyApp
{
public static void Main()
{
TestExcep exTest = new TestExcep();
try
{
// Create divide by zero in called method
int dZero = TestExcep.Calc(0);
// This statement is not executed
Console.WriteLine("Result: {0}",dZero);
}
154 Chapter 4 ■ Working with Objects in C#

Listing 4-2 Handling Exceptions Generated by the CLR (continued)


catch(DivideByZeroException ex)
{
Console.WriteLine("{0}\n{1}\n", ex.Message, ex.Source);
Console.WriteLine(ex.TargetSite.ToString());
Console.WriteLine(ex.StackTrace);
}
catch (Exception ex)
{
Console.WriteLine("General "+ex.Message);
}
finally
{
Console.WriteLine("Cleanup occurs here.");
}
}
}

In this example, TestExcep.Calc throws a division by zero exception when


MyApp calls it with a zero value. Because Calc has no code to handle the exception,
the exception is thrown back automatically to MyApp at the point where Calc was
called. From there, control passes to the block provided to handle DivideByZe-
roException exceptions. For demonstration purposes, the statements in the catch
block display the following information provided by the exception object:

Property Value Printed


ex.Message Attempted to divide by zero
ex.Source zeroexcept (assembly name)
ex.TargetSite Void Main()
ex.StackTrace at MyApp.Main()

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

How to Create a Custom Exception Class


Custom exception classes are useful when you need to describe errors in terms of the
class that issues the error. For example, you may want the exception to describe the
specific behavior causing the error or to indicate a problem with a parameter that
does not meet some required criteria. In general, first look to the most specific sys-
tem exception available; if that is inadequate, consider creating your own.
In Listing 4-3, a method throws a custom exception if the object it receives
does not implement the two required interfaces. The exception, NoDescException,
returns a message describing the error and the name of the object causing the
failure.

Listing 4-3 Building a Custom Exception Class


// Custom Exception Class
[Serializable]
public class NoDescException : ApplicationException
{ // Three constructors should be implemented
public NoDescException(){}
public NoDescException(string message):base(message){}
public NoDescException(string message, Exception innerEx)
:base(message, innerEx){ }
}

// Interfaces that shape objects are to implement


public interface IShapeFunction
{ double GetArea(); }
public interface IShapeDescription
{ string ShowMe();}

// Circle and Rectangle classes are defined


class Circle : IShapeFunction
{
private double radius;
public Circle (double rad)
{
radius= rad;
}
// Methods to implement both interfaces
public double GetArea()
{ return (3.14*radius*radius); }
}
156 Chapter 4 ■ Working with Objects in C#

Listing 4-3 Building a Custom Exception Class (continued)


class Rectangle : IShapeFunction, IShapeDescription
{
private int width, height;
public Rectangle(int w, int h)
{
width= w;
height=h;
}
// Methods to implement both interfaces
public double GetArea()
{ return (height*width); }
public string ShowMe()
{ return("rectangle"); }
}

public class ObjAreas


{
public static void ShowAreas(object ObjShape)
{
// Check to see if interfaces are implemented
if (!(ObjShape is IShapeDescription &&
ObjShape is IShapeFunction) )
{
// Throw custom exception
string objName = ObjShape.ToString();
throw new NoDescException
("Interface not implemented for "+objName);
}
// Continue processing since interfaces exist
IShapeFunction myShape = (IShapeFunction)ObjShape;
IShapeDescription myDesc = (IShapeDescription) ObjShape;
string desc = myDesc.ShowMe();
Console.WriteLine(desc+" Area= "+
myShape.GetArea().ToString());
}
}

To view the custom exception in action, let’s create two shape objects and pass
them via calls to the static ObjAreas.ShowAreas method.

Circle myCircle = new Circle(4.0);


Rectangle myRect = new Rectangle(5,2);
try
4.2 Exception Handling 157

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

• The class should be derived from ApplicationException.


• By convention, the exception name should end in Exception.
The Exception base class defines three public constructors that
should be included:
1. A parameterless constructor to serve as the default.
2. A constructor with one string parameter—usually the message.
3. A constructor with a string parameter and an Exception object
parameter that is used when an exception occurs while a previous
exception is being handled.
• Use the base initializer to call the base class to take care of the actual
object creation. If you choose to add fields or properties, add a new
constructor to initialize these values.
• The Serializable attribute specifies that the exception can be seri-
alized, which means it can be represented as XML for purposes of
storing it or transmitting it. Typically, you can ignore this attribute,
because it’s only required if an exception object is being thrown from
code in one application domain to another. Application domains are
discussed Chapter 15, “Code Refinement, Security, and Deployment”;
for now, think of them as logical partitions that .NET uses to isolate
code segments.

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:

static void Main()


{
Application.ThreadException += new
ThreadExceptionEventHandler(UnForgiven.MyUnhandledMethod);
Application.Run(new Form1());
}

The implementation code in the method is straightforward. The most interesting


aspect is the use of preprocessor directives (#if DEBUG) to execute code based on
whether the application is in release or debug mode.
4.2 Exception Handling 159

Listing 4-4 Unhandled Exceptions in a Windows Form


// Class to receive callback to process unhandled exceptions
using System.Diagnostics;
using System.Windows.Forms;
using System.Threading;
public class UnForgiven
{
// Class signature matches ThreadExceptionEventHandler
public static void MyUnhandledMethod
(object sender, ThreadExceptionEventArgs e)
{
#if DEBUG
// Statements for debug mode
// Display trace and start the Debugger
MessageBox.Show("Debug: "+e.ToString());
#else
// Statements for release mode
// Provide output to user and log errors
MessageBox.Show("Release: "+e.ToString());
#endif
}
}

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

Also, the method’s EventArgs parameter changes to UnhandledException-


EventArgs.

Exception Handling Guidelines


The use of exception handling is the key to implementing code that performs in a sta-
ble manner when unexpected conditions occur. But successful exception handling
requires more than surrounding code with try and catch blocks. Thought must be
given to which exceptions should be caught and what to do with a caught exception.
Included here are some general rules to consider. For a more extensive list, consult
online “best practices” information.
160 Chapter 4 ■ Working with Objects in C#

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

Use exceptions to handle unexpected events, not as a way to implement logic.


Distinguish between error handling and exception handling. Incorrectly formatted
credit card numbers or incorrect passwords, for example, are all occurrences that
should be anticipated and handled by the logic of your code. An unavailable database
table or hardware failure requires an exception.

Don’t catch or throw base exception types.


The base exception type System.Exception is a catch-all exception that can be
used to catch any exception this is not specifically identified and caught. Much like
“fools gold,” its intuitive appeal is deceptive. The objective of exception handling is to
identify specific exceptions and handle with code appropriate for them; catching
each exception prevents the unexpected ones from being identified. As a rule, catch
specific exceptions and allow unidentified ones to propagate up the call stack.

Make liberal use of finally blocks.


The purpose of a finally block is to perform any housekeeping chores before a
method is exited. It is implemented as part of both try/catch/finally and try/
finally constructs. Because code should be more liberal in its use of throws than
catches, an application should contain more try/finally constructs than try/
catch constructs.
The inclusion of thorough, intelligent exception handling code in an application is
one of the hallmarks of well-written code. Although you’ll find code segments in this
book where exception handling is ignored or used sparingly, understand that this is
done only in the interest of simplifying code examples. In real-world programming,
there are no excuses for leaving it out.

4.3 Implementing System.Object


Methods in a Custom Class
When creating a custom class, particularly one that will be available to other develop-
ers, it is important to ensure that it observes the proper rules of object etiquette. It
should be CLS compliant, provide adequate exception handling, and adhere to OOP
4.3 Implementing System.Object Methods in a Custom Class 161

principles of encapsulation and inheritance when providing member accessibility. A


class should also implement the features that .NET developers are accustomed to
when working with Framework classes. These features include a custom implemen-
tation of the System.Object methods that all classes inherit:

• ToString(). By default, this method returns the name of the class. It


should be overridden to display contents of the object that distinguish
it from other instances of the class.
• Equals(). This is used to compare instances of a class to determine if
they are equal. It’s up to the class to define what equality means. This
could mean that two objects have matching field values or that two
objects reference the same memory location.
• MemberwiseClone(). This method returns a copy of an object that
contains the object’s value fields only—referred to as a shallow copy. A
custom class can use this method to return a copy of itself or imple-
ment its own clone method to return a deep copy that also includes
reference type values.

The remainder of this section provides examples of overriding or using these


methods in custom classes. Note that System.Object.Finalize—a method to
perform cleanup duties before an object is claimed by garbage collection—is dis-
cussed in Section 4.6, “Object Life Cycle Management.”

ToString() to Describe an Object


This method is most commonly used with primitive types to convert numbers to a
string format that can be displayed or printed. When used with objects, the default is
to return the fully qualified name of the object: <namespace>.<classname>. It’s
common to override this and return a string containing selected values from mem-
bers of the object. A good example of this is the exception object from the previous
section (refer to Figure 4-3) that returns the Message and StackTrace values:

ex.ToString() // Output:
// Attempted to divide by zero
// at TestExcep.Calc(Int32 j)
// at MyApp.Main()

The code shown in Listing 4-5 demonstrates how easy it is to implement


ToString in your own class.
The StringBuilder class is used to create the text string returned by the
method. It provides an efficient way of handling strings and is described in Chapter
5, “C# Text Manipulation and File I/O.”
162 Chapter 4 ■ Working with Objects in C#

Listing 4-5 Overriding ToString()


using System.Text;
using System;
public class Chair
{
private double myPrice;
private string myVendor, myID;
public Upholstery myUpholstery;
public Chair(double price, string vendor, string sku)
{ myPrice = price;
myVendor = vendor;
myID = sku;
}
// Override System.Object ToString()
public override string ToString()
{
StringBuilder chairSB = new StringBuilder();
chairSB.AppendFormat("ITEM = Chair");
chairSB.AppendFormat(" VENDOR = {0}", this.myVendor);
chairSB.AppendFormat(" PRICE = {0}",
this.myPrice.ToString());
return chairSB.ToString();
}
public string MyVen
{
get {return myVendor;}
}
//... other properties to expose myPrice, myID
}
public class Upholstery
{
public string Fabric ;
public Upholstery( string fab)
{ Fabric = fab; }
}

The following statements create an instance of the object and set desc to the
more meaningful value returned by ToString():

Chair myChair = new Chair(120.0, "Broyhill", "60-1222");


string desc = myChair.ToString());
// Returns ITEM = Chair VENDOR = Broyhill PRICE = 120.0
4.3 Implementing System.Object Methods in a Custom Class 163

Equals() to Compare Objects


When comparing two reference types, Equals returns true only if they point to the
same object in memory. To compare objects based on their value (value equality
rather than referential equality), you must override the default method. An example
of this is the String class—a reference type that implements Equals to perform a
value comparison based on the characters in the strings.
The code in Listing 4-6 illustrates how to override Equals in the Chair class to
compare objects using value semantics. It also overrides the GetHashCode
method—always recommended when overriding Equals.

Listing 4-6 Overriding Equals()


public override bool Equals(Object obj)
{
// Include the following two statements if this class
// derives from a class that overrides Equals()
//if (!base.Equals(obj))
// return false;
// (1) Null objects cannot be compared
if (obj == null) return false;
// (2) Check object types
if (this.GetType() != obj.GetType()) return false;
// (3) Cast object so we can access its fields
Chair otherObj = (Chair) obj;
// (4) Compare reference fields
if (!Object.Equals(myUpholstery,
otherObj.myUpholstery)) return false;
// (5) Compare Value Type members
if (!myVendor.Equals(otherObj.myVendor)) return false;
if (!myPrice.Equals(otherObj.myPrice)) return false;
if (!myID.Equals(otherObj.myID)) return false;
return true;
}
// Override GetHashCode – Required if Equals overridden
public override int GetHashCode()
{
return myID.GetHashCode();
}

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

Override Check for Compare Compare Compare Override


Equals null object object types Ref fields Value field GetHashCode

Figure 4-5 Steps in overriding Equals()

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:

Chair myChair = new Chair(150.0, "Lane", "78-0988");


myChair.myUpholstery = new Upholstery("Silk");
Chair newChair = new Chair(150.0, "Lane", "78-0988");
newChair.myUpholstery= new Upholstery("Silk");
// Next statement returns false. Why?
bool eq = ( myChair.Equals(newChair));

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:

Upholstery otherObj = (Upholstery) obj;


if (!Fabric.Equals(otherObj.Fabric)) return false;

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.

Determining If References Point to the Same Object


After you override the original Equals method to compare object values, your appli-
cation may still need to determine if reference variables refer to the same object.
System.Object has a static method called ReferenceEquals for this purpose. It is
used here with our Chair class:

Chair chair1 = new Chair(120.0, "Broyhill", "66-9888") )


Chair chair2 = new Chair(120.0, "Broyhill", "66-9933") )
Chair chair3 = chair1;
if (Object.ReferenceEquals(chair1, chair3) )
{ MessageBox.Show("Same object");}
else
{ MessageBox.Show("Different objects");}

The method returns true because chair1 and chair3 reference the same
instance.

Cloning to Create a Copy of an Object


The usual way to create an object is by using the new operator to invoke a construc-
tor. An alternative approach is to make a copy or clone of an existing object. The
object being cloned may be a class instance, an array, a string, or any reference type;
primitives cannot be cloned. For a class to be cloneable, it must implement the
ICloneable interface. This is a simple interface defined as

public interface ICloneable


{
Object Clone();
}

It consists of a single method, Clone, that is implemented to create a copy of the


object. The cloned object may represent a shallow copy or a deep copy of the object’s
fields. A shallow copy creates a new instance of the original object type, and then
copies the non-static fields from the original object to the new object. For a refer-
ence type, only a pointer to the value is copied. Thus, the clone points to the same
reference object as the original. A deep copy duplicates everything. It creates a copy
of reference objects and provides a reference to them in the copy.
166 Chapter 4 ■ Working with Objects in C#

Figure 4-6 depicts a shallow and deep copy for this instance of the Chair class:

Chair myChair = new Chair(150.0, "Lane", "78-0988");


Upholstery myFabric = new Upholstery("Silk");
myChair.myUpholstery = myFabric;

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.

myChair myUpholstery myChair myUpholstery

myUpholstery Fabric "Silk" myUpholstery Fabric "Silk"


myPrice 150.0 myPrice 150.0
myVendor "Lane" myVendor "Lane"
myID "78-0998" myID "78-0998"

CLONE CLONE myUpholstery

myUpholstery myUpholstery Fabric "Silk"


myPrice 150.0 myPrice 150.0
myVendor "Lane" myVendor "Lane"
myID "78-0998" myID "78-0998"
Shallow Deep

Figure 4-6 Comparison of shallow and deep copy

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.

How to Create a Shallow Copy


A shallow copy is sufficient when the object to be copied contains no reference type
fields needed by the copy. The easiest way to implement shallow copying is to use the
System.Object.MemberwiseClone method, a protected, non-virtual method that
makes a shallow copy of the object it is associated with. We use this to enable the
Chair class to clone itself:

public class Chair: ICloneable


{
//... other code from Listing 4-5
4.4 Working with .NET Collection Classes and Interfaces 167

public Object Clone()


{
return MemberwiseClone(); // from System.Object
}

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:

// Make clone of myChair


Chair chairClone = (Chair)myChair.Clone();
bool isEqual;
// (1) Following evaluates to false
isEqual = Object.ReferenceEquals(myChair,chairClone);
// (2) Following evaluates to true
isEqual = Object.ReferenceEquals(
myChair.myUpholstery,chairClone.myUpholstery);

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.

4.4 Working with .NET Collection


Classes and Interfaces
In .NET, collection is a general term that applies to the set of classes that represent
the classic data structures used to group related types of data. These classes include
stacks, queues, hash tables, arrays, and dictionaries. All are contained in one of two
namespaces: System.Collections or System.Collections.Generic. In .NET
versions 1.0 and 1.1, System.Collections was home to all collection classes. In
the .NET 2.0 release, these original classes were revised to support generics—a way
to make collections type-safe.
The .NET developers left the original namespace in for backward compatibility
and added the System.Collections.Generic namespace to contain the generic
classes. With the exception of the generics features, the classes in the two
namespaces offer the same functionality—although there are a few name changes.
To avoid confusion, much of this section refers to the System.Collections
namespace. The details of generics are presented at the end of the section.
168 Chapter 4 ■ Working with Objects in C#

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.

Table 4-2 System.Collections

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.

IComparer Exposes a method that is used to compare two objects and


plays a vital role in allowing objects in a collection to be sorted.

IDictionary Allows an object to represent its data as a collection of


key-and-value pairs.

IDictionaryEnumerator Permits iteration through the contents of an object that imple-


ments the IDictionary interface.

IEnumerator Supports a simple iteration through a collection. The iteration


IEnumerable only supports reading of data in the collection.

IHashCodeProvider Supplies a hash code for an object. It contains one method:


GetHashCode(object obj)

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


IEnumerable IEnumerator IComparer
Current
GetEnumerator MoveNext Compare
Reset

<<Interface>>
ICollection <<Interface>>
IDictionaryEnumerator
Count
IsSynchronized Entry
SyncRoot Key
CopyTo Value

<<Interface>> <<Interface>>
IList IDictionary

... ...

Figure 4-7 System.Collections Interface diagram

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#

a collection while it is being accessed by multiple clients. It’s analogous to locking


records in a database to prevent conflicting updates by multiple users.

Table 4-3 ICollection Members

Member Description

int Count Property that returns the number of entries in the col-
lection.

bool IsSynchronized Property that indicates if access to the collection is


thread-safe.

object SyncRoot Property that returns an object that can be used to


synchronize access to a collection by different threads.

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.

Using the IEnumerable and IEnumerator


Interfaces to List the Contents of a Collection
For a class to support iteration using the foreach construct, it must implement the
IEnumerable and IEnumerator interfaces. IEnumerator defines the methods and
properties used to implement the enumerator pattern; IEnumerable serves only to
return an IEnumerator object. Table 4-4 summarizes the member(s) provided by
each interface.
These members are implemented in a custom collection class as a way to enable
users to traverse the objects in the collection. To understand how to use these mem-
bers, let’s look at the underlying code that implements the foreach construct.
The foreach loop offers a simple syntax:

foreach ( ElementType element in collection)


{
Console.WriteLine(element.Name);
}
4.4 Working with .NET Collection Classes and Interfaces 171

Table 4-4 IEnumerable and IEnumerator

Member Description

IEnumerable.GetEnumerator() Returns an IEnumerator object that is used to


iterate through collection.

IEnumerator.Current Property that returns the current value in a collection.

IEnumerator.MoveNext() Advances the enumerator to the next item in the


collection. Returns bool.

IEnumerator.Reset() Sets the enumerator to its beginning position in the


collection. This method is not included in the
Generic namespace.

The compiler expands the foreach construct into a while loop that employs
IEnumerable and IEnumerator members:

// Get enumerator object using collection's GetEnumerator method


IEnumerator enumerator =
((IEnumerable) (collection)).GetEnumerator();
try
{
while (enumerator.MoveNext())
{
ElementType element = (ElementType)enumerator.Current;
Console.WriteLine(element.Name);
}
}
finally
// Determine if enumerator implements IDisposable interface
// If so, execute Dispose method to release resources
{
IDisposable disposable = enumerator as System.IDisposable;
If( disposable !=null) disposable.Dispose();
}

When a client wants to iterate across the members of a collection (enumerable), it


obtains an enumerator object using the IEnumerable.GetEnumerator method.
The client then uses the members of the enumerator to traverse the collection:
MoveNext() moves to the next element in the collection, and the Current property
returns the object referenced by the current index of the enumerator. Formally, this
is an implementation of the iteration design pattern.
172 Chapter 4 ■ Working with Objects in C#

Core Note

Enumerating through a collection is intrinsically not a thread-safe


procedure. If another client (thread) modifies the collection during the
enumeration, an exception is thrown. To guarantee thread safety, lock
the collection prior to traversing it:
lock( myCollection.SyncRoot )
{
foreach ( Object item in myCollection )
{
// Insert your code here
}
}

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

Listing 4-7 Using Iterators to Implement Enumeration


using System;
using System.Collections.Generic;
public class GenStack<T>: IEnumerable<T>
{
// Use generics type parameter to specify array type
private T[] stackCollection;
private int count = 0;
// Constructor
public GenStack(int size)
{
stackCollection = new T[size];
}
// (1) Iterator
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < count; i++)
{
yield return stackCollection[i];
}
}
// (2) Property to return the collection in reverse order
public IEnumerable<T> Reverse
{
get
{
for (int i = count - 1; i >= 0; i--)
{
yield return stackCollection[i];
}
}
}
public void Add(T item)
{
stackCollection[count] = item;
count += 1;
}
// other class methods go here ...

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.

GenStack<string> myStack = new GenStack<string>(10);


myStack.Add("Aida");
myStack.Add("La Boheme");
myStack.Add("Carmen");
// uses enumerator from GetEnumerator()
foreach (string s in myStack)
Console.WriteLine(s);
// uses enumerator from Reverse property
foreach (string s in myStack.Reverse)
Console.WriteLine(s);

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

In order to stop iteration before an entire collection is traversed, use the


yield break keywords. This causes MoveNext() to return false.

There are some restrictions on using yield return or yield break:

• It can only be used inside a method, property (get accessor), or


operator.
• It cannot be used inside a finally block, an anonymous method, or a
method that has ref or out arguments.
• The method or property containing yield return must have a return
type of Collections.IEnumerable, Collections.Generic.
IEnumerable<>, Collections.IEnumerator, or Collections.
Generic.IEnumerator<>.
4.4 Working with .NET Collection Classes and Interfaces 175

Using the IComparable and IComparer


Interfaces to Perform Sorting
Chapter 2, “C# Language Fundamentals,” included a discussion of the Sys-
tem.Array object and its associated Sort method. That method is designed for
primitive types such as strings and numeric values. However, it can be extended to
work with more complex objects by implementing the IComparable and ICom-
parer interfaces on the objects to be sorted.

IComparable
Unlike the other interfaces in this section, IComparable is a member of the System
namespace. It has only one member, the method CompareTo:

int CompareTo(Object obj)

Returned Value Condition


Less than 0 Current instance < obj
0 Current instance = obj
Greater than 0 Current instance > obj

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.

public class Chair : ICloneable, IComparable


{
private double myPrice;
private string myVendor, myID;
public Upholstery myUpholstery;
//… Constructor and other code
// Add implementation of CompareTo to sort in ascending
int IComparable.CompareTo(Object obj)
{
if (obj is Chair) {
Chair castObj = (Chair)obj;
if (this.myPrice > castObj.myPrice)
return 1;
if (this.myPrice < castObj.myPrice)
return -1;
else return 0;
176 Chapter 4 ■ Working with Objects in C#

// Reverse 1 and –1 to sort in descending order


}
throw new ArgumentException("object in not a Chair");
}

The code to sort an array of Chair objects is straightforward because all the work
is done inside the Chair class:

Chair[]chairsOrdered = new Chair[4];


chairsOrdered[0] = new Chair(150.0, "Lane","99-88");
chairsOrdered[1] = new Chair(250.0, "Lane","99-00");
chairsOrdered[2] = new Chair(100.0, "Lane","98-88");
chairsOrdered[3] = new Chair(120.0, "Harris","93-9");
Array.Sort(chairsOrdered);
// Lists in ascending order of price
foreach(Chair c in chairsOrdered)
MessageBox.Show(c.ToString());

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:

public class CompareByVen : IComparer


{
public CompareByVen() { }
int IComparer.Compare(object obj1, object obj2)
{
// obj1 contains array being sorted
// obj2 is instance of helper sort class
Chair castObj1 = (Chair)obj1;
Chair castObj2 = (Chair)obj2;
return String.Compare
(castObj1.myVendor,castObj2.myVendor);
}
}
4.4 Working with .NET Collection Classes and Interfaces 177

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

ICollection IList IDictionary

Stack ArrayList HashTable


Queue SortedList
Figure 4-8 Selected classes in System.Collections

The purpose of interfaces is to provide a common set of behaviors for classes.


Thus, rather than look at the details of all the classes, we will look at the interfaces
themselves as well as some representative classes that illustrate how the members of
the interfaces are implemented. We’ve already looked at ICollection, so let’s begin
with two basic collections that inherit from it.

Stack and Queue


The Stack and the Queue are the simplest of the collection classes. Because they do
not inherit from IList or IDictionary, they do not provide indexed or keyed
access. The order of insertion controls how objects are retrieved from them. In a
Stack, all insertions and deletions occur at one end of the list; in a Queue, insertions
are made at one end and deletions at the other. Table 4-5 compares the two.
178 Chapter 4 ■ Working with Objects in C#

Table 4-5 Stack and Queue—Selected Members and Features

Description Stack Queue

Method of maintaining Last-in, first-out (LIFO) First-in, first-out (FIFO)


data

Add an item Push() EnQueue()

Remove an item Pop() DeQueue()

Return the current Peek() Peek()


item without removing
it

Determine whether an Includes() Includes()


item is in the collection

Constructors Stack() Queue()


• Empty stack with default • Default capacity and growth
capacity factor
Stack(ICollection) Queue(ICollection)
• Stack is filled with received • Filled with received
collection collection
Stack(int) Queue(int)
• Set stack to initial int • Set queue to initial int
capacity capacity
Queue(int, float)
• Set initial capacity and
growth factor

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.

Listing 4-8 Using the Stack Container


public class ShowStack {
public static void Main()
{
Stack myStack = new Stack();
myStack.Push(new Chair(250.0, "Adams Bros.", "87-00" ));
myStack.Push(new Chair(100.0, "Broyhill","87-04" ));
myStack.Push(new Chair(100.0, "Lane","86-09" ));
PrintValues( myStack ); // Adams – Broyhill - Lane
4.4 Working with .NET Collection Classes and Interfaces 179

Listing 4-8 Using the Stack Container (continued)


// Pop top object and push a new one on stack
myStack.Pop();
myStack.Push(new Chair(300.0, "American Chair"));
Console.WriteLine(myStack.Peek().ToString()); // American
}
public static void PrintValues( IEnumerable myCollection )
{
System.Collections.IEnumerator myEnumerator =
myCollection.GetEnumerator();
while ( myEnumerator.MoveNext() )
Consle.WriteLine(myEnumerator.Current.ToString());
// Could list specific chair fields with
// myChair = (Chair) myEnumerator.Current;
}
}

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#

ArrayList chairList = new ArrayList( );


// alternative: ArrayList chairList = new ArrayList(5);
chairList.Add(new Chair(350.0, "Adams", "88-00"));
chairList.Add(new Chair(380.0, "Lane", "99-33"));
chairList.Add(new Chair(250.0, "Broyhill", "89-01"));
PrintValues(chairList); // Adams – Lane - Broyhill
//
chairList.Insert(1,new Chair(100,"Kincaid"));
chairList.RemoveAt(2);
Console.WriteLine("Object Count: {0}",chairList.Count);
//
PrintValues(chairList); // Adams – Kincaid - Broyhill
// Copy objects to an array
Object chairArray = chairList.ToArray();
PrintValues((IEnumerable) chairArray);

Table 4-6 IList Members

Interface Description

bool IsFixedSize Indicates whether the collection has a fixed size. A


fixed size prevents the addition or removal of items
after the collection is created.

bool IsReadOnly Items in a collection can be read but not modified.

int IndexOf(object) Determines the index of a specific item in the


collection.

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.

void Clear() Remove all items from a collection.

bool Contains(object) Returns true if a collection contains an item with a


specified value.

This parameterless declaration of ArrayList causes a default amount of memory


to be initially allocated. You can control the initial allocation by passing a size param-
eter to the constructor that specifies the number of elements you expect it to hold. In
both cases, the allocated space is automatically doubled if the capacity is exceeded.
4.4 Working with .NET Collection Classes and Interfaces 181

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:

public class Hashtable : IDictionary, ICollection, IEnumerable,


ISerializable, IDeserializationCallback, ICloneable

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.

Table 4-7 IDictionary Member

Member Description

bool IsFixedSize Indicates whether IDictionary has a fixed size. A fixed


size prevents the addition or removal of items after the col-
lection is created.

bool IsReadOnly Elements in a collection can be read but not modified.

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.

bool Contains Returns true if a collection contains an element with a


specified key.

IDictionaryEnumerator Returns an instance of the IDictionaryEnumerator


GetEnumerator () type that is required for enumerating through a dictionary.
182 Chapter 4 ■ Working with Objects in C#

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.

Table 4-8 IDictionaryEnumerator Members

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

Hashtable chairHash = new Hashtable(1000, .6)

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:

// Check for existence of a key


bool keyFound;
if (chairHash.ContainsKey("88-00"))
{ keyFound = true;}
else
{keyFound = false;}

List Keys in a Hashtable


The following iterates through the Keys property collection:

// List Keys
foreach (string invenKey in chairHash.Keys)
{ MessageBox.Show(invenKey); }

List Values in a Hashtable


These statements iterate through the Values in a hash table:

// List Values
foreach (Chair chairVal in chairHash.Values)
{ MessageBox.Show(chairVal.myVendor);}

List Keys and Values in a Hashtable


This code lists the keys and values in a hash table:

foreach ( DictionaryEntry deChair in chairHash)


{
Chair obj = (Chair) deChair.Value;
MessageBox.Show(deChair.Key+" "+ obj.myVendor);
}
184 Chapter 4 ■ Working with Objects in C#

The entry in a Hashtable is stored as a DictionaryEntry type. It has a Value


and Key property that expose the actual value and key. Note that the value is
returned as an object that must be cast to a Chair type in order to access its fields.

Core Note

According to .NET documentation (1.x), a synchronized version of a


Hashtable that is supposed to be thread-safe for a single writer and
concurrent readers can be created using the Synchronized method:
Hashtable safeHT = Hashtable.Synchronized(newHashtable());
Unfortunately, the .NET 1.x versions of the Hashtable have been
proven not to be thread-safe for reading. Later versions may correct this
flaw.

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

ArrayList primes = new ArrayList();


primes.Add(1);
primes.Add(3);
int pSum = (int)primes[0] + (int)primes[1];
primes.Add("test");

can be replaced with the generics version:

List<int> primes = new List<int>();


primes.Add(1);
primes.Add(3);
int pSum = primes[0] + primes[1];
primes.Add("text"); // will not compile

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.

Comparison of System.Collections and


System.Collections.Generic Namespaces
As the following side-by-side comparison shows, the classes in the two namespaces
share the same name with three exceptions: Hashtable becomes Dictionary<>,
ArrayList becomes List<>, and SortedList is renamed SortedDictionary<>.

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.

An Example Using a Generics Collections Class


Switching to the generics versions of the collection classes is primarily a matter of
getting used to a new syntax, because the functionality provided by the generics and
non-generics classes is virtually identical. To demonstrate, here are two examples.
The first uses the Hashtable to store and retrieve instances of the Chair class
(defined in Listing 4-5); the second example performs the same functions but uses
the Dictionary class—the generics version of the Hashtable.
This segment consists of a Hashtable declaration and two methods: one to store
a Chair object in the table and the other to retrieve an object based on a given key
value.

// Example 1: Using Hashtable


public Hashtable ht = new Hashtable();
// Store Chair object in table using a unique product identifier
private void saveHT(double price, string ven, string sku)
{
if (!ht.ContainsKey(sku))
{
ht.Add(sku, new Chair(price,ven,sku));
}
}
// Display vendor and price for a product identifier
private void showChairHT(string sku)
{
if (ht.ContainsKey(key))
{
if (ht[key] is Chair) // Prevent casting exception
{
Chair ch = (Chair)ht[sku];
Console.WriteLine(ch.MyVen + " " + ch.MyPr);
}
else
{ Console.WriteLine("Invalid Type: " +
(ht[key].GetType().ToString()));
}
}
}

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.

// Example 2: Using Generics Dictionary to replace Hashtable


// Dictionary accepts string as key and Chair as data type
Dictionary<string,Chair> htgen = new Dictionary<string,Chair>();
//
private void saveGen(double price, string ven, string sku)
{
if (!htgen.ContainsKey(sku))
{
htgen.Add(sku, new Chair(price,ven,sku));
}
}

private void showChairGen(string sku)


{
if (htgen.ContainsKey(key))
{
Chair ch = htgen[sku]; // No casting required
Console.WriteLine(ch.MyVen + " " + ch.MyPr);
}
}

The important advantage of generics is illustrated in the showChairGen method.


It has no need to check the type of the stored object or perform casting.
In the long run, the new generic collection classes will render the classes in the
System.Collections namespace obsolete. For that reason, new code develop-
ment should use the generic classes where possible.

4.5 Object Serialization


In .NET, serialization refers to the process of converting an object or collection of
objects into a format suitable for streaming across a network—a Web Service, for
example—or storing in memory, a file, or a database. Deserialization is the reverse
process that takes the serialized stream and converts it back into its original object(s).
188 Chapter 4 ■ Working with Objects in C#

.NET support three primary types of serialization:

• Binary. Uses the BinaryFormatter class to serialize a type into a


binary stream.
• SOAP. Uses the SoapFormatter class to serialize a type into XML
formatted according to SOAP (Simple Object Access Protocol)
standards.
• XML. Uses the XmlSerializer class to serialize a type into basic
XML (described in Chapter 10, “Working with XML in .NET”). Web
Services uses this type of serialization.

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.

Listing 4-9 Serialization of a Hashtable


using System;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
4.5 Object Serialization 189

Listing 4-9 Serialization of a Hashtable (continued)


// Store Chair objects in a Hashtable
Hashtable ht = new Hashtable();
// Chair and Upholstery must have [Serializable] attribute
Chair ch = new Chair(100.00D, "Broyhill", "10-09");
ch.myUpholstery = new Upholstery("Cotton");
ht.Add("10-09", ch);
// Add second item to table
ch = new Chair(200.00D, "Lane", "11-19");
ch.myUpholstery = new Upholstery("Silk");
ht.Add("11-19", ch);
// (1) Serialize
// Create a new file; if file exits it is overwritten
FileStream fs= new FileStream("c:\\chairs.dat",
FileMode.Create);
BinaryFormatter bf= new BinaryFormatter();
bf.Serialize(fs,ht);
fs.Close();
// (2) Deserialize binary file into a Hashtable of objects
ht.Clear(); // Clear hash table.
fs = new FileStream("c:\\chairs.dat", FileMode.Open);
ht = (Hashtable) bf.Deserialize(fs);
// Confirm objects properly recreated
ch = (Chair)ht["11-19"];
Console.WriteLine(ch.myUpholstery.Fabric); // "Silk"
fs.Close();

Observe the following key points:

• The serialization and IO namespaces should be declared.


• The Chair and Upholstery classes must have the [Serializable]
attribute; otherwise, a runtime error occurs when Serialize()is
executed.
• Serialization creates an object graph that includes references from one
object to another. The result is a deep copy of the objects. In this
example, the myUpholstery field of Chair is set to an instance of the
Upholstery class before it is serialized. Serialization stores a copy of
the object—rather than a reference. When deserialization occurs, the
Upholstery object is restored.
190 Chapter 4 ■ Working with Objects in C#

Excluding Class Members from Serialization


You can selectively exclude class members from being serialized by attaching the
[NonSerialized] attribute to them. For example, you can prevent the myUphol-
stery field of the Chair class from being serialized by including this:

[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

A class cannot inherit the Serializable attribute from a base class; it


must explicitly include the attribute. On the other hand, a derived class
can be made serializable only if its base class is serializable.

Binary Serialization Events


.NET 2.0 introduced support for four binary serialization and deserialization events,
as summarized in Table 4-9.

Table 4-9 Serialization and Deserialization Events

Event Attribute Description

OnSerializing [Serializing] Occurs before objects are serialized. Event


handler is called for each object to be serialized.

OnSerialized [Serialized] Occurs after objects are serialized. Event


handler is called once for each object serialized.

OnDeserializing [Deserializing] Occurs before objects are deserialized. Event


handler is called once for each object to be
deserialized.

OnDeserialized [Deserialized] Occurs after objects have been deserialized.


Event handler is called for each deserialized
object.
4.5 Object Serialization 191

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:

void <event name>(StreamingContext context)

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.

public class Chair


{
// other code here
[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
// Edit vendor name after object is created
if (MyVen == "Lane") MyVen = "Lane Bros.";
}
}

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.

Handling Version Changes to a Serialized Object


Suppose the Chair class from the preceding examples is redesigned. A field could be
added or deleted, for example. What happens if one then attempts to deserialize
objects in the old format to the new format? It’s not an uncommon problem, and
.NET offers some solutions.
If a field is deleted, the binary formatter simply ignores the extra data in the dese-
rialized stream. However, if the formatter detects a new field in the target object, it
throws an exception. To prevent this, an [OptionalField] attribute can be
attached to the new field(s). Continuing the previous example, let’s add a field to
Chair that designates the type of wood finish:

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

void OnDeserialized(StreamingContext context)


{
if (MyVen == "Lane") finish = "Oak"; else finish = "Cherry";
}

4.6 Object Life Cycle Management


Memory allocation and deallocation have always been the bane of developers. Even
the most experienced C++ and COM programmer is faced with memory leaks and
attempts to access nonexistent objects that either never existed or have already been
destroyed. In an effort to remove these responsibilities from the programmer, .NET
implements a memory management system that features a managed heap and auto-
matic Garbage Collection.
Recall from Chapter 2, “C# Language Fundamentals,” that the managed heap is a
pre-allocated area of memory that .NET uses to store reference types and data. Each
time an instance of a class is created, it receives memory from the heap. This is a
faster and cleaner solution than programming environments that rely on the operat-
ing system to handle memory allocation.
Allocating memory from the stack is straightforward: A pointer keeps track of the
next free memory address and allocates memory from the top of the heap. The
important thing to note about the allocated memory is that it is always contiguous.
There is no fragmentation or complex overhead to keep track of free memory blocks.
Of course, at some point the heap is exhausted and unused space must be recovered.
This is where the .NET automatic Garbage Collection comes into play.

.NET Garbage Collection


Each time a managed object is created, .NET keeps track of it in a tree-like graph of
nodes that associates each object with the object that created or uses it. In addition,
each time another client references an object or a reference is assigned to a variable,
the graph is updated. At the top of this graph is a list of roots, or parts of the applica-
tion that exist as long at the program is running (see Figure 4-9). These include static
variables, CPU registers, and any local or parameter variables that refer to objects on
the managed heap. These serve as the starting point from which the .NET Frame-
work uses a reference-tracing technique to remove objects from the heap and
reclaim memory.
The Garbage Collection process begins when some memory threshold is reached.
At this point, the Garbage Collector (GC) searches through the graph of objects and
marks those that are “reachable.” These are kept alive while the unreachable ones
are considered to be garbage. The next step is to remove the unreferenced objects
4.6 Object Life Cycle Management 193

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

Managed Finalization Managed Finalization


Heap Queue Heap Queue
Roots Object G
Object J Object F Object F
References to Objects

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

Figure 4-9 .NET Garbage Collection process

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:

• Finalization degrades performance due to the increased overhead.


Only use it when the object holds resources not managed by the CLR.
• Objects may be placed in the freachable queue in any order. There-
fore, your Finalize code should not reference other objects that use
finalization, because they may have already been processed.
• Call the base Finalize method within your Finalize method so it
can perform any cleanup: base.Finalize().
• Finalization code that fails to complete execution prevents the back-
ground thread from executing the Finalize method of any other
objects in the queue. Infinite loops or synchronization locks with infi-
nite timeouts are always to be avoided, but are particularly deleterious
when part of the cleanup code.

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:

Public class Chair


{
public Chair() { }
~Chair() // Destructor
{ // finalization code }
}

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.

Listing 4-10 Pattern for Calling Dispose()


public class MyConnections: IDisposable
{
public void Dispose()
{
// code to dispose of resources
base.Dispose(); // include call to base Dispose()
}
public void UseResources() { }
}
// Client code to call Dispose()
class MyApp
{
public static void Main()
{
MyConnections connObj;
connObj = new MyConnections();
try
{
connObj.UseResources();
}
finally // Call dispose() if it exists
{
IDisposable testDisp;
testDisp = connObj as IDisposable;
if(testDisp != null)
{ testDisp.Dispose(); }
}
}
196 Chapter 4 ■ Working with Objects in C#

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() }

Using Dispose and Finalize


When Dispose is executed, the object’s unmanaged resources are released and the
object is effectively disposed of. This raises a couple of questions: First, what hap-
pens if Dispose is called after the resources are released? And second, if Finalize
is implemented, how do we prevent the GC from executing it since cleanup has
already occurred?
The easiest way to handle calls to a disposed object’s Dispose method is to raise
an exception. In fact, the ObjectDisposedException exception is available for this
purpose. To implement this, add a boolean property that is set to true when Dispose
is first called. On subsequent calls, the object checks this value and throws an excep-
tion if it is true.
Because there is no guarantee that a client will call Dispose, Finalize should
also be implemented when resource cleanup is required. Typically, the same cleanup
method is used by both, so there is no need for the GC to perform finalization if
Dispose has already been called. The solution is to execute the SuppressFinalize
method when Dispose is called. This static method, which takes an object as a
parameter, notifies the GC not to place the object on the freachable queue.
Listing 4-11 shows how these ideas are incorporated in actual code.

Pattern for Implementing Dispose() and


Listing 4-11
Finalize()

public class MyConnections: IDisposable


{
private bool isDisposed = false;
protected bool Disposed
{
get{ return isDisposed;}
}
public void Dispose()
{
if (isDisposed == false)
{
4.6 Object Life Cycle Management 197

Pattern for Implementing Dispose() and


Listing 4-11
Finalize() (continued)

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();
}
}

The key features of this code include the following:

• A common method, CleanUp, has been introduced and is called from


both Dispose and Finalize . It is defined as protected and virtual,
and contains no concrete code.
• Classes that inherit from the base class MyConnections are responsi-
ble for implementing the CleanUp. As part of this, they must be sure
to call the Cleanup method of the base class. This ensures that
cleanup code runs on all levels of the class hierarchy.
198 Chapter 4 ■ Working with Objects in C#

• The read-only property Disposed has been added and is checked


before methods in the base class are executed.

In summary, the .NET Garbage Collection scheme is designed to allow program-


mers to focus on their application logic and not deal with details of memory alloca-
tion and deallocation. It works well as long as the objects are dealing with managed
resources. However, when there are valuable unmanaged resources at stake, a deter-
ministic method of freeing them is required. This section has shown how the Dis-
pose and Finalize methods can be used in concert to manage this aspect of an
object’s life cycle.

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.

4.8 Test Your Understanding


1. What are the advantages of using a class factory to create objects?

2. Which class should custom exceptions inherit from? Which construc-


tors should be included?
4.8 Test Your Understanding 199

3. How does the default System.Object.Equals method determine if


two objects are equal?

4. Which interface(s) must a class implement in order to support the


foreach statement?

5. What is the main advantage of using generics?

6. What is the purpose of implementing IDisposable on a class?

7. Refer to the following code:


public class test: ICloneable
{
public int Age;
public string Name;
public test(string myname)
{ Name = myname; }
public Object Clone()
{ return MemberwiseClone(); }
}
// Create instances of class
test myTest = new test("Joanna");
myTest.Age = 36;
test clone1 = (test) mytest.Clone();
test clone2 = myTest;
Indicate whether the following statements evaluate to true or false:
a. Object.ReferenceEquals(myTest.Name, clone1.Name)
b. Object.ReferenceEquals(myTest.Age, clone1.Age)
c. myTest.Name = "Julie";
Object.ReferenceEquals(myTest.Name, clone1.Name)
d. Object.ReferenceEquals(myTest.Name, clone2.Name)

8. How does implementing Finalize on an object affect its Garbage


Collection?
CREATING
APPLICATIONS
USING THE
.NET FRAMEWORK
CLASS LIBRARY
II
■ Chapter 5
C# Text Manipulation and File I/O 202

■ 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

Topics in This Chapter

• Characters and Unicode: By default, .NET stores a character as a


16-bit Unicode character. This enables an application to support
international character sets—a technique referred to as localization.
• String Overview: In .NET, strings are immutable. To use strings
efficiently, it is necessary to understand what this means and how
immutability affects string operations.
• String Operations: In addition to basic string operations, .NET
supports advanced formatting techniques for numbers and dates.
• StringBuilder: The StringBuilder class offers an efficient
alternative to concatenation for constructing screens.
• Regular Expressions: The .NET Regex class provides an engine
that uses regular expressions to parse, match, and extract values
in a string.
• Text Streams: Stream classes permit data to be read and written as
a stream of bytes that can be encrypted and buffered.
• Text Reading and Writing: The StreamReader and
StreamWriter classes make it easy to read from and write to
physical files.
• System.IO: Classes in this namespace enable an application to
work with underlying directory structure of the host operating
system.
5

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

5.1 Characters and Unicode


One of the watershed events in computing was the introduction of the ASCII 7-bit
character set in 1968 as a standardized encoding scheme to uniquely identify alpha-
numeric characters and other common symbols. It was largely based on the Latin
alphabet and contained 128 characters. The subsequent ANSI standard doubled the
number of characters—primarily to include symbols for European alphabets and
currencies. However, because it was still based on Latin characters, a number of
incompatible encoding schemes sprang up to represent non-Latin alphabets such as
the Greek and Arabic languages.
Recognizing the need for a universal encoding scheme, an international consor-
tium devised the Unicode specification. It is now a standard, accepted worldwide,
that defines a unique number for every character “no matter what the platform, no
matter what the program, no matter what the language.”1

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.

Figure 5-1 Unicode memory layout of a character

1. Unicode Consortium—www.unicode.org.
5.1 Characters and Unicode 205

Unicode characters have a unique identifier made up of a name and value,


referred to as a code point. The current version 4.0 defines identifiers for 96,382
characters. These characters are grouped in over 130 character sets that include lan-
guage scripts, symbols for math, music, OCR, geometric shapes, Braille, and many
other uses.
Because 16 bits cannot represent the nearly 100,000 characters supported world-
wide, more bytes are required for some character sets. The Unicode solution is a
mechanism by which two sets of 16-bit units define a character. This pair of code
units is known as a surrogate pair. Together, this high surrogate and low surrogate
represent a single 32-bit abstract character into which characters are mapped. This
approach supports over 1,000,000 characters. The surrogates are constructed from
values that reside in a reserved area at the high end of the Unicode code space so
that they are not mistaken for actual characters.
As a developer, you can pretty much ignore the details of whether a character
requires 16 or 32 bits because the .NET API and classes handle the underlying
details of representing Unicode characters. One exception to this—discussed later in
this section—occurs if you parse individual bytes in a stream and need to recognize
the surrogates. For this, .NET provides a special object to iterate through the bytes.

Core Note

Unicode characters can only be displayed if your computer has a font


supporting them. On a Windows operating system, you can install a font
extension (ttfext.exe) that displays the supported Unicode ranges for
a .ttf font. To use it, right-click the .ttf font name and select
Properties. Console applications cannot print Unicode characters
because console output always displays in a non-proportional typeface.

Working with Characters


A single character is represented in .NET as a char (or Char) structure. The char
structure defines a small set of members (see char in Chapter 2, “C# Language Fun-
damentals”) that can be used to inspect and transform its value. Here is a brief
review of some standard character operations.

Assigning a Value to a Char Type


The most obvious way to assign a value to a char variable is with a literal value. How-
ever, because a char value is represented internally as a number, you can also assign
it a numeric value. Here are examples of each:
206 Chapter 5 ■ C# Text Manipulation and File I/O

string klm = "KLM";


byte b = 75;
char k;
// Different ways to assign 'K' to variable K
k = 'K';
k = klm[0]; // Assign "K" from first value in klm
k = (char) 75; // Cast decimal
k = (char) b; // cast byte
k = Convert.ToChar(75); // Converts value to a char

Converting a Char Value to a Numeric Value


When a character is converted to a number, the result is the underlying Unicode
(ordinal) value of the character. Casting is the most efficient way to do this, although
Convert methods can also be used. In the special case where the char is a digit and
you want to assign the linguistic value—rather than the Unicode value—use the
static GetNumericValue method.

// '7' has Unicode value of 55


char k = '7';
int n = (int) k; // n = 55
n = (int) char.GetNumericValue(k); // n = 7

Characters and Localization


One of the most important features of .NET is the capability to automatically recog-
nize and incorporate culture-specific rules of a language or country into an applica-
tion. This process, known as localization, may affect how a date or number is
formatted, which currency symbol appears in a report, or how string comparisons are
carried out. In practical terms, localization means a single application would display
the date May 9, 2004 as 9/5/2004 to a user in Paris, France and as 5/9/2004 to a user
in Paris, Texas. The Common Language Runtime (CLR) automatically recognizes
the local computer’s culture and makes the adjustments.
The .NET Framework provides more than a hundred culture names and identifi-
ers that are used with the CultureInfo class to designate the language/country to
be used with culture sensitive operations in a program. Although localization has a
greater impact when working with strings, the Char.ToUpper method in this exam-
ple is a useful way to demonstrate the concept.

// Include the System.Globalization namespace


// Using CultureInfo – Azerbaijan
char i = 'i';
// Second parameter is false to use default culture settings
// associated with selected culture
CultureInfo myCI = new CultureInfo("az", false );
i = Char.ToUpper(i,myCI);
5.1 Characters and Unicode 207

An overload of ToUpper() accepts a CultureInfo object that specifies the cul-


ture (language and country) to be used in executing the method. In this case, az
stands for the Azeri language of the country Azerbaijan (more about this follows).
When the Common Language Runtime sees the CultureInfo parameter, it takes
into account any aspects of the culture that might affect the operation. When no
parameter is provided, the CLR uses the system’s default culture.

Core Note

On a Windows operating system, the .NET Framework obtains its default


culture information from the system’s country and language settings. It
assigns these values to the Thread.CurrentThread.CurrentCulture
property. You can set these options by choosing Regional Options in the
Control Panel.

So why choose Azerbaijan, a small nation on the Caspian Sea, to demonstrate


localization? Among all the countries in the world that use the Latin character set,
only Azerbaijan and Turkey capitalize the letter i not with I (U+0049), but with an I
that has a dot above it (U+0130). To ensure that ToUpper() performs this operation
correctly, we must create an instance of the CultureInfo class with the Azeri cul-
ture name—represented by az—and pass it to the method. This results in the cor-
rect Unicode character—and a satisfied population of 8.3 million Azerbaijani.

Characters and Their Unicode Categories


The Unicode Standard classifies Unicode characters into one of 30 categories. .NET
provides a UnicodeCategory enumeration that represents each of these categories
and a Char.GetUnicodecategory() method to return a character’s category. Here
is an example:

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

The method correctly identifies K as an UppercaseLetter and the carriage


return as a Control character. As an alternative to the unwieldy GetUnicodeCate-
gory, char includes a set of static methods as a shortcut for identifying a character’s
Unicode category. They are nothing more than wrappers that return a true or
false value based on an internal call to GetUnicodeCategory. Table 5-1 lists these
methods.
208 Chapter 5 ■ C# Text Manipulation and File I/O

Table 5-1 Char Methods That Verify Unicode Categories

Unicode
Method Category Description

IsControl 4 Control code whose Unicode value is U+007F, or in


the range U+0000 through U+001F, or U+0080
through U+009F.

IsDigit 8 Is in the range 0–9.

IsLetter 0, 1, 2, 4 Letter.

IsLetterorDigit 0, 1, 8, Union of letters and digits.

IsLower 1 Lowercase letter.

IsUpper 0 Uppercase letter.

IsPunctuation 18, 19, 20, 21, Punctuation symbol—for example, DashPunctua-


22, 23, 24 tion(19) or OpenPunctuation(20), OtherPunc-
tuation(24).

IsSeparator 11, 12, 13 Space separator, line separator, paragraph separator.

IsSurrogate 16 Value is a high or low surrogate.

IsSymbol 25, 26, 28 Symbol.

IsWhiteSpace 11 Whitespace can be any of these characters: space


(0x20), carriage return (0x0D), horizontal tab (0x09),
line feed (0x0A), form feed (0x0C), or vertical tab
(0x0B).

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

5.2 The String Class


The System.String class was introduced in Chapter 2. This section expands that
discussion to include a more detailed look at creating, comparing, and formatting
strings. Before proceeding to these operations, let’s first review the salient points
from Chapter 2:

• The System.String class is a reference type having value semantics.


This means that unlike most reference types, string comparisons are
based on the value of the strings and not their location.
• A string is a sequence of Char types. Any reference to a character
within a string is treated as a char.
• Strings are immutable. This means that after a string is created, it can-
not be changed at its current memory location: You cannot shorten it,
append to it, or change a character within it. The string value can be
changed, of course, but the modified string is stored in a new memory
location. The original string remains until the Garbage Collector
removes it.
• The System.Text.StringBuilder class provides a set of methods
to construct and manipulate strings within a buffer. When the opera-
tions are completed, the contents are converted to a string. String-
Builder should be used when an application makes extensive use of
concatenation and string modifications.

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

Consider the following code:

string poem1 = "Kubla Khan";


string poem2 = "Kubla Khan";
string poem3 = String.Copy(poem2); // Create new string object
string poem4 = "Christabel";

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

Figure 5-2 String interning

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

Console.WriteLine(poem1 == poem2); // true


Console.WriteLine(poem1 == poem3); // true
Console.WriteLine(ReferenceEquals(poem1, poem3)); // false
Console.WriteLine(ReferenceEquals(poem1,
"Kubla Khan")); // true

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.

string khan = " Khan";


string poem5 = "Kubla" + khan;
Console.WriteLine(ReferenceEquals(poem5, poem1)); // false
// Place the contents of poem5 in the intern pool—if not there
poem5 = String.Intern(poem5);
Console.WriteLine(ReferenceEquals(poem5, poem1)); // true

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

Use the String.Intern method to allow a string variable to take


advantage of comparison by reference, but only if it is involved in
numerous comparisons.

Overview of String Operations


The System.String class provides a large number of static and instance methods,
most of which have several overload forms. For discussion purposes, they can be
grouped into four major categories based on their primary function:
212 Chapter 5 ■ C# Text Manipulation and File I/O

• String Comparisons. The String.Equals, String.Compare, and


String.CompareOrdinal methods offer different ways to compare
string values. The choice depends on whether an ordinal or lexical
comparison is needed, and whether case or culture should influence
the operation.
• Indexing and Searching. A string is an array of Unicode characters
that may be searched by iterating through it as an array or by using
special index methods to locate string values.
• String Transformations. This is a catchall category that includes
methods for inserting, padding, removing, replacing, trimming, and
splitting character strings.
• Formatting. NET provides format specifiers that are used in con-
junction with String.Format to represent numeric and DateTime
values in a number of standard and custom formats.

Many of the string methods—particularly for formatting and comparisons—are


culture dependent. Where applicable, we look at how culture affects the behavior of
a method.

5.3 Comparing Strings


The most efficient way to determine if two string variables are equal is to see if they
refer to the same memory address. We did this earlier using the ReferenceEquals
method. If two variables do not share the same memory address, it is necessary to
perform a character-by-character comparison of the respective values to determine
their equality. This takes longer than comparing addresses, but is often unavoidable.
.NET attempts to optimize the process by providing the String.Equals method
that performs both reference and value comparisons automatically. We can describe
its operation in the following pseudo-code:

If string1 and string2 reference the same memory location


Then strings must be equal
Else
Compare strings character by character to determine equality

This code segment demonstrates the static and reference forms of the Equals
method:

string poem1 = "Kubla Khan";


string poem2 = "Kubla Khan";
string poem3 = String.Copy(poem2);
string poem4 = "kubla khan";
5.3 Comparing Strings 213

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

int Compare (string str1, string str2)


Compare (string str1, string str2, bool IgnoreCase)
Compare (string str1, string str2, bool IgnoreCase,
CultureInfo ci)
Compare (string str1, int index1, string str2, int index2,
int len)

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

string stringLower = "autumn";


// (1) Lexical comparison: "A" is greater than "a"
result = string.Compare(stringUpper,stringLower); // 1
// (2) IgnoreCase set to false
result = string.Compare(stringUpper,stringLower,false); // 1
// (3)Perform case-insensitive comparison
result = string.Compare(stringUpper,stringLower,true); // 0

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:

CultureInfo ci = new CultureInfo("de-DE"); // German culture

To explicitly specify a default culture or no culture, the CultureInfo class has


two properties that can be passed as parameters—CurrentCulture, which tells a
method to use the culture of the current thread, and InvariantCulture, which
tells a method to ignore any culture.
Let’s look at a concrete example of how culture differences affect the results of a
Compare() operation.

using System.Globalization; // Required for CultureInfo

// Perform case-sensitive comparison for Czech culture


string s1 = "circle";
string s2 = "chair";
result = string.Compare(s1, s2,
true, CultureInfo.CurrentCulture)); // 1
result = string.Compare(s1, s2,
true, CultureInfo.InvariantCulture)); // 1
// Use the Czech culture
result = string.Compare(s1, s2,
true, new CultureInfo("cs-CZ")); // -1
5.3 Comparing Strings 215

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

When writing an application that takes culture into account, it is good


practice to include an explicit CultureInfo parameter in those
methods that accept such a parameter. This provides a measure of
self-documentation that clarifies whether the specific method is subject
to culture variation.

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:

string stringUpper = "AUTUMN";


string stringLower = "autumn";
//
result = string.Compare(stringUpper,stringLower,
false, CultureInfo.InvariantCulture); // 1
result = string.CompareOrdinal(stringUpper,stringLower); // -32

Compare performs a lexical comparison that regards the uppercase string to be


greater than the lowercase. CompareOrdinal examines the underlying Unicode
values. Because A (U+0041) is less than a (U+0061), the first string is less than the
second.
216 Chapter 5 ■ C# Text Manipulation and File I/O

5.4 Searching, Modifying, and


Encoding a String’s Content
This section describes string methods that are used to perform diverse but familiar
tasks such as locating a substring within a string, changing the case of a string, replac-
ing or removing text, splitting a string into delimited substrings, and trimming lead-
ing and trailing spaces.

Searching the Contents of a String


A string is an implicit zero-based array of chars that can be searched using the array
syntax string[n], where n is a character position within the string. For locating a
substring of one or more characters in a string, the string class offers the IndexOf
and IndexOfAny methods. Table 5-2 summarizes these.

Table 5-2 Ways to Examine Characters Within a String

String Member Description

[ n ] Indexes a 16-bit character located at position n within


a string.
int ndx= 0;
while (ndx < poem.Length)
{
Console.Write(poem[ndx]); //Kubla Khan
ndx += 1;
}

IndexOf/LastIndexOf Returns the index of the first/last occurrence of a spec-


ified string within an instance. Returns –1 if no match.
(string, [int start],
[int count]) string poem = "Kubla Khan";
int n = poem.IndexOf("la"); // 3
n = poem.IndexOf('K'); // 0
count. Number of chars to n = poem.IndexOf('K',4); // 6
examine.

IndexOfAny/LastIndexOfAny Returns the index of the first/last character in an array


of Unicode characters.
string poem = "Kubla Khan";
char[] vowels = new char[5]
{'a', 'e', 'i', 'o', 'u'};
n = poem.IndexOfAny(vowels); // 1
n = poem.LastIndexOfAny(vowels); // 8
n = poem.IndexOfAny(vowels,2); // 4
5.4 Searching, Modifying, and Encoding a String’s Content 217

Searching a String That Contains Surrogates


All of these techniques assume that a string consists of a sequence of 16-bit charac-
ters. Suppose, however, that your application must work with a Far Eastern character
set of 32-bit characters. These are represented in storage as a surrogate pair consist-
ing of a high and low 16-bit value. Clearly, this presents a problem for an expression
such as poem[ndx], which would return only half of a surrogate pair.
For applications that must work with surrogates, .NET provides the StringInfo
class that treats all characters as text elements and can automatically detect whether a
character is 16 bits or a surrogate. Its most important member is the GetTextEle-
mentEnumerator method, which returns an enumerator that can be used to iterate
through text elements in a string.

TextElementEnumerator tEnum =
StringInfo.GetTextElementEnumerator(poem) ;
while (tEnum.MoveNext()) // Step through the string
{
Console.WriteLine(tEnum.Current); // Print current char
}

Recall from the discussion of enumerators in Chapter 4, “Working with Objects in


C#,” that MoveNext() and Current are members implemented by all enumerators.

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.

Table 5-3 Methods for Manipulating and Transforming Strings

Tag Description

Insert (int, string) Inserts a string at the specified position.


string mariner = "and he stoppeth three";
string verse = mariner.Insert(
mariner.IndexOf(" three")," one of");
// verse --> "and he stoppeth one of three"

PadRight/PadLeft Pads a string with a given character until it is a specified


width. If no character is specified, whitespace is used.
string rem = "and so on";
rem = rem.PadRight(rem.Length+3,'.');
// rem --> "and so on..."
218 Chapter 5 ■ C# Text Manipulation and File I/O

Table 5-3 Methods for Manipulating and Transforming Strings (continued)

Tag Description

Remove(p , n) Removes n characters beginning at position p.


string verse = "It is an Ancient Mariner";
string newverse = (verse.Remove(0,9));
// newverse --> "Ancient Mariner"

Replace (A , B) Replaces all occurrences of A with B, where A and B are


chars or strings.
string aString = "nap ace sap path";
string iString = aString.Replace('a','i');
// iString --> "nip ice sip pith"

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

ToUpper() Returns an upper- or lowercase copy of the string.


ToUpper(CultureInfo)
ToLower() string poem2="Kubla Khan";
ToLower(CultureInfo) poem2= poem2.ToUpper(
CultureInfo.InvariantCulture);

Trim() Removes all leading and trailing whitespaces. If a char


Trim(params char[]) array is provided, all leading and trailing characters in the
array are removed.
string name = " Samuel Coleridge";
name = name.Trim(); // "Samuel Coleridge"

TrimEnd (params char[]) Removes all leading or trailing characters specified in a


TrimStart(params char[]) char array. If null is specified, whitespaces are removed.
string name = " Samuel Coleridge";
trimName = name.TrimStart(null);
shortname = name.TrimEnd('e','g','i');
// shortName --> "Samuel Colerid"

Substring(n) Extracts the string beginning at a specified position (n) and


Substring(n, l) of length l, if specified.
string title="Kubla Khan";
Console.WriteLine(title.Substring(2,3));
//bla
5.4 Searching, Modifying, and Encoding a String’s Content 219

Table 5-3 Methods for Manipulating and Transforming Strings (continued)

Tag Description

ToCharArray() Extracts characters from a string and places in an array of


ToCharArray(n, l) Unicode characters.
string myVowels = "aeiou";
char[] vowelArr;
vowelArr = myVowels.ToCharArray();
Console.WriteLine(vowelArr[1]); // "e"

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:

• UTF-8. Each character is encoded as a sequence of 1 to 4 bytes,


based on its underlying value. ASCII compatible characters are stored
in 1 byte; characters between 0x0080 and 0x07ff are stored in 2 bytes;
and characters having a value greater than or equal to 0x0800 are con-
verted to 3 bytes. Surrogates are written as 4 bytes. UTF-8 (which
stands for UCS Transformation Format, 8-bit form) is usually the
default for .NET classes when no encoding is specified.
• UTF-16. Each character is encoded as 2 bytes (except surrogates),
which is how characters are represented internally in .NET. This is
also referred to as Unicode encoding.
• ASCII. Encodes each character as an 8-bit ASCII character. This
should be used when all characters are in the ASCII range (0x00 to
0x7F). Attempting to encode a character outside of the ACII range
yields whatever value is in the character’s low byte.
220 Chapter 5 ■ C# Text Manipulation and File I/O

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.

string text= "In Xanadu did Kubla Khan";


Encoding UTF8Encoder = Encoding.UTF8;
byte[] textChars = UTF8Encoder.GetBytes(text);
Console.WriteLine(textChars.Length); // 24
// Store using UTF-16
textChars = Encoding.Unicode.GetBytes(text);
Console.WriteLine(textChars.Length); // 48
// Treat characters as two bytes
string decodedText = Encoding.Unicode.GetString(textChars);
Console.WriteLine(decodedText); // "In Xanadu did ... "

You can also instantiate the encoding objects directly. In this example, the UTF-8
object could be created with

UTF8Encoding UTF8Encoder = new UTF8Encoding();

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

Listing 5-1 Introduction to StringBuilder


using System;
using System.Text;
public class MyApp
{
static void Main()
{
// Create comma delimited string with quotes around names
string namesF = "Jan Donna Kim ";
string namesM = "Rob James";
StringBuilder sbCSV = new StringBuilder();
sbCSV.Append(namesF).Append(namesM);
sbCSV.Replace(" ","','");
// Insert quote at beginning and end of string
sbCSV.Insert(0,"'").Append("'");
string csv = sbCSV.ToString();
// csv = 'Jan','Donna','Kim','Rob','James'
}
}

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


Constructors for the StringBuilder class accept an initial string value as well as
integer values that specify the initial space allocated to the buffer (in characters) and
the maximum space allowed.

// 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);

The idea behind StringBuilder is to use it as a buffer in which string operations


are performed. Here is a sample of how its Append, Insert, Replace, and Remove
methods work:

int i = 4;
char[] ch = {'w','h','i','t','e'};
string myColor = " orange";
222 Chapter 5 ■ C# Text Manipulation and File I/O

StringBuilder sb = new StringBuilder("red blue green");


sb.Insert(0, ch); // whitered blue green
sb.Insert(5," "); // white red blue green
sb.Insert(0,i); // 4white red blue green
sb.Remove(1,5); // 4 red blue green
sb.Append(myColor); // 4 red blue green orange
sb.Replace("blue","violet"); // 4 red violet green orange
string colors = sb.ToString();

StringBuilder Versus String Concatenation


Listing 5-2 tests the performance of StringBuilder versus the concatenation oper-
ator. The first part of this program uses the + operator to concatenate the letter a to a
string in each of a loop’s 50,000 iterations. The second half does the same, but uses
the StringBuilder.Append method. The Environment.TickCount provides the
beginning and ending time in milliseconds.

Comparison of StringBuilder and Regular


Listing 5-2
Concatenation
using System;
using System.Text;
public class MyApp
{
static void Main()
{
Console.WriteLine("String routine");
string a = "a";
string str = string.Empty;
int istart, istop;
istart = Environment.TickCount;
Console.WriteLine("Start: "+istart);
// Use regular C# concatenation operator
for(int i=0; i<50000; i++)
{
str += a;
}
istop = Environment.TickCount;
Console.WriteLine("Stop: "+istop);
Console.WriteLine("Difference: " + (istop-istart));
// Perform concatenation with StringBuilder
5.6 Formatting Numeric and DateTime Values 223

Comparison of StringBuilder and Regular


Listing 5-2
Concatenation (continued)
Console.WriteLine("StringBuilder routine");
StringBuilder builder = new StringBuilder();
istart = Environment.TickCount;
Console.WriteLine("Start: "+istart);
for(int i=0; i<50000; i++)
{
builder.Append(a);
}
istop = Environment.TickCount;
str = builder.ToString();
Console.WriteLine("Stop: "+Environment.TickCount);
Console.WriteLine("Difference: "+ (istop-istart));
}
}

Executing this program results in the following output:

String routine
Start: 1422091687
Stop: 1422100046
Difference: 9359
StringBuilder routine
Start: 1422100046
Stop: 1422100062
Difference: 16

The results clearly indicate the improved performance StringBuilder provides:


The standard concatenation requires 9,359 milliseconds versus 16 milliseconds for
StringBuilder. When tested with loops of 1,000 iterations, StringBuilder shows
no significant advantage. Unless your application involves extensive text manipula-
tion, the standard concatenation operator should be used.

5.6 Formatting Numeric and


DateTime Values
The String.Format method is the primary means of formatting date and numeric
data for display. It accepts a string composed of text and embedded format items fol-
lowed by one or more data arguments. Each format item references a data argument
224 Chapter 5 ■ C# Text Manipulation and File I/O

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:

String s= String.Format("The square root of {0} is {1}.",64,8);


// output: The square root of 64 is 8.

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.

Constructing a Format Item


Figure 5-3 breaks down a String.Format example into its basic elements. The
most interesting of these is the format item, which defines the way data is displayed.

Format args
f = String.Format("There are {0} Students with {1,2:P} passing",20,.75);

format item: {1 , 2 :P}

{index [,alignment] [:format string] }

f = "There are 20 Students with 75.00 % passing"

Figure 5-3 String.Format example

As we can see, each format item consists of an index and an optional alignment
and format string. All are enclosed in brace characters:

1. The index is a zero-based integer that indicates the argument to which


it is to be applied. The index can be repeated to refer to the same
argument more than once.
2. The optional alignment is an integer that indicates the minimum
width of the area that contains the formatted value. If alignment value
5.6 Formatting Numeric and DateTime Values 225

is positive, the argument value is right justified; if the value is negative,


it is left justified.
3. The optional format string contains the formatting codes to be applied
to the argument value. If it is not specified, the output of the argu-
ment’s ToString method is used. .NET provides several standard
format codes to be used with numbers and dates as well as codes that
are used to create custom format strings.

Formatting Numeric Values


Nine characters, or format specifiers, are available to format numbers into currency,
scientific, hexadecimal, and other representations. Each character can have an inte-
ger appended to it that specifies a precision particular to that format—usually this
indicates the number of decimal places. C# recognizes the standard format specifi-
ers2 shown in Table 5-4.

Table 5-4 Formatting Numeric Values with Standard Numeric Format Strings

Format
Specifier Description Pattern Output

C or c Currency. Number is represented {0:C2}, 1458.75 $ 1,458.75


as a currency amount. The preci-
sion specifies the number of deci-
mal places.

D or d Decimal. Applies to integral val- {0:D5}, 455 00455


ues. The precision indicates the {0:D5}, -455 -00455
total number of spaces the num-
ber occupies; is padded with zeros
on left if necessary.

E or e Scientific. The number is con- {0,10:E2}, 3298.78 3.30+E003


verted to a scientific notation: {0,10:E4}, -54783.4 -5.4783+E004
ddddE+nnn. The precision speci-
fies the number of digits after the
decimal point.

F or f Fixed Point. The number is con- {0,10:F0}, 162.57 162


verted to format of: ddd.ddd. The {0,10:F2}, 8162.57 8162.57
precision indicates the number of
decimal places.

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

G or g General. The number is converted {0,10:G}, .0000099 9.9E-06


to fixed point or scientific notation {0,10:G2}, 455.89 4.6E+02
based on the precision and type of {0,10:G3}, 455.89 456
number. Scientific is used if the {0,10:G}, 783229.34 783229.34
exponent is greater than or equal
to the specified precision or less
than –4.

N or n Number. Converts to a string that {0,10:N}, 1045.78 1,045.78


uses commas as thousands separa- {0,10:N1}, 45.98 45.9
tors. The precision specifies the
number of decimal places.

P or p Percent. Number is multiplied by {0,10:P}, 0.78 78.00 %


100 and presented as percent with {0,10:P3}, 0.7865 78.650 %
number of decimal places speci-
fied by precision.

R or r Round-trip. Converts to a string {0,10:R}, 1.62736 1.62736


that retains all decimal place accu-
racy. Then number to be con-
verted must be floating point.

X or x Hexadecimal. Converts the num- {0,10:X}, 25 19


ber to its hex representation. The {0,10:X4}, 25 0019
precision indicates the minimum {0,10:x4}, 31 001f
number of digits shown. Number
is padded with zeros if needed.

The patterns in this table can also be used directly with Console.Write and
Console.WriteLine:

Console.WriteLine("The Hex value of {0} is {0:X} ",31); //1F

The format specifiers can be used alone to enhance output from the ToString
method:

decimal pct = .758M;


Console.Write("The percent is "+pct.ToString("P2")); // 75.80 %
5.6 Formatting Numeric and DateTime Values 227

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

decimal dVal = 2145.88M; // decimal values require M suffix


string myFormat;
myFormat = dVal.ToString("#####"); // 2146
myFormat = dVal.ToString("#,###.00"); // 2,145.88
myFormat = String.Format("Value is {0:#,###.00;
(#,###.00)}",-4567);
// semicolon specifies alternate formats. (4,567.00)
myFormat = String.Format("Value is {0:$#,###.00}", 4567);
// $4,567.00
Console.WriteLine("{0:##.00%}",.18); // 18.00 %

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.

Formatting Dates and Time


Date formatting requires a DateTime object. As with numbers, this object has its
own set of standard format specifiers. Table 5-5 summarizes these.

Table 5-5 Formatting Dates with Standard Characters

Format
Specifier Description Example—English Example—German

d Short date pattern 1/19/2004 19.1.2004

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

g General date/time 1/19/2004 4:05 PM 19/1/2004 16:05


pattern (short time)

G General date/time 1/19/2004 4:05:20 PM 19/1/2004 16:05:20


pattern (long time)
228 Chapter 5 ■ C# Text Manipulation and File I/O

Table 5-5 Formatting Dates with Standard Characters (continued)

Format
Specifier Description Example—English Example—German

M, m Month day pattern January 19 19 Januar

Y, y Year month pattern January, 2004 Januar, 2004

t Short time pattern 4:05 PM 16:05

T Long time pattern 4:05:20 PM 16:05:20

s Universal Sortable 2004-01-19T16:05:20 2004-01-19T16:05:20


Date-Time pattern.
Conforms to ISO
8601. Uses local time.

u Universal Sortable 2004-01-19 16:05:20Z 2004-01-19 16:05:20Z


Date-Time pattern

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.

DateTime curDate = DateTime.Now; // Get Current Date


Console.Writeline("Date: {0:d} ", curDate); // 1/19/2004
// f: --> Monday, January 19, 2004 5:05 PM
Console.Writeline("Date: {0:f} ", curDate);
// g: --> 1/19/2004 5:05 PM
Console.Writeline("Date: {0:g} ", curDate);

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.

Table 5-6 Character Patterns for Custom Date Formatting

Format Description Example

d Day of month. No leading zero. 5

dd Day of month. Always has two digits. 05


5.6 Formatting Numeric and DateTime Values 229

Table 5-6 Character Patterns for Custom Date Formatting (continued)

Format Description Example

ddd Day of week with three-character abbreviation. Mon

dddd Day of week full name. Monday

M Month number. No leading zero. 1

MM Month number with leading zero if needed. 01

MMM Month name with three-character abbreviation. Jan

MMMM Full name of month. January

y Year. Last one or two digits. 5

yy Year. Last one or two digits with leading zero if needed. 05

yyyy Four-digit year. 2004

HH Hour in 24-hour format. 15

mm Minutes with leading zero if needed. 20

Here are some examples of custom date formats:

DateTime curDate = DateTime.Now;


f = String.Format("{0:dddd} {0:MMM} {0:dd}", curDate);
// output: Monday Jan 19

f = currDate.ToString("dd MMM yyyy")


// output: 19 Jan 2004

// The standard short date format (d) is equivalent to this:


Console.WriteLine(currDate.ToString("M/d/yyyy")); // 1/19/2004
Console.WriteLine(currDate.ToString("d")); // 1/19/2004

CultureInfo ci = new CultureInfo("de-DE"); // German


f = currDate.ToString("dd-MMMM-yyyy HH:mm", ci)
// output: 19-Januar-2004 23:07

ToString is recommended over String.Format for custom date formatting. It


has a more convenient syntax for embedding blanks and special separators between
the date elements; in addition, its second parameter is a culture indicator that makes
it easy to test different cultures.
230 Chapter 5 ■ C# Text Manipulation and File I/O

Dates and Culture


Dates are represented differently throughout the world, and the ability to add cul-
ture as a determinant in formatting dates shifts the burden to .NET from the devel-
oper. For example, if the culture on your system is German, dates are automatically
formatted to reflect a European format: the day precedes the month; the day, month,
and year are separated by periods (.) rather than slashes (/); and the phrase Monday,
January 19 becomes Montag, 19. Januar. Here is an example that uses
ToString with a German CultureInfo parameter:

CultureInfo ci = new CultureInfo("de-DE"); // German


Console.WriteLine(curDate.ToString("D",ci));
// output ---> Montag, 19. Januar 2004
Console.WriteLine(curDate.ToString("dddd",ci)); // -->Montag

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 and DateTimeFormatInfo Classes


These two classes govern how the previously described format patterns are applied to
dates and numbers. For example, the NumberFormatInfo class includes properties
that specify the character to be used as a currency symbol, the character to be used as
a decimal separator, and the number of decimal digits to use when displaying a cur-
rency value. Similarly, DateTimeFormatInfo defines properties that correspond to
virtually all of the standard format specifiers for dates. One example is the
FullDateTimePattern property that defines the pattern returned when the char-
acter F is used to format a date.
NumberFormatInfo and DateTimeFormatInfo are associated with specific cul-
tures, and their properties are the means for creating the unique formats required by
different cultures. .NET provides a predefined set of property values for each cul-
ture, but they can be overridden.
Their properties are accessed in different ways depending on whether the current
or non-current culture is being referenced (current culture is the culture associated
with the current thread). The following statements reference the current culture:

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

CultureInfo ci = new CultureInfo("de-DE");


string f = ci.NumberFormat.CurrencySymbol;

NumberFormatInfo and DateTimeFormatInfo properties associated with a


non-current culture can be changed; those associated with the current thread are
read-only. Listing 5-3 offers a sampling of how to work with these classes.

Listing 5-3 Using NumberFormatInfo and DateTimeFormatInfo


using System
using System.Globalization
Class MyApp
{
// NumberFormatInfo
string curSym = NumberFormatInfo.CurrentInfo.CurrencySymbol;
int dd = NumberFormatInfo.CurrentInfo.CurrencyDecimalDigits;
int pdd = NumberFormatInfo.CurrentInfo.PercentDecimalDigits;
// --> curSym = "$" dd = 2 pdd = 2
// DateTimeFormatInfo
string ldp= DateTimeFormatInfo.CurrentInfo.LongDatePattern;
// --> ldp = "dddd, MMMM, dd, yyyy"
string enDay = DateTimeFormatInfo.CurrentInfo.DayNames[1];
string month = DateTimeFormatInfo.CurrentInfo.MonthNames[1];
CultureInfo ci = new CultureInfo("de-DE");
string deDay = ci.DateTimeFormat.DayNames[1];
// --> enDay = "Monday" month = February deDay = "Montag"
// Change the default number of decimal places
// in a percentage
decimal passRate = .840M;
Console.Write(passRate.ToString("p",ci)); // 84,00%
ci.NumberFormat.PercentDecimalDigits = 1;
Console.Write(passRate.ToString("p",ci)); // 84,0%
}

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

5.7 Regular Expressions


The use of strings and expressions to perform pattern matching dates from the earli-
est programming languages. In the mid-1960s SNOBOL was designed for the
express purpose of text and string manipulation. It influenced the subsequent devel-
opment of the grep tool in the Unix environment that makes extensive use of regular
expressions. Those who have worked with grep or Perl or other scripting languages
will recognize the similarity in the .NET implementation of regular expressions.
Pattern matching is based on the simple concept of applying a special pattern
string to some text source in order to match an instance or instances of that pattern
within the text. The pattern applied against the text is referred to as a regular expres-
sion, or regex, for short.
Entire books have been devoted to the topic of regular expressions. This section is
intended to provide the essential knowledge required to get you started using regular
expressions in the .NET world. The focus is on using the Regex class, and creating
regular expressions from the set of characters and symbols available for that purpose.

The Regex Class


You can think of the Regex class as the engine that evaluates regular expressions and
applies them to target strings. It provides both static and instance methods that use
regexes for text searching, extraction, and replacement. The Regex class and all
related classes are found in the System.Text.RegularExpressions namespace.

Syntax:

Regex( string pattern )


Regex( string pattern, RegexOptions)

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:

Regex r1 = new Regex(" "); // Regular expression is a blank


String words[] = r1.Split("red blue orange yellow");
// Regular expression matches upper- or lowercase "at"
Regex r2 = new Regex("at", RegexOptions.IgnoreCase);
5.7 Regular Expressions 233

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

If you plan to use a regular expression repeatedly, it is more efficient to


create a Regex object. When the object is created, it compiles the
expression into a form that can be used as long as the object exists. In
contrast, static methods recompile the expression each time they are
used.

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.

string searchStr = "He went that a way";


Regex myRegex = new Regex("at");
// instance methods
bool match = myRegex.IsMatch(searchStr); // true
// Begin search at position 12 in the string
match = myRegex.IsMatch(searchStr,12); // false
// Static Methods – both return true
match = Regex.IsMatch(searchStr,"at");
match = Regex.IsMatch(searchStr,"AT",RegexOptions.IgnoreCase);

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:

static Replace (string input, string pattern, string replacement


[,RegexOptions])

Replace(string input, string replacement)


Replace(string input, string replacement, int count)
Replace(string input, string replacement, int count, int startat)

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:

String[] Split(string input)


String[] Split(string input, int count)
String[] Split(string input, int count, int startat)
Static String[] Split(string input, string pattern)

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.

string impressionists = "Manet,Monet, Degas, Pissarro,Sisley";


// Regex to match a comma followed by 0 or more spaces
string patt = @",[ ]*";
// Static method
string[] artists = Regex.Split(impressionists, patt);
// Instance method is used to accept maximum of four matches
Regex myRegex = new Regex(patt);
string[] artists4 = myRegex.Split(impressionists, 4);
foreach (string master in artists4)
Console.Write(master);
// Output --> "Manet" "Monet" "Degas" "Pissarro,Sisley"

Match() and Matches()


These related methods search an input string for a match to the regular expression.
Match() returns a single Match object and Matches() returns the object Match-
Collection, a collection of all matches.

Syntax:

Match Match(string input)


Match Match(string input, int startat)
Match Match(string input, int startat, int numchars)
static Match(string input, string pattern, [RegexOptions])

The Matches method has similar overloads but returns a MatchCollection


object.
Match and Matches are the most useful Regex methods. The Match object they
return is rich in properties that expose the matched string, its length, and its location
within the target string. It also includes a Groups property that allows the matched
string to be further broken down into matching substrings. Table 5-7 shows selected
members of the Match class.
The following code demonstrates the use of these class members. Note that the
dot (.) in the regular expression functions as a wildcard character that matches any
single character.

string verse = "In Xanadu did Kubla Khan";


string patt = ".an..."; // "." matches any character
Match verseMatch = Regex.Match(verse, patt);
Console.WriteLine(verseMatch.Value); // Xanadu
236 Chapter 5 ■ C# Text Manipulation and File I/O

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.

Table 5-7 Selected Members of the Match Class

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.

Length Length of the matched string.

Success True or False depending on whether a match was found.

Value Returns the matching substring.

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:

string verse = "In Xanadu did Kubla Khan";


String newpatt = "K(..)";
foreach (Match kMatch in Regex.Matches(verse, newpatt))
5.7 Regular Expressions 237

Console.Write(kMatch.Value); // -->Kub -->Kha


// Could also create explicit collection and work with it.
MatchCollection mc = Regex.Matches(verse, newpatt);
Console.WriteLine(mc.Count); // 2

Creating Regular Expressions


The examples used to illustrate the Regex methods have employed only rudimentary
regular expressions. Now, let’s explore how to create regular expressions that are gen-
uinely useful. If you are new to the subject, you will discover that designing Regex
patterns tends to be a trial-and-error process; and the endeavor can yield a solution
of simple elegance—or maddening complexity. Fortunately, almost all of the com-
monly used patterns can be found on one of the Web sites that maintain a searchable
library of Regex patterns (www.regexlib.com is one such site).
A regular expression can be broken down into four different types of metacharac-
ters that have their own role in the matching process:

• Matching characters. These match a specific type of character—for


example, \d matches any digit from 0 to 9.
• Repetition characters. Used to prevent having to repeat a matching
character or item—for example, \d{3}can be used instead of \d\d\d
to match three digits.
• Positional characters. Designate the location in the target string
where a match must occur—for example, ^\d{3} requires that the
match occur at the beginning of the string.
• Escape sequences. Use the backslash (\) in front of characters that
otherwise have special meaning—for example, \} permits the right
brace to be matched.

Table 5-8 summarizes the most frequently used patterns.

Table 5-8 Regular Expression Patterns

Pattern Matching Criterion Example

+ Match one or more occurrences of the to+ matches too and tooo. It
previous item. does not match t.

* Match zero or more occurrences of the to* matches t or too or tooo.


previous item.

? Match zero or one occurrence of the te?n matches ten or tn. It


previous item. Performs “non-greedy” does not match teen.
matching.
238 Chapter 5 ■ C# Text Manipulation and File I/O

Table 5-8 Regular Expression Patterns (continued)

Pattern Matching Criterion Example

{n} Match exactly n occurrences of the te{2}n matches teen. It does


previous character. not match ten or teeen.

{n,} Match at least n occurrences of the te{1,}n matches ten and


previous character. teen. It does not match tn.

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

\d \D Match any digit (\d) or non-digit (\D). \d\d matches 55.


This is equivalent to [0-9] or [^0-9], \D\D matches xx.
respectively.

\w \W Match any word plus underscore (\w) or \w\w\w\w matches A_19 .


non-word (\W) character. \w is equiva- \W\W\W matches ($).
lent to [a-zA-Z0-9_]. \W is equivalent to
[^a-zA-Z0-9_] .

\n \r \t Match newline, carriage return, tab, ver- N/A


\v \f tical tab, or form feed, respectively.

\s \S Match any whitespace (\s) or \w\s\w\s\w matches A B C.


non-whitespace (\S). A whitespace is
usually a space or tab character.

. (dot) Matches any single character. Does not a.c matches abc.
match a newline. It does not match abcc.

| Logical OR. "in|en" matches enquiry.

[. . . ] Match any single character between the [aeiou] matches u. [\d\D]


brackets. Hyphens may be used to indi- matches a single digit or
cate a range. non-digit.

[^. . .] All characters except those in the [^aeiou] matches x.


brackets.
5.7 Regular Expressions 239

A Pattern Matching Example


Let’s apply these character patterns to create a regular expression that matches a
Social Security Number (SSN):

bool iMatch = Regex.IsMatch("245-09-8444",


@"\d\d\d-\d\d-\d\d\d\d");

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:

bool iMatch = Regex.IsMatch("245-09-8444",


@"\d{3}-\d{2}-\d{4}");

Another consideration in matching the Social Security Number may be to restrict


where it exists in the text. You may want to ensure it is on a line by itself, or at the
beginning or end of a line. This requires using position characters at the beginning or
end of the matching sequence.
Let’s alter the pattern so that it matches only if the Social Security Number exists
by itself on the line. To do this, we need two characters: one to ensure the match is at
the beginning of the line, and one to ensure that it is also at the end. According to
Table 5-9, ^ and $ can be placed around the expression to meet these criteria. The
new string is

@"^\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.

Table 5-9 Characters That Specify Where a Match Must Occur

Position Character Description

^ Following pattern must be at the start of a string or line.


$ Preceding pattern must be at end of a string or line.
\A Preceding pattern must be at the start of a string.
\b \B Move to a word boundary (\b), where a word character and
non-word character meet, or a non-word boundary.
\z \Z Pattern must be at the end of a string (\z) or at the end of a string
before a newline.
240 Chapter 5 ■ C# Text Manipulation and File I/O

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:

string ssn = "245-09-8444";


string ssnPatt = @"^(\d{3})-(\d{2})-(\d{4})$";
Match ssnMatch = Regex.Match(ssn, ssnPatt);
if (ssnMatch.Success){
Console.WriteLine(ssnMatch.Value); // 245-09-8444
Console.WriteLine(ssnMatch.Groups.Count); // 4
// Count is 4 since Groups[0] is set to entire SSN
Console.Write(ssnMatch.Groups[1]); // 245
Console.Write(ssnMatch.Groups[2]); // 09
Console.Write(ssnMatch.Groups[3]); // 8444
}

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})$"

match at end of string


repeat \d 4 times
match any single digit
match from beginning of string
Figure 5-4 Regular expression

Working with Groups


As we saw in the preceding example, the text resulting from a match can be automat-
ically partitioned into substrings or groups by enclosing sections of the regular
expression in parentheses. The text that matches the enclosed pattern becomes a
member of the Match.Groups[] collection. This collection can be indexed as a
zero-based array: the 0 element is the entire match, element 1 is the first group, ele-
ment 2 the second, and so on.
5.7 Regular Expressions 241

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

string txt ="Monday Hi:88 Lo:56 Tuesday Hi:91 Lo:61";

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:

string speech = "Four score and and seven years";


patt = @"(\b[a-zA-Z]+\b)\s\1"; // Match repeated words
MatchCollection mc = Regex.Matches(speech, patt);
242 Chapter 5 ■ C# Text Manipulation and File I/O

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

and and Matches a word bounded on each side by a word


@"(\b[a-zA-Z]+\b)\s boundary (\b) and followed by a whitespace.

and and The backreference indicator. Any group can be ref-


\1 erenced with a slash (\) followed by the group num-
ber. The effect is to insert the group’s matched value
into the expression.

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

Examples of Using Regular Expressions


This section closes with a quick look at some patterns that can be used to handle
common pattern matching challenges. Two things should be clear from these exam-
ples: There are virtually unlimited ways to create expressions to solve a single prob-
lem, and many pattern matching problems involve nuances that are not immediately
obvious.

Using Replace to Reverse Words


string userName = "Claudel, Camille";
userName = Regex.Replace( userName, @"(\w+),\s*(\w+)", "$2 $1" );
Console.WriteLine(userName); // Camille Claudel

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.

string hex = "00AA001CFF0C";


string hexPatt = @"^(?<hex4>[a-fA-F\d]{4})*";
Match hexMatch = Regex.Match(hex,hexPatt);
Console.WriteLine(hexMatch.Value); // --> 00AA001CFFOC
CaptureCollection cc = hexMatch.Groups["hex4"].Captures;
foreach (Capture c in cc)
Console.Write(c.Value); // --> 00AA 001C FF0C

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

5.8 System.IO: Classes to Read and


Write Streams of Data
The System.IO namespace contains the primary classes used to move and process
streams of data. The data source may be in the form of text strings, as discussed in
this chapter, or raw bytes of data coming from a network or device on an I/O port.
Classes derived from the Stream class work with raw bytes; those derived from the
TextReader and TextWriter classes operate with characters and text strings (see
Figure 5-6). We’ll begin the discussion with the Stream class and look at how its
derived classes are used to manipulate byte streams of data. Then, we’ll examine how
data in a more structured text format is handled using the TextReader and Text-
Writer classes.

System.Object

Stream <<abstract>> TextWriter <<abstract>>

Buffered Stream StreamWriter

FileStream StringWriter

MemoryStream TextReader <<abstract>>

Compression.GZipStream StreamReader

StringReader

Figure 5-6 Selected System.IO classes

The Stream Class


This class defines the generic members for working with raw byte streams. Its pur-
pose is to abstract data into a stream of bytes independent of any underlying data
devices. This frees the programmer to focus on the data stream rather than device
characteristics. The class members support three fundamental areas of operation:
reading, writing, and seeking (identifying the current byte position within a stream).
5.8 System.IO: Classes to Read and Write Streams of Data 245

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

Table 5-10 Selected Stream Members

Member Description

CanRead Indicates whether the stream supports read-


CanSeek ing, seeking, or writing.
CanWrite

Length Length of stream in bytes; returns long type.

Position Gets or sets the position within the current


stream; has long type.

Close() Closes the current stream and releases


resources associated with it.

Flush() Flushes data in buffers to the underlying


device—for example, a file.

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.

SetLength() Sets the length of the current stream. It can be


used to extend or truncate a stream.

Seek() Sets the position within the current stream.

Write(byte array, offset, count) Writes a sequence of bytes (Write) or one


WriteByte() byte (WriteByte) to the current stream.
Neither has a return value.

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);
}

As this example illustrates, a stream is essentially a byte array with an internal


pointer that marks a current location in the stream. The ReadByte and WriteByte
methods process stream bytes in sequence. The Position property moves the inter-
nal pointer to any position in the stream. By opening the FileStream for Read-
Write, the program can intermix reading and writing without closing the file.

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.

FileStream(file name, FileMode, FileAccess, FileShare)


Append Read None
Create ReadWrite Read
CreateNew Write ReadWrite
Open Write
OpenOrCreate
Truncate Default

Figure 5-7 Options for FileStream constructors


5.8 System.IO: Classes to Read and Write Streams of Data 247

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.

Table 5-11 FileMode Enumeration Values

Value Description

Append Opens an existing file or creates a new one. Writing begins at the end
of the file.

Create Creates a new file. An existing file is overwritten.

CreateNew Creates a new file. An exception is thrown if the file already exists.

Open Opens an existing file.

OpenOrCreate Opens a file if it exists; otherwise, creates a new one.

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:

FileStream fsIn = new FileStream(@"c:\manet.bmp",


FileMode.Open, FileAccess.Read);
FileStream fsOut = new FileStream(@"c:\manetcopy.bmp",
FileMode.OpenOrCreate, FileAccess.Write);
MemoryStream ms = new MemoryStream();
// Input image byte-by-byte and store in memory stream
int imgByte;
while ((imgByte = fsIn.ReadByte())!=-1){
ms.WriteByte((byte)imgByte);
}
248 Chapter 5 ■ C# Text Manipulation and File I/O

ms.WriteTo(fsOut); // Copy image from memory to disk


byte[] imgArray = ms.ToArray(); // Convert to array of bytes
fsIn.Close();
fsOut.Close();
ms.Close();

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.

private void SaveStream() {


Stream fsOut1 = new FileStream(@"c:\captured.txt",
FileMode.OpenOrCreate, FileAccess.Write);
BufferedStream fileBuffer = new BufferedStream(fsOut1);
byte[] buff; // Array to hold bytes written to buffer
bool readMore=true;
while(readMore) {
buff = FillBytes(); // Get array of bytes
for (int j = 0;j<buff[16];j++){
fileBuffer.WriteByte(buff[j]); // Store bytes in buffer
}
if(buff[16]< 16) readMore=false; // Indicates no more data
}
fileBuffer.Close(); // Flushes all remaining buffer content
fsOut1.Close(); // Must close after bufferedstream
}
// Method to simulate I/O device receiving data
private static byte[] FillBytes() {
5.8 System.IO: Classes to Read and Write Streams of Data 249

Random rand = new Random();


byte[] r = new Byte[17];
// Store random numbers to return in array
for (int j=0;j<16;j++) {
r[j]= (byte) rand.Next();
if(r[j]==171) // Arbitrary end of stream value
{
r[16]=(byte)(j); // Number of bytes in array
return r;
}
}
System.Threading.Thread.Sleep(500); // Delay 500ms
return r;
}

Using StreamReader and StreamWriter


to Read and Write Lines of Text
Unlike the Stream derived classes, StreamWriter and StreamReader are
designed to work with text rather than raw bytes. The abstract TextWriter and
TextReader classes from which they derive define methods for reading and writing
text as lines of characters. Keep in mind that these methods rely on a FileStream
object underneath to perform the actual data transfer.

Writing to a Text File


StreamWriter writes text using its Write and WriteLine methods. Note their
differences:

• WriteLine works only with strings and automatically appends a new-


line (carriage return\linefeed).
• Write does not append a newline character and can write strings as
well as the textual representation of any basic data type (int32,
single, and so on) to the text stream.

The StreamWriter object is created using one of several constructors:

Syntax (partial list):

public StreamWriter(string path)


public StreamWriter(stream s)
public StreamWriter(string path, bool append)
public StreamWriter(string path, bool append, Encoding encoding)
250 Chapter 5 ■ C# Text Manipulation and File I/O

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:

string filePath = @"c:\cup.txt";


// Could use: StreamWriter sw = new StreamWriter(filePath);
// Use FileStream to create StreamWriter
FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate,
FileAccess.ReadWrite);
StreamWriter sw2 = new StreamWriter(fs);
// Now that it is created, write to the file
sw2.WriteLine("The world is a cup");
sw2.WriteLine("brimming\nwith water.");
sw2.Close(); // Free resources

Reading from a Text File


A StreamReader object is used to read text from a file. Much like StreamWriter,
an instance of it can be created from an underlying Stream object, and it can include
an encoding specification parameter. When it is created, it has several methods for
reading and viewing character data (see Table 5-12).

Table 5-12 Selected StreamReader Methods

Member Description

Peek() Returns the next available character without moving the


position of the reader. Returns an int value of the char-
acter or –1 if none exists.
Read() Reads next character (Read()) from a stream or reads
Read(char buff, int ndx, next count characters into a character array beginning
int count) at ndx.
ReadLine() Returns a string comprising one line of text.
ReadToEnd() Reads all characters from the current position to the end
of the TextReader. Useful for downloading a small text
file over a network stream.
5.8 System.IO: Classes to Read and Write Streams of Data 251

This code creates a StreamReader object by passing an explicit FileStream


object to the constructor. The FileStream is used later to reposition the reader to
the beginning of the file.

String path= @"c:\cup.txt";


if(File.Exists(path))
{
FileStream fs = new FileStream(path,
FileMode.OpenOrCreate, FileAccess.ReadWrite);
StreamReader reader = new StreamReader(fs);
// or StreamReader reader = new StreamReader(path);
// (1) Read first line
string line = reader.ReadLine();
// (2) Read four bytes on next line
char[] buff = new char[4];
int count = reader.Read(buff,0,buff.Length);
// (3) Read to end of file
string cup = reader.ReadToEnd();
// (4) Reposition to beginning of file
// Could also use reader.BaseStream.Position = 0;
fs.Position = 0;
// (5) Read from first line to end of file
line = null;
while ((line = reader.ReadLine()) != null){
Console.WriteLine(line);
}
reader.Close();
}

Core Note

A StreamReader has an underlying FileStream even if it is not created


with an explicit one. It is accessed by the BaseStream property and can
be used to reposition the reader within the stream using its Seek
method. This example moves the reader to the beginning of a file:
reader.BaseStream.Seek(0, SeekOrigin.Begin);

StringWriter and StringReader


These two classes do not require a lot of discussion, because they are so similar in
practice to the StreamWriter and StreamReader. The main difference is that
these streams are stored in memory, rather than in a file. The following example
should be self-explanatory:
252 Chapter 5 ■ C# Text Manipulation and File I/O

StringWriter writer = new StringWriter();


writer.WriteLine("Today I have returned,");
writer.WriteLine("after long months ");
writer.Write("that seemed like centuries");
writer.Write(writer.NewLine);
writer.Close();
// Read String just written from memory
string myString = writer.ToString();
StringReader reader = new StringReader(myString);
string line = null;
while ((line = reader.ReadLine()) !=null) {
Console.WriteLine(line);
}
reader.Close();

The most interesting aspect of the StringWriter is that it is implemented


underneath as a StringBuilder object. In fact, StringWriter has a GetString-
Builder method that can be used to retrieve it:

StringWriter writer = new StringWriter();


writer.WriteLine("Today I have returned,");
// Get underlying StringBuilder
StringBuilder sb = writer.GetStringBuilder();
sb.Append("after long months ");
Console.WriteLine(sb.ToString());
writer.Close();

Core Recommendation

Use the StringWriter and StringBuilder classes to work with large


strings in memory. A typical approach is to use the
StreamReader.ReadToEnd method to load a text file into memory
where it can be written to the StringWriter and manipulated by the
StringBuilder.

Encryption with the CryptoStream Class


An advantage of using streams is the ability to layer them to add functionality. We
saw earlier how the BufferedStream class performs I/O on top of an underlying
FileStream. Another class that can be layered on a base stream is the Crypto-
Stream class that enables data in the underlying stream to be encrypted and
decrypted. This section describes how to use this class in conjunction with the
5.8 System.IO: Classes to Read and Write Streams of Data 253

StreamWriter and StreamReader classes to read and write encrypted text in a


FileStream. Figure 5-8 shows how each class is composed from the underlying
class.

StreamWriter StreamReader

CryptoStream CryptoStream

FileStream FileStream

File

Figure 5-8 Layering streams for encryption/decryption

CryptoStream is located in the System.Security.Cryptography namespace.


It is quite simple to use, requiring only a couple of lines of code to apply it to a
stream. The .NET Framework provides multiple cryptography algorithms that can
be used with this class. Later, you may want to investigate the merits of these algo-
rithms, but for now, our interest is in how to use them with the CryptoStream class.
Two techniques are used to encrypt data: assymmetric (or public key) and sym-
metric (or private key). Public key is referred to as asymmetric because a public key
is used to decrypt data, while a different private key is used to encrypt it. Symmetric
uses the same private key for both purposes. In our example, we are going to use a
private key algorithm. The .NET Framework Class Library contains four classes that
implement symmetric algorithms:

• DESCryptoServiceProvider—Digital Encryption Standard (DES)


algorithm
• RC2CryptoServiceProvider—RC2 algorithm
• RijndaelManaged—Rijndael algorithm
• TrippleDESCryptoServiceProvider—TripleDES algorithm

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

as an argument to the constructor of the CryptoStream class. The CryptoStream


object simply treats the object encapsulating the algorithm as a black box.
The example shown here includes two methods: one to encrypt and write data to a
file stream, and the other to decrypt the same data while reading it back. The encryp-
tion is performed by WriteEncrypt, which receives a FileStream object parame-
ter encapsulating the output file and a second parameter containing the message to
be encrypted; ReadEncrypt receives a FileStream representing the file to be read.

fs = new FileStream("C:\\test.txt", FileMode.Create,


FileAccess.Write);
MyApp.WriteEncrypt(fs, "Selected site is in Italy.");
fs= new FileStream("C:\\test.txt",FileMode.Open,
FileAccess.Read);
string msg = MyApp.ReadEncrypt(fs);
Console.WriteLine(msg);
fs.Close();

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();
}

ReadEncrypt reverses the actions of WriteEncrypt. It decodes the data in the


file stream and returns the data as a string object. To do this, it layers a Crypto-
Stream stream on top of the FileStream to perform decryption. It then creates a
StreamReader from the CryptoStream stream that actually reads the data from
the stream.
5.9 System.IO: Directories and Files 255

// Read and decrypt a file stream.


private static string ReadEncrypt(FileStream fs) {
// (1) Create Data Encryption Standard (DES) object
DESCryptoServiceProvider crypt =
new DESCryptoServiceProvider();
// (2) Create a key and Initialization Vector
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.CreateDecryptor(),CryptoStreamMode.Read);
// (4) Create StreamReader using CryptoStream
StreamReader sr = new StreamReader(cs);
string msg = sr.ReadToEnd();
sr.Close();
cs.Close();
return msg;
}

5.9 System.IO: Directories and Files


The System.IO namespace includes a set of system-related classes that are used to
manage files and directories. Figure 5-9 shows a hierarchy of the most useful classes.
Directory and DirectoryInfo contain members to create, delete, and query
directories. The only significant difference in the two is that you use Directory with
static methods, whereas a DirectoryInfo object must be created to use instance
methods. In a parallel manner, File and FileInfo provide static and instance
methods for working with files.

System.Object

Directory

File

FileSystemInfo <<abstract>>

DirectoryInfo

FileInfo

Figure 5-9 Directory and File classes in the System.IO namespace


256 Chapter 5 ■ C# Text Manipulation and File I/O

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
}

Working with Directories Using the


DirectoryInfo, Directory, and Path Classes
When working with directories, you usually have a choice between using the
instance methods of DirectoryInfo or the corresponding static methods of
Directory. As a rule, if you are going to refer to a directory in several operations,
use an instance of DirectoryInfo. Table 5-13 provides a comparison summary of
the available methods.
Table 5-13 Comparison of Selected DirectoryInfo and Directory Members

DirectoryInfo Directory

Member Description Member Description

Create() Create a directory or subdirectory. CreateDirectory() Pass the string path to the method.
CreateSubdirectory() Failure results in an exception.

Delete() Delete a directory. Delete(string) First version deletes an empty


Delete(string, bool) directory. Second version deletes a
directory and all subdirectories if
boolean value is true.

GetDirectories() Returns an array of DirectoryInfo GetDirectories(string) Returns a string array containing


type containing all subdirectories in GetDirectories(string, the names of directories in the path.
the current directory. string filter) A filter may be used to specify
directory names.

5.9 System.IO: Directories and Files


GetFiles() Returns an array of FileInfo types GetFiles(string) Returns string array of files in direc-
containing all files in the directory. GetFiles(string, filter) tory. A filter may be used to match
against file names. The filter may
contain wildcard characters ? or *
to match a single character or zero
or more characters.

Parent Retrieves parent directory of current GetParent() Retrieves parent directory of speci-
path. fied path.

N/A GetLogicalDrives() Returns string containing logical


drives on system. Format:
<drive>:\

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

Figure 5-10 Directory structure used in Directory examples

Create a Subdirectory
This code adds a subdirectory named cubists below expressionists:

// Directory static method to create directory


string newPath =
@"c:\artists\expressionists\cubists";
if (!Directory.Exists(newPath))
Directory.CreateDirectory(newPath);

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

string newPath = @"c:\artists\expressionists\cubists";


// Directory
if (Directory.Exists(newPath)) Directory.Delete(newPath);
// The following fails because the directory still contains files
Directory.Delete(@"c:\artists\expressionists");
// The following succeeds because true is passed to the method
Directory.Delete(@"c:\artists\expressionists",true);

// DirectoryInfo
DirectoryInfo di = new DirectoryInfo(newPath);
If (di.Exists) di.Delete();
5.9 System.IO: Directories and Files 259

List Directories and Files


This code defines a method that recursively loops through and lists the subdirecto-
ries and selected files on the C:\artists path. It uses the static Directory meth-
ods GetDirectories and GetFiles. Both of these return string values.

static readonly int Depth=4; // Specify directory level to search


ShowDir (@"c:\artists\", 0); // Call method to list files
// Display directories and files using recursion
public static void ShowDir(string sourceDir, int recursionLvl)
{
if (recursionLvl<= Depth) // Limit subdirectory search depth
{
// Process the list of files found in the directory
Console.WriteLine(sourceDir);
foreach( string fileName in
Directory.GetFiles(sourceDir,"s*.*"))
{
Console.WriteLine(" "+Path.GetFileName(fileName));

// Use recursion to process subdirectories


foreach(string subDir in
Directory.GetDirectories(sourceDir))
ShowDir(subDir,recursionLvl+1); // Recursive call
}
}

GetFiles returns a full path name. The static Path.GetFileName method is


used to extract the file name and extension from the path. For demonstration pur-
poses, a filter has been added to the GetFiles method to have it return only the
path of files that begins with s.
Here is the same operation using the DirectoryInfo class. Its GetDirecto-
ries and GetFiles methods behave differently than the Directory versions: They
return objects rather than strings, and they return the immediate directory or file
name rather than the entire path.

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

DirectoryInfo dirInfo = new DirectoryInfo(@"c:\artists\");


ShowDir(dirInfo, 0);

Using the Path Class to Operate on Path Names


To eliminate the need for developers to create code that manipulates a path string,
.NET provides a Path class that consists of static methods designed to operate on a
path string. The methods—a shortcut for Regex patterns—extract selected parts of a
path or return a boolean value indicating whether the path satisfies some criterion.
Note that because the format of a path is platform dependent (a Linux path is speci-
fied differently than a Windows path), the .NET implementation of this class is tai-
lored to the platform on which it runs.
To illustrate the static Path methods, let’s look at the results of applying selected
methods to this path:

string fullPath = @"c:\artists\impressionists\monet.htm";

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

Working with Files Using the


FileInfo and File Classes
The FileInfo and File classes are used for two purposes: to provide descriptive
information about a file and to perform basic file operations. The classes include
methods to copy, move, and delete files, as well as open files for reading and writing.
This short segment uses a FileInfo object to display a file’s properties, and the
static File.Copy method to copy a file:

string fname= @"c:\artists\impressionists\degas.txt";


// Using the FileInfo class to print file information
FileInfo fi = new FileInfo(fname); // Create FileInfo object
if (fi.Exists)
{
Console.Write("Length: {0}\nName: {1}\nDirectory: {2}",
fi.Length, fi.Name, fi.DirectoryName);
// output: --> 488 degas.txt c:\artists\impressionists
}
// Use File class to copy a file to another directory
if (File.Exists(fname))
{
try
{
// Exception is thrown if file exists in target directory
// (source, destination, overwrite=false)
File.Copy(fname,@"c:\artists\19thcentury\degas.txt",false);
}
catch(Exception ex)
{
Console.Write(ex.Message);
}
}

Using FileInfo and File to Open Files


The File and FileInfo classes offer an alternative to creating FileStream,
StreamWriter, and StreamReader objects directly. Table 5-14 summarizes the
FileInfo methods used to open a file. The static File methods are identical
except that their first parameter is always a string containing the name or path of
the file to open.
262 Chapter 5 ■ C# Text Manipulation and File I/O

Table 5-14 Selected FileInfo Methods for Opening a File

Member Returns Description

Open(mode) FileStream Opens a file with access and sharing priv-


Open(mode,access) ileges. The three overloads take File-
Open(mode,access,share) Mode, FileAccess, and FileShare
enumerations.

Create() FileStream Creates a file and returns a FileStream


object. If file exists, returns reference to
it.

OpenRead() FileStream Opens the file in read mode.

OpenWrite() FileStream Opens a file in write mode.

AppendText() StreamWriter Opens the file in append mode. If file


does not exist, it is created. Equivalent to
StreamWriter(string, true).

CreateText() StreamWriter Opens a file for writing. If the file exists,


its contents are overwritten. Equivalent
to StreamWriter(string, false).

OpenText() StreamReader Opens the file in read mode. Equivalent


to StreamReader(string).

The FileInfo.Open method is the generic and most flexible way to open a file:

public FileStream Open(FileMode mode, FileAccess access,


FileShare share)

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

FileInfo fi = new FileInfo(@"c:\temp.txt");


FileStream fs = fi.Create(); // Create file
StreamWriter sw= new StreamWriter(fs); // Create StreamWriter
sw.Write("With my crossbow\nI shot the Albatross. ");
sw.Close(); // Close StreamWriter
// Now use fi to create a StreamReader
fs = fi.OpenRead(); // Open for reading
StreamReader sr = new StreamReader(fs);
while(( string l = sr.ReadLine())!= null)
{
Console.WriteLine(l); // --> With my crossbow
} // --> I shot the Albatross.
sr.Close();
fs.Close();
fi.Delete(); // Delete temporary file

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

5.11 Test Your Understanding


1. Which class encapsulates information about a specific culture?

2. Name two ways to access the default culture within an application.

3. Match each regular expression:


1. @"(\b[^\Wa-z0-9_][^\WA-Z0-9_]*\b)"
2. @"(\b[^\Wa-z0-9_]+\b)"
3. @"(\b[^\WA-Z0-9_]+\b)"

with the function it performs:


a. Find all capitalized words.
b. Find all lowercase words.
c. Find all words with the initial letter capitalized.

4. When is it more advantageous to use the instance methods of Regex


rather than the static ones?

5. Which string comparison method(s) is (are) used to implement this


statement:
if (myString == "Monday") bool sw = true;

6. Match each statement:


1. curdt.ToString("D")
2. curdt.ToString("M")
3. curdt.ToString("ddd MMM dd")

with its output:


a. March 9
b. Tuesday, March 9, 2004
c. Tue Mar 09

7. Which of these objects is not created from an existing FileStream?


a. FileInfo
b. StreamReader
c. BufferedStream
5.11 Test Your Understanding 265

8. You can create a FileStream object with this statement:


FileStream fs = new FileStream(fname,
FileMode.OpenOrCreate,
FileAccess.Write,FileShare.None);
Which one of the following statements creates an identical
FileStream using an existing FileInfo object, fi?

a. fs = fi.OpenWrite();
b. fs = fi.Create();
c. fs = fi.CreateText();

9. Indicate whether the following comparisons are true or false:


a. (string.Compare("Alpha","alpha") >0)
b. (string.Compare("Alpha","alpha",true) ==0)
c. (string.CompareOrdinal("Alpha","alpha")>0)
d. (string.Equals("alpha","Alpha"))
BUILDING WINDOWS
FORMS APPLICATIONS

Topics in This Chapter

• Introduction: With just a few lines of code, you can build a


Windows Forms (WinForms) application that demonstrates the
basics of event handling and creating child forms.
• Using Form Controls: All controls inherit from the base Control
class. The members of this class provide a uniform way of
positioning, sizing, and modifying a control’s appearance.
• The Form Class: The Form class includes custom properties that
affect its appearance, and enable it to work with menus and
manage child forms.
• Message and Dialog Boxes: A pop-up window to provide user
interaction or information can be created as a message or dialog
box.
• MDI Forms: A Multiple Document Interface (MDI) is a container
that holds child forms. A main menu permits the forms to be
organized, accessed, and manipulated.
• Working with Menus: .NET supports two types of menus: a main
form menu and a context menu that can be associated with
individual controls.
• Adding Help to a Form: Help buttons, ToolTips, and HTML Help
are options for adding help to a form.
• Form Inheritance: Visual inheritance enables a form to be created
quickly, by inheriting the interface elements and functionality
from another form.
6

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:

• System.Windows.Forms. Windows Forms (WinForms).


• System.Web.UIWebControls. Web Forms.
• System.Web.UIMobileControls. Mobile Forms for hand-held and
pocket devices.

Although this chapter focuses on Windows Forms, it is important to recognize


that modern applications increasingly have to support multiple environments.
Acknowledging this, .NET strives to present a uniform “look and feel” for applica-
tions developed in each. The forms and controls in all three namespaces provide sim-
ilar, but not identical, functionality. The knowledge you acquire in this chapter will
shorten the learning curve required to develop .NET applications for the Web and
mobile devices.
Developers usually rely on an Integrated Development Environment (IDE), such
as Visual Studio, to develop GUI applications. This makes it easy to overlook the fact
that a form is a class that inherits from other classes and has its own properties and
methods. To provide a true understanding of forms, this chapter peers beneath the
IDE surface at the class members and how their implementation defines and affects
the behavior of the form. The discussion includes how to display forms, resize them,
make them scrollable or transparent, create inherited forms, and have them react to
mouse and keyboard actions.

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

6.1 Programming a Windows Form


All Windows Forms programs begin execution at a designated main window. This
window is actually a Form object that inherits from the System.Windows.
Forms.Form class. The initial window is displayed by passing an instance of it to the
static Application.Run method.
The challenge for the developer is to craft an interface that meets the basic rule of
design—form follows function. This means that a form’s design should support its
functionality to the greatest extent possible. To achieve this, a developer must under-
stand the properties, methods, and events of the Form class, as well as those of the
individual controls that are placed on the form.

Building a Windows Forms Application by Hand


Let’s create a simple Windows application using a text editor and the C# compiler
from the command line. This application, shown in Listing 6-1, consists of a single
window with a button that pops up a message when the button is clicked. The simple
exercise demonstrates how to create a form, add a control to it, and set up an event
handler to respond to an event fired by the control.

Basic Windows Forms Application


Listing 6-1
Built by Hand
using System;
using System.Windows.Forms;
using System.Drawing;
class MyWinApp
{
static void Main()
{
// (1) Create form and invoke it
Form mainForm = new SimpleForm();
Application.Run(mainForm);
}
}
6.1 Programming a Windows Form 269

Basic Windows Forms Application


Listing 6-1
Built by Hand (continued)
// User Form derived from base class Form
class SimpleForm:Form
{
private Button button1;
public SimpleForm() {
this.Text = "Hand Made Form";
// (2) Create a button control and set some attributes
button1 = new Button();
button1.Location = new Point(96,112);
button1.Size = new Size(72,24);
button1.Text= "Status";
this.Controls.Add(button1);
// (3) Create delegate to call routine when click occurs
button1.Click += new EventHandler(button1_Click);
}
void button1_Click(object sender, EventArgs e) {
MessageBox.Show("Up and Running");
}
}

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:

csc /t:winform.exe /r:System.Windows.Forms.dll winform.cs

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

Figure 6-1 Introductory Windows application

The code breaks logically into three sections:

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

3. Handle Button Click Event.


Event handling requires providing a method to respond to the event
and creating a delegate that invokes the method when the event
occurs (refer to Chapter 3, “Class Design in C#,” for details of event
handling). In this example, button1_Click is the method that pro-
cesses the event. The delegate associated with the Click event is cre-
ated with the following statement:

button1.Click += new EventHandler(button1_Click);


This statement creates an instance of the built-in delegate
EventHandler and registers the method button1_Click with it.

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.

6.2 Windows.Forms Control Classes


The previous examples have demonstrated how a custom form is derived from the
Windows.Forms.Form class. Now, let’s take a broader look at the class hierarchy
from which the form derives and the functionality that each class offers (see Figure
6-2).
272 Chapter 6 ■ Building Windows Forms Applications

Control

ScrollableControl DataGridView
Label
Container Control ListView
PictureBox
Form TreeView
GroupBox
User Form

Figure 6-2 Windows Forms class hierarchy

The Control Class


It may seem counterintuitive that the Form class is below the Control class on the
hierarchical chain, because a form typically contains controls. But the hierarchy rep-
resents inheritance, not containment. A Form is a container control, and the generic
members of the Control class can be applied to it just as they are to a simple Label
or TextBox.
System.Windows.Forms.dll contains more than fifty controls that are avail-
able for use on a Windows Form. A few that inherit directly from Control are
listed in Figure 6-2. All controls share a core set of inherited properties, methods,
and events. These members allow you to size and position the control; adorn it with
text, colors, and style features; and respond to keyboard and mouse events. This
chapter examines the properties that are common to all inheriting controls; Chapter
7 offers a detailed look at the individual controls and how to use their distinct fea-
tures in applications.

Control Properties
Table 6-1 defines some of the properties common to most types that inherit from
Control.

Table 6-1 Common Control Properties

Category Property Description

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

Table 6-1 Common Control Properties (continued)

Category Property Description

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.

Bounds Bounds is a rectangle that defines a control’s position


and size:
Button.Bounds =
new Rectangle (10,10,50,60)

ClientRectangle ClientRectangle is a rectangle that represents the


client area of the control—that is, the area excluding
scroll bars, titles, and so on.

Anchor Specifies which edges of the container the control is


anchored to. This is useful when the container is
resized.

Dock Controls which edge of the container the control


docks to.

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.

BackGroundImage BackGroundImage specifies an image to be used in


the background of the control.

Text Text Text associated with the control.

Font Font describes the characteristics of the text font:


typeface, size, bold, and so on.

Focus TabIndex int value that indicates the tab order of this control
within its container.

TabStop Boolean value that indicates whether this control can


receive focus from the Tab key.

Focused Boolean value indicating whether a control has input


focus.

Visible Boolean value indicating whether the control is dis-


played.

Keyboard MouseButtons Returns the current state of the mouse buttons (left,
and mouse right, and middle).
274 Chapter 6 ■ Building Windows Forms Applications

Table 6-1 Common Control Properties (continued)

Category Property Description

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.

Cursor Specifies the shape of the mouse pointer when it is


over the control. Assigned value is a static property of
the Cursors class. These include:
.Hand .Cross UpArrow Default
.Beam .Arrow WaitCursor

Runtime Handle int value representing the handle of Windows control.


status
Focused bool value indicating whether the control has focus.

Working with Controls


When you drag a control onto a form, position it, and size it, VisualStudio.NET
(VS.NET) automatically generates code that translates the visual design to the
underlying property values. There are times, however, when a program needs to
modify controls at runtime to hide them, move them, and even resize them. In fact,
size and position are often based on the user’s screen size, which can only be
detected at runtime. An IDE cannot do this, so it is necessary that a programmer
understand how these properties are used to design an effective control layout.

Size and Position


As we saw in the earlier example, the size of a control is determined by the Size
object, which is a member of the System.Drawing namespace:

button1.Size = new Size(80,40); // (width, height)


button2.Size = button1.Size; // Assign size to second button

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:

private void button1_Click(object sender, System.EventArgs e)


{
MessageBox.Show("Up and Running");
Button button1 = (Button) sender; //Cast object to Button
button1.Size = new Size(90,20); //Dynamically resize
6.2 Windows.Forms Control Classes 275

The System.Drawing.Point object can be used to assign a control’s location. Its


arguments set the x and y coordinates—in pixels—of the upper-left corner of a con-
trol. The x coordinate is the number of pixels from the left side of the container. The
y coordinate is the number of pixels from the top of the container.

button1.Location = new Point(20,40); // (x,y) coordinates

It is important to recognize that this location is relative to the control’s container.


Thus, if a button is inside a GroupBox, the button’s location is relative to it and not
the form itself. A control’s Location also can be changed at runtime by assigning a
new Point object.
Another approach is to set the size and location in one statement using the
Bounds property:

button1.Bounds = new Rectangle(20,40, 100,80);

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.

How to Anchor and Dock a Control


The Dock property is used to attach a control to one of the edges of its container. By
default, most controls have docking set to none; some exceptions are the Sta-
tusStrip/StatusBar that is set to Bottom and the ToolStrip/ToolBar that is
set to Top. The options, which are members of the DockStyle enumeration, are
Top, Bottom, Left, Right, and Fill. The Fill option attaches the control to all
four corners and resizes it as the container is resized. To attach a TextBox to the top
of a form, use

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

The Anchor property allows a control to be placed in a fixed position relative to a


combination of the top, left, right, or bottom edge of its container. Figure 6-4 illus-
trates the effects of anchoring.

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:

btnPanel.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left);


6.2 Windows.Forms Control Classes 277

Tab Order and Focus


Tab order defines the sequence in which the input focus is given to controls when the
Tab key is pressed. The default sequence is the order in which the controls are added
to the container.
The tab order should anticipate the logical sequence in which users expect to
input data and thus guide them through the process. The form in Figure 6-5 repre-
sents such a design: The user can tab from the first field down to subsequent fields
and finally to the button that invokes the final action.

Figure 6-5 Tab order for controls on a form

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:

TextBox1.TabIndex = 0; //First item in tab sequence

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

Iterating Through Controls on a Form


All controls on a form are contained in a Controls collection. By enumerating
through this collection, it is possible to examine each control, identify it by name and
type, and modify properties as needed. One common use is to clear the value of
selected fields on a form in a refresh operation. This short example examines each
control in Figure 6-5 and displays its name and type:

int ctrlCt = this.Controls.Count; // 8


foreach (Control ct in this.Controls)
{
object ob = ct.GetType();
MessageBox.Show(ob.ToString()); //Displays type as string
MessageBox.Show(ct.Name);
}

There are several things to be aware of when enumerating control objects:

• The type of each control is returned as a fully qualified name. For


example, a TextBox is referred to as System.Forms.Form.Text-
Box.
• Only a container’s top-level objects are listed. In this example, the
Controls.Count value is 8 rather than 10 because the GroupBox is
counted as one control and its child controls are excluded.
• You can use a control’s HasChildren property to determine if it is a
container. Listing 6-2 uses recursion to list all child controls.

Listing 6-2 Enumerate All Controls on a Form Recursively


void IterateThroughControls(Control parent)
{
foreach (Control c in parent.Controls)
{
MessageBox.Show(c.ToString());
if(c.HasChildren)
{
IterateThroughControls(c);
}
}
}

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.

Table 6-2 Control Events

Built-In Delegate/
Event Parameters Description

Click, EventHandler ( Events triggered by clicking,


DoubleClick, object sender, EventArgs e) double clicking, or moving
MouseEnter, the mouse.
MouseLeave,
MouseHover,
MouseWheel

MouseDown, MouseEventHandler ( Events triggered by mouse


MouseUp, object sender, MouseEventArgs) and mouse button motions.
MouseMove Note that this event is not
triggered if the mouse
action occurs within a con-
trol in the current container.

KeyUp, KeyEventHandler ( Events triggered by key


KeyDown object sender, KeyEventArgs e) being raised or lowered.

KeyPress KeyPressEventHandler ( Event triggered by pressing


object sender, any key.
KeyPressEventArgs e)

Handling Mouse Events


In addition to the familiar Click and DoubleClick events, all Windows Forms con-
trols inherit the MouseHover, MouseEnter, and MouseLeave events. The latter two
are fired when the mouse enters and leaves the confines of a control. They are useful
for creating a MouseOver effect that is so common to Web pages.
280 Chapter 6 ■ Building Windows Forms Applications

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:

TextBox userID = new TextBox();


userID.MouseEnter += new EventHandler(OnMouseEnter);
userID.MouseLeave += new EventHandler(OnMouseLeave);

The event handler methods match the signature of the EventHandler delegate
and cast the sender parameter to a Control type to access its properties.

private void OnMouseEnter(object sender, System.EventArgs e){


Control ctrl = (Control) sender;
ctrl.BackColor= Color.Bisque;
}
private void OnMouseLeave(object sender, System.EventArgs e){
Control ctrl = (Control) sender;
ctrl.BackColor= Color.White;
}

Core Note

It is possible to handle events by overriding the default event handlers of


the base Control class. These methods are named using the pattern
Oneventname—for example, OnMouseDown, OnMouseMove, and so on.
To be consistent with .NET, the delegate approach is recommended over
this. It permits a control to specify multiple event handlers for an event,
and it also permits a single event handler to process multiple events.

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

Table 6-3 Properties of MouseEventArgs

Property Description

Button Indicates which mouse button was pressed. The attribute value is
defined by the MouseButtons enumeration: Left, Middle, None,
Right

Clicks Number of clicks since last event.

Delta Number of detents the mouse wheel rotates. A positive number


means it’s moving forward; a negative number shows backward
motion.

X, Y Mouse coordinates relative to the container’s upper-left corner.


This is equivalent to the control’s MousePosition property.

Listing 6-3 Using Mouse Events to Draw on a Form


using System;
using System.Windows.Forms;
using System.Drawing;
class MyWinApp
{
static void Main() {
Form mainForm = new DrawingForm();
Application.Run(mainForm);
}
}
// User Form derived from base class Form
class DrawingForm:Form
{
Point lastPoint = Point.Empty; // Save coordinates
public Pen myPen; //Defines color of line
public DrawingForm()
{
this.Text = "Drawing Pad";
// reate delegates to call MouseUp and MouseDown
this.MouseDown += new MouseEventHandler(OnMouseDown);
this.MouseUp += new MouseEventHandler(OnMouseUp);
}
282 Chapter 6 ■ Building Windows Forms Applications

Listing 6-3 Using Mouse Events to Draw on a Form (continued)


private void OnMouseDown(object sender, MouseEventArgs e)
{
myPen = (e.Button==MouseButtons.Right)? Pens.Red:
Pens.Black;
lastPoint.X = e.X;
lastPoint.Y = e.Y;
}

private void OnMouseUp(object sender, MouseEventArgs e)


{
// Create graphics object
Graphics g = this.CreateGraphics();
if (lastPoint != Point.Empty)
g.DrawLine(myPen, lastPoint.X,lastPoint.Y,e.X,e.Y);
lastPoint.X = e.X;
lastPoint.Y = e.Y;
g.Dispose();
}
}

Even without an understanding of .NET graphics, the role of the graphics-related


classes should be self-evident. A Graphics object is created to do the actual drawing
using its DrawLine method. The parameters for this method are the Pen object that
defines the color and the coordinates of the line to be drawn. When a button is
pressed, the program saves the coordinate and sets myPen based on which button is
pressed: a red pen for the right button and black for the left. When the mouse button
is raised, the line is drawn from the previous coordinate to the current location. The
MouseEventArgs object provides all the information required to do this.

Handling Keyboard Events


Keyboard events are also handled by defining a delegate to call a custom event han-
dling method. Two arguments are passed to the event handler: the sender argument
identifies the object that raised the event and the second argument contains fields
describing the event. For the KeyPress event, this second argument is of the Key-
PressEventArgs type. This type contains a Handled field that is set to true by the
event handling routine to indicate it has processed the event. Its other property is
KeyChar, which identifies the key that is pressed.
KeyChar is useful for restricting the input that a field accepts. This code segment
demonstrates how easy it is to limit a field’s input to digits. When a non-digit is entered,
Handled is set to true, which prevents the form engine from displaying the character.
Otherwise, the event handling routine does nothing and the character is displayed.
6.2 Windows.Forms Control Classes 283

private void OnKeyPress(object sender, KeyPressEventArgs e)


{
if (! char.IsDigit(e.KeyChar)) e.Handled = true;
}

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.

Table 6-4 KeyEventArgs Properties

Member Description

Alt, Control, Boolean value that indicates whether the Alt, Control, or Shift key was
Shift pressed.

Handled Boolean value that indicates whether an event was handled.

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.

A few things to note:

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

private bool paste;

//Register event handlers for TextBox t.


//They should be registered in this order,
//because the last registered is the
//first executed
t.KeyPress += new KeyPressEventHandler(OnKeyPress);
t.KeyDown += new KeyEventHandler(OnKeyDown);

private void OnKeyDown(object sender, KeyEventArgs e)


{
if (e.Modifiers == Keys.Control && e.KeyCode == Keys.V)
{
paste=true; //Flag indicating paste attempt
string msg = string.Format("Modifier: {0} \nKeyCode: {1}
\nKeyData: {2}", e.Modifiers.ToString(),
e.KeyCode.ToString(), e.KeyData.ToString());
MessageBox.Show(msg); //Display property values
}

private void OnKeyPress(object sender, KeyPressEventArgs e)


{
if (paste==true) e.Handled = true;
}

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

6.3 The Form Class


The Form object inherits all the members of the Control class as well as the
ScrollableControl class, which provides properties that enable scrolling. To this
it adds a large number of properties that enable it to control its appearance, work
with child forms, create modal forms, display menus, and interact with the desktop
via tool and status bars. Table 6-5 shows a selected list of these properties.

Table 6-5 Selected Form Properties

Category Property Description

Appearance FormBorderStyle Gets or sets the border style of the form. It is


defined by the FormBorderStyle enumeration:
Fixed3D None
FixedSingle Sizable
FixedDialog

ControlBox Boolean value that determines whether the menu


icon in the left corner of a form and the close button
on the upper right are shown.

MaximizeBox Boolean value that indicates whether these buttons


MinimizeBox are displayed on the form.

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.

TransparencyKey A color that represents transparent areas on the


form. Any control or portion of the form that has a
back color equal to this color is not displayed. Click-
ing this transparent area sends the event to any
form below it.

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.

DesktopLocation A Point type that indicates where the form is


located on the desktop window.
286 Chapter 6 ■ Building Windows Forms Applications

Table 6-5 Selected Form Properties (continued)

Category Property Description

Size and StartPosition Specifies the initial position of a form. It takes a


position FormStartPosition enum value:
(continued) CenterParent—Centered within bounds of
parent form.
CenterScreen—Centered within the display.
Manual—Use the DeskTopLocation value.
Windows DefaultLocation—Windows sets
value.

MinimumSize A Size object that designates the maximum and


MaximumSize minimum size for the form. A value of (0,0) indi-
cates no minimum or maximum.

ShowInTaskBar Boolean value specifying whether application is rep-


resented in Windows task bar. Default is true.

TopLevel Indicates whether to display the form as a


TopMost TopLevel window or TopMost window. A top-level
window has no parent; top-most form is always dis-
played on top of all other non-TopMost forms.

WindowState Indicates how the form is displayed on startup. It


takes a value from the FormWindowState enumer-
ation: Maximized, Minimized, or Normal.

Owner forms Owner The form designated as the owner of the form.

OwnedForms A Form array containing the forms owned by a


form.

Setting a Form’s Appearance


The four properties shown in Figure 6-6 control which buttons and icon are present
on the top border of a form. The Icon property specifies the .ico file to be used as
the icon in the left corner; the ControlBox value determines whether the icon and
close button are displayed (true) or not displayed (false); similarly, the Maximize-
Box and MinimizeBox determine whether their associated buttons appear.
The purpose of these properties is to govern functionality more than appearance.
For example, it often makes sense to suppress the minimize and maximize buttons on
modal forms in order to prevent a user from maximizing the form and hiding the
underlying parent form.
6.3 The Form Class 287

Figure 6-6 Properties to control what appears on the title bar

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:

this.Deactivate += new System.EventHandler(Form_Deactivate);


this.Activated += new System.EventHandler(Form_Activate);

The code for the corresponding event handlers is trivial:

void Form_Deactivate(object sender, EventArgs e)


{ this.Opacity= .8; }

void Form_Activate(object sender, EventArgs e)


{ this.Opacity= 1; }

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.

Figure 6-7 Form using transparency to create irregular appearance

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:

CustomForm myForm = new CustomForm();


myForm.TransparencyKey = Color.Red;
myForm.FormBorderStyle= FormBorderStyle.None;
myForm.Show();

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

Using Form Transparency to Create a Non-Rectangular


Listing 6-4
Form
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
public class CustomForm : Form
{
private Point lastPoint = Point.Empty; //Save mousedown
public CustomForm()
{
InitializeComponent(); // set up form
//Associate mouse events with pictureBox
pictureBox1.MouseDown += new MouseEventHandler(
OnMouseDown);
pictureBox1.MouseUp += new MouseEventHandler(OnMouseUp);
pictureBox1.MouseMove += new MouseEventHandler(
OnMouseMove);
}
private void OnMouseDown(object sender, MouseEventArgs e)
{
lastPoint.X = e.X;
lastPoint.Y = e.Y;
}

private void OnMouseUp(object sender, MouseEventArgs e)


{
lastPoint = Point.Empty;
}

//Move the form in response to the mouse being dragged


private void OnMouseMove(object sender, MouseEventArgs e)
{
if (lastPoint != Point.Empty)
{
//Move form in response to mouse movement
int xInc = e.X - lastPoint.X;
int yInc = e.Y - lastPoint.Y;
this.Location = new Point(this.Left + xInc,
this.Top+yInc);
}
}
290 Chapter 6 ■ Building Windows Forms Applications

Using Form Transparency to Create a Non-Rectangular


Listing 6-4
Form (continued)
// Close Window
private void button1_Click(object sender, System.EventArgs e)
{
this.Close();
}
}

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.

Setting Form Location and Size


The initial location of a form is determined directly or indirectly by its StartPosi-
tion property. As described in Table 6-6, it takes its value from the FormStart-
Position enumeration. These values allow it to be placed in the center of the
screen, in the center of a parent form, at a Windows default location, or at an arbi-
trarily selected location. Manual offers the greatest flexibility because it allows the
program to set the location.
The initial location is normally set in the Form.Load event handler. This example
loads the form 200 pixels to the right of the upper-left corner of the screen:

private void opaque_Load(object sender, System.EventArgs e)


{
this.DesktopLocation = new Point(200,0);
}

The form’s initial location can also be set by the form that creates and displays the
form object:

opaque opForm = new opaque();


opForm.Opacity = 1;
opForm.TopMost = true; //Always display form on top
opForm.StartPosition = FormStartPosition.Manual;
opForm.DesktopLocation = new Point(10,10);
opForm.Show();
6.3 The Form Class 291

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

The DesktopLocation property sets coordinates within a screen’s


working area, which is the area not occupied by a task bar. The
Location property of the Control class sets the coordinates relative to
the upper-left edge of the control’s container.

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:

this.ClientSize = new System.Drawing.Size(208, 133);

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:

//w and h are the screen's width and height


this.MaximumSize = new Size(w/2,h/2);
this.MinimumSize = new Size(200, 150);

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.

The Life Cycle of a Modeless Form


A form is subject to a finite number of activities during its lifetime: It is created, dis-
played, loses and gains focus, and finally is closed. Most of these activities are accom-
panied by one or more events that enable the program to perform necessary tasks
associated with the event. Table 6-6 summarizes these actions and events.

Table 6-6 The Life Cycle of a Modeless Form

Action Events Triggered Description

Form object created The form’s constructor is called. In Visual


Studio, the InitializeComponent
method is called to initialize the form.

Form displayed: Form.Load The Load event is called first, followed by


Form.Show() Form.Activated the Activated event.

Form activated Form.Activated This occurs when the user selects the
form. This becomes an “active” form.

Form deactivated Form.Deactivate Form is deactivated when it loses focus.

Form closed Form.Deactivate Form is closed by executing Form.Close


Form.Closing or clicking on the form’s close button.
Form.Closed

Let’s look at some of the code associated with these events.


6.3 The Form Class 293

Creating and Displaying a Form


When one form creates another form, there are coding requirements on both sides.
The created form must set up code in its constructor to perform initialization and
create controls. In addition, delegates should be set up to call event handling rou-
tines. If using Visual Studio.NET, any user initialization code should be placed after
the call to InitializeComponent.
For the class that creates the new form object, the most obvious task is the cre-
ation and display of the object. A less obvious task may be to ensure that only one
instance of the class is created because you may not want a new object popping up
each time a button on the original form is clicked. One way to manage this is to take
advantage of the Closed event that occurs when a created form is closed (another
way, using OwnedForms, is discussed shortly). If the form has not been closed, a new
instance is not created. The code that follows illustrates this.
An EventHandler delegate is set up to notify a method when the new form,
opForm, is closed. A flag controls what action occurs when the button to create or
display the form is pushed. If an instance of the form does not exist, it is created and
displayed; if it does exist, the Form.Activate method is used to give it focus.

//Next statement is at beginning of form's code


public opaque opForm;
bool closed = true; //Flag to indicate if opForm exists
//Create new form or give focus to existing one
private void button1_Click(object sender, System.EventArgs e)
{
if (closed)
{
closed = false;
opForm = new opaque();
//Call OnOpClose when new form closes
opForm.Closed += new EventHandler(OnOpClose);
opForm.Show(); //Display new form object
} else {
opForm.Activate(); //Give focus to form
}
}
//Event handler called when child form is closed
private void OnOpClose(object sender, System.EventArgs e)
{
closed = true; //Flag indicating form is closed
}
294 Chapter 6 ■ Building Windows Forms Applications

Form Activation and Deactivation


A form becomes active when it is first shown or later, when the user clicks on it or
moves to it using an Alt-Tab key to iterate through the task bar. This fires the form’s
Activated event. Conversely, when the form loses focus—through closing or dese-
lection—the Deactivate event occurs. In the next code segment, the Deactivate
event handler changes the text on a button to Resume and disables the button; the
Activated event handler re-enables the button.

this.Deactivate += new System.EventHandler(Form_Deactivate);


this.Activated += new System.EventHandler(Form_Activate);
//
void Form_Deactivate(object sender, EventArgs e)
{ button1.Enabled = false;
button1.Text = "Resume"; }
void Form_Activate(object sender, EventArgs e)
{ button1.Enabled = true; }

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:

this.Closing += new CancelEventHandler(Form_Closing);


void Form_Closing(object sender, CancelEventArgs e)
{
if(MessageBox.Show("Are you sure you want to Exit?", "",
MessageBoxButtons.YesNo) == DialogResult.No)
{
//Cancel the closing of the form
e.Cancel = true;
}
}

Forms Interaction—A Sample Application


When multiple form objects have been created, there must be a way for one form to
access the state and contents of controls on another form. It’s primarily a matter of
setting the proper access modifiers to expose fields and properties on each form. To
illustrate, let’s build an application that consists of two modeless forms (see Figure
6-8). The main form contains two controls: a Textbox that holds the document
6.3 The Form Class 295

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.

Figure 6-8 Text search application using multiple forms

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

public static TextBox myText; //Declare public variable

private void docForm_Load(object sender, System.EventArgs e)


{
myText = documentText;
}

SearchForm exposes the contents of txtSearch to other objects through a


write-only string property.

public String SearchPhrase


{
set { txtSearch.Text = value;} //Write Only
}

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.

Code for the Main Form


When the button on DocForm is clicked, the application either creates a new
instance of SearchForm or passes control to an existing instance. In both cases, it
first checks its text box and passes any highlighted text (SelectedText) to the
SearchForm object via its SearchPhrase property (see Listing 6-5). Techniques
described in earlier examples are used to create the object and set up a delegate to
notify the DocForm object when the search form object closes.

Method to Pass Control to Search


Listing 6-5
Form Instance
private void btnSearch_Click(object sender, System.EventArgs e)
{
//Create instance of search form if it does not exist
if (closed)
{
closed= false;
searchForm = new SearchForm(); //Create instance
searchForm.TopMost = true;
searchForm.Closed += new EventHandler(onSearchClosed);
searchForm.StartPosition = FormStartPosition.Manual;
searchForm.DesktopLocation = new Point(this.Right-200,
this.Top-20);
searchForm.SearchPhrase = documentText.SelectedText;
searchForm.Show();
6.3 The Form Class 297

Method to Pass Control to Search


Listing 6-5
Form Instance (continued)
} else {
searchForm.SearchPhrase = documentText.SelectedText;
searchForm.Activate();
}
}
private void onSearchClosed(object sender, System.EventArgs e)
{ closed= true; }

Code for the Search Form


Listing 6-6 displays the code executed when the Find Next button is clicked. The
search for the next occurrence of the search string can proceed up the document
using the LastIndexOf method or down the document using IndexOf. Logic is also
included to ignore or recognize case sensitivity.

Listing 6-6 Search for Matching Text on Another Form


private void btnFind_Click(object sender, System.EventArgs e)
{
int ib; //Index to indicate position of match
string myBuff = DocForm.myText.Text; //Text box contents
string searchText= this.txtSearch.Text; //Search string
int ln = searchText.Length; //Length of search string
if (ln>0)
{
//Get current location of selected text
int selStart = DocForm.myText.SelectionStart;
if (selStart >= DocForm.myText.Text.Length)
{
ib = 0;
} else {
ib = selStart + ln;
}
if (!this.chkCase.Checked) //Case-insensitive search
{
searchText = searchText.ToUpper();
myBuff = myBuff.ToUpper();
}
298 Chapter 6 ■ Building Windows Forms Applications

Listing 6-6 Search for Matching Text on Another Form (continued)


if (this.radDown.Checked)ib =
myBuff.IndexOf(searchText,ib);
if (this.radUp.Checked && ib>ln-1)ib =
myBuff.LastIndexOf(searchText,ib-2,ib-1);
if (ib >= 0) //Highlight text on main form
{
DocForm.myText.SelectionStart = ib;
DocForm.myText.SelectionLength = txtSearch.Text.Length;
}
}
}

Owner and Owned Forms


When a form displays an instance of a modeless form, it does not by default create an
explicit relationship between itself and the new form. The forms operate autono-
mously: They either can be closed (except for a main form, which causes all forms to
be closed) or minimized without affecting the other; and the creator form has no easy
way to distinguish among instances of the forms it has launched.
Often, however, one form does have a dependency on the other. In the preceding
example, the floating search window exists only as a companion to a document that
it searches. Its relationship to the form that created it is referred to as an
owner-owned relationship. In .NET, this can be more than just a logical relation-
ship. A form has an Owner property that can be set to the instance of the form that
“owns” it. After this relationship is formally established, the behavior of the owner
and owned form(s) is linked. For example, the owned form is always visually on top
of its owner form. This eliminates the need to make SearchForm a TopMost form
in our preceding example.
An owner-owned relationship is easily established by setting the Owner property
of a newly created form to the form that is creating it.

opaque opForm = new opaque();


opForm.Owner = this; //Current form now owns new form
opForm.Show();

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

Another advantage of the owner-owned relationship is that an owner form has an


OwnedForms collection that contains all the owned forms it creates. The following
example demonstrates how an owner form creates two owned forms, opForm and
opForm2, and then enumerates the collection to set the caption of each form before
displaying it:

opaque opForm = new opaque();


opForm.Owner = this; //Set current form to owner form
opaque opForm2 = new opaque();
opForm2.Owner = this; //Set current form to owner form
for (int ndx=0; ndx<this.OwnedForms.Length; ndx++)
{
myForms.Text = "Owner: Form1 - Form"+ndx.ToString();
myForms.Show();
}

Note that although modal forms exhibit the features of an owned form, the Owner
property must be set to establish an explicit relationship.

Message and Dialog Boxes


.NET provides a set of classes and enumerations that make it easy to create a mes-
sage or dialog window to interact with a user. The simplest approach is to use the
MessageBox class and its versatile Show method. The other approach is to create a
custom form and invoke it with the form’s ShowDialog method. Both of these meth-
ods create modal forms.

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:

static DialogResult Show(string msg)


static DialogResult Show(string msg, string caption)
static DialogResult Show(string msg, string caption,
MessageBoxButtons buttons)
static DialogResult Show(string msg, string caption,
MessageBoxButtons buttons, MessageBoxIcon icon,
MessageBoxDefaultButton defBtn)

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

MessageBoxIcon. This enumeration places an icon on the message box. Members


include Asterisk, Error, Exclamation, Hand, Information, None, Question,
Stop, and Warning.

MessageBoxButtons. This is an enumeration with values that determine which but-


tons are displayed in the message box. Its members are AbortRetryIgnore, OK,
OKCancel, RetryCancel, YesNo, and YesNoCancel. The buttons correspond to
the text in the member name. For example, YesNo results in a form with a Yes and
No button.
MessageBoxDefaultButton. This an enumeration that defines the default button
on the screen. Its members are Button1, Button2, and Button3.

Figure 6-9, which is created with the following statement, provides a visual sum-
mary of these parameter options:

MessageBox.Show("OK to Close", "Game Status",


MessageBoxButtons.YesNoCancel,MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2 );

Figure 6-9 MessageBox.Show example

Clicking one of the three buttons returns a value of DialogResult.Yes, Dia-


logResult.No, or DialogResult.Cancel, respectively.

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

private void buttonOK_Click(object sender, System.EventArgs e)


{ this.DialogResult = DialogResult.OK; }
private void buttonCancel_Click(object sender, System.EventArgs e)
{ this.DialogResult = DialogResult.Cancel; }

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.

AcceptButton = buttonOK; //Button to receive default focus


CancelButton = buttonCancel; //Fires when Esc pushed

Figure 6-10 Creating a menu with VS.NET

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.

customer cust = new customer();


cust.MinimizeBox = false;
cust.MaximizeBox = false;
if (cust.ShowDialog() == DialogResult.OK)
{ MessageBox.Show("Returns OK"); }
else
{ MessageBox.Show("Returns Cancel"); }

Multiple Document Interface Forms


A Multiple Document Interface (MDI) application is characterized by one applica-
tion window and several document windows. Structurally, a single container is used
to hold multiple documents. To manage the collection of documents, the MDI appli-
cation includes a menu system with options to open, save, and close a document;
switch between documents; and arrange the documents in various visual layouts.
302 Chapter 6 ■ Building Windows Forms Applications

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.

Figure 6-11 MDI 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.

invForm myForm = new invForm();


myForm.MdiParent = this;
mdiCt += mdiCt; //Count number of forms created
myForm.Text= "Invoice" + mdiCt.ToString();
myForm.Show();

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

Creating a Menu and MDI Form


A discussion of MDI forms is incomplete without considering the requirements for a
menu to manage the windows within the container. Minimally, an MDI parent menu
should contain a File section for creating and retrieving forms and a Windows section
that lists all child forms and permits form selection.
A basic menu is constructed from two classes: the MainMenu class that acts as a
container for the whole menu structure and the MenuItem class that represents the
menu items in the menu. Both of these classes expose a MenuItems collection prop-
erty that is used to create the menu hierarchy by adding subitems to each class that
represent the menu options. After the menu items are in place, the next step is to tie
them to appropriate event handling routines using the familiar delegate approach.
Let’s step through an example that demonstrates how to create the menu system
shown in Figure 6-12. Afterwards, we’ll look at creating the menu in Visual Stu-
dio.NET. It is certainly much quicker and easier to use VS.NET, but it is less flexible
if you need to create menus at runtime.

Figure 6-12 MDI Form menu

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

private MainMenu mainMenu1;


private MenuItem menuItem1; //File
private MenuItem menuItem2; //Edit
private MenuItem menuItem3; //Window
private MenuItem menuItem4; //File - New

The main menu and menu items are created inside the class constructor:

this.mainMenu1 = new System.Windows.Forms.MainMenu();


this.menuItem1 = new System.Windows.Forms.MenuItem("File");
304 Chapter 6 ■ Building Windows Forms Applications

this.menuItem2 = new System.Windows.Forms.MenuItem("Edit");


this.menuItem3 = new System.Windows.Forms.MenuItem("Window");
this.menuItem4 = new System.Windows.Forms.MenuItem("New");

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.

//Add menu items to main menu object


this.mainMenu1.MenuItems.AddRange(new
System.Windows.Forms.MenuItem[] {
this.menuItem1,
this.menuItem2,
this.menuItem3});
//Add menu item below File
this.menuItem1.MenuItems.Add(this.menuItem4);
//Add menu items to Window menu item
this.menuItem3.MdiList = true; //Causes child forms to display
this.menuItem3.MenuItems.AddRange(new
System.Windows.Forms.MenuItem[] {this.menuItem5,
this.menuItem6, this.menuItem7, this.menuItem8});
//Set menu on form
this.Menu = this.mainMenu1;

The main points to observe in this code are:

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

//Following is defined in constructor


MenuItem4.Click += new System.EventHandler(menuItem4_Click);

private void menuItem4_Click(object sender, System.EventArgs e)


{
invForm myForm = new invForm();
6.3 The Form Class 305

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:

private void menuItem6_Click(object sender, System.EventArgs e){


this.LayoutMdi(MdiLayout.ArrangeIcons);
}

private void menuItem6_Click(object sender, System.EventArgs e){


this.LayoutMdi(MdiLayout.Cascade);
}

private void menuItem7_Click(object sender, System.EventArgs e){


this.LayoutMdi(MdiLayout.TileHorizontal);
}

private void menuItem8_Click(object sender, System.EventArgs e){


this.LayoutMdi(MdiLayout.TileVertical);
}

The methods reorder the window by passing the appropriate MdiLayout enumer-
ation value to the LayoutMdi method.

Creating an MDI Menu Using VisualStudio.NET


With the Form Designer open, double click the MainMenu icon in the Toolbox win-
dow. Two things happen: An icon appears in the component tray at the bottom and a
menu template appears docked to the top of the form. Type the menu item titles
into the cells that appear (see Figure 6-13). The top horizontal row of cells repre-
sents the menu bar; by moving vertically, you create drop-down menu items below
the top-level items. After typing in the item name, double click the cell to create a
Click event for the menu item. VS.NET creates the delegate and method stub
automatically.
Use the Property Window (press F4), which displays properties of the active cell,
to change the default names assigned to menu items and set any other values.
306 Chapter 6 ■ Building Windows Forms Applications

Figure 6-13 Creating a menu with VS.NET

6.4 Working with Menus


The previous section provided a solid introduction to menus. This section adds a
checklist of MenuItem properties that affect an item’s appearance and describes how
to use the ContextMenu class.

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

Figure 6-14 Context menu

Constructing a Context Menu


Creating a context menu is similar to creating a MainMenu. If using VS.NET, you
drag the ContextMenu control to the form and visually add menu items. If coding by
hand, you create an instance of the ContextMenu class and add menu items using
the MenuItems.Add method. Following is a sampling of the code used to create the
menu. Note that a single method handles the Click event on each menu item.

private ContextMenu contextMenu1; //Context menu


private TextBox txtSearch; //Text box that will use menu
// Following is in constructor
contextMenu1 = new ContextMenu();
// Add menu items and event handler using Add method
contextMenu1.MenuItems.Add("Azure Background",
new System.EventHandler(this.menuItem_Click));
308 Chapter 6 ■ Building Windows Forms Applications

contextMenu1.MenuItems.Add("White Background",
new System.EventHandler(this.menuItem_Click));
contextMenu1.MenuItems.Add("Beige Background",
new System.EventHandler(this.menuItem_Click));

The completed menu is attached to a control by setting the control’s Context-


Menu property to the context menu:

//Associate text box with a context menu


this.txtSearch.ContextMenu = this.contextMenu1;

A right-click on txtSearch causes the menu to pop up. Click one of the menu
items and this event handling routine is called:

private void menuItem_Click(object sender, System.EventArgs e)


{
//Sender identifies specific menu item selected
MenuItem conMi = (MenuItem) sender;
string txt = conMi.Text;
//SourceControl is control associated with this event
if(txt == "Azure Background")
this.contextMenu1.SourceControl.BackColor = Color.Azure;
if(txt == "White Background")
this.contextMenu1.SourceControl.BackColor = Color.White;
if(txt == "Beige Background")
this.contextMenu1.SourceControl.BackColor = Color.Beige;
}

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.

6.5 Adding Help to a Form


The majority of software users do not read documentation, except as a last resort.
Users expect a program to be intuitive and provide context-sensitive documentation
where it is needed. In addition to the Help option on the main menu bar, a polished
program should provide assistance down to the individual controls on the form.
6.5 Adding Help to a Form 309

.NET offers multiple ways to configure an integrated help system:

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

Listing 6-7 Using ToolTips to Annotate an Image


public class StarMap:Form
{
public StarMap()
{
this.Text = "Star Search";
this.Width=400;
this.Height=220;
// Place image of constellation on form
this.BackgroundImage= new Bitmap(@"c:\dracoblack.gif");
// Add name of star on Label
Label star1 = new Label();
Star1.Location = new Point(285,115);
Star1.Size = new Size(60,16);
star1.Text = "Thuban";
star1.Tag = " Alpha Draconis\n> Magnitude: 3.67\n>"+
" 310 Light Years";
star1.Font = new Font(star.Font, star.Font.Style |
FontStyle.Bold);
this.Controls.Add(star1);
ToolTip toolTip1 = new ToolTip();
toolTip1.AutoPopDelay= 1500; // Tool tip displays
// for 1.5 secs.
// Tool tip text comes from Tag property of Label
toolTip1.SetToolTip(star1, star1.Tag.ToString());
// Add labels for other stars Etamin and Gianfar here ...
}
}

Core Note

To dynamically change a control’s tool tip value, you must get an


instance of the control’s ToolTip, execute its RemoveAll method, and
then use SetToolTip to reset the value of the tool tip string.
6.5 Adding Help to a Form 311

Responding to F1 and the Help Button


Many users regard the F1 key as a de facto way to invoke help. .NET provides
built-in F1 support by causing a Control.HelpRequested event to fire when the
user presses the F1 key. This event also fires when a user clicks the context-sensitive
Help button at the top of a form and then clicks on a control using the Help cursor.
See Figure 6-16.

Figure 6-16 The Help button

The Help button is displayed by setting these form properties:

• Set MinimizeBox and MaxmizeBox to false.


• Set HelpButton to true.

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.

this.date.HelpRequested += new HelpEventHandler(ShowHelp);


this.ssn.HelpRequested += new HelpEventHandler(ShowHelp);
this.ssn.Tag = "Enter as: nnn-nn-nnnn";
this.date.Tag = "Enter as: mm/dd/yyyy";

private void ShowHelp(object sender, HelpEventArgs e)


{
Control reqControl = (Control)sender;
// Technique 1: Use tag associated with control
MessageBox.Show(reqControl.Tag.ToString());
// Technique 2: Link to specific text within a CHM file
string anchor = "#"+reqControl.Name;
// ShowHelp invokes a compiled Help file
Help.ShowHelp(reqControl,@"c:\ctest.chm",HelpNavigator.Topic,
"customers.htm"+anchor);
e.Handled = true; // Always set this
}
312 Chapter 6 ■ Building Windows Forms Applications

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.

// URL may be .chm file or html file


public static void ShowHelp(Control parent, string url);

// HelpNavigator defines the type of .chm file to be displayed


public static void ShowHelp(Control parent, string url,
HelpNavigator navigator);

// Displays contents of Help file for a specified keyword


public static void ShowHelp(Control parent, string url,
string keyword);

// param is used with HelpNavigator.Topic to refine selection


public static void ShowHelp(Control parent, string url,
HelpNavigator navigator, object param);

The HelpNavigator enumeration specifies which part of a .chm file is displayed.


It’s values include TableofContents, Find, Index, and Topic. If you are unfamil-
iar with them, compiled Help files package multiple HTML files along with an
optional table of contents and keyword indexes. The downloadable Microsoft HTML
Help Workshop is the easiest way to learn how to use and create these files.

The HelpProvider Component


This component is a wrapper that is used primarily with Visual Studio.NET. Its
main value is that it eliminates the need to explicitly handle the HelpRequested
event. It is an extender that adds several properties to each control. These proper-
ties essentially correspond to the parameters in the ShowHelp method, which it
calls underneath.
6.6 Forms Inheritance 313

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:

1. ShowHelp. Set to true to make activate Help feature.


2. HelpNavigator. Takes the HelpNavigator enumeration value.
3. HelpKeyword. Corresponds to the param or keyword parameter in
ShowHelp.
4. HelpString. This contains a message that is displayed when the Help
button is used to click a control.

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.

6.6 Forms Inheritance


Just as a class inherits from a base class, a GUI form—which is also a class—can
inherit the settings, properties, and control layout of a preexisting form. This means
that you can create forms with standard features to serve as templates for derived
forms. Before looking at the details of inheritance, let’s first examine how to store a
set of base forms in a code library and organize them by namespace.

Building and Using a Forms Library


Each form consists of a physical .cs file. A library of multiple forms is created by
compiling each .cs file into a common .dll file. After this is done, the forms can be
accessed by any compliant language—not just the one they are written in.
As an example, let’s use the compiler from the command line to compile two
forms into a single .dll file:

csc /t:library product.cs customer.cs /out:ADCFormLib.dll

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:

// User Form derived from base class Form


class NewProductForm: Products.ProductForm
{

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.

csc /t:winexe /r:ADCFormLib.dll myApp.cs

Using the Inherited Form


If the derived form provides no additional code, it generates a form identical to its
base form when executed. Of course, the derived form is free to add controls and
supporting code. The only restriction is that menu items cannot be added to an exist-
ing menu; however, an entire menu can be added to the form and even replace an
existing one on the base form.
The properties of inherited controls can be changed, but their default access modi-
fier of private must first be changed to protected, and the base form then recom-
piled. The derived form is then free to make modifications: It may reposition the
control or even set its Visible property to false to keep it from being displayed.

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:

private void btn1_Clicked(object sender, System.EventArgs e)


{
ButtonClicks(); // Have virtual method do actual work
}
protected virtual void ButtonClicks()
{
this.Close();
}
6.7 Summary 315

The derived form simply overrides the virtual method and includes its own code
to handle the event:

protected override void ButtonClicks() {


// Code to perform any data validation
this.Close();
}

Creating Inherited Forms with Visual Studio.NET


To create an inherited form using Visual Studio, open a project containing the form
to serve as the base and compile it. Then, select Project-Add Inherited Form from
the menu. Give the new form a name and open it. Next, an Inheritance Picker dialog
box appears that lists the eligible base forms. Use the Browse feature to display forms
that are in external libraries.
Select a base form and the new inherited form appears in the designer window.
All of the controls on the form are marked with a plus (+), and you will find that only
properties on the form itself are exposed. At this point, you can add controls or mod-
ify existing ones using the techniques just described.

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

6.8 Test Your Understanding


1. From which class must a Windows Forms application inherit?

2. Describe the difference between anchoring and docking.

3. What MouseEventArgs properties are used to identify the mouse


coordinates and button clicked?

4. Which Form property is used to create an irregular shaped form?

5. What is the primary difference between a modal and modeless form?

6. How does creating an owner-owned form relationship affect the


behavior of the related forms?

7. What form properties must be set in order to display a Help button?

8. Compare using a Help button and a ToolTip.

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

• Introduction: A class hierarchy diagram offers a natural way to


group Windows Forms controls by their functionality.
• Button Controls: The Button, CheckBox, and RadioButton
controls are designed to permit users to make one or more
selections on a form.
• PictureBox and TextBoxt Controls: The PictureBox control is
used to display and scale images; the TextBox control can be
used to easily display and edit single or multiple lines of text.
• List Controls: The ListBox, ComboBox, and CheckListBox
offer different interfaces for displaying and manipulating data in a
list format.
• ListView and TreeView Controls: The ListView offers multiple
views for displaying data items and their associated icons. The
TreeView presents hierarchical information in an
easy-to-navigate tree structure.
• Timer and Progress Bar Controls: A timer can be used to control
when an event is invoked, a ProgressBar to visually monitor
the progress of an operation.
• Building a User Control: When no control meets an application’s
needs, a custom one can be crafted by combining multiple
controls or adding features to an existing one.
• Moving Data Between Controls: Drag and drop provides an easy
way for users to copy or move an item from one control to
another. .NET offers a variety of classes and events required to
implement this feature.
• Using Resources: Resources required by a program, such as title,
descriptive labels, and images, can be embedded within an
application’s assembly or stored in a satellite assembly. This is
particularly useful for developing international applications.
7

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.

7.1 A Survey of .NET Windows


Forms Controls
The System.Windows.Forms namespace contains a large family of controls that add
both form and function to a Windows-based user interface. Each control inherits a
common set of members from the Control class. To these, it adds the methods, prop-
erties, and events that give the control its own distinctive behavior and appearance.

319
320 Chapter 7 ■ Windows Forms Controls

<< >> Abstract class


Superseded by new control
Control *
Label
<< ButtonBase >>
<< ListControl >>
Button
ComboBox
CheckBox
ListBox
RadioButton
CheckedListbox
DataGridView
ListView
DataGrid
*
Splitter
<< TextBoxBase >>
TabControl
TextBox
ScrollableControl
RichTextBox
Panel
GroupBox
Flow/Table LayoutPanel
PictureBox
ToolStrip
StatusBar
* MenuStrip
ToolBar
* StatusStrip
TreeView

Figure 7-1 Windows Forms control hierarchy

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

Table 7-1 Selected Windows Forms Controls

Control Use Description

Button Fires an event when a Represents a button on a form. Its


mouse click occurs or the text property determines the caption
Enter or Esc key is displayed on the button’s surface.
pressed.

CheckBox Permits a user to select Consists of a check box with text or an


one or more options. image beside it. The check box can also
be represented as a button by setting:
checkBox1.Appearance =
Appearance.Button

CheckedListBox Displays list of items. ListBox with checkbox preceding


each item in list.

ComboBox Provides TextBox and Hybrid control that consists of a text-


ListBox functionality. box and a drop-down list. It combines
properties from both the TextBox and
the ListBox.

DataGridView Manipulates data in a grid The DataGridView is the foremost


GridView format. control to represent relational data.
It supports binding to a database.
The DataGridView was introduced
in .NET 2.0 and supersedes the
DataGrid.

GroupBox Groups controls. Use primarily to group radio buttons; it


places a border around the controls it
contains.

ImageList Manages a collection of Container control that holds a collec-


images. tion of images used by other controls
such as the ToolStrip, ListView,
and TreeView.

Label Adds descriptive informa- Text that describes the contents of a


tion to a form. control or instructions for using a con-
trol or form.

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

Table 7-1 Selected Windows Forms Controls (continued)

Control Use Description

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.

MenuStrip Adds a menu to a form. Provides a menu and submenu system


for a form. It supersedes the Main-
Menu control.

Panel Groups controls. A visible or invisible container


FlowPanelLayout that groups controls. Can be made
TablePanelLayout scrollable.
FlowPanelLayout automatically aligns
controls vertically or horizontally.
TablePanelLayout aligns controls in
a grid.

PictureBox Contains a graphic. Used to hold images in a variety of


standard formats. Properties enable
images to be positioned and sized
within control’s borders.

ProgressBar Depicts an application’s Displays the familiar progress bar that


progress. gives a user feedback regarding the
progress of some event such as file
copying.

RadioButton Permits user to make one Represents a Windows radio button.


choice among a group of
options.

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.

TextBox Accepts user input. Can be designed to accept single- or


multi-line input. Properties allow it to
mask input for passwords, scroll, set
letter casing automatically, and limit
contents to read-only.

TreeView Displays data as nodes in a Features include the ability to collapse


tree. or expand, add, remove, and copy
nodes in a tree.
7.2 Button Classes, Group Box, Panel, and Label 323

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

7.2 Button Classes, Group Box,


Panel, and Label

The Button Class


A button is the most popular way to enable a user to initiate some program action.
Typically, the button responds to a mouse click or keystroke by firing a Click event
that is handled by an event handler method that implements the desired response.

constructor: public Button()

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.

Setting a Button’s Appearance


Button styles in .NET are limited to placing text and an image on a button, making it
flat or three-dimensional, and setting the background/foreground color to any avail-
able color. The following properties are used to define the appearance of buttons,
check boxes, and radio buttons:

FlatStyle This can take four values: FlatStyle.Flat, FlatStyle.Popup,


FlatStyle.Standard, and FlatStyle.System. Standard is the
usual three-dimensional button. Flat creates a flat button. Popup
creates a flat button that becomes three-dimensional on a mouse-
over. System results in a button drawn to suit the style of the operat-
ing system.
Image Specifies the image to be placed on the button. The Image.From-
File method is used to create the image object from a specified file:
button1.Image = Image.FromFile("c:\\book.gif");
324 Chapter 7 ■ Windows Forms Controls

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.

Handling Button Events


A button’s Click event can be triggered in several ways: by a mouse click of the but-
ton, by pressing the Enter key or space bar, or by pressing the Alt key in combination
with an access key. An access key is created by placing an & in front of one of the
characters in the control’s Text property value.
The following code segment declares a button, sets its access key to C, and regis-
ters an event handler to be called when the Click event is triggered:

Button btnClose = new Button();


btnClose.Text= "&Close"; // Pushing ALT + C triggers event
btnClose.Click += new EventHandler(btnClose_Clicked);
// Handle Mouse Click, ENTER key, or Space Bar
private void btnClose_Clicked(object sender, System.EventArgs e)
{ this.Close(); }

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

Set a form’s CancelButton property to a button whose Click event


handler closes the form. This provides a convenient way for users to
close a window by pushing the Esc key.

The CheckBox Class


The CheckBox control allows a user to select a combination of options on a form—in
contrast to the RadioButton, which allows only one selection from a group.

constructor: public CheckBox()

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

Setting a CheckBox’s Appearance


Check boxes can be displayed in two styles: as a traditional check box followed by text
(or an image) or as a toggle button that is raised when unchecked and flat when
checked. The appearance is selected by setting the Appearance property to
Appearance.Normal or Appearance.Button. The following code creates the two
check boxes shown in Figure 7-2.

// Create traditional check box


this.checkBox1 = new CheckBox();
this.checkBox1.Location =
new System.Drawing.Point(10,120);
this.checkBox1.Text = "La Traviata";
this.checkBox1.Checked = true;
// Create Button style check box
this.checkBox2 = new CheckBox();
this.checkBox2.Location =
new System.Drawing.Point(10,150);
this.checkBox2.Text = "Parsifal";
this.checkBox2.Appearance = Appearance.Button;
this.checkBox2.Checked = true;
this.checkBox2.TextAlign = ContentAlignment.MiddleCenter;

Figure 7-2 CheckBox styles

The RadioButton Class


The RadioButton is a selection control that functions the same as a check box
except that only one radio button within a group can be selected. A group consists of
multiple controls located within the same immediate container.

constructor: public RadioButton()

The constructor creates an unchecked RadioButton with no associated text. The


Text and Image properties allow the placement of an optional text description or
image beside the box. A radio button’s appearance is defined by the same properties
used with the check box and button: Appearance and FlatStyle.
326 Chapter 7 ■ Windows Forms Controls

Placing Radio Buttons in a Group


Radio buttons are placed in groups that allow only one item in the group to be
selected. For example, a 10-question multiple choice form would require 10 groups
of radio buttons. Aside from the functional need, groups also provide an opportunity
to create an aesthetically appealing layout.
The frequently used GroupBox and Panel container controls support back-
ground images and styles that can enhance a form’s appearance. Figure 7-3 shows the
striking effect (even more so in color) that can be achieved by placing radio buttons
on top of a GroupBox that has a background image.

Figure 7-3 Radio buttons in a GroupBox that has a background image

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.

Listing 7-1 Placing Radio Buttons in a GroupBox


using System.Drawing;
using System.Windows.Forms;
public class OperaForm : Form
{
private RadioButton radioButton1;
private RadioButton radioButton2;
private RadioButton radioButton3;
private GroupBox groupBox1;
public OperaForm()
{
this.groupBox1 = new GroupBox();
this.radioButton3 = new RadioButton();
this.radioButton2 = new RadioButton();
7.2 Button Classes, Group Box, Panel, and Label 327

Listing 7-1 Placing Radio Buttons in a GroupBox (continued)


this.radioButton1 = new RadioButton();
// All three radio buttons are created like this
// For brevity only code for one button is included
this.radioButton3.BackColor = Color.Transparent;
this.radioButton3.Font = new Font("Microsoft Sans Serif",
8.25F, FontStyle.Bold);
this.radioButton3.ForeColor =
SystemColors.ActiveCaptionText;
this.radioButton3.Location = new Point(16, 80);
this.radioButton3.Name = "radioButton3";
this.radioButton3.Text = "Parsifal";
// Group Box
this.groupBox1 = new GroupBox();
this.groupBox1.BackgroundImage =
Image.FromFile("C:\\opera.jpg");
this.groupBox1.Size = new Size(120, 112);
// Add radio buttons to groupbox
groupBox1.Add( new Control[]{radioButton1,radiobutton2,
radioButton3});
}
}

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


A GroupBox is a container control that places a border around its collection of con-
trols. As demonstrated in the preceding example, it is often used to group radio but-
tons; but it is also a convenient way to organize and manage any related controls on a
form. For example, setting the Enabled property of a group box to false disables
all controls in the group box.

constructor: public GroupBox()

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

The Panel Class


The Panel control is a container used to group a collection of controls. It’s closely
related to the GroupBox control, but as a descendent of the ScrollableControl
class, it adds a scrolling capability.

constructor: public Panel()

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.

A Panel offers no features to assist in positioning or aligning the controls it con-


tains. For this reason, it is best used when the control layout is known at design time.
But this is not always possible. Many applications populate a form with controls
based on criteria known only at runtime. To support the dynamic creation of con-
trols, .NET offers two layout containers that inherit from Panel and automatically
position controls within the container: the FlowLayoutPanel and the TableLay-
outPanel.

The FlowLayoutPanel Control


Figure 7-4 shows the layout of controls using a FlowLayoutPanel.

Figure 7-4 FlowLayoutPanel

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

are to be added to the container, and a WrapControls property that indicates


whether child controls are rendered on another row or truncated.
The following code creates a FlowLayoutPanel and adds controls to its collec-
tion:

FlowLayoutPanel flp = new FlowLayoutPanel();


flp.FlowDirection = FlowDirection.LefttoRight;
// Controls are automatically positioned left to right
flp.Controls.Add(Button1);
flp.Controls.Add(Button2);
flp.Controls.Add(TextBox1);
flp.Controls.Add(Button3);
this.Controls.Add(flp); // Add container to form

The FlowDirection enumerator members are BottomUp, LeftToRight,


RighttoLeft, and TopDown. LefttoRight is the default.

TableLayoutPanel Control
Figure 7-5 shows the grid layout that results from using a TableLayoutPanel
container.

Figure 7-5 TableLayoutPanel organizes controls in a grid

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.

TableLayoutPanel tlp = new TableLayoutPanel();


// Causes the inset around each cell
tlp.CellBorderStyle = TableLayoutPanelCellBorderStyle.Inset;
tlp.ColumnCount = 2; // Grid has two columns
tlp.RowCount = 2; // Grid has two rows
// If grid is full add extra cells by adding column
tlp.GrowStyle = TableLayoutPanelGrowStyle.AddColumns;
// Padding (pixels)within each cell (left, top, right, bottom)
330 Chapter 7 ■ Windows Forms Controls

tlp.Padding = new Padding(1,1,4,5);


tlp.Controls.Add(Button1);
tlp.Controls.Add(Button2);
// Other controls added here

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


The Label class is used to add descriptive information to a form.

constructor: public Label()

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.

Figure 7-6 Label containing an image and text

The following code creates the label shown in Figure 7-6:

Label imgLabel = new Label();


imgLabel.BackColor= Color.White;
Image img = Image.FromFile("c:\\rembrandt.jpg");
imgLabel.Image= img;
imgLabel.ImageAlign= ContentAlignment.TopCenter;
imgLabel.Text="Rembrandt";
imgLabel.TextAlign= ContentAlignment.BottomCenter;
imgLabel.BorderStyle= BorderStyle.Fixed3D;
imgLabel.Size = new Size(img.Width+10, img.Height+25);
7.3 PictureBox and TextBox Controls 331

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.

7.3 PictureBox and TextBox Controls

The PictureBox Class


The PictureBox control is used to display images having a bitmap, icon, metafile,
JPEG, GIF, or PNG format. It is a dynamic control that allows images to be selected
at design time or runtime, and permits them to be resized and repositioned within
the control.

constructor: public PictureBox()

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:

1. AutoSize. PictureBox is sized to equal the image.


2. CenterImage. Image is centered in box and clipped if necessary.
3. Normal. Image is place in upper-left corner and clipped if necessary.
4. StretchImage. Image is stretched or reduced to fit in box.

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

Figure 7-7 Thumbnail images in small picture boxes are displayed


at full size in a larger viewing window

Listing 7-2 Working with Picture Boxes


using System;
using System.Drawing;
using System.Windows.Forms;
public class ArtForm : Form
{
private PictureBox bigPicture;
private PictureBox tn1;
private PictureBox tn2;
private PictureBox tn3;
private Button btnClear;
public ArtForm()
{
bigPicture = new PictureBox();
tn1 = new PictureBox();
tn2 = new PictureBox();
tn3 = new PictureBox();
btnClear = new Button();
bigPicture.Location = new Point(90, 30);
bigPicture.Name = "bigPicture";
bigPicture.Size = new Size(160, 160);
this.Controls.Add(bigPicture);
7.3 PictureBox and TextBox Controls 333

Listing 7-2 Working with Picture Boxes (continued)


// Define picturebox to hold first thumbnail image
tn1.BorderStyle = BorderStyle.FixedSingle;
tn1.Cursor = Cursors.Hand;
tn1.Image = Image.FromFile("C:\\schiele1.jpg");
tn1.Location = new Point(8, 16);
tn1.Name = "tn1";
tn1.Size = new Size(56, 56);
tn1.SizeMode = PictureBoxSizeMode.StretchImage;
this.Controls.Add(tn1);
// Code for other thumbnails would go here
// Button to clear picture box
btnClear.Location = new Point(136, 192);
btnClear.Name = "btnClear";
btnClear.Size = new Size(88, 24);
btnClear.Text = "Clear Image";
this.Controls.Add(btnClear);
btnClear.Click += new EventHandler(this.btnClear_Click);
// Set up event handlers for double click events
tn1.DoubleClick += new EventHandler(ShowPic);
tn2.DoubleClick += new EventHandler(ShowPic);
tn3.DoubleClick += new EventHandler(ShowPic);
}
static void Main()
{
Application.Run(new ArtForm());
}
private void btnClear_Click(object sender, EventArgs e)
{
bigPicture.Image = null; // Clear image
}
private void ShowPic (object sender, EventArgs e)
{
// Sender is thumbnail image that is double clicked
bigPicture.Image = ((PictureBox) sender).Image;
}
}

The TextBox Class


The familiar TextBox is an easy-to-use control that has several properties that affect
its appearance, but few that control its content. This leaves the developer with the
task of setting up event handlers and data verification routines to control what is
entered in the box.
334 Chapter 7 ■ Windows Forms Controls

constructor: public TextBox()

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.

Figure 7-8 TextBox properties

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.

TextBoxes and Carriage Returns


When storing data from a TextBox into a database, you want to make sure there are
no special characters embedded in the text, such as a carriage return. If you look at
the TextBox properties, you’ll find AcceptsReturn, which looks like a simple solu-
tion. Setting it to false should cause a TextBox to ignore the user pressing an Enter
key. However, the name of this property is somewhat misleading. It only works when
the form’s AcceptButton property is set to a button on the form. Recall that this
property causes the associated button’s Click handler to be executed when the
Enter key is pressed. If AcceptButton is not set (and the MultiLine property of
the text box is set to true), the TextBox receives a newline (\r\n) when the Enter
key is pushed.
7.4 ListBox, CheckedListBox, and ComboBox Classes 335

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.

// Set up event handler in constructor for TextBox txtPoetry


txtPoetry.KeyPress += new KeyPressEventHandler(onKeyPress);

private void onKeyPress( object sender, KeyPressEventArgs e)


{
if(e.KeyChar == (char)13) e.Handled = true;
}

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.

txtPoetry.Text = txtPoetry.Text.Replace(Environment.NewLine," ");

Core Note

Two common approaches for entering a carriage return/linefeed


programmatically into a TextBox are
txtPoetry.Text = "Line 1\r\nLine 2";
txtPoetry.Text = "Line 1"+Environment.NewLine+"Line 2";

7.4 ListBox, CheckedListBox,


and ComboBox Classes

The ListBox Class


The ListBox control is used to provide a list of items from which the user may select
one or more items. This list is typically text but can also include images and objects.
Other features of the ListBox include methods to perform text-based searches,
sorting, multi-column display, horizontal and vertical scroll bars, and an easy way to
override the default appearance and create owner-drawn ListBox items.
336 Chapter 7 ■ Windows Forms Controls

constructor: public ListBox()

The constructor creates an empty ListBox. The code to populate a ListBox is


typically placed in the containing form’s constructor or Form.Load event handler. If
the ListBox.Sorted property is set to true, ListBox items are sorted alphabeti-
cally in ascending order. Also, vertical scroll bars are added automatically if the con-
trol is not long enough to display all items.

Adding Items to a ListBox


A ListBox has an Items collection that contains all elements of the list. Elements
can be added by binding the ListBox to a data source (described in Chapter 11,
“ADO.NET”) or manually by using the Add method. If the Sorted property is
false, the items are listed in the order they are entered. There is also an Insert
method that places an item at a specified location.

lstArtists.Items.Add("Monet");
lstArtists.Items.Add("Rembrandt");
lstArtists.Items.Add("Manet");
lstArtists.Items.Insert(0, "Botticelli"); //Place at top

Core Note

To prevent a ListBox from repainting itself each time an item is added,


execute the ListBox.BeginUpdate method prior to adding and
ListBox.EndUpdate after the last item is added.

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:

// Instances of this class will be placed in a ListBox


public class Artist
{
public string BDate, DDate, Country;
private string firstname;
private string lastname;
public Artist(string birth, string death, string fname,
string lname, string ctry)
{
BDate = birth;
7.4 ListBox, CheckedListBox, and ComboBox Classes 337

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

Selecting and Searching for Items in a ListBox


The SelectionMode property determines the number of items a ListBox allows to
be selected at one time. It takes four values from the SelectionMode enumeration:
None, Single, MultiSingle, and MultiExtended. MultiSingle allows selection
by clicking an item or pressing the space bar; MultiExtended permits the use of the
Shift and Ctrl keys.
The SelectedIndexChanged event provides an easy way to detect when an item
in a ListBox is selected. It is fired when the user clicks on an item or uses the arrow
keys to traverse a list. A common use is to display further information about the
selection in other controls on the form. Here is code that displays an artist’s dates of
birth and death when the artist’s name is selected from the ListBox in Figure 7-9:

// Set up event handler in constructor


lstArtists.SelectedIndexChanged += new EventHandler(ShowArtist);
//
private void ShowArtist(object sender, EventArgs e)
{
// Cast to artist object in order to access properties
Artist myArtist = lstArtists.SelectedItem as Artist;
if (myArtist != null) {
txtBirth.Text = myArtist.Dob; // Place dates in text boxes
txtDeath.Text = myArtist.Dod;
}
}

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:

myArtist = lstArtists.Items[lstArtists.SelectedIndex] as Artist;

Working with a multi-selection ListBox requires a different approach. You typi-


cally do not want to respond to a selection event until all items have been selected.
One approach is to have the user click a button to signal that all choices have been
made and the next action is required. All selections are exposed as part of the
SelectedItems collection, so it is an easy matter to enumerate the items:

foreach (Artist a in lstArtists.SelectedItems)


MessageBox.Show(a.GetLName);

The SetSelected method provides a way to programatically select an item or items


in a ListBox. It highlights the item(s) and fires the SelectedIndexChanged event. In
this example, SetSelected is used to highlight all artists who were born in France:
7.4 ListBox, CheckedListBox, and ComboBox Classes 339

lstArtists.ClearSelected(); // Clear selected items


for (int ndx =0; ndx < lstArtists.Items.Count-1; ndx ++)
{
Artist a = lstArtists.Items[ndx] as Artist;
if (a.country == "Fr") lstArtists.SetSelected(ndx,true);
}

Customizing the Appearance of a ListBox


The ListBox, along with the ComboBox, MenuItem, and TabControl controls, is an
owner-drawn control. This means that by setting a control property, you can have it
fire an event when the control’s contents need to be drawn. A custom event handler
takes care of the actual drawing.
To enable owner drawing of the ListBox, the DrawMode property must be set to
one of two DrawMode enumeration values: OwnerDrawFixed or OwnerDrawVari-
able. The former draws each item a fixed size; the latter permits variable-sized
items. Both of these cause the DrawItem event to be fired and rely on its event han-
dler to perform the drawing.
Using the ListBox from the previous example, we can use the constructor to set
DrawMode and register an event handler for the DrawItem event:

lstArtists.DrawMode = DrawMode.OwnerDrawFixed;
lstArtists.ItemHeight = 16; // Height (pixels) of item
lstArtists.DrawItem += new DrawItemEventHandler(DrawList);

The DrawItemEventHandler delegate has two parameters: the familiar sender


object and the DrawItemEventArgs object. The latter is of more interest. It con-
tains properties related to the control’s appearance and state as well as a couple of
useful drawing methods. Table 7-2 summarizes these.

Table 7-2 DrawItemEventArgs Properties

Member Description

BackColor Background color assigned to the control.

Bounds Defines the coordinates of the item to be drawn as a


Rectangle object.

Font Returns the font assigned to the item being drawn.

ForeColor Foreground color of the control. This is the color of the text
displayed.

Graphics Represents the surface (as a Graphics object) on which the


drawing occurs.
340 Chapter 7 ■ Windows Forms Controls

Table 7-2 DrawItemEventArgs Properties (continued)

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

DrawBackground() Draws the default background.

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.

Listing 7-3 Event Handler to Draw Items in a ListBox


private void DrawList(object sender, DrawItemEventArgs e)
{
// Draw ListBox Items
string ctry;
Rectangle rect = e.Bounds;
Artist a = lstArtists.Items[e.Index] as Artist;
string artistName = a.ToString();
if ( (e.State & DrawItemState.Selected) ==
DrawItemState.Selected )
{
7.4 ListBox, CheckedListBox, and ComboBox Classes 341

Listing 7-3 Event Handler to Draw Items in a ListBox (continued)


// Draw Black border around the selected item
e.Graphics.DrawRectangle(Pens.Black,rect);
} else {
ctry = a.Country;
Brush b; // Object used to define backcolor
// Each country will have a different backcolor
b = Brushes.LightYellow; // Netherlands
if (ctry == "Fr") b = Brushes.LightGreen;
if (ctry == "It") b = Brushes.Yellow;
e.Graphics.FillRectangle(b,rect);}
e.Graphics.DrawString(artistName,e.Font,
Brushes.Black,rect);
}
}

Other List Controls: the ComboBox


and the CheckedListBox
The ComboBox control is a hybrid control combining a ListBox with a TextBox (see
Figure 7-10). Like the ListBox, it derives from the ListControl and thus pos-
sesses most of the same properties.

Figure 7-10 ComboBox and CheckedListBox controls are variations on ListBox

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

ComboBox cbArtists = new ComboBox();


cbArtists.Size = new System.Drawing.Size(120, 21);
cbArtists.MaxDropDownItems= 4; // Max number of items to display
cbArtists.DropDownWidth = 140; // Width of drop-down portion
cbArtists.Items.Add(new Artist("1832", "1883",
"Edouard", "Manet","Fr" ));
// Add other items here...

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:

// List all items with checked box.


foreach (Artist a in clBox.CheckedItems)
MessageBox.Show(a.ToString()); // –> Monet, Claude

You can also iterate through the collection and explicitly determine the checked
state:

For (int i=0; I< clBox.Items.Count; i++)


{
if(clBox.GetItemCheckState(i) == CheckState.Checked)
{ Do something } else {do something if not checked }
}

7.5 The ListView and TreeView Classes

The ListView Class


ListView is another control that displays lists of information. It represents data rela-
tionally as items and subitems. The data can be represented in a variety of formats
that include a multi-column grid and large or small icons to represent item data.
Also, images and check boxes can adorn the control.
Figure 7-11 illustrates the basic properties and methods used to lay out a Details
view of the control—a format obviously tailored to displaying database tables. The
first column contains text for an item—as well as a picture—the remaining columns
contain subitems for the parent item.
7.5 The ListView and TreeView Classes 343

Figure 7-11 ListView control

Let’s look at how this style of the ListView is constructed.

Creating a ListView Object


The ListView is created with a parameterless constructor:

ListView listView1 = new ListView();

Define Appearance of ListView Object


// Set the view to show details
listView1.View = View.Details;

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

The ListView.View property can be changed at runtime to switch


among the possible views. In fact, you may recognize that the view
options correspond exactly to the View menu options available in
Windows Explorer.

After the Details view is selected, other properties that define the control’s
appearance and behavior are set:

// Allow the user to rearrange columns


listView1.AllowColumnReorder = true;
// Select the entire row when selection is made
listView1.FullRowSelect = true;
// Display grid lines
listView1.GridLines = true;
// Sort the items in the list in ascending order
listView1.Sorting = SortOrder.Ascending;

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.

Set Column Headers


In a Details view, data is not displayed until at least one column is added to the
control. Add columns using the Columns.Add method. Its simplest form is

ListView.Columns.Add(caption, width, textAlign)

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.

// Create column headers for the items and subitems


listView1.Columns.Add("Artist", -2, HorizontalAlignment.Left);
listView1.Columns.Add("Born", -2, HorizontalAlignment.Left);
listView1.Columns.Add("Died", -2, HorizontalAlignment.Left);
listView1.Columns.Add("Country", -2, HorizontalAlignment.Left);

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

Create ListView Items


Several overloaded forms of the ListView constructor are available. They can be
used to create a single item or a single item and its subitems. There are also options
to specify the icon associated with the item and set the foreground and background
colors.

Constructors:

public ListViewItem(string text);


public ListViewItem(string[] items );
public ListViewItem(string text,int imageIndex );
public ListViewItem(string[] items,int imageIndex );
public ListViewItem(string[] items,int imageIndex,
Color foreColor,Color backColor,Font font);

The following code demonstrates how different overloads can be used to create
the items and subitems shown earlier in Figure 7-8:

// Create item and three subitems


ListViewItem item1 = new ListViewItem("Manet",2);
item1.SubItems.Add("1832");
item1.SubItems.Add("1883");
item1.SubItems.Add("France");
// Create item and subitems using a constructor only
ListViewItem item2 = new ListViewItem
(new string[] {"Monet","1840","1926","France"}, 3);
// Create item and subitems with blue background color
ListViewItem item3 = new ListViewItem
(new string[] {"Cezanne","1839","1906","France"}, 1,
Color.Empty, Color.LightBlue, null);

To display the items, add them to the Items collection of the ListView control:

// Add the items to the ListView


listView1.Items.AddRange(
new ListViewItem[]{item1,item2,item3,item4,item5});

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:

// Create two ImageList objects


ImageList imageListSmall = new ImageList();
ImageList imageListLarge = new ImageList();
imageListLarge.ImageSize = new Size(50,50); // Set image size
// Initialize the ImageList objects
// Can use same images in both collections since they're resized
imageListSmall.Images.Add(Bitmap.FromFile("C:\\botti.gif"));
imageListSmall.Images.Add(Bitmap.FromFile("C:\\cezanne.gif"));
imageListLarge.Images.Add(Bitmap.FromFile("C:\\botti.gif"));
imageListLarge.Images.Add(Bitmap.FromFile("C:\\cezanne.gif"));
// Add other images here
// Assign the ImageList objects to the ListView.
listView1.LargeImageList = imageListLarge;
listView1.SmallImageList = imageListSmall;
ListViewItem lvItem1 = new ListViewItem("Cezanne",1);

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;

Figure 7-12 LargeIcon view


7.5 The ListView and TreeView Classes 347

Working with the ListView Control


Common tasks associated with the ListView control include iterating over the con-
tents of the control, iterating over selected items only, detecting the item that has
focus, and—when in Details view—sorting the items by any column. Following are
some code segments to perform these tasks.

Iterating over All Items or Selected Items


You can use foreach to create nested loops that select an item and then iterate
through the collection of subitems for the item in the outside loop:

foreach (ListViewItem lvi in listView1.Items)


{
string row = "";
foreach(ListViewItem.ListViewSubItem sub in lvi.SubItems)
{
row += " " + sub.Text;
}
MessageBox.Show(row); // List concatenated subitems
}

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:

foreach (ListViewItem lvisel in listView1.SelectedItems)

Detecting the Currently Selected Item


In addition to the basic control events such as Click and DoubleClick, the List-
View control adds a SelectedIndexChanged event to indicate when focus is
shifted from one item to another. The following code implements an event handler
that uses the FocusedItem property to identify the current item:

// Set this in the constructor


listView1.SelectedIndexChanged +=
new EventHandler(lv_IndexChanged);
// Handle SelectedIndexChanged Event
private void lv_IndexChanged(object sender, System.EventArgs e)
348 Chapter 7 ■ Windows Forms Controls

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

private void listView1_MouseDown(object sender, MouseEventArgs e)


{
ListViewItem selection = listView1.GetItemAt(e.X, e.Y);
if (selection != null)
{
MessageBox.Show("Item Selected: "+selection.Text);
}
}

The ListView.GetItemAt method returns an item at the coordinates where the


mouse button is pressed. If the mouse is not over an item, null is returned.

Sorting Items on a ListView Control


Sorting items in a ListView control by column values is a surprisingly simple feature
to implement. The secret to its simplicity is the ListViewItemSorter property that
specifies the object to sort the items anytime the ListView.Sort method is called.
Implementation requires three steps:

1. Set up a delegate to connect a ColumnClick event with an event


handler.
2. Create an event handler method that sets the ListViewItemSorter
property to an instance of the class that performs the sorting compari-
son.
3. Create a class to compare column values. It must inherit the
IComparer interface and implement the IComparer.Compare
method.

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

// Connect the ColumnClick event to its event handler


listView1.ColumnClick +=new ColumnClickEventHandler(ColumnClick);
// ColumnClick event handler
private void ColumnClick(object o, ColumnClickEventArgs e)
7.5 The ListView and TreeView Classes 349

{
// 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);
}
}

The TreeView Class


As the name implies, the TreeView control provides a tree-like view of hierarchical
data as its user interface. Underneath, its programming model is based on the famil-
iar tree structure consisting of parent nodes and child nodes. Each node is imple-
mented as a TreeNode object that can in turn have its own Nodes collection. Figure
7-13 shows a TreeView control that is used in conjunction with a ListView to dis-
play enum members of a selected assembly. (We’ll look at the application that creates
it shortly.)

The TreeNode Class


Each item in a tree is represented by an instance of the TreeNode class. Data is asso-
ciated with each node using the TreeNode’s Text, Tag, or ImageIndex properties.
The Text property holds the node’s label that is displayed in the TreeView control.
Tag is an object type, which means that any type of data can be associated with the
node by assigning a custom class object to it. ImageIndex is an index to an Image-
List associated with the containing TreeView control. It specifies the image to be
displayed next to the node.
350 Chapter 7 ■ Windows Forms Controls

Figure 7-13 Using TreeView control (left) and ListView (right) to list enum values

In addition to these basic properties, the TreeNode class provides numerous


other members that are used to add and remove nodes, modify a node’s appearance,
and navigate the collection of nodes in a node tree (see Table 7-3).

Table 7-3 Selected Members of the TreeNode Class

Use Member Description

Appearance BackColor, Sets the background color and text color of the
ForeColor node.

Expand(), Expands the node to display child nodes or col-


Collapse() lapses the tree so no child nodes are shown.

Navigation FirstNode, Returns the first or last node in the collection.


LastNode, Returns the next or previous node (sibling) rel-
NextNode, ative to the current node.
PrevNode

Index The index of the current node in the collection.

Parent Returns the current node’s parent.

Node Nodes.Add(), Adds or removes a node to a Nodes collection.


Manipulation Nodes.Remove(), Insert adds a node at an indexed location, and
Nodes.Insert(), Clear removes all tree nodes from the collec-
Nodes.Clear() tion.

Clone() Copies a tree node and entire subtree.


7.5 The ListView and TreeView Classes 351

Let’s look at how TreeView and TreeNode members are used to perform funda-
mental TreeView operations.

Adding and Removing Nodes


The following code creates the tree in Figure 7-14 using a combination of Add,
Insert, and Clone methods. The methods are performed on a preexisting
treeView1 control.

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

Figure 7-14 TreeView node representation

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:

// Clone first parent node and add to node B


TreeNode clNode = (TreeNode) treeView1.Nodes[0].Clone();
tNode.Nodes.Add(clNode);
// Add and remove node for demonstration purposes
tNode.Nodes.Add("G");
tNode.Nodes.Remove(tNode.LastNode);
352 Chapter 7 ■ Windows Forms Controls

Iterating Through the Nodes in a TreeView


As with any collection, the foreach statement provides the easiest way to loop
through the collection’s members. The following statements display all the top-level
nodes in a control:

foreach (TreeNode tn in treeView1.Nodes)


{
MessageBox.Show(tn.Text);
// If (tn.IsVisible) true if node is visible
// If (tn.IsSelected) true if node is currently selected
}

An alternate approach is to move through the collection using the Tree-


Node.NextNode property:

tNode = treeView1.Nodes[0];
while (tNode != null) {
MessageBox.Show(tNode.Text);
tNode = tNode.NextNode;
}

Detecting a Selected Node


When a node is selected, the TreeView control fires an AfterSelect event that
passes a TreeViewEventArgs parameter to the event handling code. This parame-
ter identifies the action causing the selection and the node selected. The TreeView
example that follows illustrates how to handle this event.
You can also handle the MouseDown event and detect the node using the Get-
NodeAt method that returns the node—if any—at the current mouse coordinates.

private void treeView1_MouseDown(object sender, MouseEventArgs e)


{
TreeNode tn = treeView1.GetNodeAt(e.X, e.Y);
// You might want to remove the node: tn.Remove()
}

A TreeView Example That Uses Reflection


This example demonstrates how to create a simple object browser (refer to Figure
7-13) that uses a TreeView to display enumeration types for a specified assembly.
When a node on the tree is clicked, the members for the selected enumeration are
displayed in a ListView control.
Information about an assembly is stored in its metadata, and .NET provides
classes in the System.Reflection namespace for exposing this metadata. The code
in Listing 7-4 iterates across the types in an assembly to build the TreeView. The
7.5 The ListView and TreeView Classes 353

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.

Using a TreeView and Reflection to List Enums in an


Listing 7-4
Assembly
using System.Reflection;
//
private void GetEnums()
{
TreeNode tNode=null;
Assembly refAssembly ;
Hashtable ht= new Hashtable(); // Keep track of namespaces
string assem = AssemName.Text; // Textbox with assembly name
tvEnum.Nodes.Clear(); // Remove all nodes from tree
// Load assembly to be probed
refAssembly = Assembly.Load(assem);
foreach (Type t in refAssembly.GetTypes())
{
// Get only types that inherit from System.Enum
if(t.BaseType!=null && t.BaseType.FullName=="System.Enum")
{
string myEnum = t.FullName;
string nSpace =
myEnum.Substring(0,myEnum.LastIndexOf("."));
myEnum= myEnum.Substring(myEnum.LastIndexOf(".")+1) ;
// Determine if namespace in hashtable
if( ht.Contains(nSpace))
{
// Find parent node representing this namespace
foreach (TreeNode tp in tvEnum.Nodes)
{
if(tp.Text == myEnum) { tNode=tp; break;}
}
}
else
{
// Add parent node to display namespace
tNode = tvEnum.Nodes.Add(nSpace);
ht.Add(nSpace,nSpace);
}
354 Chapter 7 ■ Windows Forms Controls

Using a TreeView and Reflection to List Enums in an


Listing 7-4
Assembly (continued)
// Add Child - name of enumeration
TreeNode cNode = new TreeNode();
cNode.Text= myEnum;
cNode.Tag = t; // Contains specific enumeration
tNode.Nodes.Add(cNode);
}
}
}

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

if(tn.Parent !=null) // Exclude parent nodes


{
lView.Items.Clear(); // Clear ListView before adding items
Type cNode = (Type) tn.Tag;
// Use Reflection to iterate members in a Type
foreach (MemberInfo mi in cNode.GetMembers())
{
if(mi.MemberType==MemberTypes.Field &&
mi.Name != "value__" ) // skip this
{
lView.Items.Add(mi.Name);
}
}
}
}

7.6 The ProgressBar, Timer,


and StatusStrip Classes
The ProgressBar and Timer are lightweight controls that have complementary
roles in an application: The Timer initiates action and the ProgressBar reflects the
status of an operation or action. In fact, the Timer is not a control, but a component
that inherits from the ComponentModel.Component class. It is used most often in
processes to regulate some background activity. This may be a periodic update to a
log file or a scheduled backup of data. A ProgressBar, on the other hand, provides
visual feedback regarding the progress of an operation—such as file copying or steps
in an installation.
The third class discussed in this section is the StatusStrip, which is often used
in conjunction with a timer and ProgressBar. It’s rendered on a form as a strip
divided into one or more sections or panes that provide status information. Each sec-
tion is implemented as a control that is added to the StatusStrip container. For a
control to be included in the StatusStrip, it must inherit from the ToolStrip-
Item class.

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.

Figure 7-15 StatusStrip with Label, ProgressBar, and Panel

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.

StatusStrip That Uses a ProgressBar


Listing 7-5
and Timer
// These variables have class scope
Timer currTimer;
StatusStrip statusStrip1;
StatusStripPanel panel1;
ToolStripProgressBar pb;
DateTime startDate = DateTime.Now;

private void BuildStrip()


{
currTimer = new Timer();
currTimer.Enabled = true;
currTimer.Interval = 5000; // Fire tick event every 5 seconds
currTimer.Tick += new EventHandler(timer_Tick);
// Panel to contain elapsed time
7.6 The ProgressBar, Timer, and StatusStrip Classes 357

StatusStrip That Uses a ProgressBar


Listing 7-5
and Timer (continued)
panel1 = new StatusStripPanel();
panel1.BorderStyle = Border3DStyle.Sunken;
panel1.Text = "00:00:00";
panel1.Padding = new Padding(2);
panel1.Name = "clock";
panel1.Alignment = ToolStripItemAlignment.Tail; //Right align
// Label to display application status
ToolStripLabel ts = new ToolStripLabel();
ts.Text = "Running...";
// ProgressBar to show time elapsing
pb = new ToolStripProgressBar();
pb.Step = 10; // Size of each step or increment
pb.Minimum = 0;
pb.Maximum = 120; // Allow 12 steps
// Status strip to contain components
statusStrip1 = new StatusStrip();
statusStrip1.Height = 20;
statusStrip1.AutoSize = true;
// Add components to strip
statusStrip1.Items.AddRange(new ToolStripItem[] {
ts, pb, panel1 } );
this.Controls.Add(statusStrip1);
}
private void timer_Tick(object sender, EventArgs e)
{
// Get difference between current datetime
// and form startup time
TimeSpan ts = DateTime.Now.Subtract(startDate);
string elapsed = ts.Hours.ToString("00") + ":" +
ts.Minutes.ToString("00") +
":" + ts.Seconds.ToString("00");
((StatusStripPanel)statusStrip1.Items[
"clock"]).Text= elapsed;
// Advance progress bar
if (pb.Value == pb.Maximum) pb.Value = 0;
pb.PerformStep(); // Increment progress bar
}

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.

7.7 Building Custom Controls


At some point, you will face a programming task for which a standard WinForms
control does not provide the functionality you need. For example, you may want to
extend a TextBox control so that its background color changes according to its con-
tent, group a frequently used set of radio buttons into a single control, or create a
new control that shows a digital clock face with the date underneath. These needs
correspond to the three principal types of custom controls:

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.

class NumericTextBox: TextBox


{
public NumericTextBox()
{
this.KeyPress += new KeyPressEventHandler(TextBoxKeyPress);
}
protected void TextBoxKeyPress(object sender,
KeyPressEventArgs e)
7.7 Building Custom Controls 359

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

Building a Custom UserControl


Think of a user control as a subform. Like a form, it provides a container surface on
which related widgets are placed. When compiled, the entire set of controls is
treated as a single user control. Of course, users still can interact directly with any of
the member controls. Programmatic and design-time access to control members is
available through methods and properties defined on the user control.
The easiest way to design a control is with an IDE such as Visual Studio.NET
(VS.NET), which makes it easy to position and size controls. The usual way to create
a user control in VS.NET is to open a project as a Windows Control Library type.
This immediately brings up a control designer window. The design window can also
be accessed in a Windows Application by selecting Project – Add User Control from
the top menu bar or right-clicking on the Solution Explorer and selecting Add – Add
User Control. Although VS.NET can speed up the process of creating a control, it
does not generate any proprietary code that cannot be duplicated using a text editor.

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:

public class UserControl1 : System.Windows.Forms.UserControl


{
private Panel panel1;
private RadioButton radAgree;
private RadioButton radDisagree;
private RadioButton radUn;
private Label qLabel;
360 Chapter 7 ■ Windows Forms Controls

Figure 7-16 Layout of a custom user control

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.

Implementing Properties for a Custom


Listing 7-6
User Control
public enum Choices
{
Agree = 1,
DisAgree = 2,
Undecided = 3,
}
public string SetQ
{
set {qLabel.Text = value;}
get {return(qLabel.Text);}
}
public Color PanelColor
{
set {panel1.BackColor= value;}
get {return(panel1.BackColor);}
}
public Choices Choice
{
get
{
7.7 Building Custom Controls 361

Implementing Properties for a Custom


Listing 7-6
User Control (continued)
Choices usel;
usel = Choices.Undecided;
if (radDisagree.Checked) usel= Choices.DisAgree;
if (radAgree.Checked) usel = Choices.Agree;
return(usel);}
}
}

Using the Custom User Control


If the user control is developed as part of a VS.NET Windows Application project, it
is automatically added to the tool box under the Windows Forms tab. Simply select it
and drop it onto the form. Otherwise, you have to right-click on a tool box tab, select
Customize ToolBox, browse for the control, and add it to the tool box.

Figure 7-17 Custom user controls on a form

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:

private usercontrol.UserControl1 Q1;


private usercontrol.UserControl1 Q2;
362 Chapter 7 ■ Windows Forms Controls

The properties can be set in the constructor or at runtime in the Form.Load


event handler. If using VS.NET, the properties can be set at design time using the
Property Browser.

Q1.SetQ = "The economy is performing well";


Q2.SetQ = "I'm not worried about the budget deficit.";
Q1.PanelColor = Color.Beige;

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.

private void button1_Click(object sender, System.EventArgs e)


{
foreach (Control ct in this.Controls)
{
if (ct is usercontrol.UserControl1)
{
UserControl1 uc = (UserControl1)ct;
// Display control name and user's answer
MessageBox.Show(ct.Name+" "+
uc.Choice.ToString());
}
}
}

Working with the User Control at Design Time


If you are developing an application with VS.NET that uses this custom control, you
will find that the Property Browser lists all of the read/write properties. By default,
they are placed in a Misc category and have no description associated with them. To
add a professional touch to your control, you should create a category for the con-
trol’s events and properties and add a textual description for each category member.
The categories and descriptions available in the Property Browser come from
metadata based on attributes attached to a type’s members. Here is an example of
attributes added to the PanelColor property:

[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

The Browsable attribute indicates whether the property is to be displayed in the


browser. The default is true. The other two attributes specify the category under
which the property is displayed and the text that appears below the Property Browser
when the property is selected.
Always keep in mind that the motive for creating custom user controls is reusabil-
ity. There is no point in spending time creating elaborate controls that are used only
once. As this example illustrates, they are most effective when they solve a problem
that occurs repeatedly.

7.8 Using Drag and Drop with Controls


The ability to drag data from one control and drop it onto another has long been a
familiar feature of GUI programming. .NET supports this feature with several
classes and enumerations that enable a control to be the target and/or source of the
drag-and-drop operation.

Overview of Drag and Drop


The operation requires a source control that contains the data to be moved or copied,
and a target control that receives the dragged data. The source initiates the action in
response to an event—usually a MouseDown event. The source control’s event han-
dler begins the actual operation by invoking its DoDragDrop method. This method
has two parameters: the data being dragged and a DragDropEffects enum type
parameter that specifies the effects or actions the source control supports (see Table
7-4).

Table 7-4 DragDropEffects Enumeration

Member Description

All The data is moved to the target control, and scrolling occurs in the target
control to display the newly positioned data.

Copy Data is copied from target to source.

Link Data from the source is linked to the target.

Move The data is moved from the source to the target control.

None The target control refuses to accept data.

Scroll Scrolling occurs or will occur on the target control.


364 Chapter 7 ■ Windows Forms Controls

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

DoDragDrop( data, DragDropEffects.Move) DragEnter event

N
Perform any housekeeping. Is data type correct?

DragDrop event

Figure 7-18 Sequence of events in drag-and-drop operation

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.

Figure 7-19 Drag-and-drop example

Source Control Responsibilities


The MouseDown event handler for the source ListBox first checks to ensure that an
item has been selected. It then calls DoDragDrop, passing it the value of the selected
item as well as the acceptable effects: Move and Copy. The DragDropEffects enu-
meration has a FlagsAttribute attribute, which means that any bitwise combina-
tion of its values can be passed. The value returned from this method is the effect
that is actually used by the target. The event handler uses this information to perform
any operations required to implement the effect. In this example, a move operation
means that the dragged value must be removed from the source control.

Initiating a Drag-and-Drop Operation from the Source


Listing 7-7
Control
private void Players_MouseDown(object sender, MouseEventArgs e)
{
if ( lstPlayers.SelectedIndex >=0)
{
string players;
int ndx = lstPlayers.SelectedIndex;
DragDropEffects effect;
players = lstPlayers.Items[ndx].ToString();
if(players != "")
{
366 Chapter 7 ■ Windows Forms Controls

Initiating a Drag-and-Drop Operation from the Source


Listing 7-7
Control (continued)
// Permit target to move or copy data
effect = lstPlayers.DoDragDrop(players,
DragDropEffects.Move | DragDropEffects.Copy);
// Remove item from ListBox since move occurred
if (effect == DragDropEffects.Move)
lstPlayers.Items.RemoveAt(ndx);
}
}
}

Target Control Responsibilities


The destination control must implement the event handlers for the DragEnter and
DragDrop events. Both of these events receive a DragEventArgs type parameter
(see Table 7-5) that contains the information required to process the drag-and-drop
event.

Table 7-5 DragEventArgs Properties

Member Description

AllowedEffect The effects that are supported by the source control.


Example to determine if Move is supported:
if ((e.AllowedEffect & DragDropEffects.Move) ==
DragDropEffects.Move)

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.

Effect Gets or sets the target drop effect.

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

X, Y x and y coordinates of the mouse pointer.


7.8 Using Drag and Drop with Controls 367

The Data, Effect, and KeyState members are used as follows:

• Data.GetDataPresent is used by the DragEnter event handler to


ensure that the data is a type the target control can process.
• The DragDrop event handler uses Data.GetData to access the data
being dragged to it. The parameter to this method is usually a static
field of the DataFormats class that specifies the format of the
returned data.
• The DragEnter event handler uses KeyState to determine the status
of the mouse and keys in order to determine the effect it will use to
process the data. Recall that in this example, pressing the Ctrl key sig-
nals that data is to copied rather than moved.
• Effect is set by the DragEnter event handler to notify the source
as to how—or if—it processed the data. A setting of DragDrop-
Effects.None prevents the DragDrop event from firing.

Listing 7-8 shows the code for the two event handlers.

Handling the DragEnter and DragDrop


Listing 7-8
Events
[FlagsAttribute]
enum KeyPushed
{
// Corresponds to DragEventArgs.KeyState values
LeftMouse = 1,
RightMouse = 2,
ShiftKey = 4,
CtrlKey = 8,
MiddleMouse = 16,
AltKey = 32,
}
private void TeamA_DragEnter(object sender, DragEventArgs e)
{
KeyPushed kp = (KeyPushed) e.KeyState;
// Make sure data type is string
if (e.Data.GetDataPresent(typeof(string)))
{
// Only accept drag with left mouse key
if ( (kp & KeyPushed.LeftMouse) == KeyPushed.LeftMouse)
{
if ((kp & KeyPushed.CtrlKey) == KeyPushed.CtrlKey)
{
e.Effect = DragDropEffects.Copy; // Copy
368 Chapter 7 ■ Windows Forms Controls

Handling the DragEnter and DragDrop


Listing 7-8
Events (continued)
}
else
{
e.Effect = DragDropEffects.Move; // Move
}
}
else // Is not left mouse key
{
e.Effect = DragDropEffects.None;
}
} else // Is not a string
{
e.Effect = DragDropEffects.None;
}
}
// Handle DragDrop event
private void TeamA_Drop(object sender, DragEventArgs e)
{
// Add dropped data to TextBox
lstTeamA.Items.Add(
(string) e.Data.GetData(DataFormats.Text));
}

An enum is created with the FlagsAttributes attribute to make checking the


KeyState value easier and more readable. The logical “anding” of KeyState with
the value of the CtrlKey (8) returns a value equal to the value of the CtrlKey if the
Ctrl key is pressed.
A control can serve as source and target in the same application. You could make
this example more flexible by having the list boxes assume both roles. This would
allow you to return a player from lstTeamA back to the lstPlayers ListBox. All
that is required is to add the appropriate 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

7.9 Using Resources


Figure 7-7, shown earlier in the chapter, illustrates the use of PictureBox controls
to enlarge and display a selected thumbnail image. Each thumbnail image is loaded
into the application from a local file:

tn1 = new PictureBox();


tn1.Image = Image.FromFile("c:\\schiele1.jpg");

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.

Working with Resource Files


Resource files come in three formats: *.txt files in name/value format, *.resx files
in an XML format, and *.resources files in a binary format. Why three? The text
format provides an easy way to add string resources, the XML version supports both
strings and other objects such as images, and the binary version is the binary equiva-
lent of the XML file. It is the only format that can be embedded in an assembly—the
other formats must be converted into a .resources file before they can be linked to
an assembly. Figure 7-20 illustrates the approaches that can be used to create a
.resources file.
370 Chapter 7 ■ Windows Forms Controls

.txt resgen.exe
Resources
• strings
• images ResourceWriter .resources
• cursors

ResXResourceWriter .resx resgen.exe

Figure 7-20 A .resources file can be created


from a text file, resources, or a .resx file

The System.Resources namespace contains the types required to manipulate


resource files. It includes classes to read from and write to both resource file formats,
as well as load resources from an assembly into a program.

Creating Resource Strings from a Text File


Resource files containing string values are useful when it is necessary for a single
application to present an interface that must be customized for the environment in
which it runs. A resource file eliminates the need to code multiple versions of an
application; instead, a developer creates a single application and multiple resource
files that contain the interface captions, text, messages, and titles. For example, an
English version of an application would have the English resource file embedded in
its assembly; a German version would embed the German resource file. Creating
resource strings and accessing them in an application requires four steps:

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

3. Use the System.Resources.ResourceManager class to read the


strings from the resource file. As shown here, the ResourceManager
class accepts two arguments: the name of the resource file and the
assembly containing it. The Assembly class is part of the System.
Reflection namespace and is used in this case to return the current
assembly. After the resource manager is created, its GetString
method is used by the application to retrieve strings from the resource
file by their string name:
// new ResourceManager(resource file, assembly)
ResourceManager rm = new ResourceManager(
"german",Assembly.GetExecutingAssembly());
nxtButton.Text= rm.GetString("Next");
4. For this preceding code to work, of course, the resource file must be
part of the application’s assembly. It’s bound to the assembly during
compilation:
csc /t:exe /resource:german.resources myApp.cs

Using the ResourceWriter Class to


Create a .resources File
The preceding solution works well for adding strings to a resource file. However, a
resource file can also contain other objects such as images and cursor shapes. To
place these in a .resources file, .NET offers the System.Resources.Resource-
Writer class. The following code, which would be placed in a utility or helper file,
shows how to create a ResourceWriter object and use its AddResource method to
store a string and image in a resource file:

IResourceWriter writer = new ResourceWriter(


"myResources.resources"); // .Resources output file
Image img = Image.FromFile(@"c:\schiele1.jpg");
rw.AddResource("Page","Seite"); // Add string
rw.AddResource("artistwife",img); // Add image
rw.Close(); // Flush resources to the file

Using the ResourceManager Class


to Access Resources
As we did with string resources, we use the ResourceManager class to access object
resources from within the application. To illustrate, let’s return to the code presented
at the beginning of this section:

tn1.Image = Image.FromFile("C:\\schiele1.jpg");
372 Chapter 7 ■ Windows Forms Controls

The ResourceManager allows us to replace the reference to an external file, with


a reference to this same image that is now part of the assembly. The GetString
method from the earlier example is replaced by the GetObject method:

ResourceManager rm = new
ResourceManager("myresources",
Assembly.GetExecutingAssembly());
// Extract image from resources in assembly
tn1.Image = (Bitmap) rm.GetObject("artistwife");

Using the ResXResourceWriter Class


to Create a .resx File
The ResXResourceWriter class is similar to the ResourceWriter class except that
it is used to add resources to a .resx file, which represents resources in an interme-
diate XML format. This format is useful when creating utility programs to read, man-
age, and edit resources—a difficult task to perform with the binary .resources file.

ResXResourceWriter rwx = new


ResXResourceWriter(@"c:\myresources.resx");
Image img = Image.FromFile(@"c:\schiele1.jpg");
rwx.AddResource("artistwife",img); // Add image
rwx.Generate(); // Flush all added resources to the file

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:

<data name="face" type="System.Drawing.Bitmap, System.Drawing,


Version=1.0.3300.0,Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" mimetype="application/x-
microsoft.net.object.bytearray.base64">
<value> ---- Actual Image bytes go here ----
</value>

Note that although this example stores only one image in the file, a .resx file can
contain multiple resource types.

Using the ResXResourceReader Class


to Read a .resx file
The ResXResourceReader class provides an IDictionaryEnumerator (see Chap-
ter 4) that is used to iterate through the tag(s) in a .resx file. This code segment lists
the contents of a resource file:
7.9 Using Resources 373

ResXResourceReader rrx = new


ResXResourceReader("c:\\myresources.resx");
// Enumerate the collection of tags
foreach (DictionaryEntry de in rrx)
{
MessageBox.Show("Name: "+de.Key.ToString()+"\nValue: " +
de.Value.ToString());
// Output --> Name: artistwife
// --> Value: System.Drawing.Bitmap
}
rrx.Close();

Converting a .resx File to a .resources File


The .resx file is converted to a .resources file using resgen.exe:

resgen myresources.resx myresources.resources

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.

VS.NET and Resources


Visual Studio.NET automatically creates a .resx file for each form in a project and
updates them as more resources are added to the project. You can see the resource
file(s) by selecting the Show All Files icon in the Solution Explorer.
When a build occurs, .resources files are created from the .resx files. In the
code itself, a ResourceManager object is created to provide runtime access to the
resources:

ResourceManager resources = new ResourceManager(typeof(Form1));

Using Resource Files to


Create Localized Forms
In .NET vernacular, a localized application is one that provides multi-language sup-
port. This typically means providing user interfaces that display text and images cus-
tomized for individual countries or cultures. The .NET resource files are designed to
support such applications.
In a nutshell, resource files can be set up for each culture being supported. For
example, one file may have all the control labels and text on its interface in German;
another may have the same controls with French text. When the application runs, it
374 Chapter 7 ■ Windows Forms Controls

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.

Resource Localization Using Visual Studio.NET


To make a form localized, you must set its Localizable property to true. This has
the effect of turning each control on a form into a resource that has its properties
stored in the form’s .resx file. This sets the stage for creating separate .resx files
for each culture a form supports.
Recall from Chapter 5, “C# Text Manipulation and File I/O,” that a culture is
specified by a two-character language code followed by an optional two-character
country code. For example, the code for English in the United States is en-US. The
terms neutral culture and specific culture are terms to describe a culture. A specific
culture has both the language and country specified; a neutral culture has only the
language. Consult the MSDN documentation on the CultureInfo class for a com-
plete list of culture names.
To associate other cultures with a form, set the form’s Language property to
another locale from the drop-down list in the Properties window. This causes a
.resx file to be created for the new culture. You can now customize the form for this
culture by changing text, resizing controls, or moving controls around. This new
property information is stored in the .resx file for this culture only—leaving the
.resx files for other cultures unaffected.
The resource files are stored in folders, as shown in Figure 7-21. When the project
is built, a satellite assembly is created to contain the resources for each culture, as
shown in Figure 7-22. This DLL file has the same name in each folder.

Figure 7-21 VS.NET resource files Figure 7-22 Satellite assembly


for multiple cultures
7.9 Using Resources 375

Determining Localization Resources at Runtime


By default, an application’s thread has its CurrentThread.CurrentUICulture
property set to the culture setting of the machine it is running on. Instances of the
ResourceManager, in turn, use this value to determine which resources to load.
They do this by searching for the satellite assembly in the folder associated with the
culture—a reason why the naming and location of resource folders and files is impor-
tant. If no culture-specific resources are found, the resources in the main assembly
are used.

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");

Creating a Satellite Assembly Without VS.NET


One of the advantages of using satellite assemblies is that they can be added to an
application, or modified, without recompiling the application. The only requirements
are that a folder be set up along the proper path, and that the folder and satellite
assembly have the proper name.
Suppose you have a .resx file that has been converted by your translator to
French Canadian. You can manually create and add a satellite assembly to the appli-
cation in three steps:

1. Convert the.resx file to a .resources file:


filmography.Form1.fr-CA.resources
2. Convert the .resources file to a satellite assembly using the Assembly
Linker (Al.exe):
Al.exe
/t:lib
/embed:filmography.Form1.fr-CA.resources
/culture:fr-CA
/out:filmography.resources.dll
3. Create the fr-CA folder beneath Release folder and copy the new
assembly file into it.
376 Chapter 7 ■ Windows Forms Controls

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.

7.11 Test Your Understanding


1. Why is a container control such as a GroupBox used with radio buttons?

2. What is the SizeMode property set to in order to automatically resize


and fill an image in a PictureBox?
7.11 Test Your Understanding 377

3. Suppose you place objects in a ListBox that have these properties:


string Vendor, string ProductID, int Quantity
How do you have the ListBox display the ProductID and
Quantity?

4. What event is fired when an item in a ListBox is selected? What


ListBox properties are used to identify the selected item?

5. What property and value are set on a ListView to display its full con-
tents in a grid layout?

6. Which TreeNode property can be used to store object data in a Tree-


View node?

7. Which two events must the destination control in a drag-and-drop


operation support?

8. The Property Browser in VS.NET uses metadata to categorize a con-


trol’s properties and events and assign default values. How do you gen-
erate this information for the properties in a custom control?

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+

Topics in This Chapter

• Graphics Overview: The first step in working with GDI+ is to


understand how to create and use a Graphics object. This
section looks at how this object is created and used to handle the
Paint event.
• Using the Graphics Object to Create Shapes: .NET offers a variety
of standard geometric shapes that can be drawn in outline form or
filled in. The GraphicsPath class serves as a container that
enables geometric shapes to be connected.
• Using Pens and Brushes: Pens are used to draw shapes in outline
form in different colors and widths; a brush is used to fill in shapes
and create solid and gradient patterns.
• Color: Colors may be defined according to red/green/blue (RGB)
values or hue/saturation/brightness (HSB) values. Our project
example illustrates RGB and HSB color spaces. By representing a
color as an object, .NET permits it to be transformed by changing
property values.
• Images: .NET includes methods to load, display, and transform
images. The most useful of these is the Graphics.DrawImage
method that allows images to be magnified, reduced, and rotated.
8

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+

8.1 GDI+ Overview


The types that make up GDI+ are contained in the gdiplus.dll file. .NET neatly
separates the classes and enumerations into logically named namespaces that reflect
their use. As Figure 8-1 shows, the GDI+ functions fall into three broad categories:
two-dimensional vector graphics, image manipulation, and typography (the combin-
ing of fonts and text strings to produce text output).

System.Drawing

Drawing2D Imaging Printing Text

Vector Graphics Image Manipulation Typography

Figure 8-1 GDI+ namespaces

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.

The Graphics Class


Drawing requires a surface to draw on, a coordinate system for positioning and align-
ing shapes, and a tool to perform the drawing. GDI+ encapsulates this functionality
in the System.Drawing.Graphics class. Its static methods allow a Graphics
object to be created for images and controls; and its instance methods support the
drawing of various shapes such as circles, triangles, and text. If you are familiar with
using the Win32 API for graphics, you will recognize that this corresponds closely to
a device context in GDI. But the Graphics object is of a simpler design. A device
context is a structure that maintains state information about a drawing and is passed
as an argument to drawing functions. The Graphics object represents the drawing
surface and provides methods for drawing on it.
Let’s see how code gains access to a Graphics object. Your application most likely
will work with a Graphics object inside the scope of an event handler, where the
8.1 GDI+ Overview 381

object is passed as a member of an EventArgs parameter. The Paint event, which


occurs each time a control is drawn, is by far the most common source of Graphics
objects. Other events that have a Graphics object sent to their event handler
include PaintValue, BeginPrint, EndPrint, and PrintDocument.PrintPage.
The latter three are crucial to printing and are discussed in the next chapter.
Although you cannot directly instantiate an object from the Graphics class, you
can use methods provided by the Graphics and Control classes to create an object.
The most frequently used is Control.CreateGraphics—an instance method that
returns a graphics object for the control calling the method. The Graphics class
includes the FromHwnd method that relies on passing a control’s Handle to obtain a
Graphics object related to the control. Let’s look at both approaches.

How to Obtain a Graphics Object


from a Control Using CreateGraphics
The easiest way to create a Graphics object for a control is to use its CreateGraph-
ics method. This method requires no parameters and is inherited from the Con-
trol class by all controls. To demonstrate, let’s create an example that draws on a
Panel control when the top button is clicked and refreshes all or part of the panel in
response to another button click. The user interface to this program is shown in
Figure 8-2 and will be used in subsequent examples.

Figure 8-2 Interface to demonstrate using Graphics object to draw on a control

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 Control.CreateGraphics to Obtain a


Listing 8-1
Graphics Object

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.

How to Obtain a Graphics Object


Using Graphics Methods
The Graphics class has three static methods that provide a way to obtain a
Graphics object:

• Graphics.FromHdc. Creates the Graphics object from a specified


handle to a Win32 device context. This is used primarily for interoper-
ating with GDI.
• Graphics.FromImage. Creates a Graphics object from an instance
of a .NET graphic object such as a Bitmap or Image. It is often used
in ASP.NET (Internet) applications to dynamically create images and
graphs that can be served to a Web browser. This is done by creating
an empty Bitmap object, obtaining a Graphics object using FromIm-
age, drawing to the Bitmap, and then saving the Bitmap in one of the
standard image formats.
• Graphics.FromHwnd. Creates the Graphics object from a handle to
a Window, Form, or control. This is similar to GDI programming that
requires a handle to a device context in order to display output to a
specific device.

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.

private void panel1OnMouseDown(object sender, MouseEventArgs e)


{
384 Chapter 8 ■ .NET Graphics Using GDI+

// The using statement automatically calls g.Dispose()


using( Graphics g= Graphics.FromHwnd(panel1.Handle))
{
g.DrawLine(Pens.Red,e.X,e.Y,20,20);
}
}

Figure 8-3 Output for MouseDown example

The Paint Event


A Paint event is triggered in a WinForms application when a form or control needs
to be partially or fully redrawn. This normally occurs during the natural use of a GUI
application as the window is moved, resized, and hidden behind other windows.
Importantly, a Paint event can also be triggered programatically by a call to a con-
trol’s Invalidate method.

Using Invalidate() to Request a Paint Event


The Control.Invalidate method triggers a Paint event request. The
btnRefresh_Click event handler in Listing 8-1 showed two overloads of the
method. The parameterless version requests that the entire panel control be
redrawn; the second specifies that only the portion of the control’s region specified
by a rectangle be redrawn.
Here are some of the overloads for this method:

public void Invalidate()


public void Invalidate(bool invalidatechildren)
public void Invalidate(Rectangle rc)
public void Invalidate(Rectangle rc, bool invalidatechildren)

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.

Figure 8-4 Effects of invalidating a region

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

Implementing a Paint Event Handler


After the Paint event occurs, a data class PaintEventArgs is passed as a parameter
to the Paint event handler. This class provides access to the Graphics object and to a
rectangle ClipRectangle that defines the area where drawing may occur. Together,
these properties make it a simple task to perform all the painting within the scope of
the event handler.
Let’s see how to rectify the problem in the preceding example, where our drawing
on panel1 disappears each time the paint event occurs. The solution, of course, is to
386 Chapter 8 ■ .NET Graphics Using GDI+

perform the drawing inside the paint event handler. To do this, first register our
event handler with the PaintEventHandler delegate:

this.panel1.Paint += new PaintEventHandler(paint_Panel);

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.

private void paint_Panel( object sender, PaintEventArgs e)


{
Graphics cg = e.Graphics;
int pWidth = panel1.ClientRectangle.Width;
int pHeight = panel1.ClientRectangle.Height;
cg.DrawRectangle(Pens.Black,2,2,pWidth-4, pHeight-4);
cg.DrawLine(Pens.Red,2,(pHeight-4)/2,pWidth-4,
(pHeight-4)/2);
base.OnPaint(e); // Call base class implementation
}

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.

private void paint_Panel( object sender, PaintEventArgs e)


{
Graphics g = e.Graphics;
for (int i = 0; i< this.panel1.Width;i+=20)
{
for (int j=0; j< his.panel1.Height;j+=20)
{
Rectangle r= new Rectangle(i,j,20,20);
if (r.IntersectsWith(e.ClipRectangle))
8.1 GDI+ Overview 387

{
// 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);
}
}
}
}

Figure 8-5 Pattern used in paint example

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.

// New class myPanel inherits from base class Panel


public class myPanel: Panel
{
388 Chapter 8 ■ .NET Graphics Using GDI+

protected override void OnPaint(PaintEventArgs e)


{
Graphics g = e.Graphics;
g.DrawLine(Pens.Aqua,0,0,this.Width,this.Height);
base.OnPaint(e);
}
}

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.

8.2 Using the Graphics Object


Let’s look at the details of actually drawing with the Graphics object. The Graph-
ics class contains several methods for rendering basic geometric patterns. In addi-
tion to the DrawLine method used in previous examples, there are methods for
drawing rectangles, polygons, ellipses, curves, and other basic shapes. In general,
each shape can be drawn as an outline using the Pen class, or filled in using the
Brush class. The DrawEllipse and FillEllipse methods are examples of this.
Let’s look at some examples.

Basic 2-D Graphics


Figure 8-6 demonstrates some of the basic shapes that can be drawn with the
Graphics methods. The shapes vary, but the syntax for each method is quite similar.
Each accepts a drawing object—either a pen or brush—to use for rendering the
shape, and the coordinates that determine the size and positions of the shape. A
Rectangle object is often used to provide a shape’s boundary.

DrawEllipse() DrawPolygon() DrawPie() DrawRectangle() DrawArc()

FillEllipse() FillPolygon() FillPie() DrawRectangle() FillArc()


Bevel

Figure 8-6 Basic 2-D shapes


8.2 Using the Graphics Object 389

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.

Pen blkPen = new Pen(Brushes.Black,2 ); // width=2


// Set this to draw smooth lines
g.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// (1) Draw Circle and Draw Filled Circle
Rectangle r = new Rectangle(new Point(x,y),new Size(40,40));
g.DrawEllipse(blkPen, r);
g.FillEllipse(Brushes.Black,x,y+60,40,40);
// (2) Draw Ellipse and Filled Ellipse
int w = 60;
int h= 40;
Rectangle r = new Rectangle(new Point(x,y),new Size(w,h));
g.DrawEllipse(blkPen, r);
r = new Rectangle(new Point(x,y+60), new Size(w,h));
g.FillEllipse(Brushes.Red, r);
// (3) Draw Polygon
Point pt1 = new Point(x, y);
Point pt2 = new Point(x+22, y+12);
Point pt3 = new Point(x+22, y+32);
Point pt4 = new Point(x, y+44);
Point pt5 = new Point(x-22, y+32);
Point pt6 = new Point(x-22, y+12);
Point[] myPoints = {pt1, pt2, pt3, pt4, pt5, pt6};
g.DrawPolygon(blkPen, myPoints);
// Points would be changed so as not to draw over
// original polygon
g.FillPolygon(Brushes.Black, myPoints);
// (4)Draw Pie Shape and filled pie
Rectangle r = new Rectangle( new Point(x,y),new Size(80,80));
// Create start and sweep angles
int startAngle = 0; // Clockwise from x-axis
int sweepAngle = -60; // Clockwise from start angle
g.DrawPie(blkPen, r, startAngle, sweepAngle);
g.FillPie(Brushes.Black, x,y+60,80,80,startAngle, sweepAngle);
// (5) Draw Rectangle and Rectangle with beveled edges
blkPen.Width=5; // make pen thicker to show bevel
g.DrawRectangle(blkPen,x,y,50,40);
blkPen.LineJoin = LineJoin.Bevel;
g.DrawRectangle(blkPen,x,y+60,50,40);
// (6) Draw Arc and Filled Pie
startAngle=45;
sweepAngle=180;
g.DrawArc(blkPen, x,y,40,40,startAngle, sweepAngle);
g.FillPie(Brushes.Black, x,y+60,40,40,startAngle,sweepAngle);
390 Chapter 8 ■ .NET Graphics Using GDI+

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.

Creating Shapes with the GraphicsPath Class


The GraphicsPath class, which is a member of the System.Drawing.Drawing2D
namespace, is used to create a container for a collection of primitive shapes. Suc-
cinctly, it permits you to add basic shapes to its collection and then to treat the collec-
tion as a single entity for the purpose of drawing and filling the overall shape. Before
looking at a code example, you should be aware of some of the basic features of 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.

// g is the Graphics object


g.SmoothingMode = SmoothingMode.AntiAlias;
// Define five polygons
Point[] ptsT= {new Point(120,20),new Point(160,20),
new Point(140,50)};
Point[] ptsL= {new Point(90,50),new Point(90,90),
new Point(120,70)};
Point[] ptsB= {new Point(120,120),new Point(160,120),
new Point(140,90)};
Point[] ptsR= {new Point(190,90), new Point(190,50),
new Point(160, 70)};
Point[] ptsCenter = {new Point(140,50), new Point(120,70),
new Point(140,90), new Point(160,70)};
// Create the GraphicsPath object and add the polygons to it
GraphicsPath gp = new GraphicsPath();
gp.AddPolygon(ptsT); // Add top polygon
gp.AddPolygon(ptsL); // Add left polygon
gp.AddPolygon(ptsB); // Add bottom polygon
8.2 Using the Graphics Object 391

gp.AddPolygon(ptsR); // Add right polygon


gp.AddPolygon(ptsCenter);
g.DrawPath(new Pen(Color.Red,2),gp); // Draw GraphicsPath
g.FillPath(Brushes.Gold,gp); // Fill the polygons

Figure 8-7 Infinity 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.

Matrix translateMatrix = new Matrix();


translateMatrix.Translate(50, 0); // Offset x coordinate by 50
gp.Transform(translateMatrix); // Transform path
g.DrawPath(Pens.Orange,gp); // Display at new location

Hit Testing with Shapes


One of the reasons for placing shapes on a form is to permit a user to trigger an
action by clicking a shape—as if she had clicked a button. Unlike a control, you can-
not associate an event with a shape. Instead, you associate a MouseDown event with
the container that holds the shape(s). Recall that a MouseDown event handler
receives the x and y coordinates where the event occurs. After it has these, it is a sim-
ple process to use the rectangle and GraphicsPath methods to verify whether a
point falls within their area:

bool Rectangle.Contains(Point(x,y))
bool GraphicsPath.IsVisible(Point(x,y))
392 Chapter 8 ■ .NET Graphics Using GDI+

To illustrate, consider an application that displays a map of US states and responds


to a click on a state by displaying the name of the state capital. The map image is
placed in a PictureBox, and rectangles and polygons are drawn on the states to set
up the hit areas that respond to a MouseDown event. Figure 8-8 shows how the pic-
ture box’s paint handler routine draws rectangles on three states and a polygon on
Florida. (Of course, the shapes would not be visible in the actual application.) To
respond to a pressed mouse key, set up a delegate to call an event handler when the
MouseDown event occurs:

this.pictureBox1.MouseDown += new
MouseEventHandler(down_Picture);

Figure 8-8 Hit test example

The following code implements event handler logic to determine if the mouse
down occurs within the boundary of any shape.

private void down_Picture( object sender, MouseEventArgs e)


{
// Rectangles and GraphicsPath gp are defined
// as class variables
if (rectNC.Contains(e.X,e.Y) )
{ MessageBox.Show("Capital: Raleigh"); }
else if(rectSC.Contains(e.X,e.Y))
{ MessageBox.Show("Capital: Columbia");}
else if(rectGA.Contains(e.X,e.Y))
{ MessageBox.Show("Capital: Atlanta");}
else if(gp.IsVisible(e.X,e.Y))
{MessageBox.Show("Capital: Tallahassee");}
}
8.2 Using the Graphics Object 393

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:

public Pen (Color color);


public Pen (Color color, single width);
public Pen (Brush brush);
public Pen (Brush brush, single width);

Example:

Pen p1 = new Pen(Color.Red, 5);


Pen p2 = new Pen(Color.Red); // Default width of 1

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.

Table 8-1 Selected Pen Properties

Member Description

Alignment Determines how a line is drawn for closed shapes. Specifically, it


specifies whether the line is drawn on the bounding perimeter or
inside it.

Color Color used to draw the shape or text.


394 Chapter 8 ■ .NET Graphics Using GDI+

Table 8-1 Selected Pen Properties (continued)

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.

PenType Specifies how a line is filled—for example, textured, solid, or gradi-


ent. It is determined by the Brush property of the Pen.

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.

Width Floating point value used to set width of Pen.

Let’s look at some of the more interesting properties in detail.

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.

Pen p1 = new Pen(Color.Black, 3);


p1.DashStyle = DashStyle.Dash;
g.DrawLine(p1,20,20,180,20);

Dash
DashDot
DashDotDot
Dot
Solid

Figure 8-9 DashStyles and LineCaps


8.2 Using the Graphics Object 395

StartCap and EndCap


These properties define the shape used to begin and end a line. The value comes
from the LineCap enumeration, which includes ArrowAnchor, DiamondAnchor,
Round, RoundAnchor, Square, SquareAnchor, and Triangle. Examples of the
DiamondAnchor and RoundAnchor are shown in Figure 8-9. The following code is
used to create the lines in the figure:

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:

FillClosedCurve FillEllipse FillPath FillPie


FillPolygon FillRectangle FillRectangles FillRegion

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

Brush Type Description

SolidBrush Defines a brush of a single color. It has a single constructor:


Brush b = new SolidBrush(Color.Red);

TextureBrush Uses a preexisting image (*.gif, *.bmp, or *.jpg) to fill a


shape.
Image img = Image.FromFile("c:\\flower.jpg");
Brush b = new TextureBrush(img);

HatchBrush Defines a rectangular brush with a foreground color,


background color, and hatch style. Located in the
System.Drawing.Drawing2D namespace.

LinearGradientBrush Supports either a two-color or multi-color gradient. All linear


gradients occur along a line defined by two points or a rectan-
gle. Located in the Drawing2D namespace.

PathGradientBrush Fills the interior of a GraphicsPath object with a gradient.


Located in the Drawing2D namespace.

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.

The HatchBrush Class


As the name implies, this class fills the interior of a shape with a hatched appearance.

Constructors:

public HatchBrush(HatchStyle hStyle, Color forecolor)


public HatchBrush(HatchStyle hstyle, Color forecolor,
Color backcolor)

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

The predefined HatchStyle patterns make it a simple process to create elabo-


rate, multi-color fill patterns. The following code is used to create the DarkVerti-
cal and DottedDiamond rectangles at the top of each column in Figure 8-10.

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

The LinearGradientBrush Class


In its simplest form, this class creates a transition that takes one color and gradually
blends it into a second color. The direction of the transition can be set to horizontal,
vertical, or any specified angle. The location where the transition begins can be set to
a focal point other than the beginning of the area to be filled in. In cases where the
gradient must be tiled to completely fill an area, options are available to control how
each repeat is displayed. These options can be confusing, so let’s begin with how to
398 Chapter 8 ■ .NET Graphics Using GDI+

create a gradient brush and then work with examples that demonstrate the more use-
ful properties and methods of the LinearGradientBrush class.

Constructors:

public LinearGradientBrush(Rectangle rect, Color color1,


Color color2, LinearGradientMode linearGradientMode)

public LinearGradientBrush(Rectangle rect, Color color1,


Color color2, float angle)

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

There is no substitute for experimentation when it comes to understanding graph-


ics related concepts. Figure 8-11 shows the output from filling a rectangle with vari-
ous configurations of a LinearGradientBrush object. Here is the code that creates
these examples:

// Draw rectangles filled with gradient in a pictureBox


Graphics g = pictureBox1.CreateGraphics();
Size sz = new Size(100,80);
Rectangle rb = new Rectangle(new Point(20,20),sz);
// (1) Vertical Gradient (90 degrees)
LinearGradientBrush b = new
LinearGradientBrush(rb,Color.DarkBlue,Color.LightBlue,90);
g.FillRectangle(b,rb);
rb.X=140;
// (2) Horizontal Gradient
b = new LinearGradientBrush(rb,Color.DarkBlue,
Color.LightBlue,0);
g.FillRectangle(b,rb);
rb.Y = 120;
rb.X = 20;
// (3) Horizontal with center focal point
b = new LinearGradientBrush(rb,Color.DarkBlue,
Color.LightBlue,0);
// Place end color at position (0-1) within brush
8.2 Using the Graphics Object 399

b.SetBlendTriangularShape(.5f);
g.FillRectangle(b,rb);

1 2 3 4

Figure 8-11 LinearGradientBrush examples:


(1) Vertical, (2) Horizontal, (3) Focus Point, (4) Tiling

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:

// Tiling Example – create small rectangle for gradient brush


Rectangle rb1 = new Rectangle(new Point(0,0),new Size(20,20));
b = new LinearGradientBrush(rb,Color.DarkBlue,
Color.LightBlue,0);
b.WrapMode = WrapMode.TileFlipX;
// Fill larger rectangle with repeats of small gradient rectangle
g.FillRectangle(b,rb);

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:

Tile Repeats the gradient.


TileFlipX Reverses the gradient horizontally before repeating.
TileFlipXY Reverses the gradient horizontally and vertically before repeating.
TileFlipY Reverses the gradient vertically before repeating.
400 Chapter 8 ■ .NET Graphics Using GDI+

Creating a Multi-Color Gradient


It takes only a few lines of code to create a LinearGradientBrush object that cre-
ates a multi-color gradient. The key is to set the LinearGradientBrush.Interpo-
lationColors property to an instance of a ColorBlend class that specifies the
colors to be used. As the following code shows, the ColorBlend class contains an
array of colors and an array of values that indicate the relative position (0–1) of each
color on the gradient line. This example creates a gradient with a transition from red
to white to blue—with white in the middle.

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

darkened (black has 0% brightness). There is no standard for assigning HSB/HSL


values. Programs often use values between 0 and 255 to correspond to the RGB
numbering scheme. As we will see, .NET assigns the actual angle to the hue, and val-
ues between 0 and 1 to the saturation and brightness.

green yellow
120˚ 60˚

180˚ 0˚ red
cyan

240˚ 300˚
blue magenta

Figure 8-12 Color wheel

How to Create a Color Object


The Color structure provides three static methods for creating a Color object:
FromName, FromKnownColor, and FromArgb. FromName takes the name of a color
as a string argument and creates a new struct: Color magenta = Color.From-
Name("Magenta"). The name must match one in the KnownColor enumeration
values, which is an enumeration of all the colors represented as properties in the
Color and SystemColor structures.
FromKnownColor takes a KnownColor enumeration value as its argument and
produces a struct for that color:

Color magenta = Color.FromKnownColor(KnownColor.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+

Examining the Characteristics of a Color Object


The Color structure has four properties that return the ARGB values of a color:
Color.A, Color.R, Color.G, and Color.B. All of these properties have a value in
the range 0 to 255.

Color slateGray = Color.FromArgb(255,112,128,144);


byte a = slateGray.A; // 255
byte r = slateGray.R; // 112
byte g = slateGray.G; // 128
byte b = slateGray.B; // 144

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.

Color slateGray = Color.FromArgb(255,112,128,144);


float hue = slateGray.GetHue(); // 210 degrees
float sat = slateGray.GetSaturation(); // .125
float brt = slateGray.GetBrightness(); // .501

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.

A Sample Project: Building a Color Viewer


The best way to grasp how the color spaces relate to actual .NET colors is to visually
associate the colors with their RGB and HSB values. .NET offers a ColorDialog
class that can be used to display available colors; however, it does not identify colors
by the system-defined names that most developers work with. So, let’s build a simple
color viewer that displays colors by name and at the same time demonstrates how to
use the GDI+ types discussed in this section.
Figure 8-13 shows the user interface for this application. It consists of a Tree-
View on the right that contains all the colors in the KnownColor enumeration orga-
nized into 12 color groups. These groups correspond to the primary, secondary, and
tertiary1 colors of the color wheel. In terms of hues, each section is 30 degrees on the
circle. The interface also contains two panels, a larger one in which a selected color is
displayed, and a smaller one that displays a brightness gradient for the selected color.
This is created using a multi-color gradient comprising black and white at each end,
and the color at a focus point determined by its Brightness value. The remainder

1. Tertiary colors are red-orange, yellow-orange, yellow-green, blue-green, blue-violet, and


red-violet.
8.2 Using the Graphics Object 403

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.

Color Viewer: Populate TreeNode with


Listing 8-2
All Colors
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
public class Form1 : Form
{
public Form1()
{
InitializeComponent(); // Lay out controls on Form1
// Set up event handler to be fired when node is selected
colorTree.AfterSelect += new
TreeViewEventHandler(ColorTree_AfterSelect);
BuildWheel(); // Create Parent Nodes for 12 categories
}
404 Chapter 8 ■ .NET Graphics Using GDI+

Color Viewer: Populate TreeNode with


Listing 8-2
All Colors (continued)
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
// Parent Nodes in TreeView for each segment of color wheel
private void BuildWheel()
{
TreeNode tNode;
tNode = colorTree.Nodes.Add("Red");
tNode = colorTree.Nodes.Add("Orange");
tNode = colorTree.Nodes.Add("Yellow");
// Remainder of nodes are added here ....
}
private void button1_Click(object sender, System.EventArgs e)
{
// Add Colors to TreeNode Structure
// Loop through KnownColor enum values
Array objColor = Enum.GetValues(typeof(KnownColor));
for (int i=0; i < objColor.Length; i++)
{
KnownColor kc = (KnownColor) objColor.GetValue(i);
Color c = Color.FromKnownColor(kc);
if (!c.IsSystemColor) // Exclude System UI colors
{
InsertColor(c, c.GetHue());
}
}
}
private void InsertColor(Color myColor, float hue)
{
TreeNode tNode;
TreeNode cNode = new TreeNode();
// Offset is used to start color categories at 345 degrees
float hueOffset = hue + 15;
if (hueOffset >360) hueOffset -= 360;
// Get one of 12 color categories
int colorCat = (int)(hueOffset -.1)/30;
tNode = colorTree.Nodes[colorCat]; // Get parent node
// Add HSB values to node's Tag
HSB nodeHSB = new HSB(hue, myColor.GetSaturation(),
myColor.GetBrightness());
8.2 Using the Graphics Object 405

Color Viewer: Populate TreeNode with


Listing 8-2
All Colors (continued)
cNode.Tag = nodeHSB; // Tag contains HSB values
cNode.Text = myColor.Name;
int nodeCt = tNode.Nodes.Count;
bool insert=false;
// Insert colors in ascending hue value
for (int i=0; i< nodeCt && insert==false ;i++)
{
nodeHSB = (HSB)tNode.Nodes[i].Tag;
if (hue < nodeHSB.Hue)
{
tNode.Nodes.Insert(i,cNode);
insert = true;
}
}
if (!insert) tNode.Nodes.Add(cNode);
}
public struct HSB
{
public float Hue;
public float Saturation;
public float Brightness;
public HSB(float H, float S, float B)
{
Hue = H;
Saturation = S;
Brightness = B;
}
}
// ---> Methods to Display Colors go here
}

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.

Color Viewer: Display Selected Color


Listing 8-3
and Values
private void ColorTree_AfterSelect(Object sender,
TreeViewEventArgs e)
// Event handler for AfterSelect event
{
// Call method to display color and info
if (e.Node.Parent != null) ShowColor(e.Node);
}
private void ShowColor(TreeNode viewNode)
{
Graphics g = panel1.CreateGraphics(); // Color panel
Graphics g2 = panel2.CreateGraphics(); // Brightness panel
try
{
// Convert node's text value to Color object
Color myColor = Color.FromName(viewNode.Text);
Brush b = new SolidBrush(myColor);
// Display selected color
g.FillRectangle(b, 0,0,panel1.Width,panel1.Height);
HSB hsbVal= (HSB) viewNode.Tag;
// Convert hue to value between 0 and 255 for displaying
int huescaled = (int) (hsbVal.Hue / 360 * 255);
hText.Text = huescaled.ToString();
sText.Text = hsbVal.Saturation.ToString();
lText.Text = hsbVal.Brightness.ToString();
rText.Text = myColor.R.ToString();
gText.Text = myColor.G.ToString();
bText.Text = myColor.B.ToString();
// Draw Brightness scale
Rectangle rect = new Rectangle(new Point(0,0),
new Size(panel2.Width, panel2.Height));
// Create multi-color brush gradient for brightness scale
LinearGradientBrush bg = new LinearGradientBrush(rect,
Color.Empty, Color.Empty,90);
ColorBlend myBlend = new ColorBlend();
myBlend.Colors = new Color[] {Color.White, myColor,
Color.Black};
myBlend.Positions = new float[]{0f,
1-hsbVal.Brightness,1f};
8.3 Images 407

Color Viewer: Display Selected Color


Listing 8-3
and Values (continued)
bg.InterpolationColors = myBlend;
g2.FillRectangle(bg, rect);
// Draw marker on brightness scale showing current color
int colorPt = (int)((1–hsbVal.Brightness)* panel1.Height);
g2.FillRectangle(Brushes.White,0,colorPt,10,2);
b.Dispose();
bg.Dispose();
}
finally
{
g.Dispose();
g2.Dispose();
}
}

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 standard image formats such as GIF and JPG files.


• Creating images dynamically in memory that can then be displayed in
a WinForms environment or served up as images on a Web server or
Web Service.
• Using images as a surface to draw on.

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:

• Loading and storing images. Images can be retrieved from files,


from a stream of bytes, from the system clipboard, or from resource
files. After you have loaded or created an image, it is easily saved into
a specified image format using the Save method of the Image or
Bitmap class.
• Displaying an image. Images are dynamically displayed by writing
them to the surface area of a form or control encapsulated by a
Graphics object.
• Manipulating an image. An image is represented by an array of bits
in memory. These bits can be manipulated in an unlimited number of
ways to transform the image. Traditional image operations include
resizing, cropping, rotating, and skewing. GDI+ also supports chang-
ing an image’s overall transparency or resolution and altering individ-
ual bits within an image.

Loading and Storing Images


The easiest way to bring an image into memory is to pass the name of the file to a
Bitmap constructor. Alternatively, you can use the FromFile method inherited from
the Image class.

string fname = "c:\\globe.gif";


Bitmap bmp = new Bitmap(fname);
bmp = (Bitmap)Bitmap.FromFile(fname); // Cast to convert Image

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

GDI+ support images in several standard formats: bitmaps (BMP), Graphics


Interchange Format (GIF), Joint Photographic Experts Group (JPEG), Portable
Network Graphics (PNG), and the Tag Image File Format (TIFF). These are used
for both loading and storing images and, in fact, make it quite simple to convert one
format to another. Here is an example that loads a GIF file and stores it in JPEG
format:
8.3 Images 409

string fname = "c:\\globe.gif";


bmp = new Bitmap(Image.FromFile(fname));
bmp.Save("c:\\globe.jpg",
System.Drawing.Imaging.ImageFormat.Jpeg);
// Compare size of old and new file
FileInfo fi= new FileInfo(fname);
int old = (int) fi.Length;
fi = new FileInfo("c:\\globe.jpg");
string msg = String.Format("Original: {0} New:
{1}",old,fi.Length);
MessageBox.Show(msg); // ---> Original: 28996 New: 6736

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

Figure 8-14 Codecs


410 Chapter 8 ■ .NET Graphics Using GDI+

The DrawImage method of the Graphics object is used to display an image on


the Graphics object’s surface. These two statements load an image and draw it full
size at the upper-left corner of the graphics surface (0,0). If the Graphics object sur-
face is smaller than the image, the image is cropped (see the first figure in the follow-
ing example).

Bitmap bmp = new Bitmap("C:\\globe.gif");


g.DrawImage(bmp,0,0); // Draw at coordinates 0,0

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.

1. The source image is drawn at its full size on the target


surface. Cropping occurs because the destination
panel is smaller than the source image.
Graphics g = panel1.CreateGraphics();
g.DrawImage(bmp,0,0);

2. The source image is scaled to fit the destination


rectangle.
Rectangle dRect = new
Rectangle(new Point(0,0),
new Size(panel1.Width,panel1.Height));
g.DrawImage(bmp, dRect);
//Or panel1.ClientRectangle

3. Part of the source rectangle (left corner = 100,0)


is selected.
Rectangle sRect = new
Rectangle(new Point(100,0),
new Size(192,160));
g.DrawImage(bmp,0,0,sRect,GraphicsUnit.Pixel);
8.3 Images 411

4. Combines examples 2 and 3: A portion of the source


rectangle is scaled to fit dimensions of destination
rectangle.
g.DrawImage(bmp,dRect,sRect,
GraphicsUnit.Pixel);

5. The destination points specify where the upper-left,


upper-right, and lower-left point of the original are
placed. The fourth point is determined automatically
in order to create a parallelogram.
Point[]dp = {new Point(10,0),new Point(80,10),
new Point(0,70)}; // ul, ur, ll
g.DrawImage(bmp,dp);

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.

A Note on Displaying Icons


Icons (.ico files) do not inherit from the Image class and cannot be rendered by the
DrawImage method; instead, they use the Graphics.DrawIcon method. To display
one, create an Icon object and pass the file name or Stream object containing the
image to the constructor. Then, use DrawIcon to display the image at a desired loca-
tion.

Icon icon = new Icon("c:\\clock.ico");


g.DrawIcon(icon,120,220); // Display at x=120, y=220
icon.Dispose(); // Always dispose of object
g.Dispose();

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+

Rotating and Mirroring


Operations that rotate or skew an image normally rely on the DrawImage overload
that maps three corners of the original image to destination points that define a par-
allelogram.

void DrawImage(Image image, Point destPoints[])

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˚

Figure 8-15 Manipulation using DrawImage

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.

Bitmap bmp = new Bitmap(fname); // Get image


// Mirror Image
Point ptA = new Point(bmp.Width,0); // Upper left
Point ptB = new Point(0,0); // Upper right
Point ptC = new Point(bmp.Width, bmp.Height); // Lower left
Point[]dp = {ptA,ptB,ptC};
g.DrawImage(bmp,dp);

Many of these same effects can be achieved using the Bitmap.RotateFlip


method, which has this signature:

Public void RotateFlip(RotateFlipType rft)

RotateFlipType is an enumeration that indicates how many degrees to rotate


the image and whether to “flip” it after rotating (available rotations are 90, 180, and
270 degrees). Here are a couple of examples:
8.3 Images 413

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

Working with a Buffered Image


All of the preceding examples are based on drawing directly to a visible panel control
on a form. It is also possible to load an image into, or draw your own image onto, an
internal Bitmap object before displaying it. This can offer several advantages:

• It permits you to create images such as graphs dynamically and display


them in the application or load them into Web pages for display.
• It improves performance by permitting the application to respond to a
Paint event by redrawing a stored image, rather than having to
reconstruct the image from scratch.
• It permits you to keep the current “state” of the image. As long as all
transformations, such as rotating or changing colors, are made first to
the Bitmap object in memory, it will always represent the current
state of the image.

To demonstrate, let’s input a two-color image, place it in memory, change pixels in


it, and write it to a panel. Figure 8-16 shows the initial image and the final image
after pixels are swapped.

Figure 8-16 Use GetPixel() and SetPixel() to swap pixels

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.

Graphics g = pan.CreateGraphics(); // Create from a panel


Bitmap bmp = new Bitmap("c:\\flag.gif");
g.DrawImage(bmp,0,0); // Draw flag to panel
Bitmap bmpMem = new Bitmap(bmp.Width,bmp.Height);
Graphics gMem = Graphics.FromImage(bmpMem);
gMem.DrawImage(bmp,0,0); // Draw flag to memory
// Define a color object for the red pixels
Color cnRed = Color.FromArgb(255,214,41,33); // a,r,g,b
// Loop through all pixels in image and swap them
for (int y=0; y<bmpMem.Height; y++)
{
for (int x=0; x<bmpMem.Width; x++)
{
Color px = bmpMem.GetPixel(x,y);
if(px.G > 240)
bmpMem.SetPixel(x,y, cnRed); // Set white to red
else bmpMem.SetPixel(x,y,Color.White); // Set red to white
}
}
g.DrawImage(bmpMem,0,0); // Display reversed flag on panel
gMem.Dispose();
g.Dispose();

Core Note

Applications that dynamically create images for display should draw


them offscreen onto a Bitmap and then render them to the screen when
completed.

Sample Project: Working with Images


This application brings together many of the concepts presented in this chapter: han-
dling the Paint event; using Invalidation to clear portions of a screen; and using
DrawImage to rotate, flip, and zoom in and out on an image.
The screen for the program is shown in Figure 8-17. It consists of a menu with
three main selections: File is used to load an image; Image has options to mirror,
flip, or copy an image; and Screen refreshes the screen. The panel control on the
left serves as the main viewing window into which an image is loaded. The Image
8.3 Images 415

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

Implementing Menu Operations


The following fields are defined at the form level to make them available for menu
operations:

private Bitmap bmp; // Holds original loaded image


private Bitmap newbmp; // Holds latest version of image
private bool imageStatus = false; // Indicates image is loaded
private int resizeLevel; // Level image magnified/reduced

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+

Listing 8-4 Image Viewer: Menu Items


Private void menuItem6_Click(object sender, System.EventArgs e)
{
// Load image from file
OpenFileDialog fd = new OpenFileDialog();
fd.InitialDirectory = "c:\\" ;
fd.Filter = "Image Files | *.JPG;*.GIF";
if (fd.ShowDialog() == DialogResult.OK)
{
string fname= fd.FileName;
using(Graphics g = panel1.CreateGraphics())
{
bmp = new Bitmap(fname); // Load image from file
newBmp = bmp; // Save copy of image
// Clear main panel before drawing to it
g.FillRectangle(Brushes.White,0,0,
panel1.Width,panel1.Height );
Rectangle r = new Rectangle(0,0,bmp.Width,
bmp.Height);
g.DrawImage(bmp,r); // Draw image on panel
ImageStatus = true; // Indicates image exists
// Clear small panel
Graphics gClear= panel2.CreateGraphics();
g.FillRectangle(Brushes.White,0,0,
panel2.Width,panel2.Height );
gClear.Dispose();
}
}
}
private void menuItem4_Click(object sender, System.EventArgs e)
{
// Mirror image
Graphics g= panel1.CreateGraphics();
int h = newBmp.Height;
int w = newBmp.Width;
Point[] destPts = {
new Point(w,0),
new Point(0,0),
new Point(w,h) };
Bitmap tempBmp = new Bitmap(w,h);
Graphics gr= Graphics.FromImage(tempBmp);
gr.DrawImage(newBmp, destPts); // Mirror temporary image
g.DrawImage(tempBmp,0,0); // Draw image on panel
newBmp = tempBmp; // Set to mirrored image
8.3 Images 417

Listing 8-4 Image Viewer: Menu Items (continued)


g.Dispose();
gr.Dispose();
}
private void menuItem3_Click(object sender, System.EventArgs e)
{
// Flip image vertically
newBmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
Graphics g = panel1.CreateGraphics();
g.DrawImage(newBmp,0,0);
g.Dispose();
}
private void menuItem9_Click(object sender, System.EventArgs e)
{
// Refresh Screen
panel1.Invalidate(); // Redraw entire panel
panel1.Update();
selectStatus = false; // Refreshing removes selected area
}

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.

Drawing a Selection Rectangle on the Image


Listing 8-5 contains event handlers for Paint, MouseDown, MouseUp, and Mouse-
Move. These routines permit the user to select a rectangular area on the image that
can then be copied to panel2. The event handling routines are associated with the
events using this code in the constructor:

panel1.MouseDown += new MouseEventHandler(Mouse_Down);


panel1.MouseUp += new MouseEventHandler(Mouse_Up);
panel1.MouseMove += new MouseEventHandler(Mouse_Move);
panel1.Paint += new PaintEventHandler(RePaint);
418 Chapter 8 ■ .NET Graphics Using GDI+

The following fields, defined at the form level, are used to keep status information:

private Point lastPoint = Point.Empty; // Tracks mouse movement


private Point origPoint = Point.Empty; // Mouse down coordinates
private Rectangle rectSel; // Selected area
private bool selectStatus = false; // True if area selected

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.

Listing 8-5 Image Viewer: Select Area of Image to Copy


private void RePaint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
// Redraw part of current image to panel
if (ImageStatus) g.DrawImage(newBmp,
e.ClipRectangle,e.ClipRectangle, GraphicsUnit.Pixel);
base.OnPaint(e);
}
private void Mouse_Down(object sender, MouseEventArgs e)
{
if (lastPoint != Point.Empty)
{
panel1.Invalidate(rectSel); // Clear previous rect.
panel1.Update();
}
lastPoint.X= e.X;
lastPoint.Y= e.Y;
origPoint = lastPoint; // Save origin of selected area
selectStatus=true;
}
private void Mouse_Up(object sender, MouseEventArgs e)
{
// Selected area complete. Define it as a rectangle.
rectSel.X = e.X;
if (e.X > origPoint.X) rectSel.X = origPoint.X;
rectSel.Y = origPoint.Y;
8.3 Images 419

Listing 8-5 Image Viewer: Select Area of Image to Copy (continued)


rectSel.Width = Math.Abs(e.X- origPoint.X)+1;
rectSel.Height= Math.Abs(e.Y - origPoint.Y)+1;
origPoint = Point.Empty;
if (rectSel.Width < 2) selectStatus=false;
}
private void Mouse_Move(object sender, MouseEventArgs e)
{
// Tracks mouse movement to draw bounding rectangle
if (origPoint != Point.Empty)
{
Rectangle r;
Rectangle rd;
// Get rectangle area to invalidate
int xop = origPoint.X;
if (xop > lastPoint.X) xop= lastPoint.X;
int w = Math.Abs(origPoint.X - lastPoint.X)+1;
int h = lastPoint.Y - origPoint.Y+1;
r = new Rectangle(xop,origPoint.Y,w,h);
// Get rectangle area to draw
xop = e.X >= origPoint.X ? origPoint.X:e.X;
w = Math.Abs(origPoint.X - e.X);
h = e.Y - origPoint.Y;
rd = new Rectangle(xop, origPoint.Y,w,h);
Graphics g = panel1.CreateGraphics();
// Redraw image over previous rectangle
g.DrawImage(newBmp,r,r);
// Draw rectangle around selected area
g.DrawRectangle(Pens.Red,rd);
g.Dispose();
lastPoint.X= e.X;
lastPoint.Y= e.Y;
}
}

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+

Copying and Manipulating


the Image on the Small Panel
The following code is executed when Image – Copy is selected from the menu. The
selected area is defined by the rectangle rectSel. The image bounded by this rect-
angle is drawn to a temporary Bitmap, which is then drawn to panel2. A copy of the
contents of panel2 is always maintained in the Bitmap smallBmp.

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

A Note on GDI and BitBlt for


the Microsoft Windows Platform
As we have seen in the preceding examples, Graphics.DrawImage is an easy-to-use
method for drawing to a visible external device or to a Bitmap object in memory. As
a rule, it meets the graphics demands of most programs. However, there are situa-
tions where a more flexible or faster method is required. One of the more common
graphics requirements is to perform a screen capture of an entire display or a portion
of a form used as a drawing area. Unfortunately, GDI+ does not provide a direct way
to copy bits from the screen memory. You may also have a graphics-intensive applica-
tion that requires the constant redrawing of complex images. In both cases, the solu-
tion may well be to use GDI—specifically the BitBlt function.
If you have worked with the Win32 API, you are undoubtedly familiar with BitBlt.
If not, BitBlt, which is short for Bit Block Transfer, is a very fast method for copying
bits to and from a screen’s memory, usually with the support of the graphics card. In
fact, the DrawImage method uses BitBlt underneath to perform its operations.
Even though it is part of the Win32 API, .NET makes it easy to use the BitBlt
function. The first step is to use the System.Runtime.InteropServices
namespace to provide the DllImportAttribute for the function. This attribute
makes the Win32 API available to managed code.

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

int xSrc, // x coordinate of image source


int ySrc, // y coordinate of image source
System.Int32 dwRop // Copy is specified by 0x00CC0020
);

This function copies a rectangular bitmap from a source to a destination. The


source and destination are designated by handles to their device context. (In Win-
dows, a device context is a data structure that describes the object’s drawing surface
and where to locate it in memory.) The type of bit transfer performed is determined
by the value of the dwRop parameter. A simple copy takes the value shown in the
declaration. By changing this value, you can specify that the source and target bits be
combined by AND, OR, XOR, and other logical operators.
Using bitBlt is straightforward. In this example, the contents of a panel are cop-
ied to a Bitmap object in memory. Creating the Graphics object for the panel and
Bitmap should be familiar territory. Next, use the Graphics object’s GetHdc
method to obtain a handle for the device context for the panel and Bitmap. These
are then passed to the bitBlt function along with a ropType argument that tells the
function to perform a straight copy operation.

// Draw an image on to a panel


Graphics g = panel1.CreateGraphics();
g.DrawLine(Pens.Black,10,10,50,50);
g.DrawEllipse(Pens.Blue,0,30,40,30);
// Create a memory Bitmap object to be the destination
Bitmap fxMem = new Bitmap(panel1.Width,panel1.Height);
Graphics gfxMem = Graphics.FromImage(fxMem);
int ropType= 0x00CC0020; // perform a copy operation
// Get references to the device context for the source and target
IntPtr HDCSrc= g.GetHdc();
IntPtr HDCMem= gfxMem.GetHdc();
// Copy a rectangular area from the panel of size 100 x 100
bitBlt(HDCMem,0,0,100,100,HDCSrc,0,0,ropType);
// Release resources when finished
g.ReleaseHdc(HDCSrc);
gfxMem.ReleaseHdc(HDCMem);
g.Dispose();
gfxMem.Dispose();

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

GDI+ supports a wide range of graphics-related operations ranging from drawing to


image manipulation. All require use of the Graphics object that encapsulates the
drawing surface and provides methods for drawing geometric shapes and images to
the abstract surface. The graphic is typically rendered to a form or control on the
form to be displayed in a user interface.
The task of drawing is simplified by the availability of methods to create pre-
defined geometric shapes such as lines, ellipses, and rectangles. These shapes are
outlined using a Pen object that can take virtually any color or width, and can be
drawn as a solid line or broken into a series of dashes, dots, and combinations of
these. The shapes are filled with special brush objects such as SolidBrush, Tex-
tureBrush, LinearGradientBrush, and HatchBrush.
The Image and Bitmap classes are used in .NET to represent raster-based
images. Most of the standard image formats—BMP, GIF, JPG, and PNG—are sup-
ported. After an image is loaded or drawn, the Graphics.DrawImage method may
be used to rotate, skew, mirror, resize, and crop images as it displays them to the
Graphics object’s surface.
When custom graphics are included in a form or control, it is necessary to respond
to the Paint event, to redraw all or part of the graphics on demand. A program can
force this event for a control by invoking the Control.Invalidate method.

8.5 Test Your Understanding


1. What two properties does PaintEventArgs provide a Paint handler
routine? Describe the role of each.

2. What method is called to trigger a Paint event for a control?

3. Which image is drawn by the following code?


GraphicsPath gp = new GraphicsPath();
gp.AddLine(0,0,60,0);
gp.AddLine(0,20,60,20);
g.DrawPath(new Pen(Color.Black,2),gp);
424 Chapter 8 ■ .NET Graphics Using GDI+

4. Which of these statements will cause a compile-time error?


a. Brush sb = new SolidBrush(Color.Chartreuse);
b. Brush b = new Brush(Color.Red);
c. Brush h = new HatchBrush(HatchStyle.DottedDiamond,
Color.Blue,Color.Red);

5. Which of these colors is more transparent?


Color a = FromArgb(255,112,128,144);
Color b = FromArgb(200,212,128,200);

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%

7. The Russian artist Wassily Kandinsky translated the dance movements


of Gret Palucca into a series of schematic diagrams consisting of simple
geometric shapes. The following is a computer generated schematic
(approximating Kandinsky’s) that corresponds to the accompanying
dance position. The schematic is created with a GraphicsPath object
and the statements that follow. However, the statements have been
rearranged, and your task is to place them in a sequence to draw the
schematic. Recall that a GraphicsPath object automatically connects
objects.

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

8. The following statements are applied to the original image A:


Point ptA = new Point(bmp.Height,0);
Point ptB = new Point(bmp.Height,bmp.Width);
Point ptC = new Point(0,0);
Point[]dp = {ptA,ptB,ptC};
g.DrawImage(bmp,dp);
Which image is drawn by the last statement?
FONTS, TEXT,
AND PRINTING

Topics in This Chapter

• Fonts: .NET classes support the creation of individual font objects


and font families. Treating a font as an object allows an
application to set a font’s style and size through properties.
• Text: The Graphics.DrawString method and
StringFormat class are used together to draw and format text
on a drawing surface.
• Printing: The PrintDocument class provides the methods,
properties, and events used to control .NET printing. It exposes
properties that allow printer selection, page setup, and choice of
pages to be printed.
• Creating a Custom PrintDocument Class: A custom
PrintDocument class provides an application greater control
over the printing process and can be used to ensure a consistent
format for reports.
9

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.

• A font family is at the top of the hierarchy and consists of a group of


typefaces that may be of any style, but share the same basic appear-
ance. Tahoma is a font family.
• A typeface is one step up the hierarchy and refers to a set of fonts that
share the same family and style but can be any size. Tahoma bold is a
typeface.
• A font defines how a character is represented in terms of font family,
style, and size. For example (see Figure 9-1), Tahoma Regular10
describes a font in the Tahoma font family having a regular style—as
opposed to bold or italic—and a size of 10 points.

Tahoma Font Family

normal bold italic bold italic Typeface

8 10 12 . . . 8 10 12 . . . Font

Figure 9-1 Relationship of fonts, typefaces, and font families

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

Creating a FontFamily Object


Constructors:

public FontFamily (string name);


public FontFamily (GenericFontFamilies genfamilies);

Parameters:
name A font family name such as Arial or Tahoma.

FontFamily ff = new FontFamily("Arial"); // Arial family

GenericFontFamilies An enumeration (in the System.Drawing.Text


namespace) that has three values: Monospace, Serif,
and SansSerif. This constructor is useful when you are
interested in a generic font family rather than a specific
one. The following constructor specifies a font family in
which each character takes an equal amount of space hori-
zontally. (Courier New is a common monospace family.)

FontFamily ff = new FontFamily(GenericFontFamilies.Monospace);

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.

Listing Font Families


The FontFamily.Families property returns an array of available font families.
This is useful for allowing a program to select a font internally to use for displaying
fonts to users, so they can select one. The following code enumerates the array of
font families and displays the name of the family using the Name property; it also uses
the IsStyleAvailable method to determine if the family supports a bold style
font.

string txt = "";


foreach (FontFamily ff in FontFamily.Families)
{
txt += ff.Name;
if (ff.IsStyleAvailable(FontStyle.Bold))txt= += " (B)";
txt += "\r\n"; // Line feed
}
textBox1.Text = txt; // Place font family names in textbox
430 Chapter 9 ■ Fonts, Text, and Printing

The Font Class


Although several constructors are available for creating fonts, they fall into two catego-
ries based on whether their first parameter is a FontFamily type or a string containing
the name of a FontFamily. Other parameters specify the size of the font, its style, and
the units to be used for sizing. Here are the most commonly used constructors:

Constructors:

public Font(FontFamily ff, Float emSize);


public Font(FontFamily ff, Float emSize, FontStyle style);
public Font(FontFamily ff, Float emSize, GraphicsUnit unit);
public Font(FontFamily ff, Float emSize, FontStyle style,
GraphicsUnit unit);

public Font(String famname, Float emSize);


public Font(String famname, Float emSize, FontStyle style);
public Font(String famname, Float emSize, GraphicsUnit unit);
public Font(String famname, Float emSize, FontStyle style,
GraphicsUnit unit);

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)

GraphicsUnits and sizing are discussed later in this section.

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

FontFamily ff = new FontFamily("Arial");


Font normFont = new Font(ff,10); // Arial Regular 10 pt.
9.1 Fonts 431

The simplest approach, without using a font family, is to pass the typeface name
and size to a constructor:

Font normFont = new Font("Arial",10) // Arial Regular 10 point

By default, a Regular font style is provided. To override this, pass a FontStyle


enumeration value to the constructor:

Font boldFont = new Font("Arial",10,FontStyle.Bold);


Font bldItFont = new Font("Arial",10,
FontStyle.Bold | FontStyle.Italic); // Arial bold italic 10

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.

using (Font normFont = new Font("Arial",12)) {

Using Font Metrics to Determine Font Height


The term font metrics refers to the characteristics that define the height of a font
family. .NET provides both Font and FontClass methods to expose these values.
To best understand them, it is useful to look at the legacy of typesetting and the
terms that have been carried forward from the days of Gutenberg’s printing press to
the .NET Framework Class Library.
In typography, the square grid (imagine graph paper) used to lay out the outline
(glyph) of a font is referred to as the em square. It consists of thousands of cells—
each measured as one design unit. The outline of each character is created in an em
square and consists of three parts: a descent, which is the part of the character below
the established baseline for all characters; the ascent, which is the part above the
baseline; and the leading, which provides the vertical spacing between characters on
adjacent lines (see Figure 9-2).

Ascender Line Leading

Baseline
Descender Line
Key Ascent

Descent
Font
Height

Figure 9-2 Font metrics: the components of a font


432 Chapter 9 ■ Fonts, Text, and Printing

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:

FontFamily ff = new FontFamily("Arial");


Font myFont = new Font(ff,20); // Height is 20 points
int emHeight = ff.GetEmHeight(FontStyle.Regular); // 2048
int ascHeight = ff.GetCellAscent(FontStyle.Regular); // 1854
int desHeight = ff.GetCellDescent(FontStyle.Regular); // 434
int lineSpace = ff.GetLineSpacing(FontStyle.Regular); // 2355
// Get Line Height in Points (20 x (2355/2048))
float guHeight = myFont.Size * (lineSpace / emHeight); // 22.99
float guHeight2 = myFont.GetHeight(); // 30.66 pixels

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

Member Units Description

FontFamily.GetEmHeight Design The height of the em square used to design


units the font family. TrueType fonts usually have
a value of 2,048.

FontFamily.GetCellAscent Design The height of a character above the base


units line.

FontFamily.GetCellDescent Design The height of a character below the base


units line.

FontFamily.GetLineSpacing Design The total height reserved for a character


units plus line spacing. The sum of CellAscent,
CellDescent, and Leading (see Figure
9-2). This value is usually 12 to 15 percent
greater than the em height.

Font.Size Graphics The base size (size passed to constructor).


unit
9.2 Drawing Text Strings 433

Table 9-1 Using Font and FontFamily to Obtain Font Metrics (continued)

Member Units Description

Font.SizeInPoints Points The base size in points.

Font.GetHeight Pixels The total height of a line. Calculated by


converting LineSpacing value to pixels.

Definitions:
cell height = ascent + descent
em height = cell height – internal leading
line spacing = cell height + external leading

9.2 Drawing Text Strings


The Graphics.DrawString method is the most straightforward way to place text
on a drawing surface. All of its overloaded forms take a string to be printed, a font to
represent the text, and a brush object to paint the text. The location where the text is
to be printed is specified by a Point object, x and y coordinates, or a Rectangle
object. The most interesting parameter is an optional StringFormat object that
provides the formatting attributes for the DrawString method. We’ll examine it in
detail in the discussion on formatting.
Here are the overloads for DrawString. Note that StringFormat is optional in
each.

Overloads:

public DrawString(string, font, brush, PointF


[,StringFormat]);
public DrawString(string, font, brush, float, float
[,StringFormat]);
public DrawString(string, font, brush, RectangleF
[,StringFormat]);

Example:

Font regFont = new Font("Tahoma",12);


String s = "ice mast high came floating by as green as emerald.";
// Draw text beginning at coordinates (20,5)
g.DrawString(s, regFont, Brushes.Black, 20,5);
regFont.Dispose();
434 Chapter 9 ■ Fonts, Text, and Printing

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.

Drawing Multi-Line Text


Several Drawstring overloads receive a rectangle to define where the output string
is drawn. Text drawn into these rectangular areas is automatically wrapped to the
next line if it reaches beyond the rectangle’s boundary. The following code displays
the fragment of poetry in an area 200 pixels wide and 50 pixels high.

String s = "and ice mast high came floating by as green


as emerald."
// All units in pixels
RectangleF rf = new RectangleF(20,5,200,50);
// Fit text in rectangle
g.Drawstring(s,regFont,Brushes.Black, rf);

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.

SizeF sf = g.MeasureString(String s, Font f, int w);

Using this method, the preceding code can be rewritten to handle the dynamic
creation of the bounding rectangle:

Font regFont = new Font("Tahoma",12);


String s = "and ice mast high came floating by as green
as emerald."
int lineWidth = 200;
SizeF sf = g.MeasureString(s, regFont, lineWidth);
// Create rectangular drawing area based on return
// height and width
RectangleF rf = new RectangleF(20,5,sf.Width, sf.Height);
// Draw text in rectangle
9.2 Drawing Text Strings 435

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.

Formatting Strings with the StringFormat Class


When passed as an argument to the DrawString method, a StringFormat object
can provide a number of formatting effects. It can be used to define tab positions, set
column widths, apply right or left justification, and control text wrapping and trunca-
tion. As we will see in the next section, it is the primary tool for creating formatted
reports. The members that we will make heaviest use of are shown in Table 9-2.

Table 9-2 Important StringFormat Members

Member Description

Alignment A StringAlignment enumeration value:


StringAlignment.Center—Text is centered in layout rectangle.
StringAlignment.Far—Text is aligned to the right for left-to-right text.
StringAlignment.Near—Text is aligned to the left for left-to-right text.

Trimming A StringTrimming enumeration value that specifies how to trim charac-


ters that do not completely fit in the layout rectangle:
StringTrimming.Character—Text is trimmed to the nearest character.
StringTrimming.EllipsisCharacter—Text is trimmed to the nearest
character and an ellipsis (...) is placed at the end of the line.
StringTrimming.Word—Text is trimmed to the nearest word.

SetTabStops Takes two parameters: SetTabStops(firstTabOffset, tabStops)


FirstTabOffset—Number of spaces between beginning of line and
first tab stop.
TabStops—Array of distances between tab stops.

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

Using Tab Stops


Tab stops provide a way to align proportionate-spaced font characters into columns.
To set up tab stops, you create a StringFormat object, use its SetTabStops
method to define an array of tab positions, and then pass this object to the Draw-
String method along with the text string containing tab characters (\t).

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.

Listing 9-1 Using Tab Stops to Display Columns of Data


private void RePaint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Font hdrFont = new Font("Arial", 10,FontStyle.Bold);
Font bdyFont = new Font("Arial", 10);
// (1) Create StringFormat Object
StringFormat strFmt = new StringFormat();
// (2) Define Tab stops
float[] ts = {140,60,40};
strFmt.SetTabStops(0, ts);
// (3) Define column header text to be printed with tabs
string header = "Artist\tCountry\tBorn\tDied";
9.2 Drawing Text Strings 437

Listing 9-1 Using Tab Stops to Display Columns of Data (continued)


// (4) Print column headers
g.DrawString(header, hdrFont, Brushes.Black,10,10,strFmt);
// Print one line below header
string artist = "Edouard Manet\tEngland\t1832\t1892";
g.DrawString(artist,bdyFont,Brushes.Black,10,
10 + bdyFont.GetHeight(), strFmt);
bdyFont.Dispose();
hdrFont.Dispose();
}

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.

Figure 9-3 Printing with tab stops

The unit of measurement in this example is a pixel. This unit of measurement is


determined by the Graphics.PageUnit property. To override the default (pixels),
set the property to a GraphicsUnit enumeration value—for example, g.PageUnit
= GraphicsUnit.Inch. Be aware that all subsequent drawing done with the
Graphics object will use these units.

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

String Trimming, Alignment, and Wrapping


The StringFormat Trimming and Alignment properties dictate how text is placed
within a RectangleF object. Alignment works as you would expect, allowing you to
center, right justify, or left justify a string. Trimming specifies how to truncate a string
that extends beyond the boundaries of a rectangle when wrapping is not in effect.
The basic options are to truncate on a word or character.
The following code segments demonstrate some of the common ways these prop-
erties can be used to format text strings.

Example 1: Printing Without a StringFormat Object


Font fnt = new Font("Tahoma",10,FontStyle.Bold);
RectangleF r = new RectangleF(5,5,220,60);
string txt = "dew drops are the gems of morning";
g.DrawString(txt,fnt,Brushes.Black,r);
g.DrawRectangle(Pens.Red,r.X,r.Y,r.Width,r.Height);

dew drops are the gems of


morning

Example 2: Printing with NoWrap Option


StringFormat strFmt = new StringFormat();
strFmt.FormatFlags = StringFormatFlags.NoWrap;
g.DrawString(txt,fnt,Brushes.Black,r,strFmt);

dew drops are the gems of mor

Example 3: Printing with NoWrap and Clipping on a Word


StringFormat strFmt = new StringFormat();
strFmt.FormatFlags = StringFormatFlags.NoWrap;
strFmt.Trimming = StringTrimming.Word;
g.DrawString(txt,fnt,Brushes.Black,r,strFmt);

dew drops are the gems of


9.3 Printing 439

Example 4: Printing with NoWrap, Clipping on Word,


and Right Justification
StringFormat strFmt = new StringFormat();
strFmt.FormatFlags = StringFormatFlags.NoWrap;
strFmt.Trimming = StringTrimming.Word;
strFmt.Alignment = StringAlignment.Far;
g.DrawString(txt,fnt,Brushes.Black,r,strFmt);

dew drops are the gems of


morning

StringFormat also has a LineAlignment property that permits a text string to


be centered vertically within a rectangle. To demonstrate, let’s add two statements to
Example 4:

strFmt.Alignment = StringAlignment.Center;
strFmt.LineAlignment = StringAlignment.Center;

dew drops are the gems of


morning

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

PrintDocument pd = new PrintDocument();

The printing process in initiated when the PrintDocument.Print method is


invoked. As shown in Figure 9-4, this triggers the BeginPrint and PrintPage
events of the PrintDocument class.

PrintDocument BeginPrint PrintPage HasMore EndPrint


.Print event event Pages event
N

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.

Listing 9-2 Basic Steps in Printing


using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
// Code for Form class goes here
// Respond to button click
private void btnPrint_Click(object sender,
System.EventArgs e)
{ PrintReport(); }
// Set up overhead for printing
private void PrintReport()
{
9.3 Printing 441

Listing 9-2 Basic Steps in Printing (continued)


// (1) Create PrintDocument object
PrintDocument pd = new PrintDocument();
// (2) Create PrintDialog
PrintDialog pDialog = new PrintDialog();
pDialog.Document = pd;
// (3) Create PrintPreviewDialog
PrintPreviewDialog prevDialog = new PrintPreviewDialog();
prevDialog.Document = pd;
// (4) Tie event handler to PrintPage event
pd.PrintPage += new PrintPageEventHandler(Inven_Report);
// (5) Display Print Dialog and print if OK received
if (pDialog.ShowDialog()== DialogResult.OK)
{
pd.Print(); // Invoke PrintPage event
}
}
private void Inven_Report(object sender,
PrintPageEventArgs e)
{
Graphics g = e.Graphics;
Font myFont = new Font("Arial",10);
g.DrawString("Sample Output",myFont,Brushes.Black,10,10);
myFont.Dispose();
}
}

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

Figure 9-5 Selected PrintDocument properties and events

An understanding of these classes and events is essential to implementing a robust


and flexible printing application that provides users full access to printing options.

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:

PrintDocument pd = new PrintDocument();


PrintDialog pDialog = new PrintDialog();
pDialog.Document = pd;
if (pDialog.ShowDialog()== DialogResult.OK)
{
pd.Print(); // Invoke PrintPage event
}

You can also create your own printer selection list by enumerating the
InstalledPrinters collection:

// Place names of printer in printerList ListBox


foreach(string pName in PrinterSettings.InstalledPrinters)
printerList.Items.Add(pName);

After the printer is selected, it is assigned to the PrinterName property:

string printer=
printerList.Items[printerList.SelectedIndex].ToString();
pd.PrinterSettings.PrinterName = printer;

Selecting Pages to Print


The PrinterSettings.PrintRange property indicates the range of pages to be
printed. Its value is a PrintRange enumeration value—AllPages, Selection, or
SomePages—that corresponds to the All, Pages, and Selection print range options
on the PrintDialog form. If Pages is selected, the PrinterSettings.FromPage
and ToPage properties specify the first and last page to print. There are several
things to take into consideration when working with a print range:

• To make the Selection and Pages radio buttons available on the


PrintDialog form, set PrintDialog.AllowSomePages and
PrintDialog.AllowSelection to true.
• The program must set the FromPage and ToPage values before dis-
playing the Print dialog box. In addition, it’s a good practice to set the
MinimumPage and MaximumPage values to ensure the user does not
enter an invalid page number.
• Keep in mind that the values entered on a PrintDialog form do
nothing more than provide parameters that are available to the appli-
cation. It is the responsibility of the PrintPage event handler to
implement the logic that ensures the selected pages are printed.
444 Chapter 9 ■ Fonts, Text, and Printing

The following segment includes logic to recognize a page range selection:

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.

Setting Printer Resolution


The PrinterSettings class exposes all of the print resolutions available to the
printer through its PrinterResolutions collection. You can loop through this col-
lection and list or select a resolution by examining the Kind property of the contained
PrinterResolution objects. This property takes one of five PrinterResolution-
Kind enumeration values: Custom, Draft, High, Low, or Medium. The following code
searches the PrinterResolutions collection for a High resolution and assigns that
as a PrinterSettings value:

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.

PageSetupDialog ps = new PageSetupDialog();


ps.Document = pd; // Assign reference to PrinterDocument
ps.ShowDialog();

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 Page settings layout

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;

All measurements are in hundredths of an inch. The MarginBounds rectangle


shown in the figure represents the area inside the margins. It is not a PrinterSet-
tings property and is made available only to the PrintPage event handler.

Core Note

Many printers preserve an edge around a form where printing cannot


occur. On many laser printers, for example, this is one-quarter of an
inch. In practical terms, this means that all horizontal coordinates used
for printing are shifted; thus, if DrawString is passed an x coordinate of
100, it actually prints at 125. It is particularly important to be aware of
this when printing on preprinted forms where measurements must be
exact.

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:

pd.BeginPrint += new PrintEventHandler(Rpt_BeginPrint);


9.3 Printing 447

This simple event handler creates the font to be used in the report. The font must
be declared to have scope throughout the class.

private void Rpt_BeginPrint(object sender, PrintEventArgs e)


{
rptFont = new Font("Arial",10);
lineHeight= (int)rptFont.GetHeight(); // Line height
}

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

pd.EndPrint += new PrintEventHandler(Rpt_EndPrint);

This simple event handler disposes of the font created in the BeginPrint handler:

private void Rpt_EndPrint(object sender, PrintEventArgs e)


{
rptFont.Dispose();
}

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:

private void Rpt_Query(object sender,


QueryPageSettingsEventArgs e)
{
// This is the last chance to change page settings
// If first page, change margins for title
if (currPg ==1) e.PageSettings.Margins =
new Margins(200,200,200,200);
else e.PageSettings.Margins = new Margins(100,100,100,100);
}
448 Chapter 9 ■ Fonts, Text, and Printing

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.

Select a Printer Headers and Page


Numbering
Select Paper Size PrintPage
Event Print Selected Pages
Prepare Data
Handle Page Breaks
Preview Output
Text Formatting and
PrintDocument.Print
Line Spacing

Define Printing Environment PrintPage Event Handler


Figure 9-7 Tasks required to print a report

Defining the PrintPage Event Handler


The event handler method matches the signature of the PrintPageEventHandler
delegate (refer to Listing 9-2):

public delegate void PrintPageEventHandler(


object sender, PrintPageEventArgs e);
9.3 Printing 449

The PrintPageEventArgs argument provides the system data necessary for


printing. As shown in Table 9-3, its properties include the Graphics object, Page-
Settings object, and a MarginBounds rectangle—mentioned earlier—that defines
the area within the margins. These properties, along with variables defined at a class
level, provide all the information used for printing.

Table 9-3 PrintPageEventArgs Members

Property Description

Cancel Boolean value that can be set to true to cancel the printing.

Graphics The Graphics object used to write to printer.

HasMorePages Boolean value indicating whether more pages are to be printed.


Default is false.

MarginBounds Rectangular area representing the area within the margins.

PageBounds Rectangular area representing the entire page.

PageSettings Page settings for the page to be printed.

Previewing a Printed Report


The capability to preview a report onscreen prior to printing—or as a substitute for
printing—is a powerful feature. It is particularly useful during the development pro-
cess where a screen preview can reduce debugging time, as well as your investment
in print cartridges.
To preview the printer output, you must set up a PrintPreviewDialog object
and set its Document property to an instance of the PrintDocument:

PrintPreviewDialog prevDialog = new PrintPreviewDialog();


prevDialog.Document = pd;

The preview process is invoked by calling the ShowDialog method:

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

Figure 9-8 Report can be previewed before 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.

Data Source for the Report


The data in a report can come from a variety of sources, although it most often comes
from a database. Because database access is not discussed until Chapter 11,
“ADO.NET,” let’s use a text file containing comma-delimited inventory records as
the data source. Each record consists of a product ID, vendor ID, description, and
price:

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:

// Using System.IO namespace is required


// StreamReader sr; is set up in class declaration
sr = new StreamReader("c:\\inventory.txt");

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

Code for the Application


Listing 9-3 contains the code for the PrintDocument event handlers implemented
in the application. Because you cannot pass your own arguments to an event handler,
the code must rely on variables declared at the class level to maintain state informa-
tion. These include the following:

StreamReader sr; // StreamReader to read inventor from file


string[]prtLine; // Array containing fields for one record
Font rptFont; // Font for report body
Font hdrFont; // Font for header
string title= "Acme Furniture: Inventory Report";
float lineHeight; // Height of a line (100ths inches)

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.

Listing 9-3 Creating a Report


// pd.PrintPage += new PrintPageEventHandler(Inven_Report);
// pd.BeginPrint += new PrintEventHandler(Rpt_BeginPrint);
// pd.EndPrint += new PrintEventHandler(Rpt_EndPrint);
//
// BeginPrint event handler
private void Rpt_BeginPrint(object sender, PrintEventArgs e)
{
// Create fonts to be used and get line spacing.
rptFont = new Font("Arial",10);
hdrFont = new Font(rptFont,FontStyle.Bold);
// insert code here to set up Data Source...
}
// EndPrint event Handler
private void Rpt_EndPrint(object sender, PrintEventArgs e)
{
// Remove Font resources
rptFont.Dispose();
hdrFont.Dispose();
sr.Close(); // Close StreamReader
}
// PrintPage event Handler
private void Inven_Report(object sender, PrintPageEventArgs e)
{
452 Chapter 9 ■ Fonts, Text, and Printing

Listing 9-3 Creating a Report (continued)


Graphics g = e.Graphics;
int xpos= e.MarginBounds.Left;
int lineCt = 0;
// Next line returns 15.97 for Arial-10
lineHeight = hdrFont.GetHeight(g);
// Calculate maximum number of lines per page
int linesPerPage = int((e.MarginBounds.Bottom –
e.MarginBounds.Top)/lineHeight –2);
float yPos = 2* lineHeight+ e.MarginBounds.Top;
int hdrPos = e.MarginBounds.Top;
// Call method to print title and column headers
PrintHdr(g,hdrPos, e.MarginBounds.Left);
string prod;
char[]delim= {','};
while(( prod =sr.ReadLine())!=null)
{
prtLine= prod.Split(delim,4);
yPos += lineHeight; // Get y coordinate of line
PrintDetail(g,yPos); // Print inventory record
if(lineCt > linesPerPage)
{
e.HasMorePages= true;
break;
}
}
}
private void PrintHdr( Graphics g, int yPos, int xPos)
{
// Draw Report Title
g.DrawString(title,hdrFont,Brushes.Black,xPos,yPos);
// Draw Column Header
float[] ts = {80, 80,200};
StringFormat strFmt = new StringFormat();
strFmt.SetTabStops(0,ts);
g.DrawString("Code\tVendor\tDescription\tCost",
hdrFont,Brushes.Black,xPos,yPos+2*lineHeight,strFmt);
}
private void PrintDetail(Graphics g, float yPos)
{
int xPos = 100;
StringFormat strFmt = new StringFormat();
strFmt.Trimming = StringTrimming.EllipsisCharacter;
strFmt.FormatFlags = StringFormatFlags.NoWrap;
RectangleF r = new RectangleF(xPos+160,yPos,
200,lineHeight);
9.3 Printing 453

Listing 9-3 Creating a Report (continued)


// Get data fields from array
string invenid = prtLine[0];
string vendor = prtLine[1];
string desc = prtLine[2];
decimal price = decimal.Parse(prtLine[3]);
g.DrawString(invenid, rptFont,Brushes.Black,xPos, yPos);
g.DrawString(vendor, rptFont,Brushes.Black,xPos+80, yPos);
// Print description within a rectangle
g.DrawString(desc, rptFont,Brushes.Black,r,strFmt);
// Print cost right justified
strFmt.Alignment = StringAlignment.Far; // Right justify
strFmt.Trimming= StringTrimming.None;
g.DrawString(price.ToString("#,###.00"),
rptFont,Brushes.Black, xPos+400,yPos,strFmt);
}

The PrintPage event handler Inven_Report directs the printing process by


calling PrintHdr to print the title and column header on each page and PrintDe-
tail to print each line of inventory data. Its responsibilities include the following:

• Using the MarginBounds rectangle to set the x and y coordinates of


the title at the upper-left corner of the page within the margins.
• Calculating the maximum number of lines to be printed on a page.
This is derived by dividing the distance between the top and bottom
margin by the height of a line. It then subtracts 2 from this to take the
header into account.
• Setting the HasMorePages property to indicate whether more pages
remain to be printed.

The PrintHdr routine is straightforward. It prints the title at the coordinates


passed to it, and then uses tabs to print the column headers. The PrintDetail
method is a bit more interesting, as it demonstrates some of the classes discussed
earlier in the chapter. It prints the inventory description in a rectangle and uses the
StringFormat class to prevent wrapping and specify truncation on a character.
StringFormat is also used to right justify the price of an item in the last column.
Figure 9-9 shows an example of output from this application. Measured from the
left margin, the first three columns have an x coordinate of 0, 80, and 160, respec-
tively. Note that the fourth column is right justified, which means that its x coordi-
nate of 400 specifies where the right edge of the string is positioned. Vertical spacing
is determined by the lineHeight variable that is calculated as

float lineHeight = hdrFont.GetHeight(g);


454 Chapter 9 ■ Fonts, Text, and Printing

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.

Acme Furniture: Inventory Report

Code Vendor Description Cost


1000758 1050 2PC/DRESSER/MIRROR 120.50
1000761 1050 2PC/DRESSER/MIRROR 185.50
1000762 1050 FRAME MIRROR 39.00
1000764 1050 HUTCH MIRROR 84.00
1000768 1050 DESK-NMFC 81200 120.50
Figure 9-9 Output from the report example

Creating a Custom PrintDocument Class


The generic PrintDocument class is easy to use, but has shortcomings with regard
to data encapsulation. In the preceding example, it is necessary to declare variables
that have class-wide scope—such as the StreamReader—to make them available to
the various methods that handle PrintDocument events. A better solution is to
derive a custom PrintDocument class that accepts parameters and uses properties
and private fields to encapsulate information about line height, fonts, and the data
source. Listing 9-4 shows the code from the preceding example rewritten to support
a derived PrintDocument class.
Creating a custom PrintDocument class turns out to be a simple and straightfor-
ward procedure. The first step is to create a class that inherits from PrintDocument.
Then, private variables are defined that support the fonts and title that are now
exposed as properties. Finally, the derived class overrides the OnBeginPrint,
OnEndPrint, and OnPrintPage methods of the base PrintDocument class.
The overhead required before printing the report is reduced to creating the new
ReportPrintDocument object and assigning property values.

string myTitle = "Acme Furniture: Inventory Report";


ReportPrintDocument rpd = new ReportPrintDocument(myTitle);
rpd.TitleFont = new Font("Arial",10, FontStyle.Bold);
rpd.ReportFont = new Font("Arial",10);
PrintPreviewDialog prevDialog = new PrintPreviewDialog();
9.3 Printing 455

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.

Listing 9-4 Creating a Custom PrintDocument Class


// Derived Print Document Class
public class ReportPrintDocument: PrintDocument
{
private Font hdrFont;
private Font rptFont;
private string title;
private StreamReader sr;
private float lineHeight;
// Constructors
public ReportPrintDocument()
{}
public ReportPrintDocument(string myTitle)
{
title = myTitle;
}
// Property to contain report title
public string ReportTitle
{
get {return title;}
set {title = value;}
}
// Fonts are exposed as properties
public Font TitleFont
{
get {return hdrFont;}
set {hdrFont = value;}
}
456 Chapter 9 ■ Fonts, Text, and Printing

Listing 9-4 Creating a Custom PrintDocument Class (continued)


public Font ReportFont
{
get {return rptFont;}
set {rptFont = value;}
}
// BeginPrint event handler
protected override void OnBeginPrint(PrintEventArgs e)
{
base.OnBeginPrint(e);
// Assign Default Fonts if none selected
if (TitleFont == null)
{
TitleFont =
new Font("Arial",10,FontStyle.Bold);
ReportFont = new Font("Arial",10);
}
/ Code to create StreamReader or other data source
// goes here ...
sr = new StreamReader(inputFile);
}
protected override void OnEndPrint(PrintEventArgs e)
{
base.OnEndPrint(e);
TitleFont.Dispose();
ReportFont.Dispose();
sr.Close();
}
// Print Page event handler
protected override void OnPrintPage(PrintPageEventArgs e)
{
base.OnPrintPage(e);
// Remainder of code for this class is same as in
// Listing 9-3 for Inven_Report, PrintDetail, and
// PrintHdr
}
}

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:

• PrintDocument. Sends output to the printer. Its Print method ini-


tiates the printing process and triggers the BeginPrint, QueryPage-
SettingsEvent, PrintPage, and EndPrint events. The event
handler for the PrintPage event contains the logic for performing
the actual printing.
• PrintPreviewDialog. Enables output to be previewed before
printing.
• PrinterSettings. Has properties that specify the page range to be
printed, the list of available printers, and the name of the target
printer. These values correspond to those a user can select on the
PrintDialog dialog box.
• DefaultPageSettings. Has properties that set the bounds of a
page, the orientation (landscape or portrait), the margins, and the
paper size. These values correspond to those selected on the
PageSetupDialog dialog box.

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

9.5 Test Your Understanding


1. Does Arial Bold refer to a font family, typeface, or font?

2. What is the default unit of measurement for a font? What size is it (in
inches)?

3. Which method and enumeration are used to right justify an output


string?

4. When the following code is executed, what is the x coordinate where


the third column begins?
float[] tStops = {50f, 60f, 200f, 40f};
StringFormat sf = new StringFormat();
sf.SetTabStops(0,tStops);
string hdr = "Col1\tCol2\tCol3\tCol4";
g.DrawString(hdr, myFont, Brushes.Black, 10,10,sf);

5. Which PrintDocument event is called after PrintDocument.Print


is executed?

6. Which class available to the PrintPage event handler defines the


margins for a page?

7. What three steps must be included to permit a document to be pre-


viewed before printing?
This page intentionally left blank
WORKING WITH
XML IN .NET

Topics in This Chapter

• Introduction to Using XML: Introduces some of the basic concepts


of working with XML. These include the XML validation and the
use of an XML style sheet.
• Reading XML Data: Explains how to use the .NET XML stack to
access XML data. The XmlReader, XmlNodeReader,
XmlTextReader are examined.
• Writing XML Data: The easiest way to create XML data is to use
the .NET XmlSerializer to serialize data into the XML format.
When the data is not in a format that can be serialized, an
alternative is the XmlWriter class.
• Searching and Updating XML Documents: XPath is a query
language to search XML documents. Examples illustrate how to
use it to search an XmlDocument, XmlDataDocument, and
XPathDocument.
10

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

1. W3C Extensible Markup Language (XML), 1.0 (Third Edition),


http://www.w3.org/TR/REC-xml/

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.

10.1 Working with XML


Being literate in one’s spoken language is defined as having the basic ability to read
and write that language. In XML, functional literacy embraces more than reading
and writing XML data. In addition to the XML data document, there is an XML
Schema document (.xsd) that is used to validate the content and structure of an
XML document. If the XML data is to be displayed or transformed, one or more
XML style sheets (.xsl) can be used to define the transformation. Thus, we can
define our own form of XML literacy as the ability to do five things:

1. Create an XML file.


2. Read and query an XML file.
3. Create an XML Schema document.
4. Use an XML Schema document to validate XML data.
5. Create and use an XML style sheet to transform XML 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.

Using XML Serialization to Create XML Data


As discussed in Chapter 4, “Working with Objects in C#,” serialization is a convenient
way to store objects so they can later be deserialized into the original objects. If the
natural state of your data allows it to be represented as objects, or if your application
already has it represented as objects, XML serialization often offers a good choice for
converting it into an XML format. However, there are some restrictions to keep in
mind when applying XML serialization to a class:

• The class must contain a public default (parameterless) constructor.


• Only a public property or field can be serialized.
• A read-only property cannot be serialized.
• To serialize the objects in a custom collection class, the class must
derive from the System.Collections.CollectionBase class and
include an indexer. The easiest way to serialize multiple objects is usu-
ally to place them in a strongly typed array.
10.1 Working with XML 463

An Example Using the XmlSerializer Class


Listing 10-1 shows the XML file that we’re going to use for further examples in this
section. It was created by serializing instances of the class shown in Listing 10-2.

Listing 10-1 Sample XML File


<?xml version="1.0" standalone="yes"?>
<films>
<movies>
<movie_ID>5</movie_ID>
<movie_Title>Citizen Kane </movie_Title>
<movie_Year>1941</movie_Year>
<movie_DirectorID>Orson Welles</movie_DirectorID>
<bestPicture>Y</bestPicture>
<AFIRank>1</AFIRank>
</movies>
<movies>
<movie_ID>6</movie_ID>
<movie_Title>Casablanca </movie_Title>
<movie_Year>1942</movie_Year>
<movie_Director>Michael Curtiz</movie_Director>
<bestPicture>Y</bestPicture>
<AFIRank>1</AFIRank>
</movies>
</films>

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.

Listing 10-2 Using XmlSerializer to Create an XML File


using System.Xml;
using System.Xml.Serialization;
// other code here ...
public class movies
{
public movies() // Parameterless constructor is required
{ }
public movies(int ID, string title, string dir,string pic,
int yr, int movierank)
{
464 Chapter 10 ■ Working with XML in .NET

Listing 10-2 Using XmlSerializer to Create an XML File (continued)


movieID = ID;
movie_Director = dir;
bestPicture = pic;
rank = movierank;
movie_Title = title;
movie_Year = yr;
}
// Public properties that are serialized
public int movieID
{
get { return mID; }
set { mID = value; }
}
public string movie_Title
{
get { return mTitle; }
set { mTitle = value; }
}
public int movie_Year
{
get { return mYear; }
set { mYear = value; }
}
public string movie_Director
{
get { return mDirector; }
set { mDirector = value; }
}
public string bestPicture
{
get { return mbestPicture; }
set { mbestPicture = value; }
}
[XmlElement("AFIRank")]
public int rank
{
get { return mAFIRank; }
set { mAFIRank = value; }
}
private int mID;
private string mTitle;
private int mYear;
private string mDirector;
private string mbestPicture;
private int mAFIRank;
}
10.1 Working with XML 465

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.

// (1) Create array of objects to be serialized


movies[] films = {new movies(5,"Citizen Kane","Orson Welles",
"Y", 1941,1 ),
new movies(6,"Casablanca","Michael Curtiz",
"Y", 1942,2)};
// (2) Create serializer
// This attribute is used to assign name to XML root element
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "films";
xRoot.Namespace = "http://www.corecsharp.net";
xRoot.IsNullable = true;
// Specify that an array of movies types is to be serialized
XmlSerializer xSerial = new XmlSerializer(typeof(movies[]),
xRoot);
string filename=@"c:\oscarwinners.xml";
// (3) Stream to write XML into
TextWriter writer = new StreamWriter(filename);
xSerial.Serialize(writer,films);

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:

XmlAttribute Is attached to a property or field and causes it to be rendered as


an attribute within an element.
Example: XmlAttribute("movieID")]
Result: <movies movieID="5">
466 Chapter 10 ■ Working with XML in .NET

XmlIgnore Causes the field or property to be excluded from the XML.


XmlText Causes the value of the field or property to be rendered as text.
No elements are created for the member name.
Example: [XmlText]
public string movie_Title{
Result: <movies movieID="5">Citizen Kane

XML Schema Definition (XSD)


The XML Schema Definition document is an XML file that is used to validate the
contents of another XML document. The schema is essentially a template that
defines in detail what is permitted in an associated XML document. Its role is similar
to that of the BNF (Backus-Naur Form) notation that defines a language’s syntax for
a compiler.
.NET provides several ways (others are included in Chapter 11, “ADO.NET”) to
create a schema from an XML data document. One of the easiest ways is to use the
XML Schema Definition tool (Xsd.exe). Simply run it from a command line and
specify the XML file for which it is to produce a schema:

C:/ xsd.exe oscarwinners.xml

The output, oscarwinners.xsd, is shown in Listing 10-3.

XML Schema to Apply Against XML


Listing 10-3
in Listing 10-1
<xs:schema id="films" xmlns=""
xmlns:xs=http://www.w3.org/2001/XMLSchema
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="films" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="movies">
<xs:complexType>
<xs:sequence>
<xs:element name="movie_ID" type="xs:int"
minOccurs="0" />
<xs:element name="movie_Title" type="xs:string"
minOccurs="0" />
<xs:element name="movie_Year" type="xs:int"
minOccurs="0" />
10.1 Working with XML 467

XML Schema to Apply Against XML


Listing 10-3
in Listing 10-1 (continued)
<xs:element name="movie_Director" type="xs:string"
minOccurs="0" />
<xs:element name="bestPicture" type="xs:string"
minOccurs="0" />
<xs:element name="AFIRank" type="xs:int"
minOccurs="0"
/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>

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.

XML Schema Validation


A schema is used by a validator to check an XML document for conformance to the
layout and content defined by the schema. .NET implements validation as a read and
check process. As a class iterates through each node in an XML tree, the node is val-
idated. Listing 10-4 illustrates how the XmlValidatingReader class performs this
operation.
First, an XmlTextReader is created to stream through the nodes in the data doc-
ument. It is passed as an argument to the constructor for the XmlValidating-
Reader. Then, the ValidationType property is set to indicate a schema will be
used for validation. This property can also be set to XDR or DTD to support older vali-
dation schemas.
468 Chapter 10 ■ Working with XML in .NET

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.

Listing 10-4 XML Schema Validation


private static bool ValidateSchema(string xml, string xsd)
{
// Parameters: XML document and schemas
// (1) Create a validating reader
XmlTextReader tr = new XmlTextReader(xml");
XmlValidatingReader xvr = new XmlValidatingReader(tr);
// (2) Indicate schema validation
xvr.ValidationType= ValidationType.Schema;
// (3) Add schema to be used for validation
xvr.Schemas.Add(null, xsd);
try
{
Console.WriteLine("Validating: ");
// Loop through all elements in XML document
while(xvr.Read())
{
Console.Write(".");
}
}catch (Exception ex)
{ Console.WriteLine( "\n{0}",ex.Message); return false;}
return true;
}

Note that the XmlValidatingReader class implements the XmlReader class


underneath. We’ll demonstrate using XmlReader to perform validation in the next
section. In fact, in most cases, XmlReader (.NET 2.0 implmentation) now makes
XmlValidatingReader obsolete.

Using an XML Style Sheet


A style sheet is a document that describes how to transform raw XML data into a dif-
ferent format. The mechanism that performs the transformation is referred to as an
XSLT (Extensible Style Language Transformation) processor. Figure 10-1 illustrates
the process: The XSLT processor takes as input the XML document to be trans-
formed and the XSL document that defines the transformation to be applied. This
approach permits output to be generated dynamically in a variety of formats. These
include XML, HTML or ASPX for a Web page, and a PDF document.
10.1 Working with XML 469

XSL Document

XML Document XML


XSL HTML
Transformation
ASPX
PDF

Figure 10-1 Publishing documents with XSLT

The XslTransform Class


The .NET version of the XSLT processor is the XslTransform class found in the
System.Xml.Xsl namespace. To demonstrate its use, we’ll transform our XML
movie data into an HTML file for display by a browser (see Figure 10-2).

Movie Title Movie Year AFI Rank Director


Casablanca 1942 2 Michael Curtiz
Citizen Kane 1941 1 Orson Welles
Figure 10-2 XML data is transformed into this HTML output

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

Listing 10-5 XML Style Sheet to Create HTML Output


<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<HTML>
<TITLE>Movies</TITLE>
<Table border="0" padding="0" cellspacing="1">
<THEAD>
<TH>Movie Title</TH>
<TH>Movie Year </TH>
<TH>AFI Rank </TH>
<TH>Director </TH>
</THEAD>
<xsl:for-each select="//movies">
<xsl:sort select="movie_Title" />
<tr>
<td><xsl:value-of select="movie_Title"/> </td>
<td align="center"><xsl:value-of select=
"movie_Year"/></td>
<td align="center"><xsl:value-of select=
"AFIRank" /></td>
<td><xsl:value-of select="movie_Director" /></td>
</tr>
</xsl:for-each>
</Table>
</HTML>
</xsl:template>
</xsl:stylesheet>

Some points of interest:

• The URL in the namespace of the <xsl:stylesheet> element must


be exactly as shown here.
• The match attribute is set to an XPath query that indicates which ele-
ments in the XML file are to be converted. Setting match="/" selects
all elements.
• The for-each construct loops through a group of selected nodes
specified by an XPath expression following the select attribute.
XPath is discussed in Section 10.4, “Using XPath to Search XML.”
• The value-of function extracts a selected value from the XML docu-
ment and inserts it into the output.
10.1 Working with XML 471

• The <xsl:sort> element is used to sort the incoming data and is


used in conjunction with the for-each construct. Here is its syntax:

select = XPath expression


order = {"ascending" | "descending"}
data-type = {"text" | "number"}
case-order = {"upper-first" | "lower-first"}

After a style sheet is created, using it to transform a document is a breeze. As


shown by the following code, applying the XslTransform class is straightforward.
After creating an instance of it, you use its Load method to specify the file containing
the style sheet. The XslTransform.Transform method performs the transforma-
tion. This method has several overloads. The version used here requires an Xpath-
Document object that represents the XML document, as a parameter, and an
XmlWriter parameter that designates where the output is written—an HTML file in
this case.

// Transform XML into HTML and store in movies.htm


XmlWriter writer = new
XmlTextWriter("c:\\movies.htm",Encoding.UTF8);
XslTransform xslt = new XslTransform();
XPathDocument xpd = new
XPathDocument("c:\\oscarwinners.xml");
xslt.Load("movies.xsl");
xslt.Transform(xpd, null, writer,null);

Core Note

You can link a style sheet to an XML document by placing an href


statement in the XML document on the line preceding the root element
definition:
<?xml:stylesheet type="text/xsl" href="movies.xsl" ?>
If a document is linked to a style sheet that converts XML to HTML, most
browsers automatically perform the transformation and display the
HTML. This can be a quick way to perform trial-and-error testing when
developing a style sheet.

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

10.2 Techniques for Reading XML Data


XML can be represented in two basic ways: as the familiar external document contain-
ing embedded data, or as an in-memory tree structure know as a Document Object
Model (DOM). In the former case, XML can be read in a forward-only manner as a
stream of tokens representing the file’s content. The object that performs the reading
stays connected to the data source during the read operations. The XmlReader and
XmlTextReader shown in Figure 10-3 operate in this manner.
More options are available for processing the DOM because it is stored in mem-
ory and can be traversed randomly. For simply reading a tree, the XmlNodeReader
class offers an efficient way to traverse a tree in a forward, read-only manner. Other
more sophisticated approaches that also permit tree modification are covered later in
this section.

XmlReader
<< abstract >>
.Create()

XmlReader XmlTextReader XmlNodeReader

XML Document XML Document


(text) (text) Document Object Model
Figure 10-3 Classes to read XML data

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.

Listing 10-6 Using XmlReader to Read an XML Document


// Include these namespaces:
// using System.Xml;
// using System.Xml.XPath;
public void ShowNodes()
{
//(1) Settings object enables/disables features on XmlReader
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
settings.IgnoreWhitespace = true;
try
{
//(2) Create XmlReader object
XmlReader rdr = XmlReader.Create("c:\\oscarsshort.xml",
settings);
while (rdr.Read())
{
Format(rdr);
}
rdr.Close();
}
catch (Exception e)
{
Console.WriteLine ("Exception: {0}", e.ToString());
}
}
private static void Format(XmlTextReader reader)
{
//(3) Print Current node properties
Console.Write( reader.NodeType+ "<" + reader.Name + ">" +
reader.Value);
Console.WriteLine();
}

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

Before creating the XmlReader, the code first creates an XmlReaderSettings


object. This object sets features that define how the XmlReader object processes the
input stream. For example, the ConformanceLevel property specifies how the
input is checked. The statement

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:

XmlReader rdr = XmlReader.Create("c:\\oscarsshort.xml,settings);

The file’s content is read in a node at a time by the XmlReader.Read method,


which prints the NodeType, Name, and Value of each node. Listing 10-7 shows the
input file and a portion of the generated output. Line numbers have been added so
that an input line and its corresponding node information can be compared.

Listing 10-7 XML Input and Corresponding Nodes

Input File: oscarsshort.xml

(1) <?xml version="1.0" standalone="yes"?>


(2) <films>
(3) <movies>
(4) <!-- Selected by AFI as best movie -->
(5) <movie_ID>5</movie_ID>
(6) <![CDATA[<a href="http://www.imdb.com/tt0467/">Kane</a>]]>
(7) <movie_Title>Citizen Kane </movie_Title>
(8) <movie_Year>1941</movie_Year>
(9) <movie_Director>Orson Welles</movie_Director>
(10) <bestPicture>Y</bestPicture>
(11) </movies>
(12)</films>

Program Output (NodeType, <Name>, Value):

(1) XmlDeclaration<xml>version="1.0" standalone="yes"


(2) Element<films>
(3) Element<movies>
(4) Comment<> Selected by AFI as best movie
10.2 Techniques for Reading XML Data 475

Listing 10-7 XML Input and Corresponding Nodes (continued)


(5) Element<movie_ID>
Text<>5
EndElement<movie_ID>
(6) CDATA<><a href="http://www.imdb.com/tt0467/">Kane</a>
(7) Element<movie_Title>
Text<>Citizen Kane
EndElement<movie_Title>

(12)EndElement<films>

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.

Table 10-1 XmlNodeType Enumeration

Option Description and Use

Attribute An attribute or value contained within an element. Example:


<movie_title genre="comedy">The Lady Eve
</movie_title>
Attribute is genre="comedy". Attributes must be located
within an element.
if(reader.NodeType==XmlNodeType.Element){
while(reader.MoveToNextAttribute())
{
Console.WriteLine(reader.Name+reader.Value);
}

CData Designates that the element is not to be parsed. Markup char-


acters are treated as text:
![CDATA[<ELEMENT>
<a href=”http://www.imdb.com”>movies</a>
</ELEMENT>]]>

Comment To make a comment:


<!-- comment -->
To have comments ignored:
XmlReaderSettings.IgnoreComment = true;

Document A document root object that provides access to the entire


XML document.

DocumentFragment A document fragment. This is a node or subtree with a docu-


ment. It provides a way to work with part of a document.

DocumentType Document type declaration indicated by <!DOCTYPE … >.


Can refer to an external Document Type Definition (DTD)
file or be an inline block containing Entity and Notation
declarations.

Element An XML element. Designated by the < > brackets:


<movie_Title>

EndElement An XML end element tag. Marks the end of an element:


</movie_Title>
10.2 Techniques for Reading XML Data 477

Table 10-1 XmlNodeType Enumeration (continued)

Option Description and Use

EndEntity End of an Entity declaration.

Entity Defines text or a resource to replace the entity name in the


XML. An entity is defined as a child of a document type node:
<!DOCTYPE movies[
<!ENTITY leadingactress "stanwyck">
]>
XML would then reference this as:
<actress>&leadingactress;</actress>

EntityReference A reference to the entity. In the preceding example,


&leadingactress; is an EntityReference.

Notation A notation that is declared within a DocumentType declara-


tion. Primary use is to pass information to the XML processor.
Example:
<!NOTATION homepage="www.sci.com" !>

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

Text The text content of a node.

Whitespace Whitespace refers to formatting characters such as tabs, line


feeds, returns, and spaces that exist between the markup and
affect the layout of a document.

XmlDeclaration The first node in the document. It provides version information.


<?xml version="1.0" standalone="yes"?>

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:

• It processes nodes from an in-memory DOM tree structure rather


than a text file.
• It can begin reading at any subtree node in the structure—not just at
the root node (beginning of the document).
478 Chapter 10 ■ Working with XML in .NET

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.

Listing 10-8 Using XmlNodeReader to Read an XML Document


private void ListMovies()
{
// (1) Specify XML file to be loaded as a DOM
XmlDocument doc = new XmlDocument();
doc.Load("c:\\oscarwinners.xml");
// (2) Settings for use with XmlNodeReader object
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
settings.IgnoreWhitespace = true;
settings.IgnoreComments = true;
// (3) Create a nodereader object
XmlNodeReader noderdr = new XmlNodeReader(doc);
// (4) Create an XmlReader as a wrapper around node reader
XmlReader reader = XmlReader.Create(noderdr, settings);
while (reader.Read())
{
if(reader.NodeType==XmlNodeType.Element){
if (reader.Name == "movie_Title")
{
reader.Read(); // next node is text for title
Console.Write(reader.Value); // Movie Title
}
if (reader.Name == "movie_Year")
{
reader.Read(); // next node is text for year
Console.WriteLine(reader.Value); // year
}
}
}
}
10.2 Techniques for Reading XML Data 479

The parameter passed to the XmlNodeReader constructor determines the first


node in the tree to be read. When the entire document is passed—as in this exam-
ple—reading begins with the top node in the tree. To select a specific node, use the
XmlDocument.SelectSingleNode method as illustrated in this segment:

XmlDocument doc = new XmlDocument();


doc.Load("c:\\oscarwinners.xml"); // Build tree in memory
XmlNodeReader noderdr = new
XmlNodeReader(doc.SelectSingleNode("films/movies[2]"));

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.

The XmlReaderSettings Class


A significant advantage of using an XmlReader object—directly or as a wrapper—is
the presence of the XmlReaderSettings class as a way to define the behavior of the
XmlReader object. Its most useful properties specify which node types in the input
stream are ignored and whether XML validation is performed. Table 10-2 lists the
XmlReaderSettings properties.

Table 10-2 Properties of the XmlReaderSettings Class

Default
Property Value Description

CheckCharacters true Indicates whether characters and XML


names are checked for illegal XML
characters. An exception is thrown if
one is encountered.

CloseInput false An XmlReader object may be created


by passing a stream to it. This property
indicates whether the stream is closed
when the reader object is closed.

ConformanceLevel Document Indicates whether the XML should


conform to the standards for a Docu-
ment or DocumentFragment.
480 Chapter 10 ■ Working with XML in .NET

Table 10-2 Properties of the XmlReaderSettings Class (continued)

Default
Property Value Description

DtdValidate false Indicates whether to perform DTD


validation.

IgnoreComments false Specify whether a particular node


IgnoreInlineSchema true type is processed or ignored by the
IgnoreProcessingInstructions false XmlReader.Read method.
IgnoreSchemaLocation true
IgnoreValidationWarnings
true
IgnoreWhitespace
false

LineNumberOffset 0 XmlReader numbers lines in the XML


LinePositionOffset 0 document beginning with 0. Set this
property to change the beginning line
number and line position values.

Schemas is empty Contains the XmlSchemaSet to be


used for XML Schema Definition
Language (XSD) validation.

XsdValidate false Indicates whether XSD validation is


performed.

Using an XML Schema to Validate XML Data


The final two properties listed in Table 10-2—Schemas and XsdValidate—are
used to validate XML data against a schema. Recall that a schema is a template that
describes the permissible content in an XML file or stream. Validation can be
(should be) used to ensure that data being read conforms to the rules of the schema.
To request validation, you must add the validating schema to the XmlSchemaSet col-
lection of the Schemas property; next, set XsdValidate to true; and finally, define
an event handler to be called if a validation error occurs. The following code frag-
ment shows the code used with the schema and XML data in Listings 10-1 and 10-3:

XmlReaderSettings settings = new XmlReaderSettings();


// (1) Specify schema to be used for validation
settings.Schemas.Add(null,"c:\\oscarwinners.xsd");
// (2) Must set this to true
settings.XsdValidate = true;
// (3) Delegate to handle validation error event
settings.ValidationEventHandler += new
System.Xml.Schema.ValidationEventHandler(SchemaValidation);
10.2 Techniques for Reading XML Data 481

// (4) Create reader and pass settings to it


XmlReader rdr = XmlReader.Create("c:\\oscarwinners.xml",
settings);
// process XML data ...
...
// Method to handle errors detected during schema validation
private void SchemaValidation(object sender,
System.Xml.Schema.ValidationEventArgs e)
{
MessageBox.Show(e.Message);
}

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.

Options for Reading XML Data


All the preceding examples that read XML data share two characteristics: data is read
a node at a time, and a node’s value is extracted as a string using the Xml-
Reader.Value property. This keeps things simple, but ignores the underlying XML
data. For example, XML often contains numeric data or data that is the product of
serializing a class. Both cases can be handled more efficiently using other Xml-
Reader methods.
XmlReader has a suite of ReadValueAsxxx methods that can read the contents
of a node in its native form. These include ReadValueAsBoolean, ReadValueAs-
DateTime, ReadValueAsDecimal, ReadValueAsDouble, ReadValueAsInt32,
ReadValueAsInt64, and ReadValueAsSingle. Here’s an example:

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.

// Deserialize XML into a movies object


if (rdr.NodeType == XmlNodeType.Element && rdr.Name == "movies")
{
movies m = (movies)rdr.ReadAsObject(typeof(movies));
// Do something with object
}
// XML data is read directly into this class
public class movies
482 Chapter 10 ■ Working with XML in .NET

{
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); }
}
}

10.3 Techniques for Writing XML Data


In many cases, the easiest way to present data in an XML format is to use .NET seri-
alization. As demonstrated in Section 10.1, if the data is in a collection class, it can be
serialized using the XmlSerializer class; as we see in the next chapter, if it’s in a
DataSet, the DataSet.WriteXml method can be applied. The advantages of serial-
ization are that it is easy to use, generates well-formed XML, and is symmetrical—
the XML that is written can be read back to create the original data objects.
For cases where serialization is not an option—a comma delimited file, for
instance—or where more control over the XML layout is needed, the XmlWriter
class is the best .NET solution.

Writing XML with the XmlWriter Class


The XmlWriter class offers precise control over each character written to an XML
stream or file. However, this flexibility does require a general knowledge of XML and
can be tedious to code, because a distinct Writexxx method is used to generate each
node type. On the positive side, it offers several compliance checking features, and
the ability to write CLR typed data directly to the XML stream:

• XmlWriterSettings.CheckCharacters property configures the


XmlWriter to check for illegal characters in text nodes and XML
names, as well as check the validity of XML names. An exception is
thrown if an invalid character is detected.
• XmlWriterSettings.ConformanceLevel property configures the
XmlWriter to guarantee that the stream complies with the conform-
ance level that is specified. For example, the XML may be set to con-
form to a document or document fragment.
10.3 Techniques for Writing XML Data 483

• XmlWriter.WriteValue method is used to write data to the XML


stream as a CLR type (int, double, and so on) without having to first
convert it to a string.

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.

Listing 10-9 Write XML Using XmlWriter Class


private void WriteMovie()
{
string[,] movieList = { { "Annie Hall", "Woody Allen" },
{ "Lawrence of Arabia", "David Lean" } };
// (1) Define settings to govern writer actions
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = (" ");
settings.ConformanceLevel = ConformanceLevel.Document;
settings.CloseOutput = false;
settings.OmitXmlDeclaration = false;
// (2) Create XmlWriter object
XmlWriter writer = XmlWriter.Create("c:\\mymovies.xml",
settings);
writer.WriteStartDocument();
writer.WriteComment("Output from xmlwriter class");
writer.WriteStartElement("films");
for (int i = 0; i <= movieList.GetUpperBound(0) ; i++)
{
try
{
writer.WriteStartElement("movie");
writer.WriteElementString("Title", movieList[i, 0]);
writer.WriteElementString("Director", movieList[i, 1]);
writer.WriteStartElement("Movie_ID");
writer.WriteValue(i); // No need to convert to string
484 Chapter 10 ■ Working with XML in .NET

Listing 10-9 Write XML Using XmlWriter Class (continued)


writer.WriteEndElement();
writer.WriteEndElement();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
writer.WriteEndElement();
writer.Flush(); // Flush any remaining content to XML stream
writer.Close();
/*
Output:
<?xml version="1.0" encoding="utf-8"?>
<!--Output from xmlwriter class-->
<films>
<movie>
<Title>Annie Hall</Title>
<Director>Woody Allen</Director>
<Movie_ID>0</Movie_ID>
</movie>
<movie>
<Title>Lawrence of Arabia</Title>
<Director>David Lean</Director>
<Movie_ID>1</Movie_ID>
</movie>
</films>
*/
}

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

10.4 Using XPath to Search XML


A significant benefit of representing XML in a tree model—as opposed to a data
stream—is the capability to query and locate the tree’s content using XML Path Lan-
guage (XPath). This technique is similar to using a SQL command on relational data.
An XPath expression (query) is created and passed to an engine that evaluates it. The
expression is parsed and executed against a data store. The returned value(s) may be
a set of nodes or a scalar value.
XPath is a formal query language defined by the XML Path Language 2.0 specifi-
cation (www.w3.org/TR/xpath). Syntactically, its most commonly used expressions
resemble a file system path and may represent either the absolute or relative position
of nodes in the tree.

XPathNavigator

XmlDocument XPathDocument

XmlDataDocument
Figure 10-4 XML classes that support XPath navigation

In the .NET Framework, XPath evaluation is exposed through the XPathNavi-


gator abstract class. The navigator is an XPath processor that works on top of any
XML data source that exposes the IXPathNavigable interface. The most important
member of this interface is the CreateNavigator method, which returns an
XPathNavigator object. Figure 10-4 shows three classes that implement this inter-
face. Of these, XmlDocument and XmlDataDocument are members of the Sys-
tem.Xml namespace; XPathDocument (as well as the XmlNavigator class) resides
in the System.Xml.XPath namespace.

• XmlDocument. Implements the W3C Document Object Model


(DOM) and supports XPath queries, navigation, and editing.
486 Chapter 10 ■ Working with XML in .NET

• XmlDataDocument. In addition to the features it inherits from Xml-


Document, it provides the capability to map XML data to a DataSet.
Any changes to the DataSet are reflected in the XML tree and vice
versa.
• XPathDocument. This class is optimized to perform XPath queries
and represents XML in a tree of read-only nodes that is more stream-
lined than the DOM.

Constructing XPath Queries


Queries can be executed against each of these classes using either an XPathNaviga-
tor object or the SelectNodes method implemented by each class. Generic code
looks like this:

// XPATHEXPRESSION is the XPath query applied to the data


// (1) Return a list of nodes
XmlDocument doc = new XmlDocument();
doc.Load("movies.xml");
XmlNodeList selection = doc.SelectNodes(XPATHEXPRESSION);
// (2) Create a navigator and execute the query
XPathNavigator nav = doc.CreateNavigator();
XPathNodeIterator iterator = nav.Select(XPATHEXPRESSION);

The XpathNodeIterator class encapsulates a list of nodes and provides a way to


iterate over the list.
As with regular expressions (refer to Chapter 5, “C# Text Manipulation and File
I/O”), an XPath query has its own syntax and operators that must be mastered in
order to efficiently query an XML document. To demonstrate some of the funda-
mental XPath operators, we’ll create queries against the data in Listing 10-10.

XML Representation of Directors/Movies


Listing 10-10
Relationship
<films>
<directors>
<director_id>54</director_id>
<first_name>Martin</first_name>
<last_name>Scorsese</last_name>
<movies>
<movie_ID>30</movie_ID>
<movie_Title>Taxi Driver</movie_Title>
<movie_DirectorID>54</movie_DirectorID>
<movie_Year>1976</movie_Year>
10.4 Using XPath to Search XML 487

XML Representation of Directors/Movies


Listing 10-10
Relationship (continued)
</movies>
<movies>
<movie_ID>28</movie_ID>
<movie_Title>Raging Bull </movie_Title>
<movie_DirectorID>54</movie_DirectorID>
<movie_Year>1980</movie_Year>
</movies>
</directors>
</films>

Table 10-3 summarizes commonly used XPath operators and provides an example
of using each.

Table 10-3 XPath Operators

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

Table 10-3 XPath Operators (continued)

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

Filter operator ([ ]) Allows nodes to be filtered based on a matching criteria. The


following example is used to retrieve all movie titles directed by
Martin Scorsese:
//directors[last_name='Scorsese']
/movies/movie_Title

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.

Table 10-4 Functions and Operators used to Create an XPath Filter

Function/Operator Description

and, or Logical operators.

Example: "directors[last_name= 'Scorsese' and first_name= 'Martin']"

position( ) Selects node(s) at specified position.

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

contains(node,string) Matches if node value contains specified string.

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-after('The Graduate','The ')='Graduate']"


substring(string, pos,length) Extracts substring from node value.

Example: "//movies[substring(movie_Title,2,1)='a']"

Refer to the XPath standard (http://www.w3.org/TR/xpath) for a compre-


hensive list of operators and functions.
Let’s now look at examples of using XPath queries to search, delete, and add data
to an XML tree. Our source XML file is shown in Listing 10-10. For demonstration
purposes, examples are included that represent the XML data as an XmlDocument,
XPathDocument, and XmlDataDocument.

XmlDocument and XPath


The expression in this example extracts the set of last_name nodes. It then prints
the associated text. Note that underneath, SelectNodes uses a navigator to evaluate
the expression.

string exp = "/films/directors/last_name";


XmlDocument doc = new XmlDocument();
doc.Load("directormovies.xml"); // Build DOM tree
XmlNodeList directors = doc.SelectNodes(exp);
foreach(XmlNode n in directors)
Console.WriteLine(n.InnerText); // Last name or director

The XmlNode.InnerText property concatenates the values of child nodes and


displays them as a text string. This is a convenient way to display tree contents during
application testing.
490 Chapter 10 ■ Working with XML in .NET

XPathDocument and XPath


For applications that only need to query an XML document, the XPathDocument is
the recommended class. It is free of the overhead required for updating a tree and
runs 20 to 30 percent faster than XmlDocument. In addition, it can be created using
an XmlReader to load all or part of a document into it. This is done by creating the
reader, positioning it to a desired subtree, and then passing it to the XPathDocument
constructor. In this example, the XmlReader is positioned at the root node, so the
entire tree is read in:

string exp = "/films/directors/last_name";


// Create method was added with .NET 2.0
XmlReader rdr = XmlReader.Create("c:\\directormovies.xml");
// Pass XmlReader to the constructor
xDoc = new XPathDocument(rdr);
XPathNavigator nav= xDoc.CreateNavigator();
XPathNodeIterator iterator;
iterator = nav.Select(exp);
// List last name of each director
while (iterator.MoveNext())
Console.WriteLine(iterator.Current.Value);
// Now, list only movies for Martin Scorsese
string exp2 =
"//directors[last_name='Scorsese']/movies/movie_Title";
iterator = nav.Select(exp2);
while (iterator.MoveNext())
Console.WriteLine(iterator.Current.Value);

Core Note

Unlike the SelectNodes method, the navigator’s Select method


accepts XPath expressions as both plain text and precompiled objects.
The following statements demonstrate how a compiled expression could
be used in the preceding example:
string exp = "/films/directors/last_name";
// use XmlNavigator to create XPathExpression object
XPathExpression compExp = nav.Compile(exp);
iterator = nav.Select(compExp);
Compiling an expression improves performance when the expression
(query) is used more than once.
10.4 Using XPath to Search XML 491

XmlDataDocument and XPath


The XmlDataDocument class allows you to take a DataSet (an object containing
rows of data) and create a replica of it as a tree structure. The tree not only repre-
sents the DatSet, but is synchronized with it. This means that changes made to the
DOM or DataSet are automatically reflected in the other.
Because XmlDataDocument is derived from XmlDocument, it supports the basic
methods and properties used to manipulate XML data. To these, it adds methods
specifically related to working with a DataSet. The most interesting of these is the
GetRowFromElement method that takes an XmlElement and converts it to a corre-
sponding DataRow.
A short example illustrates how XPath is used to retrieve the set of nodes repre-
senting the movies associated with a selected director. The nodes are then converted
to a DataRow, which is used to print data from a column in the row.

// Create document by passing in associated DataSet


XmlDataDocument xmlDoc = new XmlDataDocument(ds);
string exp = "//directors[last_name='Scorsese']/movies";
XmlNodeList nodeList =
xmlDoc.DocumentElement.SelectNodes(exp);
DataRow myRow;
foreach (XmlNode myNode in nodeList)
{
myRow = xmlDoc.GetRowFromElement((XmlElement)myNode);
if (myRow != null){
// Print Movie Title from a DataRow
Console.WriteLine(myRow["movie_Title"].ToString());
}
}

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.

Adding and Removing Nodes on a Tree


Besides locating and reading data, many applications need to add, edit, and delete
information in an XML document tree. This is done using methods that edit the con-
tent of a node and add or delete nodes. After the changes have been made to the
tree, the updated DOM is saved to a file.
To demonstrate how to add and remove nodes, we’ll operate on the subtree pre-
sented as text in Listing 10-10 and as a graphical tree in Figure 10-5.
492 Chapter 10 ■ Working with XML in .NET

films

directors Parent Node

54 Martin Scorsese movies movies movies

30 54 1976 28 54 1980 94 54 1990

Taxi Driver Raging Bull Goodfellas

Figure 10-5 Subtree used to delete and remove nodes

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.

Using XmlDocument and XPath to Add


Listing 10-11
and Remove Nodes
Public void UseXPath()
{
XmlDocument doc = new XmlDocument();
doc.Load("c:\\directormovies.xml");
// (1) Locate movie to remove
string exp = "//directors[last_name='Scorsese']/
movies[movie_Title='Raging Bull']";
XmlNode movieNode = doc.SelectSingleNode(exp);
// (2) Delete node and child nodes for movie
XmlNode directorNode = movieNode.ParentNode;
directorNode.RemoveChild(movieNode);
// (3) Add new movie for this director
// First, get and save director's ID
10.5 Summary 493

Using XmlDocument and XPath to Add


Listing 10-11
and Remove Nodes (continued)
string directorID =
directorNode.SelectSingleNode("director_id").InnerText;
// XmlElement is dervied from XmlNode and adds members
XmlElement movieEl = doc.CreateElement("movies");
directorNode.AppendChild(movieEl);
// (4) Add Movie Description
AppendChildElement(movieEl, "movie_ID", "94");
AppendChildElement(movieEl, "movie_Title", "Goodfellas");
AppendChildElement(movieEl, "movie_Year", "1990");
AppendChildElement(movieEl, "movie_DirectorID",
directorID);
// (5) Save updated XML Document
doc.Save("c:\\directormovies2.xml");
}
// Create node and append to parent
public void AppendChildElement(XmlNode parent, string elName,
string elValue)
{
XmlElement newEl =
parent.OwnerDocument.CreateElement(elName);
newEl.InnerText = elValue;
parent.AppendChild(newEl);
}

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.

10.6 Test Your Understanding


1. XmlReader is an abstract class. How do you create an instance of it to
read an XML document?

2. What is the purpose of the XmlReaderSettings class?

3. Which of these classes cannot be used to update an XML document?


a. XmlDocument
b. XmlDataDocument
c. XmlPathDocument

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

5. Describe two ways to perform schema validation on an XML


document.
This page intentionally left blank
ADO.NET

Topics in This Chapter

• ADO.NET Architecture: ADO.NET provides access to data using


custom or generic data providers. This section looks at the classes
a provider must supply that enable an application to connect to a
data source and interact with it using SQL commands.
• Introduction to Using ADO.NET: ADO.NET supports data access
using a connected or disconnected connectivity model. An
introduction and comparison of the two architectures is provided.
• The Connected Model: Use of the connected model requires an
understanding of the role that Connection and Command
classes have in retrieving data. Examples illustrate how to create a
connection and use the Command class to issue SQL commands,
invoke stored procedures, and manage multi-command
transactions.
• The Disconnected Model: Disconnected data is stored in an
in-memory DataTable or DataSet. The latter is usually made
up of multiple DataTables that serve as a local relational data
store for an application. A DataAdapter is typically used to
load this data and then apply updates to the original data source.
Techniques are introduced for handling synchronization issues
that arise when updating disconnected data.
• XML Data Access: Although it does not provide intrinsic XML
classes, ADO.NET supports XML integration through properties
and methods of the DataSet class. Examples show how to use
WriteXml and ReadXml to create an XML file from a DataSet
and populate a DataSet from an XML file, respectively.
11

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

11.1 Overview of the ADO.NET


Architecture
The ADO.NET architecture is designed to make life easier for both the application
developer and the database provider. To the developer, it presents a set of abstract
classes that define a common set of methods and properties that can be used to
access any data source. The data source is treated as an abstract entity, much like a
drawing surface is to the GDI+ classes. Figure 11-1 depicts this concept.
For database providers, ADO.NET serves as a blueprint that describes the base
API classes and interface specifications providers must supply with their product.
Beneath the surface, the vendor implements the custom code for connecting to their
database, processing SQL commands, and returning the results. Many database
products, such as MySQL and Oracle, have custom .NET data provider implementa-
tions available. In addition, they have generic OLE DB versions. The .NET data pro-
vider should always be the first choice because it offers better performance and often
supports added custom features. Let’s look at both the OLE DB and native .NET
data providers

Command Object
"select * from Actors" Sql Connection

Data Access Layer


.NET Managed Provider
.NET Data Provider ODBC
OLE DB

ODP.NET

MS SQL Oracle MS
MySQL
Server Access

Figure 11-1 ADO.NET data access options

OLE DB Data Provider in .NET


An OLE DB provider is the code that sits between the data consumer and the native
API of a data source. It maps the generic OLE DB API to the data source’s native
APIs. It is a COM-based solution in which the data consumer and provider are COM
objects that communicate through COM interfaces. Database vendors and third par-
ties have written OLE DB providers for just about every significant data source. In
11.1 Overview of the ADO.NET Architecture 499

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.

.NET Data Provider


The .NET data provider provides the same basic service to the client as the OLE DB
provider: exposing a data source’s API to a client. Its advantage is that it can directly
access the native API of the data source, rather than relying on an intermediate data
access bridge. Native providers may also include additional features to manipulate
data types native to the data source and improve performance. For example, the Ora-
cle provider, ODP.NET, includes adjustable settings to control connection pooling,
the number of rows to be pre-fetched, and the size of non-scalar data being fetched.

Data Provider Objects for Accessing Data


A managed data provider exposes four classes that enable a data consumer to access
the provider’s data source. Although these classes are specific to the provider, they
derive from abstract ADO.NET classes:

• DbConnection. Establishes a connection to the data source.


• DbCommand. Used to query or send a command to the data source.
• DbDataReader. Provides read-only and forward-only access to the
data source.
• DBDataAdapter. Serves as a channel through which a DataSet
connects to a provider.

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.

Figure 11-2 Data provider information returned by GetFactoryClasses()

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:

SqlConnection conn = new SqlConnection();


SqlCommand cmd = new SqlCommand();
SqlDataReader dr = cmd.ExecuteReader();
11.1 Overview of the ADO.NET Architecture 501

However, suppose your application has to support multiple data sources. A


switch/case construct could be used, but a better—and more flexible—approach
is to use a class factory to create these objects dynamically based on the provider
selected. ADO.NET provides just that—a DbProviderFactory class that is used
to return objects required by a specific data provider. It works quite simply. A
string containing the provider name is passed to the GetFactory method of the
DbProviderFactories class. The method returns a factory object that is used to
create the specific objects required by the provider. Listing 11-1 demonstrates
using a factory.

Listing 11-1 Using the DbProviderFactory Class


// System.Data.Common namespace is required
DbProviderFactory factory ;
string provider = "System.Data.SqlClient"; // data provider
string connstr = "Data Source=MYSERVER;Initial Catalog=films;
User Id=filmsadmin;Password=bogart;";
// Get factory object for SQL Server
factory = DbProviderFactories.GetFactory(provider);
// Get connection object. using ensures connection is closed.
using (DbConnection conn = factory.CreateConnection())
{
conn.ConnectionString = connstr;
try
{
conn.Open();
DbCommand cmd = factory.CreateCommand(); // Command object
cmd.CommandText = "SELECT * FROM movies WHERE movie_ID=8" ;
cmd.Connection = conn;
DbDataReader dr;
dr = cmd.ExecuteReader();
dr.Read();
MessageBox.Show((string)dr["movie_Title"]);
conn.Close();
}
catch (DbException ex)
{ MessageBox.Show(ex.ToString()); }
catch (Exception ex)
{ MessageBox.Show(ex.ToString()); }
finally { conn.Close(); }
}
502 Chapter 11 ■ ADO.NET

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:

string provider= "System.Data.Odbc";


// The DSN (Data Source Name) is defined using an ODBC utility
string connstr = "DSN=movies;Database=films";

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.

11.2 Data Access Models:


Connected and Disconnected
This section offers an overview of using ADO.NET to access data stored in relational
tables. Through simple examples, it presents the classes and concepts that distinguish
the connected and disconnected access models.
All examples in this section—as well as the entire chapter—use data from the
Films database defined in Figure 11-3. It consists of a movies table containing the
top 100 movies as selected by the American Film Institute (AFI) in 1996, an actors
table that lists the principal actors who performed in the movies, and an
actor-movie helper table that links the two. The data is downloadable as a
Microsoft Access (.mdb) file and an XML text (.xml) file.

Figure 11-3 Films database tables

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.

DataReader command connection


ExecuteReader() connection string
Read()

Figure 11-4 DataReader is used in ADO.NET connected mode

Working with the DataReader typically involves four steps:

1. The connection object is created by passing a connection string to its


constructor.
2. A string variable is assigned the SQL command that specifies the data
to fetch.
3. A command object is created. Its overloads accept a connection
object, a query string, and a transaction object (for executing a group
of commands).
4. The DataReader object is created by executing the Command.Exe-
cuteReader() method. This object is then used to read the query
results one line at a time over the active data connection.

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.

//System.Data.SqlClient namespace is required


// (1) Create Connection
SqlConnection conn = new SqlConnection(connstr);
conn.Open();
// (2) Query string
string sql = "SELECT movie_Title FROM movies ORDER BY
movie_Year";
// (3) Create Command object
SqlCommand cmd = new SqlCommand(sql, conn);
DbDataReader rdr;
504 Chapter 11 ■ ADO.NET

// (4) Create DataReader


rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
while (rdr.Read())
{
listBox1.Items.Add(rdr["movie_Title"]); // Fill ListBox
}
rdr.Close(); // Always close datareader

The parameter to ExecuteReader specifies that the connection is closed when


the data reader object is closed.

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

Among the numerous methods and properties exposed by the DataAdapter


class, the Fill and Update methods are the two most important. Fill passes a
query to a database and stores the returned set of data in a selected DataTable;
11.2 Data Access Models: Connected and Disconnected 505

Update performs a deletion, insertion, or update operation based on changes within


the DataSet. The actual update commands are exposed as DataAdapter proper-
ties. The DataAdapter is presented in much more detail in Section 11.4, “DataSets,
DataTables, and the Disconnected Model.”

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.

As a simple introduction to how a DataAdapter and DataSet work together,


Listing 11-2 shows how to create a DataTable, fill it with data from a database, and
add it to a DataSet.

Listing 11-2 Using a DataAdapter to Load Data from a Database


string sql = "SELECT movie_Title, movie_Year FROM movies";
string connStr = " Data Source=MYSERVER;Initial Catalog=films;
User Id=filmsadmin;Password=bogart;";
// (1) Create data adapter object
SqlDataAdapter da = new SqlDataAdapter(sql,connStr);
// (2) Create dataset
DataSet ds = new DataSet();
// (3) Create table in dataset and fill with data
da.Fill(ds, "movies"); // Fill table with query results
DataTable dt = ds.Tables["movies"];
// (4) Add movie titles to list box
for (int i=0; i< dt.Rows.Count;i++)
{
DataRow row = dt.Rows[i];
listBox1.Items.Add(row["movie_Title"]);
}

The first step is to create an instance of a SqlDataAdapter by passing the select


command and the connection string to its constructor. The data adapter takes care of
creating the Connection object and opening and closing the connection as needed.
506 Chapter 11 ■ ADO.NET

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.

11.3 ADO.NET Connected Model


As described earlier, the connected model is based on establishing a connection to a
database and then using commands to update, delete, or read data on the connected
source. The distinguishing characteristic of this model is that commands are issued
directly to the data source over a live connection—which remains open until the
operations are complete. Whether working with a connected or disconnected model,
the first step in accessing a data source is to create a connection object to serve as a
communications pathway between the application and database.

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.

Table 11-1 Members of the IDbConnection Interface

Category Name Description

Property ConnectionString Gets or sets the string used to connect to a data


source.

Property ConnectionTimeout The number of seconds to wait while trying to estab-


lish a connection to a data source before timing out.

Property Database Name of the database associated with the current


connection.
11.3 ADO.NET Connected Model 507

Table 11-1 Members of the IDbConnection Interface (continued)

Category Name Description

Property State Current state of the connection. Returns a Connec-


tionState enumeration name: Broken, Closed,
Connecting, Executing, Fetching, or Open.

Method Open Opens a connection. Rolls back any pending opera-


Close tions and closes the connection—returning it to a
connection pool, if one is used.

Method BeginTransaction Initiates a database transaction.

Method ChangeDatabase Changes the current database for an open connec-


tion. The new database name is passed as string to
the method.

Method CreateCommand Creates a command object to be associated with


connection.

Core Note

Even though connection classes implement the IDbConnection


interface, they do not necessarily have to provide meaningful
functionality. For example, the OracleConnection class does not
support the ConnectionTimeOut, Database, or ChangeDatabase
members.

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.

string cn= "Data Source=MYSERVER;Initial Catalog=films;


User Id=filmsadmin;Password=bogart;";
SqlConnection conn = new SqlConnection(cn);
conn.Open(); // Open connection
508 Chapter 11 ■ ADO.NET

Table 11-2 Connection String Examples

Connection Type Description and Use

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+";"

ODBC (DSN) "DSN=movies;".

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.

SqlConnectionStringBuilder sqlBldr = new


SqlConnectionStringBuilder();
scb.DataSource = "MYSERVER";
// Or scp["Data Source"] = "MYSERVER";
sqlBldr.Password = "bogart";
sqlBldr.UserID = "filmsadmin";
sqlBldr.InitialCatalog = "films";
SqlConnection conn = new
SqlConnection(sqlBldr.ConnectionString);
conn.Open();

2. SqlClient, Oracle, OleDB, and ODBC implementations are available with ADO.NET 2.0.
11.3 ADO.NET Connected Model 509

The ConnectionStringBuilder object is also useful for applications that input


the connection string from a configuration file or other source. Setting the Connec-
tionString property to the connection string value exposes members that control
the behavior of the connection. Table 11-3 lists selected properties of the SqlCon-
nectionStringBuilder class.

Table 11-3 Selected Properties of the SqlConnectionStringBuilder Class

Method Description

Asynchronous- Boolean value that indicates whether asynchronous process is


Processing permitted on the connection. The command object is responsible
for making asynchronous requests.

ConnectionTimeout Corresponds to the ConnectionTimeout property of the


Connection object.

DataSource Name or address of the SQL Server to connect to.

MaxPoolSize Sets or returns the maximum and minimum number of connec-


MinPoolSize tions in the connection pool for a specific connection string.

Password Password for accessing SQL Server account.

Pooling A boolean value that indicates whether connection pooling is


used.

UserID User ID required to access a SQL Server account.

Core Note

For demonstration purposes, the connection strings in these examples


are shown as cleartext within the application’s code. In reality, a
connection string should be stored outside of an application’s assembly.
For Web applications, the Web.Config file is often a reasonable choice.
As described in Chapter 17, “The ASP.NET Application Environment,”
.NET includes a special configuration section to hold connection strings
and supports techniques to encrypt the configuration information.
Desktop applications that access a central database can store the
information on the client’s machine (in the registry or a configuration
file) or have it downloaded as part of an application’s startup. The latter
approach provides better scalability and security, particularly if the
server returns a connection object rather than the string.
510 Chapter 11 ■ ADO.NET

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:

• Connection pooling is turned on by default. It can be disabled for a


SqlConnection by including "Pooling=false" in the connection
string; an OleDbConnection requires "OLE DB Services=-4".
• Each connection pool is associated with a distinct connection string.
When a connection is requested, the pool handler compares the con-
nection string with those of existing pools. If it matches, a connection
is allocated from the pool.
• If all connections in a pool are in use when a request is made, the
request is queued until a connection becomes free. Connections are
freed when the Close or Dispose method on a connection is called.
• The connection pool is closed when all connections in it are released
by their owners and have timed out.

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

The Command Object


After a connection object is created, the next step in accessing a database—for the
connected model—is to create a command object that submits a query or action
command to a data source. Command classes are made available by data providers
and must implement the IDbCommand interface.

Creating a Command Object


You can use one of its several constructors to create a command object directly, or
use the ProviderFactory approach mentioned in Section 11.1.
This segment demonstrates how to create a command object and explicitly set its
properties:

SqlConnection conn = new SqlConnection(connstr);


Conn.open();
string sql = "insert into movies(movie_Title,movie_Year,
movie_Director) values(@title,@yr,@bestpicture)";
SqlCommand cmd = new SqlCommand();
// Assign connection object and sql query to command object
cmd.Connection = conn;
cmd.commandText = sql;
// Fill in parameter values in query
// This is recommended over concatenation in a query string
cmd.Parameters.AddWithValue ("@title", "Schindler's List");
cmd.Parameters.AddWithValue ("@yr", "1993");
cmd.Parameters.AddWithValue ("@bestpic", "Y");

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.

string provider = "System.Data.SqlClient";


DBProviderFactory factory =
DbProviderFactories.GetFactory(provider);
DbCommand cmd = factory.CreateCommand();
cmd.CommandText = sql; // Query or command
cmd.Connection = conn; // Connection 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

SqlCommand. However, casting is required if you need to access custom features of a


provider’s command class—for example, only SqlCommand has an ExecuteXml-
Reader method.

Executing a Command
The SQL command assigned to the CommandText property is executed using one of
the four command methods in Table 11-4.

Table 11-4 Command Executexxx Methods

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();

ExecuteReader Executes a query and returns a DataReader object that provides


access to the query’s resultset. This method accepts an optional
CommandBehavior object that can improve execution efficiency.
cmd.CommandText="SELECT * FROM movies
WHERE movie_year > '1945';
SqlDataReader rdr= cmd.ExecuteReader();

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

The ExecuteReader method is the most important of these methods. It returns a


DataReader object that exposes the rows returned by the query. The behavior of
this method can be modified by using its overload that accepts a CommandBehavior
type parameter. As an example, the following statement specifies that a single row of
data is to be returned:

rdr = cmd.ExecuteReader(sql, CommandBehavior.SingleResult);

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.

Executing Stored Procedures


with the Command Object
A stored procedure is a set of SQL code stored in a database that can be executed as
a script. It’s a powerful feature that enables logic to be encapsulated, shared, and
reused among applications. ADO.NET supports the execution of stored procedures
for OleDb , SqlClient, ODBC, and OracleClient data providers.
Executing a stored procedure is quite simple: set the SqlCommand.CommandText
property to the name of the procedure; set the CommandType property to the enu-
meration CommandType.StoredProcedure; and then call the ExecuteNonQuery
method.

cmd.CommandText = "SP_AddMovie"; // Stored procedure name


cmd.CommandType = CommandType.StoredProcedure;
cmd.ExecuteNonQuery();

When a stored procedure contains input or output parameters, they must be


added to the command object’s Parameters collection before the procedure is exe-
cuted. To demonstrate, let’s execute the stored procedure shown in Listing 11-3. This
procedure allows records to be fetched from the movies table as pages containing 10
rows of data. Input to the procedure is the desired page; the output parameter is the
total number of pages available. This code fragment illustrates how parameters are
set and how the procedure is invoked to return the first page:

SqlCommand cmd = new SqlCommand();


cmd.CommandText = "SPMOVIES_LIST"; // Stored procedure name
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(@PageRequest", SqlDbType.Int);
514 Chapter 11 ■ ADO.NET

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.

Stored SQL Server Procedure to Return a


Listing 11-3
Page of Records
CREATE PROCEDURE SPMOVIES_LIST
@PageRequest int,
@TotalPages int output
AS
/*
Procedure to return a resultset of movies ordered
by title.
Resultset contains 10 movies for the specified page.
*/
SET NOCOUNT ON
select @TotalPages = CEILING(COUNT(*)/10) from movies
if @PageRequest = 1 or @PageRequest <1
begin
select top 10 * from movies order by movie_Title
set @PageRequest = 1
return 0
end
begin
if @PageRequest > @TotalPages
set @PageRequest = @TotalPages
declare @RowCount int
set @RowCount = (@PageRequest * 10)
11.3 ADO.NET Connected Model 515

Stored SQL Server Procedure to Return a


Listing 11-3
Page of Records (continued)
exec ('SELECT * FROM
(SELECT TOP 10 a.* FROM
(SELECT TOP ' + @RowCount + ' * FROM movies ORDER BY
movie_Title) a
ORDER BY movie_Title desc) b
ORDER BY Movie_Title')

return 0
end

Using Parameterized Commands


Without Stored Procedures
An earlier example (see “Creating a Command Object” on page 511) used this state-
ment to create a SQL command to store a movie in the Films database:

string sql = "insert into movies(movie_Title,movie_Year,


bestpicture) values(@title,@yr,@bestpic)";
// Parameters set values to be stored
cmd.Parameters.AddWithValue ("@title", "Schindler's List");
cmd.Parameters.AddWithValue ("@yr", "1993");
cmd.Parameters.AddWithValue ("@bestpic", "Y");

The alternative, which uses concatenation, looks like this:

string title = "Schindler''s List"; // Two single quotes needed


string yr = "1993";
string pic = "Y";
sql = "insert into movies(movie_Title,movie_Year,
bestpicture) values";
sql += "('"+title+"',"+yr+",'"+pic+"') ";

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.

Accessing Rows with DataReader


A DataReader returns a single row from a resultset each time its Read method is
executed. If no rows remain, the method returns false. The reader should be closed
after row processing is completed in order to free system resources. You can check
the DataReader.IsClosed property to determine if a reader is closed.
Although a DataReader is associated with a single command, the command may
contain multiple queries that return multiple resultsets. This code fragment demon-
strates how a DataReader processes the rows returned by two queries.

string q1 = "SELECT * FROM movies WHERE movie_Year < 1940";


string q2 = "SELECT * FROM movies WHERE movie_Year > 1980";
cmd.CommandText = q1 + ";" + q2;
DbDataReader rdr = cmd.ExecuteReader();
bool readNext = true;
while (readNext)
{
while (rdr.Read())
{
MessageBox.Show(rdr.GetString(1));
}
readNext = rdr.NextResult(); // Another resultset?
}
rdr.Close();
conn.Close();

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 DataReader has no property or method that provides the number of


rows returned in its resultset. Because data is received one row at a time,
the resultset could be altered by additions and deletions to the database
as records are read. However, there is a HasRows property that returns
true if the data reader contains one or more rows.
11.3 ADO.NET Connected Model 517

Accessing Column Values


with DataReader
There are numerous ways to access data contained in the columns of the current
DataReader row: as an array with column number (zero-based) or name used as
an index; using the GetValue method by passing it a column number; and using
one of the strongly typed Getxxx methods that include GetString, GetInt32,
GetDateTime, and GetDouble. The following code segment contains an example
of each technique:

cmd.CommandText="SELECT movie_ID, movie_Title FROM movies";


rdr = cmd.ExecuteReader();
rdr.Read();
string title;
// Multiple ways to access data in a column
title = rdr.GetString(1);
title = (string)rdr.GetSqlString(1); // SqlClient provider
title = (string)rdr.GetValue(1);
title = (string)rdr["movie_Title"]; // Implicit item
title = (string)rdr[1]; // Implicit item

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:

// List column names for a DataReader


DbDataReader rdr = GetReader(); // Get a DataReader
for (int k = 0; k < rdr.FieldCount; k++)
{
Console.WriteLine(rdr.GetName(k)); // Column name
}
rdr.Close();

Complete column schema information is available through the GetSchemaTable


method. It returns a DataTable in which there is one row for each field (column) in
the resultset. The columns in the table represent schema information. This code seg-
ment demonstrates how to access all the column information for a resultset. For
brevity, only three of the 24 columns of information are shown:
518 Chapter 11 ■ ADO.NET

DataTable schemaTable = rdr.GetSchemaTable();


int ict = 0;
foreach (DataRow r in schemaTable.Rows)
{
foreach (DataColumn c in schemaTable.Columns){
Console.WriteLine(ict.ToString()+"
"+c.ColumnName + ": "+r[c]);
ict++;
}
}
// Selected Output:
// 0 ColumnName: movie_ID
// 1 ColumnOrdinal: 0
// 12 DataType: System.Int32

11.4 DataSets, DataTables, and the


Disconnected Model
The ADO.NET disconnected model is based on using a DataSet object as an
in-memory cache. A DataAdapter serves as the intermediary between the DataSet
and the data source that loads the cache with data. After it has completed its task, the
DataAdapter returns the connection object to the pool, thus disconnecting the data
from the data source. Interestingly, the DataAdapter is actually a wrapper around a
data provider’s DataReader, which performs the actual data loading.

The DataSet Class


In many ways, a DataSet object plays the role of an in-memory database. Its Tables
property exposes a collection of DataTables that contain data and a data schema
describing the data. The Relations property returns a collection of DataRelation
objects that define how tables are interrelated. In addition, DataSet methods are
available to Copy, Merge, and Clear the contents of the DataSet.
Keep in mind that the DataSet and DataTable are core parts of ADO.NET
and—unlike the Connection, DataReader, and DataAdapter—they are not tied
to a specific data provider. An application can create, define, and populate a
DataSet with data from any source.
Besides tables and their relations, a DataSet can also contain custom information
defined by the application. A look at Figure 11-6 shows the major collection classes in
the DataSet hierarchy. Among these is PropertyCollection, which is a set of cus-
tom properties stored in a hash table and exposed through the DataSet.Extended-
Properties property. It is often used to hold a time stamp or descriptive information
such as column validation requirements for tables in the data set.
11.4 DataSets, DataTables, and the Disconnected Model 519

DataSet

DataRelationCollection

Hashtable Item
PropertyCollection

DataTableCollection

DataTable

DataRow
DataRowCollection

DataColumn
DataColumnCollection

DataView

ChildRelations

ParentRelations

Figure 11-6 DataSet class hierarchy

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

Table 11-5 Properties of the DataColumn Class

Method Description

ColumnName Name of the column.

DataType Type of data contained in this column.

Example: col1.DataType = System.Type.GetType("System.String")


MaxLength Maximum length of a text column. -1 if there is no maximum length.

ReadOnly Indicates whether the values in the column can be modified.

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.

Expression An expression defining how the value of a column is calculated.

Example: colTax.Expression = "colSales * .085";


Caption The caption displayed in the user interface.

DataTable The name of the DataTable containing this column.

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.

DataTable tb = new DataTable("Order");


DataColumn dCol = new DataColumn("ID",
Type.GetType("System.Int16"));
dCol.Unique = true; // ID must be unique for each data row
dCol.AllowDBNull = false;
tb.Columns.Add(dCol);
dCol= new DataColumn("Price", Type.GetType("System.Decimal"));
tb.Columns.Add(dCol);
dCol=new DataColumn("Quan",Type.GetType("System.Int16"));
tb.Columns.Add(dCol);
11.4 DataSets, DataTables, and the Disconnected Model 521

dCol= new DataColumn("Total",Type.GetType("System.Decimal"));


dCol.Expression= "Price * Quan";
tb.Columns.Add(dCol);
// List column names and data type
foreach (DataColumn dc in tb.Columns)
{
Console.WriteLine(dc.ColumnName);
Console.WriteLine(dc.DataType.ToString());
}

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:

DataColumn[] col = {tb.Columns["ID"]};


tb.PrimaryKey = col;

We’ll see how to use a primary key to create table relationships and merge data
later in this section.

Core Note

If a primary key consists of more than one column—such as a first and


last name—you can enforce a unique constraint on these columns in
three steps: by creating an array to hold the columns, creating a
UniqueConstraint object by passing the array to its constructor; and
adding the constraint to the data table’s Constraints collection:
DataColumn[] cols = {tb.Columns["fname"]
tb.Columns["lname"]};
tb.Constraints.Add(new UniqueConstraint("nameKey", cols));

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

The two DataTable methods AcceptChanges and RejectChanges are equiva-


lent to the commit and rollback operations in a database. These apply to all changes
made from the time the table was loaded or since AcceptChanges was previously
invoked. In this example, we are able to restore a deleted row because the deletion is
not committed before RejectChanges is called. Note that the changes are to the
data table—not the original data source.
For each column value in a row, ADO.NET maintains a current and original
value. When RejectChanges is called, the current values are set to the original val-
ues. The opposite occurs if AcceptChanges is called. The two sets of values can be
accessed concurrently through the DataRowVersion enumerations Current and
Original:

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.

Loading Data into a DataSet


Now that we have seen how to construct a DataTable and punch data into it
row-by-row, let’s look at how data and a data schema can be automatically loaded
from a relational database into tables in a DataSet. For details on loading XML data,
refer to Section 11.5, “XML and ADO.NET,” on page 533.

Using the DataReader to Load Data into a DataSet


A DataReader object can be used in conjunction with a DataSet or DataTable to
fill a table with the rows generated by a query. This requires creating a DataReader
object and passing it as a parameter to the DataTable.Load method:

cmd.CommandText = "SELECT * FROM movies WHERE movie_Year < 1945";


DBDataReader rdr =
cmd.ExecuteReader(CommandBehavior.CloseConnection);
DataTable dt = new DataTable("movies");
dt.Load(rdr); // Load data and schema into table
Console.WriteLine(rdr.IsClosed); // True

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:

cmd.CommandText = "SELECT * FROM movies WHERE movie_Year < 1945";


DBDataReader rdr = cmd.ExecuteReader( );
DataTable dt = new DataTable("movies");
dt.Load(rdr); // Load rows into table
Console.Write(dt.Rows[0]["movie_Title"]); // Casablanca

// Assign primary key so rows can be merged


DataColumn[] col = new DataColumn[1];
524 Chapter 11 ■ ADO.NET

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

Using the DataAdapter to Load Data into a DataSet


A DataAdapter object can be used to fill an existing table, or create and fill a new
table, with the results from a query. The first step in this process is to create an
instance of the DataAdapter for a specific data provider. As the following code
shows, several constructor overloads are available:

// (1) The easiest: a query and connection string as arguments


String sql = "SELECT * FROM movies";
SqlDataAdapter da = new SqlDataAdapter(sql, connStr);

// (2) Assign a command object to the SelectCommand property


SqlDataAdapter da = new SqlDataAdapter();
SqlConnection conn = new SqlConnection(connStr);
da.SelectCommand = new SqlCommand(sql,conn);

// (3) Pass in a query string and connection object


SqlConnection conn = new SqlConnection(connStr);
SqlDataAdapter da = new SqlDataAdapter(sql, conn);

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

DataSet ds = new DataSet();


// Create DataTable, load data, and add to DataSet
// Could use da.Fill(ds,"movies") to specify table name.
int numrecs = da.Fill(ds); // Returns number of records loaded

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.

Using the DataAdapter to Update a Database


After a DataAdapter has loaded data into a table, the underlying connection is
closed, and subsequent changes made to the data are reflected only in the
DataSet—not the underlying data source. To apply changes to the data source, a
DataAdapter is used to restore the connection and send the changed rows to the
database. The same DataAdapter used to fill the DataSet can be used to perform
this task.
The DataAdapter has three properties—InsertCommand, DeleteCommand,
and UpdateCommand—that are assigned the actual SQL commands to perform the
tasks that correspond to the property name. These commands are executed when the
Upate method of the DataAdapter is invoked. The challenge lies in creating the
SQL commands that post the changes and assigning them to the appropriate Data-
Adapter properties. Fortunately, each data provider implements a Command-
Builder class that can be used to handle this task automatically.

The CommandBuilder Object


A CommandBuilder object generates the commands necessary to update a data
source with changes made to a DataSet. It’s amazingly self-sufficient. You create an
instance of it by passing the related DataAdapter object to its constructor; then,
when the DataAdapter.Update method is called, the SQL commands are gener-
ated and executed. The following segment shows how changes to a DataTable are
flushed to the database associated with the DataAdapter:

DataTable dt= ds.Tables["movies"]; // Shortcut to reference table

// (1) Use command builder to generate update commands


SqlCommandBuilder sb = new SqlCommandBuilder(da);

// (2) Add movie to table


DataRow drow = dt.NewRow();
drow["movie_Title"] = "Taxi Driver";
drow["movie_Year"] = "1976";
526 Chapter 11 ■ ADO.NET

dt.Rows.Add(drow);

// (3) Delete row from table


dt.Rows[4].Delete();

// (4) Edit Column value


dt.Rows[5]["movie_Year"] = "1944";

// (5) Update underlying Sql Server table


int updates = da.Update(ds, "movies");
MessageBox.Show("Rows Changed: " +updates.ToString()); // 3

There are a couple of restrictions to be aware of when using the Command-


Builder: The Select command associated with the DataAdapter must refer to a
single table, and the source table in the database must include a primary key or a col-
umn that contains unique values. This column (or columns) must be included in the
original Select command.

Core Note

You can create your own update commands without using a


CommandBuilder. Although it can be a lengthy process, it can also yield
more efficient commands. For applications that require considerable
database updates, you may want to consult an ADO.NET book for
details on coding update logic directly.

Synchronizing the DataSet and the DataBase


As demonstrated in this example, the use of a DataAdapter simplifies and auto-
mates the process of updating a database—or any data store. However, there is a rock
in this snowball: the problem of multi-user updates. The disconnected model is
based on optimistic concurrency, an approach in which the rows of the underlying
data source are not locked between the time they are read and the time updates are
applied to the data source. During this interval, another user may update the data
source. Fortunately, the Update method recognizes if changes have occurred since
the previous read and fails to apply changes to a row that has been altered.
There are two basic strategies for dealing with a concurrency error when multi-
ple updates are being applied: roll back all changes if a violation occurs, or apply
the updates that do not cause an error and identify the ones that do so they can be
reprocessed.
11.4 DataSets, DataTables, and the Disconnected Model 527

Using Transactions to Roll Back Multiple Updates


When the DataAdapter.ContinueUpdateonErrors property is set to false, an
exception is thrown when a row update cannot be completed. This prevents subse-
quent updates from being attempted, but does not affect updates that occurred prior
to the exception. Because updates may be interdependent, applications often require
an all-or-none strategy. The easiest way to implement this strategy is to create a .NET
transaction in which all of the update commands execute. To do so, create a Sql-
Transaction object and associate it with the SqlDataAdapater.SelectCommand
by passing it to its constructor. If an exception occurs, the transaction’s Rollback
method is used to undo any changes; if no exceptions occur, the Commit method is
executed to apply all the update commands. Listing 11-4 is an example that wraps the
updates inside a transaction.

Using Transaction to Roll Back Database


Listing 11-4
Updates
SqlDataAdapter da = new SqlDataAdapter();
SqlCommandBuilder sb = new SqlCommandBuilder(da);
SqlTransaction tran;
SqlConnection conn = new SqlConnection(connStr);
conn.Open(); // Must open to use with transaction
// (1) Create a transaction
SqlTransaction tran = conn.BeginTransaction();
// (2) Associate the SelectCommand with the transaction
da.SelectCommand = new SqlCommand(sql, conn, tran);
DataSet ds = new DataSet();
da.Fill(ds, "movies");
//
// Code in this section makes updates to DataSet rows
try
{
int updates = da.Update(ds, "movies");
MessageBox.Show("Updates: "+updates.ToString());
}
// (3) If exception occurs, roll back all updates in transaction
catch (Exception ex)
{
MessageBox.Show(ex.Message); // Error updating
if (tran != null)
{
tran.Rollback(); // Roll back any updates
tran = null;
MessageBox.Show("All updates rolled back.");
}
}
528 Chapter 11 ■ ADO.NET

Using Transaction to Roll Back Database


Listing 11-4
Updates (continued)
finally
{
// (4) If no errors, commit all updates
if (tran != null)
{
tran.Commit();
MessageBox.Show("All updates successful. ");
tran = null;
}
}
conn.Close();

Identifying Rows That Cause Update Errors


When DataAdapter.ContinueUpdateonErrors is set to true, processing does
not halt if a row cannot be updated. Instead, the DataAdapter updates all rows that
do not cause an error. It is then up to the programmer to identify the rows that failed
and determine how to reprocess them.
Rows that fail to update are easily identified by their DataRowState property
(discussed earlier in the description of DataRows). Rows whose update succeeds
have a value of Unchanged; rows that fail have their original Added, Deleted, or
Modified value. A simple code segment demonstrates how to loop through the rows
and identify those that are not updated (see Listing 11-5).

Identify Attempts to Update a Database


Listing 11-5
That Fails
// SqlDataAdapter da loads movies table
da.ContinueUpdateOnError = true;
DataSet ds = new DataSet();
try
{
da.Fill(ds, "movies");
DataTable dt = ds.Tables["movies"];
SqlCommandBuilder sb = new SqlCommandBuilder(da);
// ... Sample Update operations
dt.Rows[29].Delete(); // Delete
dt.Rows[30]["movie_Year"] = "1933"; // Update
dt.Rows[30]["movie_Title"] = "King Kong"; // Update
11.4 DataSets, DataTables, and the Disconnected Model 529

Identify Attempts to Update a Database


Listing 11-5
That Fails (continued)
dt.Rows[31]["movie_Title"] = "Fantasia"; // Update
DataRow drow = dt.NewRow();
drow["movie_Title"] = "M*A*S*H";
drow["movie_Year"] = "1970";
dt.Rows.Add(drow); // insert
// Submit updates
int updates = da.Update(ds, "movies");
// Following is true if any update failed
if (ds.HasChanges())
{
// Load rows that failed into a DataSet
DataSet failures = ds.GetChanges();
int rowsFailed = failures.Rows.Count;
Console.WriteLine("Update Failures: "+rowsFailed);
foreach (DataRow r in failures.Tables[0].Rows )
{
string state = r.RowState.ToString());
// Have to reject changes to show deleted row
if (r.RowState == DataRowState.Deleted)
r.RejectChanges();
string ID= ((int)r["movie_ID"]).ToString();
string msg= state + " Movie ID: "+ID;
Console.WriteLine(msg);
}
}

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

Defining Relationships Between


Tables in a DataSet
A DataRelation is a parent/child relationship between two DataTables. It is
defined on matching columns in the two tables. The columns must be the same
DataType, and the column in the parent table must have unique values. The syntax
for its constructor is

public DataRelation(
string relationName,
DataColumn parentColumn,
DataColumn childColumn)

A DataSet has a Relations property that provides access to the collection of


DataRelations defined for tables contained in the DataSet. Use the Rela-
tions.Add method to place relations in the collection. Listing 11-6 illustrates these
ideas. It contains code to set up a parent/child relationship between the directors
and movies tables in order to list movies by each director.

Create a Relationship Between the Directors and


Listing 11-6
Movies Tables
DataSet ds = new DataSet();
// (1) Fill table with movies
string sql = "SELECT movie_ID,movie_Title,movie_DirectorID,
movie_Year FROM movies";
SqlConnection conn = new SqlConnection(connStr);
SqlCommand cmd = new SqlCommand();
SqlDataAdapter da = new SqlDataAdapter(sql, conn);
da.Fill(ds, "movies");
// (2) Fill table with directors
sql = "SELECT director_id,(first_name + ' '+ last_name) AS
fullname FROM directors";
da.SelectCommand.CommandText = sql;
da.Fill(ds, "directors");
// (3) Define relationship between directors and movies
DataTable parent = ds.Tables["directors"];
DataTable child = ds.Tables["movies"];
DataRelation relation = new DataRelation("directormovies",
parent.Columns["director_ID"],
child.Columns["movie_DirectorID"]);
11.4 DataSets, DataTables, and the Disconnected Model 531

Create a Relationship Between the Directors and


Listing 11-6
Movies Tables (continued)
// (4) Add relation to DataSet
ds.Relations.Add(relation);
// (5) List each director and his or her movies
foreach (DataRow r in parent.Rows)
{
Console.WriteLine(r["fullname"]; // Director name
foreach (DataRow rc in
r.GetChildRows("directormovies"))
{
Console.WriteLine(" "+rc["movie_title"]);
}
}
/*
Sample Output:
David Lean
Lawrence of Arabia
Bridge on the River Kwai, The
Victor Fleming
Gone with the Wind
Wizard of Oz, The
*/

Relations and Constraints


When a relationship is defined between two tables, it has the effect of adding a
ForeignKeyConstraint to the Constraints collections of the child DataTable.
This constraint determines how the child table is affected when rows in a parent
table are changed or deleted. In practical terms, this means that if you delete a row in
the parent table, you can have the related child row(s) deleted—or optionally, have
their key value set to null. Similarly, if a key value is changed in the parent table, the
related rows in the child can have their key value changed or set to null.
The rule in effect is determined by the value of the DeleteRule and UpdateRule
properties of the constraint. These can take one of four Rule enumeration values:

• Cascade. Deletes or updates related rows in child table. This is the


default.
• None. Takes no action.
• SetDefault. Sets key values in child rows to column’s default value.
• SetNull. Sets key values in child rows to null.
532 Chapter 11 ■ ADO.NET

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.

// (1) Try to add row with new key to child table


DataRow row = child.NewRow();
row["movie_directorID"] = 999;
child.Rows.Add(row); // Fails – 999 does not exist in parent
// (2) Delete row in parent table
row = parent.Rows[0];
row.Delete(); // Deletes rows in child having this key
// (3) Relax constraints and retry adding row
ds.EnforceConstraints = false;
row["movie_directorID"] = 999;
child.Rows.Add(row); // Succeeds
ds.EnforceConstraints = true; // Turn back on
// (4) Change constraint to set rows to null if parent changed
((ForeignKeyConstraint)child.Constraints[0]).DeleteRule =
Rule.SetNull ;

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.

Choosing Between the Connected


and Disconnected Model
The DataReader and DataSet offer different approaches to processing data—each
with its advantages and disadvantages. The DataReader provides forward-only,
read-only access to data. By processing a row at a time, it minimizes memory require-
ments. A DataSet, on the other hand, offers read/write access to data, but requires
enough memory to hold a copy of the data fetched from a data source. From this, you
can derive a couple of general rules: If the application does not require the capability
to update the data source and is used merely for display and selection purposes, a
DataReader should be the first consideration; if the application requires updating
data, a DataSet should be considered.

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.

A DataReader is a good choice when the following apply:

• A large number of records must be handled so that the memory


requirements and time to load make a DataSet impracticable.
• The data is read-only and bound to a Windows or Web Form list
control.
• The database is highly volatile, and the contents of a DataSet might
be updated often.

11.5 XML and ADO.NET


Just as relational data has a schema that defines its tables, columns, and relationships,
XML uses a Schema Definition language (XSD) to define the layout of an XML
document. Its main use is to validate the content of XML data. See Section 10.1,
“Working with XML,” for a discussion of XML Schema.
The XML classes reside in the System.Xml namespace hierarchy and are not part
of ADO.NET. However, the ADO.NET DataSet class provides a bridge between
the two with a set of methods that interacts with XML data and schemas:

• ReadXML. Loads XML data into a DatSet.


• WriteXml and GetXml. Writes the DataSet’s contents to an XML
formatted stream.
• WriteXmlSchema and GetXmlSchema. Generates an XML Schema
from the DataSet schema.
• ReadXmlSchema. Reads an XML Schema file and creates a database
schema.
• InferXmlSchema. Creates a DataSet schema from XML data.
534 Chapter 11 ■ ADO.NET

• GetXml and GetXmlSchema, Returns a string containing the


XML representation of the data or the XSD schema for XML
representation.

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.

Using a DataSet to Create XML


Data and Schema Files
When working with XML, the DataSet is used as an intermediary to convert
between XML and relational data. For this to work, the XML data should be struc-
tured so that it can be represented by the relationships and row-column layout that
characterizes relational data.
The following code segment illustrates how easy it is to create an XML data file
and schema from a DataSet’s contents. A DataAdapter is used to populate a
DataSet with a subset of the movies table. The WriteXml and WriteXmlSchema
methods are then used to translate this to XML output.

DataSet ds = new DataSet("films");


DataTable dt = ds.Tables.Add("movies");
string sql = "SELECT * FROM movies WHERE bestPicture='Y'";
SqlDataAdapter da = new SqlDataAdapter(sql, conn);
da.Fill(dt);
// Write Schema representing DataTable to a file
ds.WriteXmlSchema("c:\\oscars.xsd"); // create schema
// Write Table data to an XML file
ds.WriteXml("c:\\oscarwinners.xml"); // data in xml format
/* To place schema inline with XML data in same file:
ds.WriteXml(("c:\\oscarwinners.xml",
XmlWriteMode.WriteSchema);
*/

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-7 XML Schema from Movies Table—oscars.xsd


<?xml version="1.0" encoding="utf-16"?>
<xs:schema id="films" xmlns=""
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="films" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="movies">
<xs:complexType>
<xs:sequence>
<xs:element name="movie_ID" type="xs:int"
minOccurs="0" />
<xs:element name="movie_Title" type="xs:string"
minOccurs="0" />
<xs:element name="movie_Year" type="xs:int"
minOccurs="0" />
<xs:element name="movie_DirectorID" type="xs:int"
minOccurs="0" />
<xs:element name="AFIRank" type="xs:int"
minOccurs="0" />
<xs:element name="bestPicture" type="xs:string"
minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>

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

Movies Data as an XML Document—


Listing 11-8
oscarwinners.xml

<?xml version="1.0" encoding="utf-16"?>


<?xml version="1.0" standalone="yes"?>
<films>
<movies>
<movie_ID>5</movie_ID>
<movie_Title>Citizen Kane </movie_Title>
<movie_Year>1941</movie_Year>
<movie_DirectorID>1</movie_Director>
<AFIRank>1</AFIRank>
<bestPicture>Y</bestPicture>
</movies>
<movies>
<movie_ID>6</movie_ID>
<movie_Title>Casablanca </movie_Title>
<movie_Year>1942</movie_Year>
<movie_DirectorID>2</movie_Director>
<AFIRank>2</AFIRank>
<bestPicture>Y</bestPicture>
</movies>
...
</films>

Creating a DataSet Schema from XML


Each ADO.NET DataSet has a schema that defines the tables, table columns, and
table relationships that comprise the DataSet. As we saw in the preceding example,
this schema can be translated into an XML schema using the WriteXmlSchema
method. ReadXmlSchema mirrors the process—adding tables and relationships to a
DataSet. In this example, the XML schema for the movies table (refer to Listing
11-7) is used to create a DataSet schema:

DataSet ds = new DataSet();


ds.ReadXmlSchema("c:\\oscars.xsd");
DataTable tb = ds.Tables[0];
// List Columns for table
string colList = tb.TableName +": ";
for (int i = 0; i < tb.Columns.Count; i++)
{ colList += tb.Columns[i].Caption + " "; }
Console.WriteLine(colList);
11.5 XML and ADO.NET 537

/* 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:

// (1) Create schema by inferring it from XML data


ds.Tables.Clear(); // Remove tables from DataSet
ds.InferXmlSchema("c:\\oscarwinners.xml",null);

// (2) Create schema using Data Adapter


ds.Tables.Clear();
string sql = "SELECT * FROM movies";
SqlDataAdapter da = new SqlDataAdapter(sql, connStr);
// Creates DataTable named "movies"
da.FillSchema(ds, SchemaType.Source, "movies");

Core Note

By creating the DataSet schema(s) in a separate step from reading in


XML data, you can control the data that is read from the source XML file.
Only data for the columns defined by the schema are read in.
Conversely, if the schema defines more columns than are in the XML
file, these columns are empty in the DataSet.

Reading XML Data into a DataSet


The DataSet.ReadXml method provides a way to read either data only or both the
data and schema into a DataSet. The method has several overloads that determine
whether a schema is also created. The two overloads used with files are

XmlReadMode ReadXml(string XMLfilename);


XmlReadMode ReadXml(string XMLfilename, XmlReadMode mode);

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

XmlReadMode Inline DataSet Infer Comment

Auto 1 2 3 The default when no XmlReadMode is


provided.

IgnoreSchema 1 Uses only the DataSet’s schema. Data in


the file that is not in the schema is
ignored.

InferSchema 1 Ignores inline schema, and builds tables


by inference from XML file. Error occurs
if DataSet already contains conflicting
schema.

ReadSchema 1 2 If tables created from inline schema


already exist in DataSet, an exception is
thrown.

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.

Using ReadXml to Load XML Data into


Listing 11-9
a DataSet
// Load XML data into dataset and create schema if one does
// not exist
DataSet ds = new DataSet();
ds.ReadXml("c:\\oscarwinners.xml");
11.5 XML and ADO.NET 539

Using ReadXml to Load XML Data into


Listing 11-9
a DataSet (continued)
// Save source of data in dataset
ds.ExtendedProperties.Add("source", "c:\\oscarwinners.xml");
ShowTable(ds.Tables[0]);

// Display each row in table


private void ShowTable(DataTable t)
{
foreach(DataRow dr in t.Rows)
{
StringBuilder sb = new StringBuilder("Table: ");
sb.Append(t.TableName).Append("\n");
foreach(DataColumn c in t.Columns)
{
sb.Append(c.Caption).Append(": ");
sb.Append(dr[c.ColumnName].ToString()).Append("\n");
}
Console.WriteLine(sb.ToString());

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:

string src = (string)ds.ExtendedProperties["source"];


ds.ExtendedProperties.Clear(); // clear hash table

Using ReadXml with Nested XML Data


The XML file used in the preceding example has a simple structure that is easily
transformed into a single table: The <movies> tag (refer to Listing 11-8) represents
a row in a table, and the elements contained within it become column values. Most
XML is more complex than this example and requires multiple tables to represent it.
Although ReadXml has limitations (it cannot handle attributes), it can recognize
nested XML and render multiple tables from it. As an example, let’s alter the oscar-
winners.xml file to include a <director> tag within each <movies> block.

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

DataSet ds = new DataSet();


ds.ReadXml("c:\\oscarwinnersv2.xml");
foreach (DataTable dt in ds.Tables)
ShowTable(dt);

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

Figure 11-7 DataSet tables and relationship created from XML

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.

11.7 Test Your Understanding


1. What four classes must a .NET data provider supply?

2. Given the following code:


SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
string s = "insert into movies(movie_title, movie_year,
bestpicture)values(&p1,&p2,&p3); "
cmd.CommandText= s;
which of the following code segments completes the SQL query string
to insert the movie row?
a. string p1="Star Wars";
int p2 = 1977;
string p3= "N";
b. cmd.Parameters.AddWithValue ("@p1","Star Wars");
cmd.Parameters.AddWithValue ("@p2", "1977");
cmd.Parameters.AddWithValue ("@p3", "N");
c. cmd.Parameters.Add("@p1", SqlDbType.NVarChar, 100);
cmd.Parameters.Add("@p2", SqlDbType.NVarChar, 4);
cmd.Parameters.Add("@p3", SqlDbType.NVarChar, 1);
p1.Value = "Star Wars";
p2.Value = "1977";
p3.Value = "N";
542 Chapter 11 ■ ADO.NET

3. Describe the purpose of these three command object methods:


ExecuteNonQuery
ExecuteReader
ExecuteScalar

4. Compare the role of the DataReader and the DataAdapter.

5. What is the difference between a DataSet and a DataTable?

6. The DataRowState property maintains the status of a row. Which of


these is not a valid DataRowState value?
a. Added
b. Deleted
c. Rejected
d. Changed

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

Topics in This Chapter

• DataBinding Overview: Associating data with a control is easy on


the surface; however, it is important to understand what’s going
on underneath. This section provides an overview of simple and
complex data binding, one-way and two-way data binding, and
the role of the Binding and BindingManagerBase classes.
• A Data Binding Application: A Windows Forms application
illustrates how to bind data in a DataSet and DataTable to
simple and complex controls.
• The DataGridView: The DataGridView introduced with .NET
2.0 has a rich set of features that enable it to display and
manipulate relational data. Examples illustrate how to create a
master-detail grid and a virtual mode grid.
12

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

12.1 Overview of Data Binding


Data binding provides a way to link the contents of a control with an underlying data
source. The advantage to this linkage or “binding” is that changes to the immediate
data source can be reflected automatically in data controls bound to it, and changes
in the data control are posted automatically to the intermediate data source. The
term intermediate data source is used to distinguish it from the original data source,
which may be an external database. The controls cannot be bound directly to a data
source over an active connection. Binding is restricted to the in-memory representa-
tion of the data. Figure 12-1 shows the basic components of the binding model: the
original data source, the intermediate storage, and the Form controls that are bound
to values in the local storage through a binding object. Let’s examine the model in
more detail.

Windows Form

binding
binding
CurrencyManager

image binding

DataTable
or Array

Figure 12-1 Multiple controls bound to a single data source

Simple Data Binding


Simple data binding, which is available to all controls, links a data source to one or
more properties of a control. A good example is the Textbox control that exposes
easily recognizable properties such as Text, Width, and BackColor. An application
can set these dynamically by binding them to a data source. Here is a code segment
that creates an object whose public properties are mapped to the properties on the
TextBox.
12.1 Overview of Data Binding 547

// Create object (width, text, color)


TextParms tp = new TextParms(200, "Casablanca", Color.Beige);
// Bind text and BackColor properties of control
txtMovie.DataBindings.Add("Text", tp, "Tb_Text");
txtMovie.DataBindings.Add("BackColor", tp, "Tb_Background");

// Or create binding and then add in two steps


Binding binding = new Binding("Width", tp, "Tb_Width");
txtMovie.DataBindings.Add(binding);

The DataBindings.Add method creates a collection of bindings that links the


data source to the control’s properties. The method’s syntax is

DataBindings.Add( control property, data source, data member)

control Property on the control that is being bound.


property
data source Object that contains data being bound to control.
data member Data member on the data source that is being used. Set this to
null if the data source’s ToString() method provides the value.

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

ArrayList tbList = new ArrayList();


// Beige color indicated movie won oscar as best picture
tbList.Add(new TextParms(200,"Casablanca",Color.Beige));
tbList.Add(new TextParms(200, "Citizen Kane", Color.White));
tbList.Add(new TextParms(200, "King Kong", Color.White));
// Bind to properties on the Textbox
txtMovie.DataBindings.Add("Text", tbList, "Tb_Text");
txtMovie.DataBindings.Add("BackColor", tbList,
"Tb_Background");
txtMovie.DataBindings.Add("Width", tbList, "Tb_Width");

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

Simple Binding with ADO.NET


Binding to a table in a DataSet is basically the same as binding to a list. In this exam-
ple, the Text property of the control is bound to the movie_Year column in a
DataTable.

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

Although the control could be bound directly to a DataTable, the recommended


approach is to bind the property to a DataSet and use the DataTable name as a
qualifier to specify the column that provides the data. This makes it clear which table
the value is coming from.

Complex Data Binding with List Controls


Complex binding is only available on controls that include properties to specify a data
source and data members on the data source. This select group of controls is limited
to the ListBox, CheckedListBox, ComboBox, DataGrid, and DataGridView.
Complex binding allows each control to bind to a collection of data—the data source
must support the IList interface—and display multiple items at once. Because the
DataGridView is discussed at length in the last half of this chapter, let’s look at how
complex binding is implemented on the ListBox control. The details also apply to
other List controls.
Binding a list control to a data source requires setting a minimum of two proper-
ties: DataSource, which specifies the source, and DisplayMember, which describes
the member—usually a data column or property—in the data source that is displayed
in the control. This code segment illustrates how a ListBox bound to a DataSet dis-
plays movie titles:

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

// Optional property that assigns a value to each item row


listBox1.ValueMember = "movies.movie_ID";

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

Figure 12-3 Using data binding to populate controls on a form

One-Way and Two-Way Data Binding


The data bound to a control can be changed in two ways: by updating the underlying
data source, such as adding a row to a table, or by modifying the visible contents of
the control. In both cases, the changes should be reflected in the associated control
or data source—a process referred to as two-way data binding. In general, that is
what happens. However, a control may be bound to a data source in read-only mode
when its only purpose is to present data. To understand how these techniques are
implemented, let’s look at how updating occurs—from the perspective of the control
and the data source.

Effects on the Data Source of Updating a Control Value


By default, changes made to data in a control are also made to the underlying
in-memory data source. If the year value in Figure 12-3 is changed, the value in the
corresponding row and column of the DataTable is also changed. Note that if the
year is represented as an integer in the table, the value entered in the control must be
an integer value. Data binding automatically checks types and rejects values (keeps
the same value in the control) that do not match the type of the underlying data.
In the case where a control is bound to a property on an object, the property must
provide write support in order for its value to be updated. For example, if the year
and studio list boxes in the preceding example were bound to the following proper-
ties, respectively, only year could be updated; changes made to the studio control
would be ignored and it would revert to its original value.
12.1 Overview of Data Binding 551

public int Movie_Year { set { myYear = value; }


get { return myYear; } }

// Read only property. Control cannot update this.


public string Studio { get { return myStudio; } }

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.

Effects on a Control of Updating the Data Source


When a DataSet is used as the data source for controls, any additions, deletions, or
changes made to the data are automatically reflected in the associated bound con-
trol(s). Custom data sources require some programming assistance to accomplish this.
If a control is bound to an object property, a change to the value of that property is
not automatically sent to the control. Instead, the binding manager looks for an event
named propertyChanged on the data source. If found, it provides a handler for this
event to receive notice when that property’s value changes. To enable the binding
manager to handle a changed value, you must define a propertyChanged event on
the data source class, and fire the event when a change occurs. To illustrate, let’s
extend the previous example to add the event to the class containing the
Movie_Year property, and add code to fire the event when the property changes.

// Event to notify bound control that value has changed


public event EventHandler Movie_YearChanged;

// Property control is bound to year value


public int Movie_Year {
set {
myYear = value;
// Notify bound control(s) of change
if (Movie_YearChanged != null)
Movie_YearChanged(this, EventArgs.Empty);
}
get { return myYear; }
}

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

Using Binding Managers


As illustrated in Figure 12-4, each data source has a binding manager that keeps
track of all connections to it. When the data source is updated, the binding manager
is responsible for synchronizing the values in all controls bound to the data. Con-
versely, if a value is changed on one of the bound controls, the manager updates the
source data accordingly. A binding manager is associated with only one data source.
Thus, if an application has controls bound to multiple data sources, each will have its
own manager.

1
Binding

Binding

4 Bindings Bindings

Binding Context 2 3
CurrencyManager PropertyManager

Data Source Data Source

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.

1. Binding. Maintains a simple binding between a property on a control


and a property on a single object. The following statements demon-
strate how to create a binding and gain access to it:
txtYr.DataBindings.Add("Text", ds,
"movies.movie_Year");
Binding binding = txtYr.DataBindings["Text"];
// "txtYr"
MessageBox.Show(binding.Control.ToString());
12.1 Overview of Data Binding 553

// Create a binding manager object


BindingManagerBase mgr= binding.BindingManagerBase;
2. CurrencyManager. This class derives from the abstract Binding-
ManagerBase class and serves as a binding manager for list data
sources such as a DataTable or Array. This object provides five
members that an application can use to manage the relationship
between a data source and control:
• Bindings. Returns the collection of bindings being managed.
• Count. The number of rows in the list that is being managed.
• Current. Returns the current item (such as a row) in the data
source as an object.
• Position. Gets/sets the position in the data source currently
indexed by the control.
• PositionChanged. Fires when the Position in the list changes.
• CurrentChanged. Is triggered when the bound value changes.
3. PropertyManager. This class, which also derives from BindingMan-
agerBase, maps the properties on an object to properties on a bound
control.
4. BindingContext. Observe in Figure 12-4 that the BindingContext
is linked to a form and a collection of BindingManagerBase objects.
Its job is to manage a collection of binding managers for a specific
control—in this case, a Form. The control could just as well be a
Panel or GroupBox on a form. A program’s main interest in the
BindingContext is to use it to gain access to the binding manager
for a data source. These statements, for example, return the manager
for the table movies.
BindingManagerBase mgr = this.BindingContext[ds,"movies"];
// Or use casting to get specific manager.
CurrencyManager mgr= (CurrencyManager)
this.BindingContext[ds,"movies"];

Using the BindingManagerBase to Navigate a List


Let’s now look at how the members of the BindingManagerBase class are used to
move through the items in a source data list and simultaneously update the contents
of controls bound to the list. The example binds a list box and text box to the familiar
movies data table.

// Bind listbox to a dataset.datatable


listBox1.DataSource = ds;
listBox1.DisplayMember = "movies.movie_Title";
554 Chapter 12 ■ Data Binding with Windows Forms Controls

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

// This method moves to the next row in the table.


// If at the end of the table it moves to the beginning.
private void Forward_Click (object sender, EventArgs e)
{
if (listBox1.Items.Count > 0)
{
bmb.Position = bmb.Position >= bmb.Count - 1 ? 0 :
++bmb.Position;
}
}

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.

private void bmb_PositionChanged(object sender,


EventArgs e)
{
BindingManagerBase bmb = (BindingManagerBase)sender;
// Item should be a DataRowView if from a table
object ob = bmb.Current.GetType();
if (ob == typeof(System.Data.DataRowView))
{
DataRowView view = (DataRowView)bmb.Current;
// Could access: ((string)view["movie_Title"]);
}
}

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

12.2 Using Simple and Complex Data


Binding in an Application
Several concepts were introduced in the first section. Let’s bring them together in an
application that relies on data binding to display and update information from the
Films database. Figure 12-5 shows a screen shot of the application’s user interface.
Each control on the Windows Form—except buttons—is bound to a data source.
A ListBox and ComboBox illustrate complex binding; two text boxes, a CheckBox,
and a PictureBox, are bound using simple binding. The controls can be bound
dynamically to either a data table or an array that contains custom objects. The
Scroll button moves down the list box by internally using a binding manager to
advance to the next item in the data source list. Let’s dissect the program by looking
at code associated with the buttons. Much of the code should be familiar from code
segments in the previous section and does not require further explanation.

Figure 12-5 Application combining complex and simple binding

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.

Listing 12-1 Binding Controls to a DataSet


// Bind control to data from a database
private void btnTableBind_Click(object sender, EventArgs e)
{
SqlConnection conn = new SqlConnection(GetString());
conn.Open();
ds = new DataSet("films");
string sql = "SELECT movie_ID, movie_title, movie_year,
studio, afi_rank, CASE WHEN bestpicture ='Y'
THEN 1 ELSE 0 END as BestPicture FROM movies ORDER BY
movie_Year";
da = new SqlDataAdapter(sql, conn);
// Command builder keeps track of changes to data
SqlCommandBuilder sb = new SqlCommandBuilder(da);
da.Fill(ds,"movies");
DataTable dt = ds.Tables[0];
Data Column dCol = new DataColumn("movie_Image",
Type.GetType("System.Object"));
dt.Columns.Add(dCol);
// Place image in new column. Name is based on movie ranking.
Image defaultImage = Image.FromFile(@"c:\defaultimg.jpg");
foreach (DataRow dRow in dt.Rows)
{
string rank = ((int)dRow["afi_rank"]).ToString();
string imgFile = "c:\\afi" + rank + ".gif";
try
{
Image imgObject = Image.FromFile(imgFile);
dRow["movie_Image"] = imgObject;
}
catch (Exception ex)
{
dRow["movie_Image"] = defaultImage;
}
}
// Nothing to this point should be considered a change
dt.AcceptChanges();
12.2 Using Simple and Complex Data Binding in an Application 557

Listing 12-1 Binding Controls to a DataSet (continued)


// Bind listbox and combobox to datasource
listBox1.DataSource = ds;
listBox1.DisplayMember = "movies.movie_Title";
listBox1.ValueMember = "movies.movie_ID";
comboBox1.DataSource = ds;
comboBox1.DisplayMember = "movies.movie_Title";
// Binding manager has global scope
bmb = this.BindingContext[ds, "movies"];
bmb.PositionChanged += new
EventHandler(bmb_PositionChanged);
try
{
// TextBox.Text – binds to studio name
if(txtStudio.DataBindings["text"] != null)
txtStudio.DataBindings.Remove(
txtStudio.DataBindings["Text"]);
txtStudio.DataBindings.Add("text", ds, "movies.studio");

// TextBox.Text – binds to year movie released


if(txtYear.DataBindings["text"] != null)
txtYear.DataBindings.Remove(
txtYear.DataBindings["Text"]);
txtYear.DataBindings.Add("text", ds,
"movies.movie_year");

// CheckBox.Checked - binds to best picture value (0 or 1)


if (checkBox1.DataBindings["Checked"] != null)
checkBox1.DataBindings.Remove(
checkBox1.DataBindings["Checked"]);
checkBox1.DataBindings.Add("Checked", ds,
"movies.BestPicture");

// PictureBox.BackgroundImage – Binds to image


if (pictureBox1.DataBindings["BackgroundImage"] != null)
pictureBox1.DataBindings.Remove(
pictureBox1.DataBindings["BackgroundImage"]);
pictureBox1.DataBindings.Add("BackgroundImage", ds,
"movies.movie_Image");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
558 Chapter 12 ■ Data Binding with Windows Forms Controls

Binding Controls to an ArrayList


Clicking the Bind to Array button, binds the controls to an ArrayList that is filled
with instances of the custom class MyMovie (see Listing 12-2). After the data source
is created, the binding process is identical to that followed with the data set.

Listing 12-2 Binding Controls to an Array of Objects


// Bind control to array populated with instances of custom
class
private void BindToArray()
{
movieList = new ArrayList();
Image movieImg = Image.FromFile(@"c:\defaultimg.jpg");
// Create objects and add to array
movieList.Add(new MyMovie("2","Casablanca",1942,
"Warner Bros.",true, Image.FromFile("c:\afi2.gif")));
movieList.Add(new MyMovie("1","Citizen Kane", 1941,
"RKO", false,
Image.FromFile("c:\afi1.gif")));
movieList.Add(new MyMovie("4","Gone with the Wind", 1941,
"Selznick International", true,
Image.FromFile("c:\afi4.gif")));
//
listBox1.DataSource = movieList;
listBox1.DisplayMember = "Movie_Title";
//
comboBox1.DataSource = movieList;
comboBox1.DisplayMember = "Movie_Title";
bmb = this.BindingContext[movieList]; ;
bmb.PositionChanged += new
EventHandler(bmb_PositionChanged);
if (txtStudio.DataBindings["Text"] != null)
txtStudio.DataBindings.Remove(
txtStudio.DataBindings["Text"]);
txtStudio.DataBindings.Add("Text", movieList, "Studio");
//
if (txtYear.DataBindings["Text"] != null)
txtYear.DataBindings.Remove(
txtYear.DataBindings["Text"]);
txtYear.DataBindings.Add("Text", movieList, "Movie_Year");
12.2 Using Simple and Complex Data Binding in an Application 559

Listing 12-2 Binding Controls to an Array of Objects (continued)


//
if (checkBox1.DataBindings["Checked"] != null)
checkBox1.DataBindings.Remove(
checkBox1.DataBindings["Checked"]);
checkBox1.DataBindings.Add("Checked", movieList,
"BestPicture");
//
if (pictureBox1.DataBindings["BackgroundImage"] != null)
pictureBox1.DataBindings.Remove(
pictureBox1.DataBindings["BackgroundImage"]);
pictureBox1.DataBindings.Add("BackgroundImage", movieList,
"Movie_Image");
}

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

Listing 12-3 Custom Data Source Class


// Bind control to array populated with instances of
// custom class
public class MyMovie
{
private string myID;
private string myTitle;
private int myYear;
private string myStudio;
private bool myBestPicture;
private Image myImage;
//
public event EventHandler Movie_YearChanged;
public event EventHandler StudioChanged;
public MyMovie(string id, string title, int year,
string studio,
bool bp, Image img)
560 Chapter 12 ■ Data Binding with Windows Forms Controls

Listing 12-3 Custom Data Source Class (continued)


{
myTitle = title;
myYear = year;
myStudio = studio;
myBestPicture = bp;
myImage = img;
myID = id;
}
// Only public properties can be bound to control
public string Movie_Title { get { return myTitle; } }
// Make read/write so update can occur
public int Movie_Year {
get { return myYear; }
set {
myYear = value;
if (Movie_YearChanged != null)
Movie_YearChanged(this, EventArgs.Empty);
}
}

public string Studio {


get { return myStudio; }
set {
myStudio = value;
if (StudioChanged != null) StudioChanged(this,
EventArgs.Empty);
}
}
public Image Movie_Image { get { return myImage; } }
public bool BestPicture { get { return myBestPicture; } }
}

Adding an Item to the Data Source


Clicking the Add Movie button causes information about a single movie to be added
to the data source (see Listing 12-4). If the source is a table, a row is added; if an
array, an object is created and inserted. An addition to a data table is automatically
pushed to the control and made visible. When a custom object is added, the
Refresh method of the CurrencyManager must be executed to synchronize the
control. Note that Refresh is specific to the CurrencyManager class and not avail-
able on BindingManagerBase.
12.2 Using Simple and Complex Data Binding in an Application 561

Listing 12-4 Add an Item to a Data Source


// Test effects of adding a new item to the data source
private void button2_Click(object sender, EventArgs e)
{
if (ds != null)
{
// Add a row to the table
DataTable dt = ds.Tables[0];
DataRow dRow = dt.NewRow();
dRow["movie_ID"] = 99;
dRow["movie_Title"] = "Rear Window";
dRow["movie_Year"] = "1954";
dRow["studio"] = "Paramount";
dRow["BestPicture"] = 0;
dRow["afi_rank"] = 42;
Image defaultImage = Image.FromFile(@"c:\afi42.gif");
dRow["movie_Image"] = defaultImage;
dt.Rows.Add(dRow);
}
else
{
Image movieImg = Image.FromFile(@"c:\afi42.gif");
movieList.Add(new MyMovie("42", "Rear Window", 1954,
"Paramount", false, movieImg));
// Refresh() is needed to display item in ListBox/ComboBox
CurrencyManager cm =
(CurrencyManager)this.BindingContext[movieList];
cm.Refresh();
}
}

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

Listing 12-5 Check Data Source for Any Updates


// Checks status of each row in data table to identify any
// changes. This works only when data source is a Data Table.
private bool DataIsDirty(DataTable dt){
bool result = false;
foreach(DataRow drw in dt.Rows){
// Check all rows in the table for a modified state
if(drw.RowState == DataRowState.Modified)
{
string msg = (string)drw["movie_Title"]+":";
string curr;
string orig;
// Examine each column in the row for a change
foreach(DataColumn col in dt.Columns)
{
curr= drw[col,
DataRowVersion.Current].ToString().Trim();
orig= drw[col,
DataRowVersion.Original].ToString().Trim();
if(!curr.Equals(orig) || curr != orig ||
string.CompareOrdinal(curr,orig) !=0)
{
msg += "\r\n" + orig + " " + curr;
result=true;
}
}
MessageBox.Show(msg); // Display changes in a row
}
}
return result;
}

Update Original Database with Changes


When the modifiable data source is a data table, the Update method of its associated
DataAdapter can be used to flush changes to the database. This topic is discussed in
detail in Section 11.4, “DataSets, DataTables, and the Disconnected Model.”

try
{
int updates = da.Update(ds, "movies");
MessageBox.Show("Updates: "+updates.ToString());
}
12.3 The DataGridView Class 563

catch (Exception ex)


{
MessageBox.Show(ex.Message);
}

12.3 The DataGridView Class


The DataGridView control, introduced with .NET 2.0, supersedes the DataGrid—
which now exists primarily for legacy purposes. With more than a hundred properties
and methods, the DataGridView is by far the most complex Windows Forms control
for displaying data. Accordingly, it is also the most flexible. Styles that govern appear-
ance can be applied on a cell-by-cell basis, by rows, by columns, or across all cells in
the grid. Cells are not limited to text. They may contain a TextBox, Image, Check-
Box, Link, or Button control.
Data binding is supported by the DataSource property, just as with the controls
defined in the previous section. In addition, the DataGridView provides a unique
virtual mode that permits it to handle more than 100,000 rows of data. DataGrid-
View methods, events, and properties allow an application to easily manage the map-
ping between virtual and physical storage.
All of these features are discussed in this section. We’ll look at selected properties
and events along with code examples that illustrate their use.

Figure 12-6 Basic DataGridView elements


564 Chapter 12 ■ Data Binding with Windows Forms Controls

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.

Table 12-1 Selected Properties of the DataGridView Class

Category Property Name Description

User AllowUserToAddRows Indicates whether a user may add/delete


functionality AllowUserToDeleteRows rows. Default: true.

AllowUserToOrderColumns Indicates whether user can rearrange


columns.

ColumnHeadersHeight- Indicates whether the user can change


Resizable the height of the column headers.
Default: true.

MultiSelect Indicates whether user may select one or


more rows at a time.

SelectionMode Indicates the cells selected when click-


ing any individual or header cell.
enum DataGridViewSelectionMode
values:
ColumnHeaderSelect
RowHeaderSelect
FullColumnSelect
FullRowSelect
CellSelect

ReadOnly Indicates whether user can modify data


in cells. Values: true or false.

Appearance AlternatingRowsDefault- Gets or sets the default cell style applied


CellStyle to the odd numbered rows in the grid.

BackColor The background color of the grid.


12.3 The DataGridView Class 565

Table 12-1 Selected Properties of the DataGridView Class (continued)

Category Property Name Description

Appearance BackgroundColor Gets or sets the background color for the


(continued) area of the grid not containing data cells
or column/row headers.

BorderStyle Gets or sets the border style for the


DataGridView.
enum BorderStyle values:
BorderStyle.Fixed3D
BorderStyle.FixedSingle
BorderStyle.None

CellBorderStyle Gets or sets the border style used for


cells in the grid.
enum DataGridViewCellBorderStyle
values:
(to draw between rows)
SingleHorizontal
SunkenHorizontal
RaisedHorizontal
(to draw between columns)
SingleVertical
SunkenVertical
RaisedVertical
(to place a border between rows and
columns)
SingleSunken
Raised

ColumnCount Gets or sets the number of columns in


the DataGridView.

ColumnHeadersBorderStyle Border style applied to to column/row


RowHeadersBorderStyle headers.
enum DataGridViewHeaderBorder-
Style values:
Custom Raised Sunk
None Single

ColumnHeadersVisible Displays or suppresses headers.


RowHeadersVisible Values: true or false.

ColumnHeaderDefaultCell- Defines cell style properties for column


Style header cells.
566 Chapter 12 ■ Data Binding with Windows Forms Controls

Table 12-1 Selected Properties of the DataGridView Class (continued)

Category Property Name Description

Appearance DefaultCellStyle DataGridViewCellStyle object that


(continued) defines the default cell style properties
for cells. Note that this includes column
header cells.

FirstDisplayedCell The first cell displayed in the grid,


usually upper-left corner.

GridColor The color of the lines separating the


cells.

Collections Columns Collection of all grid columns. Individual


columns are accessed by an index:
Columns[iI].

Rows Collection of all grid rows. Individual


rows are accessed by an index: Rows[i].

SelectedColumns Collection of columns selected.

SelectedRows Collection of rows selected.

SelectedCells Collection of cells selected.

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.

Setting DataGridView Properties and


Listing 12-6
Adding Rows of Data
// Set properties of a DataGridView and fill with data
private void CreateGrid()
{
// (1) Define column headers
dataGridView1.ColumnCount = 3;
dataGridView1.Columns[0].HeaderText = "Movie Title";
dataGridView1.Columns[1].HeaderText = "Year";
12.3 The DataGridView Class 567

Setting DataGridView Properties and


Listing 12-6
Adding Rows of Data (continued)
dataGridView1.Columns[2].HeaderText = "Director";
dataGridView1.Columns[1].Name = "Year";
dataGridView1.Columns[0].Width = 150;
dataGridView1.Columns[1].Width = 40;
dataGridView1.Columns[2].Width = 110;
// (2) Define style for data cells
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor = Color.Bisque;
style.Font = new Font("Arial", 8, FontStyle.Bold);
style.ForeColor = Color.Navy;
// (left,top,right,bottom)
style.Padding = new Padding(5, 2, 5, 5);
style.SelectionBackColor = Color.LightBlue;
dataGridView1.DefaultCellStyle = style;
// (3) Define style for column headers
DataGridViewCellStyle styleHdr = new
DataGridViewCellStyle();
styleHdr.Padding = new Padding(1, 1, 1, 1);
styleHdr.BackColor = Color.OldLace;
styleHdr.ForeColor = Color.Black;
dataGridView1.ColumnHeadersDefaultCellStyle = styleHdr;
// (4) Define user capabilities
dataGridView1.AllowUserToAddRows = false;
dataGridView1.AllowUserToOrderColumns = false;
dataGridView1.AllowUserToDeleteRows = false;
// (5) Place data in grid manually (datasource is better)
object[] row1 = {"Casablanca", "1942","Michael Curtiz"};
dataGridView1.Rows.Add(row1);
object[] row2 = {"Raging Bull","1980","Martin Scorsese"};
dataGridView1.Rows.Add(row2);
object[] row3 = {"On the Waterfront","1954","Elia Kazan"};
dataGridView1.Rows.Add(row3);
object[] row4 = {"Some Like it Hot","1959","Billy Wilder"};
dataGridView1.Rows.Add(row4);
}

Figure 12-7 shows the DataGridView created by this code.


568 Chapter 12 ■ Data Binding with Windows Forms Controls

Figure 12-7 DataGridView built from code in Listing 12-6

DataBinding with a DataGridView


A DataGridView is bound to a data source using complex binding. As in our list box
example, the DataSource property specifies the data source. The similarity ends
there, however, because a DataGridView must display multiple data values. To do
so, the DataMember property is set to the name of a table within the data source. The
data to be displayed in each column is specified by setting the column’s DataProp-
ertyName property to the name of the underlying data table column.

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

private void buttonAdd_Click(object sender, EventArgs e)


{
// Adding to underlying table is okay
r[0] = "TAXI";
12.3 The DataGridView Class 569

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

A DataGridView may have a mixture of bound and non-bound


columns. Thus, columns can be added to a bound control, but rows
cannot.

Setting the Row Height


The default height of rows in a DataGridView is based on accommodating a single
line of text. If the row contains large sized fonts or images, they are truncated. It is
usually better to force the grid to take the size of each cell in the row into account
and base the overall height on the tallest cell. That’s the role of the grid’s AutoSize-
Rows method. Its simplest overloaded version takes a single parameter—a Data-
GridViewAutoSizeRowsMode enumeration value—that indicates the criterion used
for setting row height. The two most useful enumeration members are ColumnAll-
Rows, which bases the row height on all columns in the row, and ColumnsDis-
playedRows, which applies the same criterion, but to visible rows only.

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.

Working with Columns and Column Types


The DataGridView is not a full-blown spreadsheet, but it does offer some features a
user expects from a spreadsheet. These include the following:

• Frozen Column(s). For a grid that requires horizontal scrolling, it is


often useful to “freeze” columns so that they always remain on the
screen. Setting a column’s Frozen property to true has the effect of
freezing it and all columns to its left.
dataGridView1.Columns[0].Frozen = true;

• ReadOnly Columns. Selected column can be made read-only.


dataGridView1.Columns[2].ReadOnly = true;

• Minimum Width. By default, a user can widen and narrow columns


in a grid. The minimum size permitted for a column can be controlled
by setting the MinimumWidth property to a value in pixels:
dataGridView1.Columns[0].MinimumWidth=100;

• Sorting. By default, clicking a column header sorts the rows based on


values in that column—if the column contains sortable values. It’s
SortMode property can be used to disable sorting:

dataGridView1.Columns[0].SortMode =
DataGridViewColumnSortMode.NotSortable;

• Multiple Column Types. Six predefined column classes are available


that can be used to represent information in a grid, using the familiar
formats of the TextBox, CheckBox, Image, Button, ComboBox, and
Link. The name for each of these controls follows the format Data-
GridViewControlnameColumn.

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.

// (1) Create instance of column type


DataGridViewButtonColumn buttons = new
DataGridViewButtonColumn();
// Text to place in column header
buttons.HeaderText = "Delete";
12.3 The DataGridView Class 571

// (2) Set characteristics of control


buttons.Text = "Delete"; // Default text for button
buttons.FlatStyle = FlatStyle.Standard;
// Create a datagridview cell to use as a template to set
// all buttons in the column to the same style.
buttons.CellTemplate = new DataGridViewButtonCell();
buttons.CellTemplate.Style.BackColor = Color.Yellow ;
buttons.CellTemplate.Style.Font = new Font("Arial", 8);
// Specify column position on grid
buttons.DisplayIndex = 1;
// (3) Add column to grid
dataGridView.Columns.Add(buttons);

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:

// Use the DataGridviewButtonColumn class


buttons.DataPropertyName = "Title";
// Use the Columns class (button is in column 1 of the grid)
dataGridView3.Columns[1].DataPropertyName = "Title";

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

Table 12-2 Selected DataGridView Events

Category Event (Delegate) Description

Cell actions CellValueChanged (1) Occurs when the value of a cell


changes.

CurrentCellChanged (3) Occurs when the value of the


current cell changes
572 Chapter 12 ■ Data Binding with Windows Forms Controls

Table 12-2 Selected DataGridView Events (continued)

Category Event (Delegate) Description

Cell actions CellClick (1) Occurs when any part of the cell
(continued) is clicked. This includes cell bor-
ders and padding.

CellContentClick (1) Occurs only if the cell content is


clicked.

CellEnter (1) Occurs when cell receives/loses


CellLeave (1) input focus.

CellFormatting (5) Occurs prior to formatting a cell


for display.

CellMouseClick (2) Occurs whenever a mouse


CellMouseDoubleClick (2) clicks/double clicks anywhere on
a cell.

CellMouseDown (2) Occurs when a mouse button is


CellMouseUp (2) pressed/raised while it is over a
cell.

CellMouseEnter (1) Occurs when the mouse pointer


CellMouseLeave (1) enters or leaves a cell’s area.

CellPainting (6) Raised when a cell is to be


painted.

Column ColumnHeaderMouseClick (2) Occurs when a column header is


actions ColumnHeaderMouseDouble-Click (2) clicked/double clicked.

Row actions RowEnter (1) Occurs when a row receives/loses


RowLeave (1) the input focus.

RowHeaderMouseClick (2) Occurs when a user clicks/double


RowHeaderDoubleMouse-Click (2) clicks a row header.

UserAddedRow (4) Occurs when a user adds/deletes


UserDeletedRow (4) a row in the grid.

Data error DataError (7) Occurs when an external data


parsing or validation operations
fails. Typically occurs due to an
attempt to load invalid data into a
data grid cell.
12.3 The DataGridView Class 573

The following are delegates associated with events in Table 12-2:

(1) public sealed delegate void DataGridViewCellEventHandler(


object sender, DataGridViewCellEventArgs e)

(2) public sealed delegate void DataGridViewCellMouseEventHandler(


object sender, DataGridViewCellMouseEventArgs e)

(3) public sealed delegate void EventHandler(


object sender, EventHandlerArgs e)

(4) public sealed delegate void DataGridViewRowEventHandler (


object sender, DataGridViewRowEventArgs e)

(5) public sealed delegate void


DataGridViewCellFormattingEventHandler(
object sender, DataGridViewCellFormattingEventArgs e)

(6) public sealed delegate void


DataGridViewCellPaintingEventHandler(
object sender, DataGridViewCellPaintingEventArgs e)

(7) public sealed delegate void


DataGridViewDataErrorEventHandler(
object sender, DataGridViewDataErrorEventArgs e)

Let’s look at some common uses for these events.

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.

// Set cells in year column to red if year is less than 1950


private void Grid3_CellFormatting(object sender,
DataGridViewCellFormattingEventArgs e)
{
if (this.dataGridView3.Columns[e.ColumnIndex].Name == "Year")
{
string yr = (string)e.Value;
if (Int32.Parse(yr) < 1950)
{
e.CellStyle.ForeColor = Color.Red;
574 Chapter 12 ■ Data Binding with Windows Forms Controls

e.CellStyle.SelectionForeColor = Color.Red;
// Indicate that event was handled
e.FormattingApplied = true;
}
}
}

The ColumnIndex property of the EventArgs parameter is used to determine if


the year column is being formatted. If so, the code checks the year and formats the
cell accordingly. Note that the FormattingApplied property must be set if custom
formatting is performed.

Recognizing Selected Rows, Columns, and Cells


As shown in Table 12-2, selecting a cell in a grid can trigger any number of events
that can be used to indicate the current cell or the cell just left. Some of the events
are almost over-engineered. For example, there seems little to distinguish CellCon-
tentClick and CellClick. Others exist to recognize grid navigation using both the
mouse and keyboard: The CellClick is not triggered by arrow keys; however, the
CellEnter event is fired no matter how a cell is selected. All of these cell-related
events have a consistent event handler signature. The EventArgs parameter pro-
vides column and row index properties to identify the cell. Here is an example:

private void Grid1_CellEnter(object sender,


DataGridViewCellEventArgs e)
{
// Both of these display the column index of the selected cell
MessageBox.Show("enter "+e.ColumnIndex.ToString());
MessageBox.Show(
DataGridView1.CurrentCell.ColumnIndex.ToString());
}

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

Multiple row selection is made available on a DataGridView by setting its Mul-


tiSelect property to true—which is the default value. A row is selected by click-
ing its row header. It can also be selected by clicking any cell in the row if the grid’s
SelectionMode property is set to DataGridViewSelectionMode.FullRowSe-
lect. The property can also be set to FullColumnSelect, which causes a cell’s col-
umn to be selected. Note that column and row selection are mutually exclusive: only
one can be in effect at a time.
This segment illustrates how to iterate through the collection of selected rows.
The same approach is used for columns and cells.

// Display selected row numbers and content of its column 1


if (dataGridView1.SelectedRows.Count > 0)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < dataGridView1.SelectedRows.Count; i++)
{
sb.Append("Row: ");
sb.Append(
dataGridView1.SelectedRows[i].Index.ToString() );
sb.Append( dataGridView1.SelectedRows[i].Cells[1].Value);
sb.Append(Environment.NewLine);
}
MessageBox.Show (sb.ToString(), "Selected Rows");
}

Data Error Handling


The DataError event fires when a problem occurs loading data into a grid or post-
ing data from the grid to the underlying data store. The error is quite easy to detect:
compare the value of the Context property of the ErrorEventArgs parameter with
the DataGridViewDataErrorContext enumeration values. Here is an example:

// Define event handler


DataGridView1.DataError += new
DataGridViewDataErrorEventHandler(DataGridView1_DataError);

// DataError Event Handler


private void dataGridView1_DataError(object sender,
DataGridViewDataErrorEventArgs dgError)
{
// Context provides information about the grid when the
// error occurred.
MessageBox.Show("Error: " + dgError.Context.ToString());
// Problem committing grid data to underlying data source
if (dgError.Context == DataGridViewDataErrorContext.Commit)
{
576 Chapter 12 ■ Data Binding with Windows Forms Controls

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");
}
}

Setting Up Master-Detail DataGridViews


One of the more common relationships between tables in a database is that of the
master-detail relationship, where the records in the master table have multiple asso-
ciated records in the detail table. DataGridViews provide a natural way of displaying
this relationship. To illustrate, let’s create an application based on the Films data-
base that displays a master grid containing a list of movies and a detail grid that dis-
play actors who played in the movie selected in the first grid. To make it interesting,
we’ll include an image column in the movie grid that contains a picture of the Oscar
statuette for movies that won for best picture.
The master grid is bound to the movies table; the details grid is bound to the
actors table. Both tables, as shown in Figure 12-8, contain the columns that are
bound to their respective DataGridView columns. In addition, they contain a mov-
ieID column that links the two in the master-detail relationship.
The tables and their relationships are created using the techniques described in
Chapter 11:

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

movieID Title Year Picture

Table: actors

movieID first name last name

Figure 12-8 Master-detail tables

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.

Bitmap oscar = new Bitmap(@"c:\oscar.gif"); // Oscar image


Bitmap nooscar = new Bitmap(@"c:\nooscar.gif"); // Blank image
// Populate movies table from datareader
while (dr.Read())
{
DataRow drow = dt.NewRow();
drow["Title"] = (string)(dr["movie_Title"]);
drow["Year"] = ((int)dr["movie_Year"]).ToString();
drow["movieID"] = (int)dr["movie_ID"];
578 Chapter 12 ■ Data Binding with Windows Forms Controls

if ((string)dr["bestPicture"] == "Y") drow["picture"] =


oscar; else drow["picture"] = nooscar;
dt.Rows.Add(drow);
}

The actors table is filled with the results of the query:

sql = "SELECT am.movie_ID, actor_first,actor_last FROM actors a


JOIN actor_movie am ON a.actor_ID = am.actor_ID";

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.

DataGridViewImageColumn vic = new DataGridViewImageColumn();


dataGridView1.Columns.Add(vic); // Add image type column
//
dataGridView1.ColumnCount = 3;
dataGridView1.Columns[0].Name = "Oscar";
dataGridView1.Columns[1].HeaderText = "Movie Title";
dataGridView1.Columns[2].HeaderText = "Year";

Then, the binding is performed:

// Bind grids to dataset


dataGridView1.DataSource = ds;
dataGridView1.DataMember = "movies";
dataGridView2.DataSource = ds;
// ***Set to DataRelation for detail
dataGridView2.DataMember = dt.TableName+".movieactor";
// Bind grid columns to table columns
dataGridView1.Columns[0].DataPropertyName = "picture";
dataGridView1.Columns[1].DataPropertyName = "Title";
dataGridView1.Columns[2].DataPropertyName = "Year";
dataGridView1.Columns[3].DataPropertyName = "director";
dataGridView2.Columns[0].DataPropertyName = "firstname";
dataGridView2.Columns[1].DataPropertyName = "lastname";

Pay close attention to the binding of dataGridView2. It is bound to the relation-


ship defined between the tables, rather than directly to the actors table. This bind-
ing causes the names of the movie’s cast to be displayed in the grid when a movie is
selected.
Figure 12-9 shows a sample screen. Much of the excluded code in this example
deals with setting grid styles and capabilities. A full code listing is available in the
book’s code download. (See the Preface for the download URL addresses and
instructions.)
12.3 The DataGridView Class 579

Figure 12-9 Master-detail relationship

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

DataTable Cache (List)


Map
1 DataGridView 1
Rows

10,000 1,000

Disk

Figure 12-10 Data binding versus virtual mode

Table 12-3 DataGridView Events Used to Implement Virtual Mode

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.

To illustrate the fundamentals of implementing a DataGridView in virtual mode,


let’s look at the code used to create the DataGridView shown in Figure 12-11.
The variables having class scope are shown here. Note that the data cache is
implemented as a generics List object that holds instances of the movie class. The
movie class exposes three properties that are displayed on the grid: Title,
Movie_Year, and Director.

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

Figure 12-11 DataGridView using virtual mode

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

Listing 12-7 Virtual DataGridView: Initialization


// Set properties of a DataGridView and fill with data
dgv = new DataGridView();
// Event handlers for virtual mode events
dgv.CellValueNeeded += new
DataGridViewCellValueEventHandler(CellNeeded);
dgv.CellValuePushed += new
DataGridViewCellValueEventHandler(CellPushed);
dgv.NewRowNeeded += new
DataGridViewRowEventHandler(RowNeeded);
// Event handlers always available for DataGridView
dgv.UserDeletingRow += new
DataGridViewRowCancelEventHandler (RowDeleting);
dgv.RowValidated += new
DataGridViewCellEventHandler( RowValidated);
dgv.VirtualMode = true;
dgv.RowCount = 5;
dgv.ColumnCount = 3;
// Headers for columns
dgv.Columns[0].HeaderText = "title";
dgv.Columns[1].HeaderText = "year";
dgv.Columns[2].HeaderText = "director";
// Fill cache. In production, this would come from database.
movieList.Add(new movie("Citizen Kane",1941,"Orson Welles"));
movieList.Add(new movie("The Lady Eve",1941,"
"Preston Sturges"));
// ... Add other movies here
582 Chapter 12 ■ Data Binding with Windows Forms Controls

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.

Listing 12-8 Virtual DataGridView: Event Handlers


// Called when a new row is appended to grid
private void RowNeeded(object sender,
DataGridViewRowEventArgs e)
{
rowNeeded = true;
currRow = dgv.Rows.Count - 1;
}

// Called when a cell must be displayed/refreshed


private void CellNeeded(object sender,
DataGridViewCellValueEventArgs e)
{
if (rowNeeded)
{
rowNeeded = false;
currMovie = new movie();
return;
}
12.3 The DataGridView Class 583

Listing 12-8 Virtual DataGridView: Event Handlers (continued)


storeRow = MapRow(e.RowIndex);
if(storeRow >=0 && currRow ==-1)
currMovie = movieList[storeRow];
string colName = dgv.Columns[e.ColumnIndex].HeaderText;
if(storeRow>=0) // Refresh cell from cache
{
if (colName == "title")e.Value =
movieList[storeRow].Title;
if (colName == "year") e.Value =
movieList[storeRow].Movie_Year.ToString();
if (colName == "director") e.Value =
movieList[storeRow].Director;
} else // refresh cell from object for new row
{
if (colName == "title")e.Value = currMovie.Title;
if (colName == "year")e.Value =
currMovie.Movie_Year.ToString();
if (colName == "director") e.Value = currMovie.Director;
}
}
// Cell has been updated
private void CellPushed(object sender,
DataGridViewCellValueEventArgs e)
{
// Update property on movie object for this row
storeRow = MapRow(e.RowIndex);
string colName = dgv.Columns[e.ColumnIndex].HeaderText;
if (colName == "title") currMovie.Title = (string)e.Value;
if (colName == "year")
{
int retval;
if(int.TryParse((string)e.Value,out retval))
currMovie.Movie_Year = retval;
}
if (colName == "director") currMovie.Director =
(string)e.Value;
}
// Occurs when user changes current row
// Update previous row in cache when this occurs
private void RowValidated(object sender,
DataGridViewCellEventArgs e)
584 Chapter 12 ■ Data Binding with Windows Forms Controls

Listing 12-8 Virtual DataGridView: Event Handlers (continued)


{
storeRow = MapRow(e.RowIndex);
if (storeRow < 0) storeRow = movieList.Count;
currRow = -1;
if (currMovie != null)
{
// Save the modified Customer object in the data store.
storeRow = MapRow(e.RowIndex);
if (storeRow >= 0)
movieList[storeRow] = currMovie;
else movieList.Add(currMovie);
currMovie = null;
}
}
// Row selected and Del key pushed
private void RowDeleting(object sender,
DataGridViewRowCancelEventArgs e)
{
if (MapRow(e.Row.Index)>=0)
{ movieList.RemoveAt(e.Row.Index); }
if (e.Row.Index == currRow)
{
currRow = -1;
currMovie = null;
}
}
// Maps grid row to row in cache. More logic would be added
// for application that refreshes cache from database.
private int MapRow(int dgvRow)
{
if (dgvRow < movieList.Count)return dgvRow;
else return -1;
}

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.

12.5 Test Your Understanding


1. Indicate whether the following are true or false:
a. A TextBox supports complex binding.
b. The width of a control can be bound to a data source.
c. All controls support simple binding.
d. A data source can only be bound to one control at a time.
e. A data source may have multiple binding managers.
f. Changes made to the value of a control are always propagated to
the data source.
g. Controls can be bound to custom data objects.
h. The PropertyManager class inherits from the
CurrencyManager class.
i. Only public properties may be bound to a control.
586 Chapter 12 ■ Data Binding with Windows Forms Controls

2. What is the difference between simple and complex binding?


One-way and two-way binding?

3. Describe how to allow a custom data source to support two-way


binding.

4. Which property and enumeration cause the entire row in a Data-


GridView to be highlighted when a single cell in the row is selected?

5. Which of these cannot be included in a DataGridView cell?


a. TextBox
b. ListBox
c. Button
d. ComboBox

6. How do you ensure that a column in a DataGridView is always


displayed on the screen?
This page intentionally left blank
ADVANCED USE OF
C# AND THE .NET
FRAMEWORK
III

■ 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

Topics in This Chapter

• Asynchronous Programming: Unlike synchronous programming,


in which a task can begin only when the preceding one
completes, asynchronous programming permits multiple tasks to
be performed simultaneously.
• Multithreading: Multiple threads can enhance the performance of
an application that can separate its tasks into operations that can
run on separate threads. This section describes how a program
can implement multithreading, the factors that affect thread
scheduling, and when it’s useful to create multiple threads.
• Thread Synchronization: The use of multiple threads in an
application raises several synchronization issues regarding how to
create thread-safe code. Several .NET manual synchronization
techniques are presented, including the Monitor class, the
Mutex class, and the use of semaphores.
13

An application or component can be designed to operate in a synchronous or asyn-


chronous manner. In the synchronous model, tasks are performed in sequence—as
in a relay race, one runner (task) must complete his segment before the next one can
start. In contrast, asynchronous programming permits an application to be broken
into subtasks that perform concurrently. This approach (sometimes referred to as
send and forget) allows one method to call another method and then continue pro-
cessing without waiting for the called method to finish.
The key to asynchronous programming is the use of threads. A thread is essen-
tially a code sequence that runs independently. This permits a program to work on
multiple tasks in a parallel manner. For example, an application may use one thread
to accept user input to a form, while a second thread concurrently processes a print
request. When used judiciously, threads can greatly improve a program’s perfor-
mance and responsiveness; when used incorrectly, they can cause programs to hang
or terminate without properly completing a task.
A thread is created and run by the operating system—not by .NET. What .NET
does is create a wrapper around a thread so that it obeys the rules of the .NET man-
aged environment. An asynchronous application may work indirectly or directly with
threads. In the former case, delegates are used to automatically allocate and handle
threads; in the latter case, a program explicitly creates instances of the Thread class
and takes responsibility for synchronizing thread behavior.
The chapter begins with an overview of threads and then looks at asynchronous
programming using both delegates and explicit thread creation. The final section
examines the synchronization issues that arise when multiple threads are running,
and introduces several synchronization techniques that can be used to enable threads
to share resources.

591
592 Chapter 13 ■ Asynchronous Programming and Multithreading

13.1 What Is a Thread?


When an assembly (.exe file) begins execution, a primary thread is created that
serves as the entry point to the application—in C#, this is an application’s Main()
method. The thread is the unit or agent responsible for executing code.
.NET does not physically create threads—that is the responsibility of the operat-
ing system. Instead, it provides a Thread class that serves as a managed version of
the unmanaged physical thread. The Thread class, located in the System.Thread-
ing namespace, exposes properties and methods that allow a program to perform
thread-related operations. These class members allow an application to create a
thread, set its priority, suspend, activate or kill it, and have it run in the background
or foreground.
Figure 13-1 is a simplified representation of the relationship between a process,
applications, and threads. Physically, a thread consists of CPU registers, a call stack
(memory used for maintaining parameter data and method calls), and a container
known as Thread Local Storage (TLS) that holds the state information for a thread.

Process

App1.exe App2.exe

Thread D Thread A Thread B Thread C

Call Call Call Call


Stack Stack Stack Stack

TLS TLS TLS TLS

Figure 13-1 Threads contained in a process

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

.NET provides support for multiple processor systems by permitting a


process to be assigned to a processor. This is set using the
ProcessAffinity property of the System.Diagnostics.Process
class.

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.

Foreground and Background Threads


.NET classifies each thread as either a background or foreground thread. The differ-
ence in these two types is quite simple: An application ends when all foreground
threads stop; and any background threads still running are stopped as part of the
shutdown process.
594 Chapter 13 ■ Asynchronous Programming and Multithreading

By default, a new thread is set to run as a foreground thread. It can be changed to


background by setting its IsBackground property to true. Clearly, you only want to
set this for noncritical tasks that can logically and safely end when the program does.
Note that even though .NET attempts to notify all background threads when the pro-
gram shuts down, it’s good practice to explicitly manage thread termination.

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

Figure 13-2 Thread states


13.2 Asynchronous Programming 595

A thread’s current state is available through a read-only property named Thread-


State. This property’s value is based on the ThreadState enum that defines 10 states:

Aborted = 256 StopRequested = 1


AbortRequested = 128 Suspended = 64
Background = 4 SuspendRequested = 2
Running = 0 Unstarted = 8
Stopped = 16 WaitSleepJoin = 32

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.

13.2 Asynchronous Programming


In a synchronous (single-threaded) application, program execution follows a single
path; in an asynchronous (multithreaded) version, operations occur in parallel on
multiple paths of execution. This advantage of this latter approach is that slow appli-
cations, such as file I/O, can be performed on a separate thread while the main
thread continues execution.
Figure 13-3 provides an abstract representation of the two techniques. In the syn-
chronous version, each method is executed in sequence; in the asynchronous version,
method B runs at the same time as A and C. This prospect of two or more tasks run-
ning (nearly) simultaneously raises a set of questions not present in a single-threaded
program:

• What type of communication between the main thread and worker


thread is required? The code on the worker thread can be invoked and
forgotten, or it may be necessary for the main thread to know when
the task is completed.
• How does the main thread know when the worker thread is com-
pleted? Two approaches are available: the callback technique, in
which the worker thread returns control to the main thread when it is
finished; or a polling approach, in which the main thread calls a
method that returns the results of the worker thread execution.
• How to synchronize thread requests for the same resources? The
issues here are similar to those faced when synchronizing access to a
database. The integrity of the data must be maintained and deadlock
situations must be avoided.
• How to shutdown an application while worker threads are still execut-
ing? Several choices are available: They can be terminated; the main
application can continue to run until all threads finish; or the main
application can end and allow the threads to continue running.
596 Chapter 13 ■ Asynchronous Programming and Multithreading

Synchronous A B End
Start C
Processing

Worker
B Thread

Asynchronous Main
Start A C End
Processing Thread

Figure 13-3 Synchronous versus asynchronous execution

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.

//(1) Declare delegate. Declare anywhere a class can be declared.


public delegate void myDelegate(string msg);
13.2 Asynchronous Programming 597

private void TestDelegate()


{
// (2) Create instance of delegate and pass method to it
myDelegate msgDelegate= new myDelegate(ShowMessage);
// Second method is placed on delegate invocation chain
msgDelegate+= new myDelegate(ShowUpper);
// (3) Invoke delegate
msgDelegate("Delegate Called.");
}
// First method called by delegate
private void ShowMessage(string msg)
{
MessageBox.Show(msg);
}
// Second method called by delegate
private void ShowUpper(string msg)
{
msg = msg.ToUpper(); // Make uppercase before displaying
MessageBox.Show(msg);
}

Understanding the Delegate Class


When a delegate is defined, .NET automatically creates a class to represent the dele-
gate. Here is the code generated for the delegate in the preceding example:

// Class created from delegate declaration


public class myDelegate : MulticastDelegate
{
// Constructor
public myDelegate(Object target, Int32 methodPtr);
public void virtual Invoke(string msg);
// Used for asynchronous invocation
public virtual IAsyncResult BeginInvoke(
string msg, AsyncCallback callback,
Object state);
// Used to get results from called method
public virtual void EndInvoke(IAsyncResult result);
// Other members are not shown
}

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.

private delegate void myDelegate(string msg);


myDelegate d= new myDelegate(ShowMessage);
d.BeginInvoke("OK",new AsyncCallback(myCallBack),null);
13.2 Asynchronous Programming 599

It is important to be aware that myCallBack is run on a thread from the thread


pool rather than the application’s main thread. As we will see, this affects the design
of UI (user interface) applications.

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:

// Save the interface returned


IAsyncResult IAsync = GetStatus.BeginInvoke(null,null);
// ... Do some work here; then get returned value
int status = GetStatus.EndInvoke(IAsync);

EndInvoke should be called even if the asynchronous method returns no value. It


can be used to detect exceptions that may be thrown by the asynchronous method;
and more importantly, it notifies the Common Language Runtime (CLR) to clean up
resources that were used in creating the asynchronous call.

Examples of Implementing Asynchronous Calls


The challenge in using BeginInvoke is to determine when the called asynchronous
method finishes executing. As touched on earlier, the .NET Framework offers sev-
eral options:

• EndInvoke. After BeginInvoke is called, the main thread can con-


tinue working and then call this method. The call to EndInvoke
blocks process on the main thread until the asynchronous worker
thread completes its execution. This should never be used on a thread
that services a user interface because it will lock up the interface.
• Use a WaitHandle Synchronization object. The IAsyncResult
object returned by BeginInvoke has a WaitHandle property that
contains a synchronization object. The calling thread can use this
object (or objects) to wait until one or more asynchronous tasks com-
plete execution.
• CallBack Method. As mentioned earlier, one of the parameters to
BeginInvoke can be a delegate that specifies a method to be called
when the asynchronous method finishes. Because the callback method
is run on a new thread from the thread pool, this technique is useful
only when the original calling thread does not need to process the
results of the asynchronous method.
600 Chapter 13 ■ Asynchronous Programming and Multithreading

• Polling. The IAsyncResult object has an IsCompleted property


that is set to true when the method called by BeginInvoke finishes
executing. Polling is achieved by periodically checking this value.

Figure 13-4 illustrates the four options.

Main Worker Main Worker Main Worker Main Worker


Thread Thread Thread Threads Thread Thread Thread Thread

Begin Begin Begin Begin


Invoke Invoke Invoke Invoke

End
Invoke WaitAll

Blocked Blocked
Callback
Method

EndInvoke WaitHandle Callback Polling

Figure 13-4 Options for detecting the completion of an asynchronous task

Using Polling and Synchronization Objects


Table 13-1 lists the IAsyncResult properties that are instrumental in implementing
the various asynchronous models. The class is in the System.Runtime.Remot-
ing.Messaging namespace.

Table 13-1 Selected IAsyncResult Properties

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

Table 13-1 Selected IAsyncResult Properties (continued)

Property Description

AsyncDelegate Returns the delegate used for the asynchronous call.

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.

Listing 13-1 Asynchronous Invocation Using Polling to Check Status


// Code to return a Body Mass Index Value
private delegate decimal bmiDelegate(decimal ht, decimal wt);
decimal ht_in = 72;
decimal wt_lbs=168;
// (1) Invoke delegate asynchronously
bmiDelegate bd= new bmiDelegate(CalcBMI);
IAsyncResult asRes= bd.BeginInvoke(ht_in, wt_lbs,null,null);
int numPolls=0;
while(!asRes.IsCompleted)
{
// Do some work here
// (2) Wait 20 milliseconds for method to signal completion
asRes.AsyncWaitHandle.WaitOne(20,false);
numPolls+=1;
}
// (3) Get result now that asynchronous method has finished
decimal myBMI = bd.EndInvoke(asRes);
Console.WriteLine("Polls: {0} BMI: {1:##.00}",
numPolls, myBMI); // --> Polls: 3 BMI: 22.78
// Calculate BMI
private decimal CalcBMI(decimal ht, decimal wt)
{
Thread.Sleep(200); // Simulate a delay of 200 ms
Console.WriteLine("Thread:{0}",
Thread.CurrentThread.GetHash());
return((wt * 703 *10/(ht*ht))/10);
}
602 Chapter 13 ■ Asynchronous Programming and Multithreading

For demonstration purposes, this example includes a 200-millisecond delay in the


asynchronous method CalcBMI. This causes WaitOne, which blocks for up to 20 mil-
liseconds, to execute seven times (occasionally eight) before the loop ends. Because
EndInvoke is not reached until the asynchronous calculation has ended, it causes no
blocking.
A more interesting use of the WaitHandle methods is to manage multiple asyn-
chronous tasks running concurrently. In this example, the static WaitAll method is
used to ensure that three asynchronous tasks have completed before the results are
retrieved. The method is executed by passing it an array that contains the wait handle
created by each call to BeginInvoke. As a side note, this point where threads must
rendezvous before execution can proceed is referred to as a barrier.

int istart= Environment.TickCount; // Start Time


bmiDelegate bd1 = new bmiDelegate(Form1.CalcBMI);
IAsyncResult asRes1 = bd1.BeginInvoke(72, 168,null,null);
//
bmiDelegate bd2 = new bmiDelegate(CalcBMI);
IAsyncResult asRes2 = bd2.BeginInvoke(62, 124,null,null);
//
bmiDelegate bd3 = new bmiDelegate(CalcBMI);
IAsyncResult asRes3 = bd3.BeginInvoke(67, 132,null,null);
// Set up array of wait handles as required by WaitAll method
WaitHandle[] bmiHandles = {asRes1.AsyncWaitHandle,
asRes2.AsyncWaitHandle,
asRes3.AsyncWaitHandle);
// Block execution until all threads finish at this barrier point
WaitHandle.WaitAll(bmiHandles);
int iend = Environment.TickCount;
// Print time required to execute all asynchronous tasks
Console.WriteLine("Elapsed Time: {0}", iend – istart);
// Get results
decimal myBMI1 = bd1.EndInvoke(asRes1);
decimal myBMI2 = bd2.EndInvoke(asRes2);
decimal myBMI3 = bd3.EndInvoke(asRes3);

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

Applications that need to host ActiveX controls or interact with the


clipboard must apply the STAThread (single-threaded apartment)
attribute to their Main() method. Unfortunately, you cannot use
WaitAll() in applications that have this attribute due to conflicts
between COM and the Win32 method that WaitAll wraps. Visual
Studio users should be aware of this because C# under VS.NET adds the
attribute by default.

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.

Using a Callback Method with Asynchronous


Listing 13-2
Calls
using System.Runtime.Remoting.Messaging ;
// Delegate is defined globally for class
public delegate decimal bmiDelegate(decimal wt, decimal ht);

public class BMIExample


{
604 Chapter 13 ■ Asynchronous Programming and Multithreading

Using a Callback Method with Asynchronous


Listing 13-2
Calls (continued)
public void BMICaller(decimal ht, decimal wt, string name)
{
bmiDelegate bd= new bmiDelegate(CalcBMI);
// Pass callback method and state value
bd.BeginInvoke(ht,wt,new AsyncCallback(OnCallBack),name);
}
// This method is invoked when CalcBMI ends
private void OnCallBack(IAsyncResult asResult)
{
// Need AsyncResult so we can get original delegate
AsyncResult asyncObj = (AsyncResult)asResult;
// Get state value
string name= (string)asyncObj.AsyncState ;
// Get original delegate so EndInvoke can be called
bmiDelegate bd= (bmiDelegate)asyncObj.AsyncDelegate;
// Always include exception handling
try {
decimal bmi = bd.EndInvoke(asResult);
Console.WriteLine("BMI for {0}: {1:##.00}",name,bmi);
} catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private decimal CalcBMI(decimal ht, decimal wt)
{
Console.WriteLine("Thread:{0}",
Thread.CurrentThread.GetHashCode());
return((wt * 703 *10/(ht*ht))/10);
}
}

Things to note:

• The BeginInvoke signature includes optional data parameters as


well as a delegate containing the callback method and a state object:
bd.BeginInvoke(ht,wt,new AsyncCallback(OnCallBack),name);
• The final parameter can be information of any type that is useful to
the code that receives control after the asynchronous method com-
pletes. In this example, we pass the name of the person whose BMI is
calculated.
13.2 Asynchronous Programming 605

• 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

Multiple Threads and User Interface Controls


When working with Windows Forms and user interfaces in general, it is important to
understand that all controls on a form belong to the same thread and should be
accessed only by code running on that thread. If multiple threads are running, a con-
trol should not be accessed—even though it’s technically accessible—by any code not
running on the same thread as the control. This is a .NET commandment; and as is
the nature of commandments, it can be broken—but with unpredictable results.
Suppose our application wants to use the callback method in the preceding example
to display the calculated BMI value on a label control. One’s instinct might be to
assign the value directly to the control:

private void OnCallBack(IAsyncResult asResult)


{
// ... Initialization code goes here
decimal bmi = bd.EndInvoke(asResult);
Label.Text= bmi.ToText(); // Set label on UI to BMI value
}

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.

// Delegate to pass BMI value to method


private delegate void labelDelegate(decimal bmi);

private void DisplayBMI(decimal bmi)


{
// Determines if the current thread is the same thread
// the Form was created on.
if(this.InvokeRequired == false)
{
labelthread.Text= bmi.ToString("##.00");
}
else
{
// The Form's Invoke method is executed, which
// causes DisplayBMI to run on the UI thread.
// bmiObj is array of arguments to pass to method.
object[] bmiObj= {bmi};
this.Invoke(new labelDelegate(DisplayBMI),bmiObj);
}
}

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.

Using MethodInvoker to Create a Thread


In situations where your code needs to create a new thread but does not require
passing arguments or receiving a return value, the system-defined MethodInvoker
delegate should be considered. It is the simplest possible delegate—it takes no
13.2 Asynchronous Programming 607

parameters and returns no value. It is created by passing the name of a method to be


called to its constructor. It may then be invoked synchronously (Invoke) or asyn-
chronously (BeginInvoke):

// NewThread is method called by delegate


MethodInvoker mi = new MethodInvoker(NewThread);
// Note that parameters do not have to be null
mi.BeginInvoke(null,null); // Asynchronous call
mi(); // Synchronous call

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.

Using Asynchronous Calls to Perform I/O


Asynchronous operations are not new; they were originally implemented in operating
systems via hardware and software as a way to balance the slow I/O (Input/Output)
process against the much faster CPU operations. To encourage asynchronous I/O,
the .NET Framework includes methods on its major I/O classes that can be used to
implement the asynchronous model without explicitly creating delegates or threads.
These classes include FileStream, HttpWebRequest, Socket, and Network-
Stream. Let’s look at an example using the FileStream class that was introduced in
Chapter 5, “C# Text Manipulation and File I/O.”
FileStream inherits from the System.IO.Stream class an abstract class that
supports asynchronous operations with its BeginRead, BeginWrite, EndRead, and
EndWrite methods. The Beginxxx methods are analogous to BeginInvoke and
include callback and status parameters; the Endxxx methods provide blocking until a
corresponding Beginxxx method finishes.
The code in Listing 13-3 uses BeginRead to create a thread that reads a file and
passes control to a callback method that compresses the file content and writes it as a
.gz file. The basic callback method operations are similar to those in Listing 13-2.
Note how the file name is retrieved from the AsyncState property. The compression
technique—based on the GZipStream class—is available only in .NET 2.0 and above.

Listing 13-3 Using Aysnchronous I/O to Compress a File


// Special namespaces required:
using System.IO.Compression;
using System.Runtime.Remoting.Messaging;
//
// Variables with class scope
Byte[] buffer;
FileStream infile;
608 Chapter 13 ■ Asynchronous Programming and Multithreading

Listing 13-3 Using Aysnchronous I/O to Compress a File (continued)


// Compress a specified file using GZip compression
private void Compress_File(string fileName)
{
bool useAsync = true; // Specifies asynchronous I/O
infile = new FileStream(fileName, FileMode.Open,
FileAccess.Read, FileShare.Read, 2000, useAsync);
buffer = new byte[infile.Length];
int ln = buffer.Length;
// Read file and let callback method handle compression
IAsyncResult ar = infile.BeginRead(buffer, 0, ln,
new AsyncCallback(Zip_Completed), fileName);
//
}
// Callback method that compresses raw data and stores in file
private void Zip_Completed(IAsyncResult asResult)
{
// Retrieve file name from state object
string filename = (string)asResult.AsyncState;
infile.EndRead(asResult); // Wrap up asynchronous read
infile.Close();
//
MemoryStream ms = new MemoryStream();
// Memory stream will hold compressed data
GZipStream zipStream = new GZipStream(ms,
CompressionMode.Compress, true);
// Write raw data in compressed form to memory stream
zipStream.Write(buffer, 0, buffer.Length);
zipStream.Close();
// Store compressed data in a file
FileStream fs = new FileStream(filename+".gz",
FileMode.OpenOrCreate,FileAccess.Write,FileShare.Read);
byte[] compressedData = ms.ToArray();
fs.Write(compressedData, 0, compressedData.Length);
fs.Close();
}

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

13.3 Working Directly with Threads


The asynchronous techniques discussed in the previous section work best when an
application or component’s operations can be run on independent threads that con-
tain all the data and methods they need for execution—and when the threads have
no interest in the state of other concurrently running threads. The asynchronous
techniques do not work as well for applications running concurrent threads that do
have to share resources and be aware of the activities of other threads.
The challenge is no longer to determine when a thread finishes executing, but
how to synchronize the activities of multiple threads so they do not corrupt each
other’s work. It’s not an easy thing to do, but it can greatly improve a program’s per-
formance and a component’s usability. In this section, we’ll look at how to create and
manage threads running concurrently. This serves as a background for the final sec-
tion that focuses on synchronization techniques used to ensure thread safety.

Creating and Working with Threads


An application can create a thread, identify it, set its priority, set it to run in the back-
ground or foreground, coordinate its activities with other threads, and abort it. Let’s
look at the details.

The Current Thread


All code runs on either the primary thread or a worker thread that is accessible
through the CurrentThread property of the Thread class. We can use this thread to
illustrate some of the selected Thread properties and methods that provide informa-
tion about a thread:

Thread currThread = Thread.CurrentThread;


Console.WriteLine(currThread.GetHashCode());
Console.WriteLine(currThread.CurrentCulture); // en-US
Console.WriteLine(currThread.Priority); // normal
Console.WriteLine(currThread.IsBackground); // false
Console.WriteLine(AppDomain.GetCurrentThreadId()); // 3008

Thread.GetHashCode overrides the Object.GetHashCode method to return a


thread ID. The thread ID is not the same as the physical thread ID assigned by the
operating system. That ID, which .NET uses internally to recognize threads, is
obtained by calling the AppDomain.GetCurrentThreadID method.
610 Chapter 13 ■ Asynchronous Programming and Multithreading

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:

Thread newThread = new Thread(new ThreadStart(GetBMI));


newThread.Start(); // Launch thread asynchronously

Thread newThread = new Thread(new


ParameterizedThreadStart(GetBMI));
newThread.Start(40); // Pass data to the 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.

Listing 13-4 Passing Parameters to a Thread’s Method


// Create instance of class and set properties
BMI b = new BMI();
decimal[] bmiParms = { 168M, 73M }; // Weight and height
// Thread will execute method in class instance
Thread newThread = new Thread(
new ParameterizedThreadStart(b.GetBMI));
newThread.Start(bmiParms); // Pass parameter to thread
Console.WriteLine(newThread.ThreadState); // Unstarted
Console.WriteLine(b.Bmi); // Use property to display result
// Rest of main class ...
}
public class BMI
{
private decimal bmival;
public void GetBMI(object obj)
{
decimal[] parms= (decimal[])obj;
decimal weight = parms[0];
decimal height = parms[1] ;
13.3 Working Directly with Threads 611

Listing 13-4 Passing Parameters to a Thread’s Method (continued)


// Simulate delay to do some work
Thread.Sleep(1000); // Build in a delay of one second
bmival = (weight * 703 * 10/(height*height))/10 ;
}
// Property to return BMI value
public decimal Bmi
{ get {return bmival; }}
}

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.

Listing 13-5 How to Abort a Thread


using System;
using System.Threading;
class TestAbort
{
public static void Main()
{
Thread newThread = new Thread(new ThreadStart(TestMethod));
newThread.Start();
Thread.Sleep(1000);
if(newThread.IsAlive)
{
Console.WriteLine("Aborting thread.");
// (1) Call abort and send message to Exception handler
newThread.Abort("Need to close all threads.");
// (2) Wait for the thread to terminate
newThread.Join();
Console.WriteLine("Shutting down.");
}
}

static void TestMethod()


{
try
{
bool iloop=true;
while(iloop)
{
Console.WriteLine("Worker thread running.");
Thread.Sleep(500);
// Include next statement to prevent abort
// iloop=false;
}
}
catch(ThreadAbortException abortException)
{
// (3) Display message sent with abort command
Console.WriteLine((string)abortException.ExceptionState);
}
}
}
13.3 Working Directly with Threads 613

The Abort command should not be regarded as a standard way to terminate


threads, any more than emergency brakes should be regarded as a normal way to
stop a car. If the thread does not have adequate exception handling, it will fail to
perform any necessary cleanup actions—leading to unpredictable results. Alter-
nate approaches to terminating a thread are presented in the section on thread
synchronization.

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 2 Byte Stream

Thread 3

Main
Thread 4
Thread

Thread 5

Thread 6

Thread 7

Figure 13-5 Multithreading used to return images as a byte array


614 Chapter 13 ■ Asynchronous Programming and Multithreading

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.

Listing 13-6 Class to Return Images


public class ImageServer
{
public static byte[] GetMovieImage(string imageName,
int threadNum )
{
// Returns requested image to client as a series of bytes,

// and displays thread number of calling thread.


int imgByte;
imageName= "c:\\images\\"+imageName;
// If file not available exception is thrown and caught by
// client.
FileStream s = File.OpenRead(imageName);
MemoryStream ms = new MemoryStream();
while((imgByte =s.ReadByte())!=-1)
{
ms.WriteByte(((byte)imgByte));
}
// Display order in which threads are processed
Console.WriteLine("Processing on Thread: {0}",threadNum);
return ms.ToArray();
}
}

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

Listing 13-7 Using Multithreading to Retrieve Images


using System;
using System.Collections;
using System.Threading;
namespace ThreadExample
{
class SimpleClient
{
static void Main(string[] args)
{
Threader t=new Threader();
}
}
class Threader
{
ImageServer server;
public Threader(){
server = new ImageServer(); Object used to fetch images
StartThreader();
}
public void StartThreader()
{
// Create seven threads to retrieve images
for (int i=0; i<7; i++)
{
// (1) Create delegate
ThreadStart threadStart = new ThreadStart(FetchImage);
// (2) Create thread
Thread workerThread = new Thread(threadStart);
// (3) Set two priorities for comparison testing
if( i % 2 == 1)
workerThread.Priority = ThreadPriority.Lowest;
else
workerThread.Priority = ThreadPriority.AboveNormal;
// (4) Launch Thread
workerThread.Start();
}
}
public void FetchImage()
{
// Display Thread ID
Console.WriteLine(
"Spinning: "+Thread.CurrentThread.GetHashCode());
616 Chapter 13 ■ Asynchronous Programming and Multithreading

Listing 13-7 Using Multithreading to Retrieve Images (continued)


string[] posters = {"afi1.gif","afi2.gif",
"afi4.gif", "afi7.gif","afi89gif"};
// Retrieve five images on each thread
try
{
for (int i=0;i<5;i++)
{
byte[] imgArray = server.GetMovieImage(
posters[i],
Thread.CurrentThread.GetHashCode());
MemoryStream ms = new MemoryStream(imgArray);
Bitmap bmp = new Bitmap(ms);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
} // FetchImage
} // Threader
// ImageServer class goes here...
} // ThreadExample

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.

Sequence in Which Images Are Returned


Thread 1 5 10 15 20 25 30 35
14
16
* 18
20
15
17
**
19

* Above Normal ** Lowest


Figure 13-6 Effect of thread priority on thread execution
13.3 Working Directly with Threads 617

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.

Using the Thread Pool


Creating threads can be a relatively expensive process, and for this reason, .NET
maintains a collection of predefined threads known as a thread pool. Threads in
this pool can be acquired by an application and then returned for reuse when they
have finished running. Recall from Section 13.2 that when a program uses asyn-
chronous delegate invocation to create a thread, the thread actually comes from
the thread pool. An application can also access this pool directly by following two
simple steps.
The first step is to create a WaitCallback delegate that points to the method to
be executed by the thread. This method must, of course, match the signature of the
delegate, which takes one object parameter and returns no value. Next, the
QueueUserWorkItem static method of the ThreadPool class is called. The first
parameter to this method is the delegate; it also takes an optional second parameter
that can be used to pass information to the method called by the delegate.
To illustrate, let’s alter the previous example to acquire threads from a pool rather
than creating them explicitly. An object parameter must be added to FetchImage so
that it matches the delegate signature. Then, replace the code to create threads with
these two statements:

WaitCallback callBack = new WaitCallback(FetchImage);


ThreadPool.QueueUserWorkItem(callBack, "image returned");

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

Threads exist in the thread pool in a suspended state. If a thread is not


used in a given time interval, it destroys itself—freeing its resources.

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:

public delegate void ElapsedEventHandler(object sender,


ElapsedEventArgs e);

The Elapsed event occurs at an interval specified by the Timer.Interval prop-


erty. A thread from the thread pool is used to make the call into the event handler(s).
This code segment demonstrates how the Timer causes a method to be called every
second:

using System;
using System.Timers;
13.3 Working Directly with Threads 619

public class TimerTest


{
public static void Main()
{
SetTimer t = new SetTimer();
t.StartTimer();
}
}
class SetTimer
{
int istart;
public void StartTimer()
{
istart= Environment.TickCount; //Time when execution begins
Timer myTimer = new myTimer();
myTimer.Elapsed+=new ElapsedEventHandler(OnTimedEvent);
myTimer.Interval=1000; // 1000 milliseconds
myTimer.Enabled=true;
Console.WriteLine("Press any key to end program.");
Console.Read();
myTimer.Stop();
}
// Timer event handler
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
Console.WriteLine("Elapsed Time: {0}",
Environment.TickCount-istart);
}
}

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

13.4 Thread Synchronization


Thread synchronization refers to the techniques employed to share resources among
concurrent threads in an efficient and orderly manner. The specific objective of these
techniques is to ensure thread safety. A class (or its members) is thread-safe when it
can be accessed by multiple threads without having its state corrupted. The potential
corruption arises from the nature of thread scheduling. Recall from the previous sec-
tion that a thread executes in time slices. If it does not finish its task, its state is pre-
served and later restored when the thread resumes execution. However, while
suspended, another thread may have executed the same method and altered some
global variables or database values that invalidate the results of the original thread.
As an example, consider the pseudo-code in Figure 13-7 that describes how concur-
rent threads execute the same code segment.

Thread A Thread B

Open Log File for reading


Read counter Ct from file
Increment Ct by 1
Time Slice Ends
Open Log File for reading
Read counter Ct from file
Increment Ct by 1
Time Slice Ends
Move to beginning of file
Write value of counter Ct to file
Close Log File
Thread Ends
Move to beginning of file
Write value of counter Ct to file
Close Log File
Thread Ends

Figure 13-7 Execution path that requires synchronization

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

The implementation of the pseudo-code is presented in Listing 13-8. Executing


this code multiple times produces inconsistent results, which is the pitfall of using
code that is not thread-safe. About half the time, the counter is incremented cor-
rectly by 2; other times, the first thread is preempted and the second thread gets in
before the first finishes updating. In this case, the counter is incorrectly incremented
by 1.

Example of Class That Requires


Listing 13-8
Synchronization
using System;
using System.Threading;
using System.IO;
public class MyApp
{
public static void Main()
{
CallerClass cc = new CallerClass();
Thread worker1 =
new Thread(new ThreadStart(cc.CallUpdate));
Thread worker2 =
new Thread(new ThreadStart(cc.CallUpdate));
worker1.Start();
worker2.Start();
}
}
public class CallerClass
{
WorkClass wc;
public CallerClass()
{
wc= new WorkClass(); // create object to update log
}
public void CallUpdate()
{
wc.UpdateLog();
}
}
public class WorkClass
{
public void UpdateLog()
{
622 Chapter 13 ■ Asynchronous Programming and Multithreading

Example of Class That Requires


Listing 13-8
Synchronization (continued)
// Open stream for reading and writing
try
{
FileStream fs = new
FileStream(@"c:\log.txt",FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.ReadWrite);
StreamReader sr = new StreamReader(fs);
// Read current counter
string ctr = sr.ReadLine();
if(ctr==null) ctr="0";
int oldCt = int.Parse(ctr) + 1;
// If the thread's time slice ends here, the counter
// is not updated.
fs.Seek(0,SeekOrigin.Begin);
StreamWriter sw= new StreamWriter(fs);
sw.WriteLine(oldCt.ToString());
Console.WriteLine(oldCt);
sw.Close();
sr.Close();
} catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
} // WorkClass

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.

The Synchronization Attribute


The developers of .NET recognized that the overhead required to make all classes
thread-safe by default would result in unacceptable performance. Their solution was
to create a .NET architecture that naturally supports the ability to lock code seg-
ments, but leaves the choice and technique up to the developer. An example of this is
the optional Synchronization attribute. When attached to a class, it instructs
13.4 Thread Synchronization 623

.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

The class to which the [Synchronization] attribute is applied should derive


from the ContextBoundObject class. When .NET sees this is a base class, it places
the object in a context and applies the synchronization to the context. This is referred
to as context-bound synchronization. For this to make sense, let’s look at the .NET
architecture to understand what a context is.
When an application starts, the operating system runs it inside a process. The
.NET runtime is then loaded and creates one or more application domains (App-
Domains) inside the process. As we will see in the next chapter, these are essentially
logical processes that provide the managed environment demanded by .NET appli-
cations. Just as a process may contain multiple AppDomains, an AppDomain may
contain multiple contexts.
A context can be defined as a logical grouping of components (objects) that share
the same .NET component services. Think of a context as a layer that .NET wraps
around an object so that it can apply a service to it. When a call is made to this object,
it is intercepted by .NET and the requested service is applied before the call is
routed to the object. Synchronization is one type of component service. In our exam-
ple, .NET intercepts the call to UpdateLog and blocks the calling thread if another
thread has ownership of the context containing this method. Another component ser-
vice of interest—call authorization—enables .NET to check the calling thread to
ensure it has the proper credentials to access the object.
The [Synchronization] attribute is the easiest way to control thread access to a
class—only two statements are changed in our preceding example. The drawback to
this approach is that it must be applied to the entire class—even if only a small sec-
tion of the class contains critical code that requires thread synchronization. The man-
ual synchronization approaches we look at next permit a more granular
implementation.

The Monitor Class


The Monitor class allows a single thread to place a lock on an object. Its methods are
used to control thread access to an entire object or selected sections of code in an
object. Enter and Exit are its most commonly used methods. Enter assigns owner-
ship of the lock to the calling thread and prevents any other thread from acquiring it
as long as the thread owns it. Exit releases the lock. Let’s look at these methods in
action.
624 Chapter 13 ■ Asynchronous Programming and Multithreading

Using Monitor to Lock an Object


Monitor.Enter takes an object as a parameter and attempts to grant the current
thread exclusive access to the object. If another thread owns the object, the request-
ing thread is blocked until the object is free. The object is freed by executing the
complementary Monitor.Exit.
To illustrate the use of a monitor, let’s return to the example in Listing 13-8 in
which two threads compete to read and update a log file. The read and write opera-
tions are performed by calling the UpdateLog method on a WorkClass object. To
ensure these operations are not interrupted, we can use a monitor to lock the object
until the method completes executing. As shown here, it requires adding only two
statements:

public void CallUpdate()


{
Monitor.Enter(wc); // wc is WorkClass object
wc.UpdateLog();
Monitor.Exit(wc);

In addition to Monitor.Enter, there is a Monitor.TryEnter method that


attempts to acquire an exclusive lock and return a true or false value indicating
whether it succeeds. Its overloads include one that accepts a parameter specifying
the number of millseconds to wait for the lock:

if (!Monitor.TryEnter(obj) return; // Return if lock unavailable


if (!Monitor.TryEnter(obj, 500) return; // Wait 500 ms for lock

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

public void UpdateLog()


{
Monitor.Enter(this); // Acquire a lock
try
{
// Code to be synchronized
}
finally // Always executed
13.4 Thread Synchronization 625

{
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

Be wary of using synchronization in static methods. Deadlocks can


result when a static method in class A calls static methods in class B,
and vice versa. Even if a deadlock does not occur, performance is likely
to suffer.

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 two optional parameters are important. The initallyOwned parameter


indicates whether the thread creating the object wants to have immediate ownership
of it. This is usually set to false when the mutex is created within a class whose
resources it is protecting. The name parameter permits a name or identifier to be
assigned to the mutex. This permits a specific mutex to be referenced across App-
Domains and even processes. Because thread safety usually relies on encapsulating
the locking techniques within an object, exposing them by name to outside methods
is not recommended.
Using a mutex to provide thread-safe code is a straightforward process. A mutex
object is created, and calls to its wait methods are placed strategically in the code
where single thread access is necessary. The wait method serves as a request for own-
ership of the mutex. If another thread owns it, the requesting thread is blocked and
placed on a wait queue. The thread remains blocked until the mutex receives a signal
from its owner that it has been released. An owner thread releases a mutex in two
ways: by calling the object’s ReleaseMutex method or when the thread is termi-
nated. Here is an example of how the log update application is altered to use a mutex
to provide thread safety:

public class WorkClass


{
Mutex logMutex;
public WorkClass()
{
logMutex = new Mutex(false);
}

public void UpdateLog()


{
logMutex.WaitOne(); // Wait for mutex to become available
// Code to be synchronized
logMutex.ReleaseMutex();
}
}

As part of creating an instance of WorkClass, the constructor creates an instance


of the Mutex class. The Boolean false parameter passed to its constructor indicates
that it is not owned (the parameterless constructor also sets ownership to false).
The first thread that executes UpdateLog then gains access to the mutex through the
WaitOne call; when the second thread executes this statement, it is blocked until the
first thread releases the mutex.
13.4 Thread Synchronization 627

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:

public Semaphore(int initialCount, int maximumCount );

The maximumCount parameter specifies the maximum number of concurrent


thread requests the semaphore can handle; initialCount is the initial number of
requests the semaphore can handle. Here is an example:

Semaphore s = new Semaphore(5,10);

This semaphore permits a maximum of 10 concurrent threads to access a


resource. When it is first created, only 5 are permitted. To increase this number,
execute the Semaphore.Release(n) command—where n is the number used to
increment the count permitted. The intended purpose of this command is to free
resources when a thread completes executing and wants to exit a semaphore.
However, the command can be issued even if the thread has never requested the
semaphore.
Now let’s see how the Semaphore class can be used to provide synchronization for
the log update example. As a WaitHandle derived class, its implementation is almost
identical to the mutex. In this example, the semaphore is created with its initial and
maximum values set to 1—thus restricting access to one thread at a time.

public class WorkClass


{
private Semaphore s;
public WorkClass()
{
// Permit one thread to have access to the semaphore
s = new Semaphore(1, 1);
}
628 Chapter 13 ■ Asynchronous Programming and Multithreading

public void UpdateLog(object obj)


{
try {
s.WaitOne(); // Blocks current thread
// code to update log ...
} finally
{
s.Release();
}
}
}

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.

Waiting for Resource 1


1

Thread A Thread B

2
Waiting for Resource 2
Figure 13-8 Deadlock situation

Most deadlocks can be traced to code that allows resources to be locked in an


inconsistent manner. As an example, consider an application that transfers money
from one bank account to another using the method shown here:

public void Transfer(Account acctFrom,


Account acctTo, decimal amt)
{
13.4 Thread Synchronization 629

Monitor.Enter(acctFrom); // Acquire lock on from account


Monitor.Enter(acctTo); // Acquire lock on to account
// Perform transfer ...
Monitor.Exit(acctFrom); // Release lock
Monitor.Exit(acctTo); // Release lock
}

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:

Thread A: Transfer(Acct1000, Acct1500, 500.00);


Thread B: Transfer(Acct1500, Acct1000, 300.00);

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
}

Unfortunately, this can produce a performance bottleneck. Suppose another


method is working with one of the account objects required for the current transac-
tion. The thread executing the method is blocked as well as all other threads waiting
to perform a funds transfer.
A second solution—recommended for multithreading in general—is to impose
some order on the condition variables that determine how locking can occur. In this
example, we can impose a lock sequence based on the objects’ account numbers.
Specifically, a lock must be acquired on the account with the lower account number
before the second lock can be obtained.

If(acctFrom < acctTo)


{
Monitor.Enter(acctFrom);
Monitor.Enter(acctTo);
}else
{
Monitor.Enter(acctTo);
Monitor.Enter(acctFrom);
}
630 Chapter 13 ■ Asynchronous Programming and Multithreading

As this example should demonstrate, a deadlock is not caused by thread synchro-


nization per se, but by poorly designed thread synchronization. To avoid this, code
should be designed to guarantee that threads acquire resource locks in a consistent
order.

Summary of Synchronization Techniques


Table 13-2 provides an overview of the synchronization techniques discussed in this
chapter and provides general advice on selecting the one to best suit your applica-
tion’s needs.

Table 13-2 Overview of Selected Thread Synchronization Techniques

Technique Description When to Use

Synchronization An attribute that can be used To limit thread access to an entire


attribute with classes that inherit from the object. If you need to protect only a
ContextBoundObject class. small section of code while permit-
ting access to other class members,
choose another technique.

Monitor/lock Locks selected code segments To provide single thread access to


that are encased between a selected code segments in an object.
Monitor.Enter and Monitor. To synchronize access to value types,
Exit statement. Lock provides use a mutex.
equivalent code with built-in
event handling.

Mutex Uses wait methods inherited To permit a thread to request exclu-


from the WaitHandle class to sive access to one or more resources.
manage thread access to Requests can be made across App-
resources. Domains and processes.

Semaphore Uses wait methods inherited To make a limited number of


from the WaitHandle class to resources available concurrently to
manage multiple concurrent more than one thread.
thread access to resources.

In addition to these, .NET offers specialized synchronization classes that are


designed for narrowly defined tasks. These include Interlocked, which is used to
increment and exchange values, and ReaderWriterLock, which locks the writing
operation on a file but leaves reading open to all threads. Refer to online documenta-
tion (such as MSDN) for details on using these.
13.6 Test Your Understanding 631

13.5 Summary

Designing an application to perform tasks concurrently can result in an application


that provides better responsiveness to a user and manages system resources more
efficiently. This requires replacing the traditional synchronous approach to code exe-
cution with an asynchronous approach that uses threads. A thread is a path of execu-
tion. Each program begins running on a main thread that may create worker threads
to perform tasks concurrent to its own processing.
One way to create a thread that executes a specified method is to make an asyn-
chronous delegate invocation. This is done by creating a delegate and passing it the
name of the method to be called. The delegate is then invoked with its BeginInvoke
method. This causes the delegate’s method to be executed on a thread that is fetched
from a thread pool managed by .NET. An optional parameter to BeginInvoke is a
callback method that is called when the worker thread ends. Unlike synchronous
processing, in which a call to a method blocks processing in the calling method, asyn-
chronous invocation returns control to the calling method so that it can continue pro-
cessing.
Applications that require more control over a thread can create their own by pass-
ing a ThreadStart or ParameterizedThreadStart delegate to the Thread con-
structor. A thread is executed by calling its Start method.
After the decision is made to use threads, the problem of thread-safe code must
be considered. An operating system executes threads in time slices. When a thread’s
time expires, it is swapped out and another thread begins executing. The effects of a
thread being interrupted in the middle of a task can produce unpredictable results.
Thread synchronization is used to ensure that one thread has exclusive access to a
code path until it completes processing. .NET provides several approaches to syn-
chronization: an automatic approach that uses the Synchronization attribute to
lock an object until a thread has finished using it; and the Mutex, Monitor, and
Semaphore classes that provide a manual—but more granular—approach to imple-
menting thread safety.

13.6 Test Your Understanding


1. An asynchronous delegate must have a void return value.
a. True
b. False
632 Chapter 13 ■ Asynchronous Programming and Multithreading

2. Given this delegate


private delegate void myDelegate(string msg);
myDelegate d = new myDelegate(PrintMessage);
identify the role of ia, p1, p2, and p3 in this BeginInvoke call:
ia = d.BeginInvoke(p1, p2, p3);
3. What is thread local storage used for?

4. What is the default maximum number of threads that a thread pool


can hold?

5. What two delegates are used to create a thread directly, and how do
they differ?

6. Describe a syntactically simpler way to generate the following code:


Monitor.Enter(obj);
{
// Code to synchronize
} finally {
Monitor.Exit(obj);
}

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

8. What happens when you attempt to run this code?


class UseMutex
{
public void ThreadStart()
{
Mutex mutex = new Mutex(false, "MyMutex");
mutex.WaitOne();
Console.WriteLine("Worker Thread");
}

static void Main()


{
UseMutex obj = new UseMutex();
Thread thread = new Thread(
new ThreadStart(obj.ThreadStart));
Mutex mutex = new Mutex(true, "MyMutex");
thread.Start();
Thread.Sleep(1000);
Console.WriteLine("Primary Thread");
mutex.ReleaseMutex();
}
}
a. It prints: Worker Thread
Primary Thread
b. It prints: Primary Thread
Worker Thread
c. The program deadlocks and there is no output.
9. To illustrate deadlocking, Edsger Dijkstra introduced a “Dining Phi-
losopher” metaphor that has become a classical way of introducing
resource allocation and deadlocking. The metaphor (with variations)
goes like this:

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

Your challenge is to design a program that creates five threads—one to


represent each philosopher—that continually perform the tasks of
thinking and eating. The program should implement a synchroniza-
tion scheme that allows each thread to periodically acquire two chop-
sticks so that it can eat for a fixed or random (your choice) amount of
time. After eating, a philosopher should release its chopsticks and
think for a while before trying to eat again.

Hint: Use Monitor.Wait() to try to acquire chopsticks, and


Monitor.PulseAll() to notify all threads that chopsticks are being
released.
This page intentionally left blank
CREATING
DISTRIBUTED
APPLICATIONS WITH
REMOTING

Topics in This Chapter

• Application Domains: The logical partition within which a .NET


application runs is called an application domain (or AppDomain).
In this chapter, we examine how applications communicate and
access resources across AppDomain boundaries.
• Remoting: An application may call a method that resides in
another AppDomain, another process, or on another machine.
This process, known as remoting, requires creating a client and
server that agree to communicate across these boundaries. This
chapter looks at the options available to implement remoting.
• Leasing and Sponsorship: Garbage Collection in .NET does not
recognize remote references when removing objects. To prevent a
remote object from being destroyed, .NET provides a Lease
object that is used to reference a remote object so that it avoids
Garbage Collection.
14

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

14.1 Application Domains


When you install .NET on a machine, you create a virtual, managed environment in
which applications can run. This environment is designed to insulate applications
from the demands and vagaries of the host operating system. The AppDomain is one
of the key architectural features supporting the managed environment.
Most operating systems see the world in terms of processes that provide the
resources, such as memory and tables required by applications. Because a .NET
application cannot run directly in the unmanaged process, .NET partitions a process
into one or more logical areas in which assemblies execute. These logical areas are
AppDomains. As shown in Figure 14-1, a process may contain more than one App-
Domain and an AppDomain may contain one or more assemblies. A default AppDo-
main is created when the Common Language Runtime (CLR) initializes, and
additional ones are created by the CLR as needed. An application may also instruct
the CLR to create a new AppDomain.

Device 1 Device 2
Process 1 Process 1
AppDomain 1 AppDomain 1 AppDomain 2

Assembly A Assembly C Assembly D

Assembly B
Domain-Neutral Assemblies

Figure 14-1 Multiple application domains may reside in a single process

Advantages of AppDomains
Aside from the need for a managed environment, the use of AppDomains provides
several advantages over the traditional process-based architecture:

• Code Isolation. AppDomains institute a level of fault isolation that


prevents a code failure in one AppDomain from causing another App-
Domain to crash. .NET achieves this code separation in two ways: by
preventing an AppDomain from directly referencing objects in another
14.1 Application Domains 639

AppDomain, and by having each AppDomain load and maintain its


own copy of key assemblies that allow it to run independently. As a
by-product of this, an AppDomain can be selectively debugged and
unloaded without directly affecting other AppDomains in the process.
• Performance. Implementing an application to run in multiple App-
Domains can produce better performance than a comparable design
that relies on multiple processes. This efficiency derives from several
factors: A physical process requires more memory and resources than
AppDomains, which share the resources of a single process; creating
and disposing of processes is much more time consuming than compa-
rable operations on AppDomains; and making a call between pro-
cesses is slower and requires more overhead than making a call
between AppDomains residing in the same process.
• Security. By its very nature, an AppDomain presents a security
boundary between its contained resources and assemblies attempting
to access them. To cross this boundary, an outside assembly must rely
on remoting, which requires cooperation between the AppDomains.
In addition, an AppDomain has its own security policy that it can
impose upon assemblies to restrict their permissible operations. This
“sandbox” security model allows AppDomains to ensure that assem-
blies are well behaved. In Chapter 15, “Code Refinement, Security,
and Deployment,” we’ll look at examples of using AppDomains to
enforce code security.

Application Domains and Assemblies


When an application runs, it may reference code in several assemblies. By default,
the CLR loads these referenced assemblies into the AppDomain of the calling
assembly. This means that if a process contains multiple AppDomains that reference
the same assembly, a copy of that assembly is placed in each domain. However, it is
possible to override the default and share a single assembly among multiple domains.
Assemblies used in this manner are referred to as domain-neutral assemblies.
Some crucial assemblies required by all applications are automatically loaded as
domain-neutral. The most prominent of these is mscorlib.dll, an assembly that
contains the native types integral to the .NET Framework. However, a developer can
also specify that a custom assembly be made domain-neutral so that domains can
share it.
The use of a domain-neutral assembly saves memory. However, there is a trade-off
in terms of performance and flexibility in assigning permissions to assemblies. Even
shared assemblies require that their static data and methods be copied into the App-
Domains that reference them. The overhead to manage this can slow performance.
Only one set of permissions, defining what operations an assembly can perform, can
640 Chapter 14 ■ Creating Distributed Applications with Remoting

be assigned to domain-neutral assemblies. Thus, if an AppDomain requires a differ-


ent set of permissions, it must have its own copy of the assembly.

Working with the AppDomain Class


The System.AppDomain class exposes methods and properties that enable an appli-
cation to create an AppDomain, remove an AppDomain from a process, enumerate
the assemblies it contains, and identify itself by name or internal ID. The majority of
applications do not require programming at the AppDomain level; however, applica-
tions with a need to isolate a custom type library, or include special security features,
will find direct interaction with the class a necessity.
In this section, we’ll look at an example that illustrates some of the basic tech-
niques used to manipulate an AppDomain. Working though this code should not only
make you familiar with the basic class members, but also further clarify the relation-
ship that exists among a process, an AppDomain, and an assembly.
The example defines two classes: RemoteClass, which is compiled into a DLL
file, and AppDomainClient, which is compiled into AppdTestClient.exe.
RemoteClass is packaged as an assembly that does nothing more than display the
name of the domain in which it is running. It uses the static AppDomain.Current-
Domain property to retrieve the current domain, and displays its name using the
FriendlyName property.
Note that RemoteClass derives from MarshalByRefObject. As we see in the
next section on remoting, this is required if an object is to be accessed from an App-
Domain other than its own.

// 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);
}
}
}

AppDomainClient, shown in Listing 14-1, illustrates how to use several AppDo-


main class members. It begins by creating an instance of RemoteClass using the fol-
lowing statement:
14.1 Application Domains 641

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

Listing 14-1 Working with Application Domains


// AppdTestClient.exe assembly
using System;
using System.Reflection;
using AppDomainTest;
public class AppDomainClient
{
static void Main()
{
AppDomain currentAppDomain;
currentAppDomain = AppDomain.CurrentDomain;
RemoteClass rc;
// (1) Create instance of RemoteClass
rc=(RemoteClass)currentAppDomain.CreateInstanceAndUnwrap(
"appdtestclass", "AppDomainTest.RemoteClass");
// (2) Execute method on RemoteClass
rc.ShowDomain();
// (3) Show assemblies in this appdomain
Assembly[] currAssemblies =
currentAppDomain.GetAssemblies();
ShowAssemblies (currAssemblies);
// (4) Create a new AppDomain
AppDomain myAppDomain =
AppDomain.CreateDomain("New Domain");
rc=(RemoteClass)myAppDomain.CreateInstanceAndUnwrap(
"appdtestclass", "AppDomainTest.RemoteClass");
rc.ShowDomain();
// (5) Show assemblies in new appdomain
currAssemblies = myAppDomain.GetAssemblies();
ShowAssemblies(currAssemblies);
// (6) The domain a thread runs in can be displayed
Console.Write(
System.Threading.Thread.GetDomain().FriendlyName);
642 Chapter 14 ■ Creating Distributed Applications with Remoting

Listing 14-1 Working with Application Domains (continued)


// (7) Unload the new appdomain from the process
AppDomain.Unload(myAppDomain);
}

private static void ShowAssemblies(


Assembly[] currAssemblies)
{
foreach(Assembly a in currAssemblies)
{
Console.WriteLine(
" Assembly Name: {0}",a.GetName().Name);
}
}
}

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:

AppDomain myAppDomain = AppDomain.CreateDomain("New Domain");

The two assemblies are loaded when AppDomainClient creates an instance of


RemoteClass. As you would expect, executing ShowDomain causes the console to
display “New Domain” as the name of the current domain.

Process: appdtestclient.exe

Default AppDomain New Domain

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:

• Create a TCP or HTTP connection between the client and server.


• Select how the messages sent between server and client are
formatted.
• Register the type that is to be accessed remotely.
• Create the remote object and activate it from the server or the client.

.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

applications communicate. For information on these topics, refer to a book on


advanced .NET remoting, such as Advanced .NET Remoting by Ingo Rammer.1

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

CHANNEL Request CHANNEL


TCP HTTP Response TCP HTTP

Figure 14-4 High-level view of .NET remoting architecture

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

The real proxy that is responsible for sending a message to a remote


object is an implementation of the RealProxy class that is generated
automatically by the CLR. Although this meets the needs of most
remoting applications, a developer has the option of creating a custom
implementation.

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:

HttpChannel c = new HttpChannel();


ChannelServices.RegisterChannel(c);

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.

As an alternative to embedding the channel and protocol information within code,


.NET permits it to be specified in configuration files associated with the client and
host assemblies. For example, if the host assembly is named MessageHost.exe, we
could have a configuration file name MessageHost.exe.config containing the fol-
lowing port and formatting specification.

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

Assigning a Name to a Channel


To open multiple channels using the same protocol, a host must assign a name to
each channel. To do so, it must use this form of the HttpChannel constructor:

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:

IDictionary chProps = new Hashtable();


chProps["name"] = "Httpchannel01";
chProps["port"] = "3202";
ChannelServices.RegisterChannel(new HttpChannel(chProps,
null, null));

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 HealthTools class that is marshaled by reference returns an instance of


BMICalculator:

public class HealthTools: MarshalByRefObject


{
// Return objects to calculate BMI
public BMICalculator GetBMIObj(){
return new BMICalculator();
}

The client creates an instance of HealthTools and calls the GetBMIObj method
to return the calculator object:

HealthMonitor remoteObj = new HealthMonitor();


BMICalculator calc= remoteObj.GetBMIObj();
calc.pounds= 168M;
calc.inches= 73M;
Console.WriteLine(calc.GetBMI());

It is important to understand that this example uses both marshaling by value


and marshaling by reference: an MBR type (HealthTools) implements a method
(GetBMIObj) that returns an MBV type (BMICalculator). You should recognize
this as a form of the factory design pattern discussed in Chapter 4, “Working with
Objects in C#.”

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

Client Proxy Host AppDomain

Object 1

AppDomain 2
Object 2

Client Proxy

Figure 14-5 Client-activated objects: client retains control of object

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

Client Proxy 1 Host AppDomain

3
Singleton
AppDomain 2 Object

2
Client Proxy

Figure 14-6 Server-activated singleton object: one object handles all calls

Server-Activated Single Call


In single call activation mode, the server creates a new object each time a call is
made on an object. After the call has been handled, the object is deactivated. Figure
14-7 illustrates how multiple calls are handled: The first call has been completed and
the object created for it destroyed; at this point, the proxy of the client that made this
call references a null value. The second call comes from another client and results in
the creation of the second server object. Finally, the first client makes its second call
and a third object is created.
The advantage of single call activation is that resources are made available as soon
as a call is completed. This is in contrast to the client-activated call where the client
holds on to the resources until it has finished using the object. The disadvantage of
the single call is that it does not inherently maintain state information between calls.
If you do want to take advantage of single call scalability, but require that information
about previous calls be maintained, you can design the server to maintain its own
state information in a file or database.
652 Chapter 14 ■ Creating Distributed Applications with Remoting

AppDomain 1

Client Proxy 1 Host AppDomain

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.

Registering Server-Activated Objects


A host assembly uses the RegisterWellKnowServiceType method of the Remot-
ingConfiguration class to register a type. The method has three parameters: the
type of the object, a string representing the object’s URI (universal resource identi-
fier), and a WellKnownObjectMode enum that indicates whether the object is
implemented as a singleton or single call object. This code segment registers a Mes-
sageManager object to execute as a singleton.

// Server Registration: Server-Activated Objects


Type ServerType = typeof(SimpleServer.MessageManager);
RemotingConfiguration.RegisterWellKnownServiceType(
ServerType, // Type of Object
"MyObject", // Arbitrary name
WellKnownObjectMode.Singleton );

Replace Singleton with SingleCall to register the object to run in single call
mode.
14.2 Remoting 653

To access an SAO, a client uses the RegisterWellKnownClientType method. It


takes two parameters: the object type and a string containing the URL where the
object can be located. Note that the client does not have any say in whether it uses a
singleton or single call object—it uses whichever the server provides.

// Client Registration: Server-Activated Objects


Type ServerType = typeof(SimpleServer.MessageManager);
string url= "http://localhost:3200/MyObject";
// Register type for Server Activation Mode
RemotingConfiguration.RegisterWellKnownClientType(
ServerType,
url);
MessageManager mm = new MessageManager();

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.

Registering Client-Activated Objects


A host uses the RegisterActivatedServiceType method to register a CAO. It
requires only one parameter—the object type:

// Server Registration: Client-ativated Objects


Type ServerType = typeof(ImageServer); // ImageServer class
RemotingConfiguration.RegisterActivatedServiceType(
ServerType);

The client registration is almost as easy. It invokes the RegisterActivatedCli-


entType method and passes it the object type and URL where the object can be
located:

// Client Registration: Client-ativated Objects


Type ServerType = typeof(ImageServer);
RemotingConfiguration.RegisterActivatedClientType(
ServerType,
"tcp://localhost:3201");

Type Registration Using a Configuration File


As with channels, the type registration instructions can be placed in an assembly’s
configuration file. These two code segments illustrate how to register the SAO from
the preceding example on the server and client:

//Server: Register MessageManager object as a singleton


<application >
<service>
654 Chapter 14 ■ Creating Distributed Applications with Remoting

<wellknown
mode="Singleton"
type="SimpleServer.MessageManager, msgserver"
objectUri="MyObject"/>
</service>
</application>

//Client: Register MessageManager object on port 3200


<application >
<client >
<wellknown
type="SimpleServer.MessageManager, msgserver"
url="http://localhost:3200/MyObject" />
</client>
</application>

Observe that the registration information is represented as attributes in the


<wellknown> tag, and that the type attribute denotes the object by its namespace
and name as well as the assembly containing it.
Client-activated registration uses an <activated> tag to specify the remote
object in both the server and client configuration file.

//Server: Register ImageServer to be client-activated


<application >
<service >
<activated type="ImageServer,caoimageserver"/>
</service>
</application>

The client also includes a url attribute to provide the address of the remote
object:

//Client: Register ImageServer to be client-activated


<application >
<client url="tcp://localhost:3201" >
<activated type="ImageServer,caoimageserver"/>
</client>
</application>

Remoting with a Server-Activated Object


With an understanding of how to register channels and types, you’re ready to imple-
ment a remoting application. Our first example builds an application that permits
users to post and retrieve messages from other users. It’s based on SAO, and we’ll
look at two ways to design it. Our second example uses CAO to retrieve images from
an image server.
14.2 Remoting 655

A Message Server Example


The minimum requirements for a remoting application are an assembly containing
the client code, and an assembly that runs as a server and provides the implementa-
tion code for the remoting objects. As Figure 14-8 shows, the more common model
uses three assemblies: a client, a host that performs channel and type registration,
and a server that contains code for the objects. This is the model used for our initial
message server project.

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:

csc /t:library msgserver.cs


csc /r:msgserver.dll msghost.cs
csc /r:msgserver.dll msgclient.cs

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.

Listing 14-2 Remoting Server


// msgserver.cs (DLL)
using System;
using System.Collections;
namespace SimpleServer{
public class MessageManager: MarshalByRefObject
{
ArrayList Messages = new ArrayList();

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

Listing 14-2 Remoting Server (continued)


{
// Save Message received from client as object
// in an array
Envelope env= new Envelope();
env.Message= msg;
env.SenderID= sender;
env.RecipientID= recipient;
Messages.Add(env); // add message to array
Console.WriteLine("Received:\n{0}", msg);
}
}
// Messages are stored as instances of Envelope
public class Envelope
{
public string Message;
public string SenderID;
public string RecipientID;
}
}

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.

Listing 14-3 Remoting Host/Listener


// msghost.cs (exe)
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using SimpleServer; // Namespace of server
namespace SimpleHost
{
public class MessageHost
{
658 Chapter 14 ■ Creating Distributed Applications with Remoting

Listing 14-3 Remoting Host/Listener (continued)


static void Main()
{
Console.WriteLine("Host Started.");
// Channel Registration
HttpChannel c = new HttpChannel(3200);
ChannelServices.RegisterChannel(c);
// Type Registration–Use server-activated object (SAO)
// Type is specified as (namespace.class)
Type ServerType = typeof(SimpleServer.MessageManager);
RemotingConfiguration.RegisterWellKnownServiceType(
ServerType, // Type of Object
"MyObject", // Arbitrary name
WellKnownObjectMode.Singleton );
Console.Read(); // Keep host running
}
}
}

After registration is completed, this assembly continues running and monitors


port 3200 for calls to MessageManager. Any messages received are passed on to the
object.

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:

> msgclient 005

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.

Listing 14-4 Remoting Client


// msgclient.cs (exe)
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
14.2 Remoting 659

Listing 14-4 Remoting Client (continued)


using System.Runtime.Remoting.Channels.Http;
using System.Collections;
using SimpleServer; // Namespace of server
namespace SimpleClient
{
public class MessageClient
{
static void Main(string[] args)
{
string myID;
// Client ID is passed as command line argument
if(args.Length>0) myID= args[0]; else myID="001";
Console.WriteLine("Client Started.");
// (1) Channel Registration
HttpChannel c = new HttpChannel();
ChannelServices.RegisterChannel(c);
// (2) Type Registration: SAO using port 3200
Type ServerType = typeof(SimpleServer.MessageManager);
string url= "http://localhost:3200/MyObject";
// Register type for Server Activation Mode
RemotingConfiguration.RegisterWellKnownClientType(
ServerType,url);
// (3) Create instance of Remote Object
MessageManager mm = new MessageManager();
string msg;
string oper="";
// Allow user to send or receive a message
while(oper !="Q")
{
Console.WriteLine("(S)end, (R)eceive, (Q)uit");
oper= Console.ReadLine();
oper = oper.ToUpper();
if(oper=="S"){
Console.WriteLine("enter Recipient ID: messsage");
msg= Console.ReadLine();
// : Separates ID and message
int ndx= msg.IndexOf(":");
if(ndx>0) {
string recipientID=msg.Substring(0,ndx).Trim();
msg= msg.Substring(ndx+1);
mm.SetMessage(msg, myID, recipientID);
}
} else
660 Chapter 14 ■ Creating Distributed Applications with Remoting

Listing 14-4 Remoting Client (continued)


{
if (oper=="R"){
Console.WriteLine(mm.FetchMessages(myID));
}
}
} // while
} // method
} // class
} // namespace

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.

Figure 14-9 Client interacting with remote MessageServer configured as singleton

MessageServer Configuration Files


Both msghost and msgclient contain code to perform explicit channel and type
registration. As mentioned previously in this section, an alternative is to place chan-
nel and/or type registration information in an assembly’s configuration file. The
in-line registration code is then replaced with a call to a static Configure method
that reads the configuration file and performs the registration:

RemotingConfiguration.Configure("MsgHost.exe.config");
14.2 Remoting 661

Here is how the configuration for msghost.exe is expressed in XML:

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

Using an Interface Assembly with


Server-Activated Objects
In the preceding example, the msgserver assembly is packaged as a DLL and must
be deployed on both the host and client machine. On the server, it provides the actual
code that is executed when a call to the remote object occurs. The client requires the
assembly’s metadata to compile and build a proxy at runtime, but has no use for the
Intermediate Language (IL). Rather than placing this code on each client’s
machine—exposing it to anyone with a disassembler—the application can be rede-
signed to use an interface assembly to provide the metadata required by the client.
To do this, we’ll combine the code in the msghost and msgserver file to create
an assembly that both implements the object and performs registration. The third
assembly, now called msggeneral, contains an interface that defines the methods
supported by the MessageServer class (see Figure 14-10). The client then gets its
metadata from this lightweight assembly rather than the full server assembly.

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

// Define an interface to provide method descriptions


// used by client and implemented by server.
public interface IMessageManager
{
string FetchMessages(string clientID);
void SetMessage(string message, string sender,
string recipient);
}
}

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

server-activated object. As this code segment illustrates, Activator.GetObject


performs registration and returns an object in one step:

Type ServerType = typeof(IMessageManager);


// Activate Remote Object and perform registration
object remoteObj = Activator.GetObject(
ServerType,
"http://localhost:3200/MyObject");
IMessageManager mm = (IMessageManager) remoteObj;

No other changes are required in the msgclient.cs code.


The three source files are compiled into the msgserver.exe, msggeneral.dll,
and msgclient.exe assemblies:

csc /t:library msggeneral.cs


csc /r:msggeneral.dll /out:msgserver.exe msgserverv2.cs
csc /r:msggeneral.dll msgclient.cs

Remoting with a Client-Activated Object (CAO)


In the preceding server-activated singleton example, each call to the remote object is
handled by the same object. In the client-activated model, each client creates its own
object and can make multiple calls to that object (refer to Figure 14-5 on page 650).
The model does not scale particularly well because a large number of concurrent
client-created objects can deplete system resources. However, when the expected
number of users is small and there is a need to maintain state information between
calls, this model should be considered.

An Image Server Example


This project implements a remote server that serves up images to clients upon
request. It loads the requested file from local storage and delivers it as a byte stream
to the client. After being received by the client, the bytes are then reassembled into
the original image.2
Prior to examining the code, let’s consider the rationale behind implementing the
server using client activation, rather than one of the server-activation modes. A sin-
gleton can be ruled out; because each client’s request is independent of any other cli-
ent’s request, there is no need to maintain state information. The choice between
single call activation and client activation is less clear-cut and depends on expected
client behavior. If a large number of clients requesting a single image are expected,

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;

// Channel Registration for TCP


TcpChannel c = new TcpChannel(3201);
ChannelServices.RegisterChannel(c);
// Type Registration for CAO
Type ServerType = typeof(ImageServer);
RemotingConfiguration.RegisterActivatedServiceType(
ServerType);

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

Using Remoting to Implement an


Listing 14-5
Image Server
// caoimageserver.cs (DLL)
using System;
using System.Runtime.Remoting;
using System.Collections;
using System.Drawing;
using System.IO;
666 Chapter 14 ■ Creating Distributed Applications with Remoting

Using Remoting to Implement an


Listing 14-5
Image Server (continued)
public class ImageServer: MarshalByRefObject
{
// Return list of available images as a string array
public ArrayList GetFiles()
{
ArrayList a = new ArrayList();
string dir=@"c:\images\";
foreach(string fileName in Directory.GetFiles(dir))
{
// Strip path from file name
int ndx= fileName.LastIndexOf("\\");
string imgName = fileName.Substring(ndx+1);
a.Add(imgName);
}
return a;
}
// Return requested image as byte stream
public byte[] GetMovieImage(string imageName)
{
int imgByte;
imageName= "c:\\images\\"+imageName;
FileStream s = File.OpenRead(imageName);
MemoryStream ms = new MemoryStream();
while((imgByte =s.ReadByte())!=-1)
{
ms.WriteByte(((byte)imgByte));
}
return ms.ToArray();
}
}

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

Using a Client-Activated Object to Access


Listing 14-6
ImageServer

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

Using a Client-Activated Object to Access


Listing 14-6
ImageServer (continued)

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

The code implementation is based on a simple command-line menu. When L is


entered, a list of available images is displayed on the console; when R is entered, the
program prompts for the name of a file to be downloaded. This image name is sent to
the server, which returns the image—if it exists—as an array of bytes. A Bitmap
object is created and its Save method is used to store the image on the client’s disk.

Deploying the CAO Application


Use the C# compiler to create caoimagelistener.exe, caoimageserver.dll,
and caoimageclient.exe:

csc /t:library caoimageserver.cs


csc /r:caoimageserver.dll caoimagelistener.cs
csc /r:caoimageserver.dll caoimageclient.cs

Note that this implementation requires packaging caoimageserver.dll with


caoimageclient.exe on the client’s machine. As discussed earlier, this exposes the
server code on the client machine. For the SAO application described earlier, using
an interface implementation solved this problem. A similar approach can be used
with client-activated objects, but requires creating a “factory” that returns an inter-
face for the server object. Another solution is to use the .NET SoapSuds utility to
extract metadata from the server assembly into a DLL that can be deployed instead
of the server assembly. This utility is executed at the command-line prompt:

soapsuds –ia:caoimageserver –oa:serverstub.dll –nowp

-ia: specifies the input assembly; do not include .dll or .exe


-oa: specifies the output assembly that will contain the metadata.
-nowp specifies that a nonwrapped proxy is to be created. A wrapped
proxy can be used only with SOAP channels and is designed
for working with Web Services.

The output assembly, serverstub.dll, is now referenced as the client is com-


piled and is deployed on the client’s machine along with the client assembly.

csc /r:serverstub.dll caoimageclient.cs

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:

• If the application requires that the server maintain state informa-


tion—as was the case in our message server example—an SAO single-
ton is the obvious choice. If the application does not require a
singleton, do not use it. Scalability and synchronization can be a prob-
lem when accommodating a large number of users.
• If each call to a remote object is independent of other calls, an SAO
single call model is the best choice. This is also the most scalable solu-
tion because it does not maintain state information and is destroyed as
soon as it’s no longer needed.
• The third choice—the client-activated object—allows the client to cre-
ate the object and maintain state information in instance variables
between calls. One drawback is that the model does not support the use
of a shared interface assembly to provide the metadata required to cre-
ate a proxy. Instead, a class-factory approach or SoapSuds must be used.

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.

Client Machine Remote Machine


SoapSuds Server Server/ Server
Metadata Interface Code Host Host Code Interface
Server- ✔ ✔ ✔
Activated ✔ ✔ ✔
Objects
✔ ✔ ✔
Client- ✔ ✔ ✔
Activated
Objects ✔ ✔ ✔
Figure 14-11 Assembly combinations that can be used to deploy a remoting application
14.3 Leasing and Sponsorship 671

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.

14.3 Leasing and Sponsorship


One of the signature features of .NET is its use of Garbage Collection to remove
unused objects. As was discussed in Chapter 4, .NET maintains a graph to keep track
of all references to objects in the managed heap. When Garbage Collection occurs,
those objects for which references cannot be found are destroyed. Because .NET
only recognizes references within a process, objects referenced by remote clients
would be subject to Garbage Collection leaving the remote client(s) with no host
object to call.
The rather clever .NET solution is to assign a lease object to each client-activated
object or server-activated singleton when they are created. (Server-activated single
call objects are not affected because they only exist for the duration of a call.) The
lease provides a reference to the object that keeps the object from being made avail-
able for Garbage Collection as long as the lease exists—which is based on the lease
time assigned to the lease. The time associated with the lease is referred to as the
object’s time-to-live (TTL).
As we see next, a lease may be renewed programatically by calling methods on the
lease object to increase its lease time. If the lease does expire, .NET looks for an
object associated with the lease, known as a sponsor, and gives it a chance to renew
the lease. Let’s look at the details.
672 Chapter 14 ■ Creating Distributed Applications with Remoting

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.

Table 14-1 ILease Interface Members

Member Description

CurrentLeaseTime Amount of time remaining before the object is made available


for Garbage Collection. If it is invoked, the lease time is reset.

CurrentState Returns the current state of the lease, defined by a LeaseState


enumeration: Active, Expired, Initial, Null, or Renewing.

InitialLeaseTime Gets or sets the initial amount of time-to-live that a lease assigns
to an object. Default is 5 minutes.

RenewOnCallTime Gets or sets the amount of time—specified using TimeSpan—


that a call to an object increases the CurrentLeaseTime.
Default is 2 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.

Register() Registers a sponsor for the lease.

Renew() Renews a lease for a specified amount of time.

UnRegister() Removes a sponsor from an object’s list of sponsors.

Setting Initial Lease Values for


Objects in an Application
You can override the default values assigned to lease properties in an application’s
configuration file. For example, to set the lease values for all objects in our image
server example, insert the following into the caoimagehost.exe.config file:

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

Setting the Lease Values for an Individual Object


Both of the aforementioned techniques for setting initial lease values apply to all
objects hosted by the server. To set lease values on an object-by-object basis, a differ-
ent approach is required. Recall that for a class to implement remoting it must
inherit the MarshalByRefObject class. This class includes an InitializeLife-
TimeService method that is responsible for returning lease objects. It is a simple
matter to override this method in a program to assign your own initial values to the
lease object.

using System.Runtime.Remoting.Lifetime; // Contains ILease


public class MessageManager: MarshalByRefObject, IMessageManager
{
// Override this method to set lease initialization values
public override object InitializeLifetimeService()
{
ILease msgLease = (ILease)base.InitializeLifetimeService();
msgLease.InitialLeaseTime = TimeSpan.FromMinutes(8);
msgLease.RenewOnCallTime = TimeSpan.FromMinutes(6);
return msgLease;
}

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.

The Lease Manager


Running in the background of each AppDomain is a lease manager that keeps track
of each server object and its associated lease. The primary function of the manager is
to periodically examine the leases for time expiration and invoke a sponsor if the
lease has expired.
The lease manager uses a timer to control how frequently it checks for lease expi-
rations. By default, this is set to 10 seconds. To override the default, add this setting
in the configuration file:

<application >
<lifetime
LeaseManagerPollTime"5S"
/>

The value can also be set programmatically using the LifetimeServices class:

LifetimeServices.LeaseManagerPollTime =
System.TimeSpan.FromSeconds(5);

The Lifetime of a Lease


It should be clear from the discussion thus far that a lease has a nondeterministic life-
time whose actual length is determined by calls on the associated object, direct invo-
cation of the lease’s Renew method, and any renewal coming from a lease’s
sponsor(s). The state diagram in Figure 14-12 illustrates the relationship between
these actions and the current state of a lease. Note that a lease may be in one of four
states—Initial, Active, Renewing, or Expired.
When a remote object is created, .NET creates a corresponding lease and sets its
InitialLeaseTime—in descending order of priority—to either a value set pro-
grammatically, a value in the configuration file, or a default value of 5 minutes. The
lease moves into an active state, and its time begins to tick away. When a call is made
14.3 Leasing and Sponsorship 675

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

Object TTL < RenewTime


Accessed

LeaseState. LeaseState. LeaseState.


Initial Active N Expired
TT
L=
0 LeaseState.
Renew Renewing
Y
Sponsor
Renews Lease

Figure 14-12 Different states in which a lease may exist

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

Table 14-2 Similarities Between an Event Handler and a Sponsor

Event Handler Sponsor

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.

Implements a method that is called to Implements a class that contains a method,


take some action when the event occurs. Renewal, that is called when the TTL=0 event
occurs. The method then takes action to extend
or end the lease.

Signature of event handling method must Renewal must always have this signature:
match that of the delegate. Timespan Renewal(ILease lease)

Although an event handler is a method, a sponsor is a class that meets two


requirements: It inherits from MarshalByRefObject, and it implements the
ISponsor interface:

public class ImageSponsor: MarshalByRefObject, ISponsor

The ISponsor interface defines only one member—a method, Renewal—that


returns the new TTL value for the object as a TimeSpan type:

public TimeSpan Renewal(ILease lease)

A sponsor can be implemented on the server, client, or in a third-party assembly.


The only requirement is that the lease manager can locate it. In reality, a sponsor
should be defined where the application can best assess the factors that determine
how or if an object’s lease is extended. For an SAO singleton, this is on the
server/host; for a CAO, the code should be on the client, because only the client
knows how it’s going to be using the object.
To demonstrate the details of implementing a sponsor, let’s add one to the client
code (see Listing 14-6) used in the image server example. It can be done in four
steps:

1. Add namespaces that define an HTTP channel to be used to receive


the callback from the lease manager, and the ILease and ISponsor
interfaces:
using System.Runtime.Remoting.Channels.Http;
// Contains ILease and ISponsor
using System.Runtime.Remoting.Lifetime;
14.3 Leasing and Sponsorship 677

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.

14.5 Test Your Understanding


1. True or False?
a. A process may contain multiple AppDomains.
b. An AppDomain may contain multiple assemblies.
c. An AppDomain can contain both EXE and DLL assemblies.
d. An AppDomain can be unloaded from memory.

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

4. Explain the difference between CurrentLeaseTime, Initial-


LeaseTime, and RenewalOncallTime.

5. What determines how often the lease manager checks for lease
expirations?

6. Which activation modes support the use of leases?

7. When is a server-activated singleton object created?


a. When the host begins running.
b. When the client uses the new operator to create an instance of the
object.
c. When the client first calls a method on the object.
d. When the object type is registered by the host.

8. Compare channel registration and type registration.

9. What is the reason to use SoapSuds or an interface when designing a


remoting application? How is SoapSuds used?
CODE REFINEMENT,
SECURITY, AND
DEPLOYMENT

Topics in This Chapter

• Code Refinement: .NET provides a tool, FxCop, which analyzes


code by checking it against a set of best practice rules and
recommendations. This tool is designed for building components,
but most applications can benefit from it as way to amend and
refine how code is implemented. An example demonstrates how
to analyze code using this tool’s command-line interface.
• Strongly Named Assemblies: One aspect of code security is being
able to verify an application’s origin and version. .NET provides a
way to mark an assembly with a key that identifies it, and
supports an assembly versioning scheme that distinguishes
between code versions—allowing multiple versions of a
component to coexist.
• Application Security: The .NET Code Access Security model is
based on a simple principle: allow code to access system
resources and perform operations only when it has permission to
do so. Before an assembly can access resources such as files,
sockets, or the registry, it is checked for evidence to determine the
permissions that it can be given. This chapter explains the overall
security model and looks at how it is applied administratively and
within code.
• Application Deployment: One of the touted benefits of .NET is the
ability to install an application using XCOPY deployment—simple
file copying. However, many applications require a more
sophisticated approach that takes into account security policies
and resource management. This chapter presents a checklist of
issues to be considered.
15

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

Figure 15-1 Deliverable software should meet coding


standards, be secure, and be easily deployed

The chapter concludes with a look at the issues to be considered in deploying an


application to users or customers. The advantages and disadvantages of using
XCOPY or an installer to physically distribute an application are discussed.

15.1 Following .NET Code


Design Guidelines
The developers of .NET included a set of rules1 that are intended to serve as a guide
for writing code that runs on the .NET platform. Although the rules are written with
component developers in mind (Microsoft uses them for its managed code libraries),
any application can benefit from them.
To help a developer incorporate these rules and best practices in their code, .NET
includes the FxCop tool that analyzes code against these rules. Some of the rules may
be stricter than your development environment requires; or they may simply conflict
with your own standards—you may prefer non-Microsoft naming conventions, for

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

FxCop is available as both a GUI (fxcop.exe) and command line


(fxcopcmd.exe) application. Downloads of the latest version and documentation
are free and available at several Web locations. The download also includes an SDK
that can be used to create custom rules—a topic beyond the scope of this section.

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:

C:/> csc fxtest.cs


C:/> fxcopcmd /file:fxtest.exe /out:fxoutput.txt

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

Listing 15-1 Code to Be Analyzed by FxCop


//fxtest.cs
using System;
using System.Text;
namespace FxTesting
{
public class TestApp
{
static void Main()
{
string msg;
string oper="";
while(oper !="Q")
{
Console.WriteLine("(R)everse, (Q)uit");
oper= Console.ReadLine();
oper = oper.ToUpper();
if(oper=="R"){
Console.WriteLine("Enter phrase to reverse:");
msg= Console.ReadLine();
if(msg.Length>1) msg= Reverse(msg);
Console.WriteLine(msg);
}
}
}
// Function to reverse a string
public static String Reverse(String stringParameter)
{
if(stringParameter.Length==1)
{
return stringParameter;
}
else
{
return Reverse(stringParameter.Substring(1)) +
stringParameter.Substring(0,1);
}
}
}
}

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

A review of the output shows that the assembly recommendations (1 through 5)


are oriented toward components that will be stored in libraries and used by other
code. As one would expect, the emphasis is on how component code interacts with
other code. In general, security and type visibility is of more importance here than in
general application development. Recommendations 6 and 7 promote more efficient
code, whereas rule 8 is a subjective naming convention.
Many of the rules are overly restrictive for most programming environments, and
you’ll want to disable them. However, it is beneficial to be aware of them. They rep-
resent a set of best practices that have evolved with .NET. Even if you do not incor-
porate them in your code, understanding the reasoning behind them will deepen
your knowledge of .NET.

15.2 Strongly Named Assemblies


The software development process relies increasingly on integrating code with com-
ponents from multiple vendors. The vendor may be well known and trusted, or the
component may be Internet freeware from an unknown developer. In both cases,
security concerns demand a way to identify and authenticate the software. The most
reliable solution is to sign the software with some unique digital signature that guar-
antees its origin and trustworthiness. The certificates that identify the publisher of
downloadable software on the Internet are an example of this.
One of the integral parts of .NET security is the use of signing to create a uniquely
identifiable assembly. .NET refers to such an assembly as strongly named. A strongly
named assembly uses four attributes to uniquely identify itself: its file or simple
name, a version number, a culture identity, and a public key token (discussed later).
Together, these four are referred to as the “name” of the assembly. In addition, the
assembly has the digital signature created by signing it. All of this is stored in the
assembly’s manifest.2
Although an assembly does need to be strongly named, doing so offers several
advantages:

• It enables version control. Although you can add a version number to


an assembly, the Common Language Runtime (CLR) ignores the ver-
sion information unless the assembly is strongly named. As we will see
later, having versioning in effect permits multiple versions of a compo-
nent to run and ensures compatibility between assemblies sharing a
version number.

2. The manifest is a set of tables containing metadata that describes the files in the assembly.
15.2 Strongly Named Assemblies 687

• It permits an assembly to be placed in the Global Assembly Cache


(GAC) where it can be shared among calling assemblies.
• The .NET Code Access Policy can be used to grant or restrict permis-
sions to assemblies based on their strong name. In addition, the strong
name can also be used programmatically to control access to
resources.
• It allows assemblies to have the same name, because the assemblies
are identified by their unique information—not their name.

Creating a Strongly Named Assembly


The first step in creating a strong name is to create a pair of public and private
encryption keys. During compilation, the private key is used to encrypt the hashed
contents of the files contained in the assembly. The encrypted string that is produced
is the digital signature that “signs” the assembly. It is stored in the manifest, along
with the public key. Here are the steps to create and use a strongly named assembly:

1. A strong name is generated using asymmetric public key cryptography.


This scheme relies on a private key that is used for encryption and a
public key that decrypts. To create a file containing this key pair, use
the Strong Name command-line utility as shown here:
SN –k <keyfilename>
SN –k KeyLib.snk
2. Because the public key that is created is so large, .NET creates a more
manageable public key token that is a 64-bit hash of the public key.
When a client assembly is built that references a strongly named
assembly, the public key token of the referenced assembly is stored in
the manifest of the client assembly as a way to reference the target
assembly.
3. After you have the file containing the public/private key pair, creating
the strongly named assembly is simple: just place the AssemblyKey-
File attribute (located in the System.Refection namespace) in
your code at the assembly level:
[assembly: AssemblyKeyFile("KeyLib.snk")]
4. This statement causes the compiler to extract the private key from the
specified file and use it to sign the assembly. At the same time, the
public key is placed in the assembly’s manifest. Note that if you invoke
the C# compiler from the command line, you can leave out the
AssemblyKeyFile attribute and use the /keyfile flag to specify the
path to the .snk file.
688 Chapter 15 ■ Code Refinement, Security, and Deployment

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)

Compare hashed output for equality


Hash

Figure 15-3 Using private and public keys to sign and verify a strong assembly

Core Note

A digital signature is not the same thing as a digital certificate. The


digital signature in a strongly named assembly tells you nothing about
who created the assembly, whereas a certificate contains the identity
information that is used to authenticate the certificate publisher. Refer to
documentation on Authenticode to learn how to obtain and use a
certificate with an assembly.

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:

1. Sn.exe is used to create a file containing the public/private key pair.


This should be the task of the security administrator.
2. Sn.exe is run again to extract the public key into a separate file. The
command uses the –p switch, and specifies the original file and the file
to contain the public key:
SN –p KeyLib.snk PubKeyLib.snk
3. The public key file is distributed to developers who must add two
attributes to their assemblies to perform partial signing and have the
public key stored in the assembly’s manifest.
[assembly: AssemblyKeyFile("PubKeyLib.snk")]
[assembly: AssemblyDelaySign(true)]
4. If you follow these preceding steps to create an assembly, you’ll find
that an exception occurs (“strong name validation failed”)
when you try to load it. This is because the assembly does not have a
valid signature. To instruct the runtime to skip signature verification,
run Sn.exe with the –Vr switch and specify the delay-signed assembly:
SN –Vr <delay-signed assembly>
5. Prior to deployment, the assembly should be officially signed. Use SN
with the –R switch to sign it:
SN –R <delay-signed assembly>
To reinstate signature verification for the assembly, run
SN –Vu <assembly>

Because an assembly references another strongly named assembly using its public
key, there is no need to rebuild assemblies dependent on this one.

Global Assembly Cache (GAC)


The Global Assembly Cache is a special directory set aside where strongly named
assemblies can be stored and located by the CLR. As part of resolving references at
load time, the CLR automatically looks in the GAC for the requested assembly. The
obvious advantage of storing assemblies in the GAC is that they are located in a cen-
690 Chapter 15 ■ Code Refinement, Security, and Deployment

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:

>gacutil /i <assembly> Installs assembly in GAC.


>gacutil /u <assembly> Uninstalls an assembly from the GAC.
>gacutil /if <assembly> Installs assembly in GAC; if an assembly with that
name already exists, it is overwritten.
>gacutil /l Lists contents of GAC.

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

Assigning a Version Number to an Assembly


Every assembly has a version number. A default value is used if one is not explicitly
defined. The version can be assigned using the Assembly Linker (Al.exe) tool, but is
usually declared within the code using an AssemblyVersion attribute:

[assembly: AssemblyVersion("1.0.0.0")]

The version number has four parts:

<major version>.<minor version>.<build number>.<revision>

You can specify all the values or you can accept the default build number, revision
number, or both by using an asterisk (*). For example:

[assembly:AssemblyVersion("2.3.")] yields 2.3.0.0


[assembly:AssemblyVersion("2.3.*")] yields 2.3.1830,4000

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.

fxtest, Version=1.0.1839.24546, Culture=neutral,


PublicKeyToken=1f081c4ba0eeb6db
692 Chapter 15 ■ Code Refinement, Security, and Deployment

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

This abstract representation of .NET security shown in Figure 15-4 is imple-


mented in concrete types: The Evidence class is a collection that holds evidence
objects; permission classes grant access to a resource or the right to perform some
action; and a PermissionSet is a collection class that groups permissions and con-
tains methods to manipulate them. The following sections take a close look at evi-
dence, permissions, and how they are related. You’ll then see how to implement a
security policy, both as an administrator and by accessing the permission classes
through code.

Permissions and Permission Sets


A permission is the right to access a resource or perform some action. An assembly is
assigned a permission when its evidence matches the evidence requirements for a
permission set. Figure 15-5 illustrates how permissions come into play when an
assembly attempts to access a resource. In this case, assembly A calls assembly B,
which creates a FileStream object and attempts to use its Write method. The code
in the .NET Framework Class Library that implements this method demands that
the calling assembly have permission to perform file I/O. Moreover, it requires that
all assemblies further up the call stack also have this permission. This check—known
as walking the call stack—ensures that an assembly that does not have a required
permission cannot use one that does to illegally perform an operation.

Figure 15-5 An assembly must have permission to perform file I/O

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.

Named Permission Sets


.NET provides seven built-in or named permission sets. They range from the most
restrictive Nothing, which prevents an assembly from loading, to the least restrictive
Full-Trust, which permits unrestricted access to all of the permissions listed in
Table 15-1. Here they are in ascending order of trust:

• Nothing. Prevents an assembly from being loaded. This is used pri-


marily when the code is deemed untrustworthy because its origin
cannot be determined.
• Execution. Permits code to load and run, but little else. It cannot
access external resources. Such code is useful only for making calcula-
tions.
• Internet. Allows code to display and implement a user interface. By
default, .NET grants this permission to all code coming from the
Internet. Its permissions include the ability to open a file, perform
safe printing, create safe top-level and subwindows, and connect to
the originating site using HTTP or HTTPS.
• LocalIntranet. Specifies the default permissions for code originat-
ing in the local intranet. In addition to the permissions granted the
Internet set, its permissions include unrestricted access to FileDia-
logs, default printing privileges, and unrestricted user interface
access.
• Everything. Grants all permissions except permission to skip verifi-
cation, because this is needed to verify the code is type-safe.
• FullTrust. Grants access to all built-in permissions. All code run-
ning on the local machine is given this by default.

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

Figure 15-6 Permissions associated with selected named permission sets

Permission Set Attributes


The built-in permission sets provide a convenient way to request permission for
more than one permission type at a time. This code segment shows the syntax to
request permission for the LocalIntranet permission set. It attaches a Permis-
sionSetAttribute with a Name value set to the name of the desired permission set:

[PermissionSet(SecurityAction.Demand,Name="LocalIntranet")]
public string GetTitle() // Requires LocalIntranet
{}

Note that all named permission sets except Everything can be applied as an
attribute.

.NET Built-in Security Permissions


The individual permissions shown in Figure 15-5 are implemented in .NET as
built-in permission classes. In addition to being accessed by the security configura-
tion tools, they can also be accessed by code to implement a finer-grained security
than can be configured using administrative tools.
696 Chapter 15 ■ Code Refinement, Security, and Deployment

Table 15-1 summarizes the more important built-in permission classes.

Table 15-1 Built-in Permission Classes

Namespace Class Name Controls Permission To:

System.Security. Environment Access user and OS environment


Permissions variables

FileDialog Access files or folders using a file


dialog box.

FileIO Access files or folders.

IsolatedStorage Configure isolated storage and set


quota on size of user’s store.

KeyContainer Access key containers. A key con-


tainer is an area within a key data-
base that contains key pairs used for
cryptography.

Reflection Access metadata using Reflection


commands.

Registry Access system registry.

Security Access security permissions.

Store Stores containing X.509 certificates.


X.509 is a standard for public key
certificates. Most commonly used
for securing transmission of data
over Internet.

UI User interfaces and the clipboard.

System.Net Dns Domain name servers.

Socket Connect or accept connects at a


specified socket. A socket is an
address designated by a port # and
IP address.

Web Connect to or from a Web host.

System.Drawing Printing Access printers.

System.Data.Common DBData Access data using a data provider.


15.3 Security 697

Table 15-1 Built-in Permission Classes (continued)

Namespace Class Name Controls Permission To:

System.Data.SqlClient SqlClient Use SQL Server data provider.

System EventLog Write to or browse a machine’s event


log.

System.ServiceProcess ServiceController Access to control or browse a


machine’s services.

System.Messaging MessageQueue Access the message queue.

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

Table 15-2 Identity Permission Classes

Class Identity Represented

PublisherIdentityPermission The digital signature of the assembly’s publisher.

SiteIdentityPermission Web site where the code comes from.

StrongNamedIdentityPermission Strong name of the assembly.

URLIdentityPermission URL where the code comes from. This includes


the protocols HTTP, HTTPS, and FTP.

ZoneIdentityPermission Zone where the code originates: Internet,


Intranet, MyComputer, NoZone, Trusted,
Untrusted.

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.

How .NET Applies Security Policies


Each security policy level is made up of one or more code sets. Each code set, in
turn, contains a set of permissions that are mapped to a specific evidence type. Fig-
ure 15-8 illustrates how code sets and policy levels are combined to yield a permis-
sion set for an assembly.
The .NET security manager is responsible for evaluating evidence and policy to
determine the permissions granted. It begins at the enterprise level and determines
the permissions in it that can be granted to the assembly. In this example, enterprise
contains three code groups—two of which the assembly’s evidence satisfies. The log-
ical union of these permissions produces the permission set at this level. The other
two policy levels are evaluated in the same way, yielding their associated permission
set. The logical intersection of the three permission sets produces the permission set
that is assigned to the assembly. In this case, the final set consists of permissions 2
and 5—the only permissions present on each level.
702 Chapter 15 ■ Code Refinement, Security, and Deployment

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

Configuring Security Policy


Physically, each policy level is stored as a configurable XML file that defines a hierar-
chy of code groups for the policy. The Enterprise policy file, enterprisec.con-
fig , and the Machine policy file, security.config3, are stored in the same folder
on a Microsoft Windows system:

<Windows Directory>\Microsoft.NET\Framework\<Version>\config\

The User policy file is named security.config and is located on the path

<Documents and Settings>\<User Name>\Application Data\Microsoft\


CLR Security Config\<Version>\

Listing 15-2 contains an extract from the Machine policy file that illustrates the
file layout. It comprises four major sections:

<SecurityClasses> Defines all individual permissions.


<NamedPermissionSets> Defines named permission sets.
<CodeGroup> Provides a name and permission set for code group.
<FullTrustAssemblies> List of trusted assemblies.

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.

Extracts from the Machine Policy File


Listing 15-2
Security.Config

<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

Extracts from the Machine Policy File


Listing 15-2
Security.Config (continued)

... additional code groups here


</CodeGroup>
<FullTrustAssemblies>
<IMembershipCondition class="StrongNameMembershipCondition"
version="1"
PublicKeyBlob="00000000000000000400000000000000"
Name="mscorlib.resources"
AssemblyVersion="Version=2.0.3600.0"/>
<IMembershipCondition class="StrongNameMembershipCondition"
version="1"
PublicKeyBlob="00000000000000000400000000000000"
Name="System"
AssemblyVersion="Version=2.0.3600.0"/>
...
</FullTrustAssemblies>

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

The .NET Framework Configuration Tool


On a Microsoft Windows system, you start the Configuration tool by selecting
Microsoft .NET Framework Configuration from the Administrative Tools folder or by
selecting Run and typing MSCORCFG.MSC. The program interface consists of a
window divided into two panes. As shown in Figure 15-9, the left side contains a tree
structure comprising multiple folders. Of these, the Runtime Security Policy
folder expands to display a hierarchy of security information.
At the top level of the hierarchy are the three folders representing the Enter-
prise, Machine, and User policies. Beneath each of these are folders that contain
code groups, permission sets, and policy assemblies. This hierarchy is, of course, sim-
ply a visual representation of the underlying XML policy files. You can observe this
by comparing the raw XML tags in Listing 15-2 with the items displayed under the
Machine policy.
15.3 Security 705

[Child Code Groups]

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

Figure 15-9 Interface for the .NET Framework Configuration tool

The Default Configuration Policies


The Enterprise and User policies contain a single default code group named
All_Code. If you click it, you’ll see this description in the right panel:

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

My_Computer_Zone Code Group


This code group grants the FullTrust permission set to assemblies that satisfy the
My Computer zone evidence. Its two child code groups grant FullTrust to assem-
blies that have Strong Name evidence containing either the Microsoft public key or
the ECMA public key, also known as the Standard Public Key.4 It is important to
understand that an assembly can satisfy the Strong Name evidence without satisfy-
ing the My Computer zone evidence. This is because .NET evaluates child code
groups even if the conditions for the parent group are not satisfied. Conversely, a
code group’s properties can be set to instruct .NET not to evaluate child groups if the
conditions of the parent code group are satisfied.

LocalIntranet_Zone Code Group


This code group grants the LocalIntranet permission set to assemblies that satisfy
the Local Intranet zone evidence—any computers on the same local area network
as the machine on which the host code runs. This code group has two child code
groups: Intranet_Same_Site_Access and Intranet_Same_Directory_Access.
The former permits code to access the site of its origin; the latter permits code to
access its original install directory. The practical effect of these permissions is to permit
code to perform I/O on its local storage.

Configuring Code Access Security with the


Configuration Tool—An Example
To illustrate how to use the Configuration tool, we’ll create a new permission set and
assign it the Reflection and Execution permissions. Next, we’ll create a code

4. Assemblies based on the European Computer Manufacturers Association (ECMA) specifications


for the Common Language Infrastructure (CLI) contain an ECMA public key. These include
system.dll and mscorlib.dll. The key is actually a placeholder that is mapped to a key
pair provided by the particular CLR installation.
15.3 Security 707

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:

// (movieclass.dll) Will use reflection to access this class


using System;
public class Movies
{
private int ID;
private string Director;
public string title;
public string year;
}

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.

Listing 15-3 Assembly Requiring Reflection Permission


// configtest.cs
using System;
using System.Reflection;
class ClassEvidence
{
public static void Main()
{
Assembly ClientAssembly;
ClientAssembly = Assembly.Load("movieclass");
// Get the desired Type in the Assembly
Type myType = ClientAssembly.GetType("Movies");
// Get the FieldInfo for private field "Director".
// Specify nonpublic field and instance class.
// Accessing private members requires Reflection
// Permission.
FieldInfo myFieldInfo = myType.GetField("Director",
BindingFlags.NonPublic | BindingFlags.Instance);
if (myFieldInfo !=null)
Console.WriteLine("Field: {0} Type: {1}",
myFieldInfo.Name,
myFieldInfo.FieldType);
708 Chapter 15 ■ Code Refinement, Security, and Deployment

Listing 15-3 Assembly Requiring Reflection Permission (continued)


{
// output: Field: Director Type: System.String
} else {
Console.WriteLine("Could not access field.");
}
}
}

Creating a Permission Set


Follow these steps to create the permission set:

1. Right-click the Permission Sets folder under Machine policy and


select New.
2. Enter Reflection as the name of the set and provide a description.
3. On the next screen, select Security from the Available Permissions.
Check Enable Assembly Execution and Allow Evidence Control from
the permission settings window.
4. Select Reflection from the Available Permissions. Check Grant
Assemblies Unrestricted Permission to Discover Information About
Other Assemblies.
5. Click Finish and the permission set named Reflection appears in
the left pane.

You now have a permission set that allows an assembly the rights associated with
the Reflection permission.

Creating a Code Group


Follow these steps to create the code group:

1. Right-click the All_Code node—under Machine-Code Groups—and


select New.
2. Enter My_Computer_Url as the name of the group and provide a
description.
3. For the Condition Type, choose URL.
4. For the URL, enter file://c:/cas/*. This specifies the folder from
which an assembly must originate in order to satisfy the Url evidence.
5. Next, assign the new Reflection permission set to the code group
and click Finish.
15.3 Security 709

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.

Testing the New Code Group


To demonstrate the effects of this new code group, compile the program and store a
copy of configtest.exe in C:\ and C:\CAS\. Run both copies from the command
line, and they should succeed. Now, use the Configuration tool to change the permis-
sion set for the My_Computer_Url code group from Reflection to Internet.
When you now run the program from the CAS subdirectory, it fails because the
Internet permission set does not include the Reflection permission. The pro-
gram still runs fine from C:\, because its permissions come from the other code
groups in the Machine policy.
This is a simple example, but it illustrates the core principle behind Code Access
Security of assigning permissions to an assembly based on an analysis of its evidence.
In this case, identical assemblies receive different permissions based on the directory
in which the code resides. The evidence is a Url specified directory, but could just as
easily be an Internet site, the assembly’s Strong Name, or its security zone.

Determining the Permissions


Granted to an Assembly
It is not easy to empirically determine the permissions that are granted to a given
assembly. You must gather the evidence for the assembly, determine the security pol-
icies in effect from the XML policy files, and evaluate the evidence in light of the pol-
icies. Fortunately, the .NET Configuration tool and Caspol utility can perform this
evaluation for you.
To use the Configuration tool, right-click the Runtime Security Policy folder
and select Evaluate Assembly from the context menu. Use the Browse button to
locate the assembly in the file directory. Select View Permissions Granted to Assem-
bly, and click the Next button to display the individual permissions. For
c:\cas\configtest.exe from the preceding example, Security and Reflec-
tion are listed. For c:\configtest.exe, Unrestricted is listed.
710 Chapter 15 ■ Code Refinement, Security, and Deployment

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:

C:\>caspol –rsp c:\cas\configtest.exe

Output is in an XML format and correctly includes Security and Reflection,


as well as two identity permissions: UrlIdentity and ZoneIdentity (MyCom-
puter). The identity permissions can be ignored because specific permissions are
provided.
You can also use caspol to understand why an assembly qualifies for its permis-
sions by identifying the code groups that the assembly belongs to. Enter the previous
command, except replace the –rsp option with the –rsg (resolve code group)
option:

C:\>caspol –rsg c:\cas\configtest.exe

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

Determining the Permissions


Required by an Assembly
One of the objectives of designing an effective security policy is to grant an assembly
only those permissions it requires, and no more. The ease of using predefined per-
mission sets should be resisted when it allows an assembly to access resources that it
does not require. In the preceding example, the configtest assembly required
only the capability to execute and access members of the Reflection namespace.
To satisfy these narrow requirements, we created a custom permission set containing
the Security and Reflection permissions. This custom permission set was clearly
a better choice than the unrestricted permissions offered by Everything and Full-
Trust—the only predefined permission sets granting the Reflection permission.
Given the obvious advantage of using customized permission sets, the question
becomes how an administrator identifies the minimum permissions required by an
assembly. .NET offers two utilities for this purpose: PermView and PermCalc. Perm-
View, which is discussed in the next section, displays the permissions explicitly
requested—using attributes—by an application; PermCalc evaluates an assembly
and produces serialized XML output of the required permission classes. It is run
from the command line, as shown here:

C:\>permcalc c:\cas\configtest.exe

Evaluating an assembly to determine its required permissions is a difficult task


and not always guaranteed to provide accurate results. In fact, when the utility is run
against configtest, it displays FileIOPermission—but not Reflection—as a
required permission. However, if we add an attribute (described next) to the code
requesting the Reflection permission, the utility correctly displays this as a
required permission. As a rule, the only way to be certain which permissions an
assembly requires is to declare them in the code—our next topic.

Requesting Permissions for an Assembly


Including permission attributes in code is referred to as declarative security, and it
serves two primary purposes: When applied at the assembly level, the attribute
serves to inform the runtime which permissions the assembly requires—or does not
require—to function; when applied at the class or method levels, it protects
resources by demanding that callers possess specific permissions to access a resource
through the current assembly. Attributes used for this latter purpose trigger or mod-
ify a stack walk that verifies all assemblies in the call chain can access the called
assembly. We’ll examine this in “Programmatic Security” on page 715. For now, the
focus is on how to use assembly-level attributes to adjust security within a program.
712 Chapter 15 ■ Code Refinement, Security, and Deployment

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:

• An assembly cannot receive any more permissions than are defined by


the security policy rules—no matter what it “requests.”
• Although requests cannot cause code to receive extra permissions
to which it is not entitled, it can influence the runtime to deny
permissions.
• An assembly receives permissions whether it requests them or not.
The example shown in Listing 15-3 does not include a permission
attribute, but it receives all the permissions that it qualifies for based
in the evidence it provides the runtime.

Despite the fact that you do not have to include permission requests in your code,
there are important reasons for doing so:

• It allows PermView to display the permissions required by an assem-


bly and also improves the reliability of PermCalc. This provides a
quick way for administrators to collect assembly requirements and
design an appropriate security policy.
• Permission attribute information is stored in the assembly’s manifest
where it is evaluated by the CLR when loading the assembly. This per-
mits the runtime to prevent an assembly from executing if it does not
have the requested permissions. In most cases, this is preferable to
having the program begin execution and then shut down unexpectedly
because it has inadequate permissions.
• It enables code to specify only those permissions it needs. So why are
extra permissions a problem if you don’t use them? Because the code
may have a bug, or exploitable feature, that malicious calling code can
use to take advantage of the “extra” permissions.

For a full trust environment where code has unrestricted permissions—typically


in-house applications—security is not a significant factor, and it may not be necessary
to apply security attributes. However, if you operate in a security-conscious environ-
ment or are creating components for use by other software, you should include secu-
rity attributes as a means of providing self-documenting security.

How to Apply a Permission Attribute


Because the permission attribute is to have assembly scope, it is declared in the first
line of code following the using statement(s):
15.3 Security 713

[assembly : PermissionAttribute(
SecurityAction.membername,
PermissionAttribute property)
]

Let’s examine its construction:

Assembly Indicates the attribute has assembly scope.


PermissionAttribute The permission class being requested.
SecurityAction An enumeration describing the type of permission
request. Three values can be assigned to the assembly
scope:
RequestMinimum—The minimum permissions required
to run.
RequestOptional—Permissions the code can use but
does not require. This implicitly refuses all other permis-
sions not requested. Be careful with this, because it is not
obvious that a request for one permission causes all other
non-requested permissions to be denied.
RequestRefuse—Permissions that should not be
assigned to the assembly even if it qualifies for them.

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.

Testing a Permission Attribute


To demonstrate the effects of applying a permission attribute, let’s add this statement
to the source for configtest, shown in Listing 15-3:

//place on line preceding: class ClassEvidence


[assembly : ReflectionPermission(
SecurityAction.RequestMinimum,
Flags=ReflectionPermissionFlag.TypeInformation)
]

The ReflectionPermission attribute class is used to request the minimum


permissions required to run. The second parameter, TypeInformation, is an enu-
meration property of the class that permits reflection on nonvisible members of a
class. (Recall that the sole purpose of this code is to use reflection in order to access a
private field on a class.)
714 Chapter 15 ■ Code Refinement, Security, and Deployment

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.

Table 15-3 Effect of Changing Permission Attribute on Assembly

Attribute Parameters Result Explanation

SecurityAction.RequestMinimum Succeeds RequestMinimum ensures that


assembly is granted all permissions
Flags = ReflectionPermission- determined by security policy.
Flag.TypeInformation Includes Reflection.
TypeInformation permits
reflection on members that are not
visible.

SecurityAction.RequestMinimum Succeeds RequestMinimum ensures that


assembly is granted all permissions
Flags = ReflectionPermission- determined by security policy.
Flag.ReflectionEmit Includes Reflection.
ReflectionEmit parameter does
not specify the permission
required by the assembly.

SecurityActionRequestOptional Fails RequestOptional causes all


non-requested permissions to be
Flags = ReflectionPermission- denied.
Flag.ReflectionEmit
Because ReflectionEmit does
not provide permission to access a
non-public field, the assembly
fails.
15.3 Security 715

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

Figure 15-10 Invoking the Permission.Demand method triggers a stack walk


716 Chapter 15 ■ Code Refinement, Security, and Deployment

The Demand method is used extensively throughout the .NET Framework to


ensure that applications requesting operations such as file I/O or database access
have the proper permissions. Although most calls to the Framework Class Library
(FCL) are protected in this way, it can still be useful to demand a stack walk within
your code. You may want to apply even stricter permission requirements than the
FCL demands, or you may want to ensure that a lengthy operation has all the
required permissions before it is launched.
To provide a simple illustration, let’s create a component that returns information
about a requested movie from our Films database (see Chapter 11). Listings 15-4
and 15-5 contain the component and client code, respectively. For brevity, the
ADO.NET code is excluded, but is available as downloadable code.
Because the SqlClient permission is required to access a SQL Server database,
the component includes the following code to ensure that the calling assembly has
this permission:

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.

Listing 15-4 Component to Illustrate Code Access Security


// filmcomponent.cs (.dll)
using System.Data.SqlClient;
using System;
namespace moviecomponents
{
public class MovieData
{
// Return MovieProfile object
public static MovieProfile GetMovie(string movietitle)
{
// Return null if movie not found
MovieProfile mp = null;
// Demand SqlClient permission from methods on call stack
SqlClientPermission sqlPerm= new
SqlClientPermission(PermissionState.Unrestricted);
sqlPerm.Demand();
15.3 Security 717

Listing 15-4 Component to Illustrate Code Access Security (continued)


//*** Code here to query database for movie information
//*** Requires SqlClient permission
return mp;
}
}
public class MovieProfile
{
private string pTitle;
private int pYear;
private int pRank;
private string pOscar;
public MovieProfile(string title, int year,
int afiRank, string bestFilm)
{
pTitle= title;
pYear = year;
pRank = afiRank;
pOscar = bestFilm;
}
// Readonly properties
public string Title
{
get{return pTitle;}
}
public int Year
{
get{return pYear;}
}
public int Ranking
{
get{return pRank;}
}
public string BestPicture
{
get{return pOscar;}
}
} // class
} // namespace
718 Chapter 15 ■ Code Refinement, Security, and Deployment

Listing 15-5 Assembly to Access Film Component


// movieclient.cs (.exe)
using System;
using moviecomponents;
namespace MovieClient
{
class MovieMgr
{
static void Main(string[] args)
{
string myMovie;
if(args.Length>0)
{
myMovie= args[0];
// Call component to fetch movie data
MovieProfile mp = MovieData.GetMovie(myMovie);
if(mp==null)
{
Console.WriteLine("Movie not found");
}
else
{
Console.WriteLine("Year:{0} AFI Rank: {1}",
mp.Year, mp.Ranking);
}
}
}
} // class
} // namespace

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:

C:\>csc /t:library filmcomponent.cs


C:\>csc /r:filmcomponent.dll movieclient.cs
C:\>movieclient casablanca
Year: 1942 AFI Rank:2

Because code running on a local machine has unrestricted permissions (by


default), the stack walk verifies that movieclient has the necessary permission. To
make the example more interesting, let’s run the client when it does not have the
SqlClient permission. The easiest way to remove this permission is by adding an
assembly-level permission attribute that explicitly refuses the permission. You do this
15.3 Security 719

by passing the SecurityAction.RequestRefuse enumeration to the constructor


of the permission attribute you do not want granted to the assembly. To refuse the
SqlClient permission, place this statement before the namespace statement in the
client code:

[assembly : SqlClientPermission(SecurityAction.RequestRefuse)]

Building and running the modified SqlClient results in a security exception


being thrown.

Stack Walk Modifiers


Just as a called assembly has the rights to initiate a stack walk, the objects on the call
stack have the right to modify the behavior of the stack walk. For example, they can
cause the walk to fail, or stop the walk at the current object so that it does not check
objects further up the stack. These capabilities have significant security implications:
inducing a stack walk failure is a way for a client to restrict what actions a called
assembly may perform; and terminating a stack early can improve performance by
eliminating unnecessary steps in a repeated stack walk.
The ability to modify a stack walk is provided by methods in the IStackWalk inter-
face (refer to Figure 15-7) that all permissions must implement. This interface defines
the Demand method that initiates a stack walk, and three other methods, Assert,
Deny, and PermitOnly, that modify a stack walk’s normal operation. Assert stops the
stack walk at the current stack frame; Deny causes the walk to fail; and PermitOnly
specifies the only permission that an object will allow a stack walk to verify.
To demonstrate these methods, let’s modify the code in Listing 15-5 to implement
each method. First, we add namespaces so that we can access IStackWalk and the
permission:

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.

// Remove effects of previous PermitOnly call


CodeAccessPermission.RevertPermitOnly();
stackWalker = new ReflectionPermission(
ReflectionPermissionFlag.TypeInformation);
// Allow stack walk to verify Reflection permission only
stackWalker.PermitOnly();
// Now call a method that requires Reflection
15.3 Security 721

Declarative Security Using Permission Attributes


The security technique just described that explicitly calls a permission’s Demand
method to invoke a stack walk is referred to as imperative security. An alternative
form of security that uses attributes to achieve the same effect is known as declara-
tive security. We looked at one version of this in the earlier discussion of how to use
permission attributes to request permissions. In this section, we’ll see how the other
form of declarative security is used to attach attributes to classes or methods to verify
that clients have the necessary permissions to use current code.
The easiest way to explain declarative security is to compare it with its imperative
counterpart. Here are the statements from Listing 15-4 that trigger a stack walk.
Below it is an equivalent attribute declaration that results in the same stack walk.

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

• Demand. Triggers a stack walk at runtime that requires all callers on


the call stack to have a specified permission or identity.
• LinkDemand. Only the immediate caller is required to have a speci-
fied permission or identity. This check occurs during loading and elim-
inates the performance penalty of using a stack walk. However,
because all objects on the call stack are not checked, this should only
be used when you are certain the call stack is secure.
• InheritanceDemand. Requires that any subclass inheriting from the
current code has required security permissions. Without this check,
malicious code could use inheritance or the capability of overriding
protected methods, for its own purposes. This check occurs during
loading.
722 Chapter 15 ■ Code Refinement, Security, and Deployment

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.

15.4 Application Deployment


Considerations
The purpose of this section is to take a broad look at the issues that should be consid-
ered when deciding on a strategy for installing your application. Much of the discus-
sion centers on practical issues such as how deployment is affected by the use of
public or private assemblies, how configuration files are used to tell the CLR where
to search for an assembly, and how configuration files ease the problems of distribut-
ing new versions of an assembly.

Microsoft Windows Deployment: XCOPY


Deployment Versus the Windows Installer
Almost always included in the list of the top .NET features is the promise of using
XCOPY deployment to install your application on client machines. The idea is to cre-
ate a simple script that uses the XCOPY utility to copy your assemblies and resource
files to the specified directories on the client’s machine. It is a terrific idea and
works—if you have an application with only a few assembly files that can be stored in
a local directory. However, there are a lot of installation scenarios where XCOPY
does not work: It cannot be used to register a COM component in your application,
and it cannot be used to store an assembly in the GAC. In addition, for a professional
application, XCOPY does not provide the well-designed interface that customers
expect.
The most popular alternative to XCOPY is the Microsoft Windows Installer that
ships with most of its operating systems and is also available as a separate download.
15.4 Application Deployment Considerations 723

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.

Deploying Assemblies in the


Global Assembly Cache
.NET supports two assembly deployment models: private and shared. A private
assembly is identified by its file name—without the extension—and is stored locally
with other assemblies that it references or that reference it. Shared assemblies are
stored in the Global Assembly Cache. Although this is a convenient way to make an
assembly available to multiple applications, it does present an installation problem.
Recall from the earlier discussion that an assembly cannot be copied into the GAC;
724 Chapter 15 ■ Code Refinement, Security, and Deployment

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.

Deploying Private Assemblies


The easiest way to deploy an application with multiple private assemblies is to place
all of the assemblies in the same directory. This home directory, referred to as the
application’s base directory, is the first place the CLR looks when trying to locate an
assembly. However, using a single directory prevents assemblies from being grouped
into folders that logically describe their purpose. For example, an application may
have a set of assemblies dedicated to graphics and another dedicated to database
access. Placing these in \Graphics and \Data subfolders represents a more logical
and easier-to-maintain code deployment.
To see how to set up a meaningful directory structure for an application, let’s first
look at how the CLR locates private assemblies. This discovery process, called prob-
ing, begins with the application’s base directory. If the assembly is not located there,
it searches for an immediate subdirectory having the same name as the target assem-
bly. For example, if the assembly is myClass, it looks first for the directory
myClass.dll and then myClass.exe. The CLR loads the assembly from the first
directory in which it locates it.
In our case, we want to use a directory structure that is not based on the name of
the assemblies. To force the CLR to extend its probe into other folders, we must add
a special <probing> element to the application’s configuration file. For an applica-
tion named myApp.exe, we create a configuration file named myApp.exe.config
and store it in the application’s base directory. Included in the file is a <probing> tag
with a privatePath attribute that specifies the subdirectories (below the base
directory) for the CLR to search.

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

Using CodeBase Configuration


A <codebase> element provides another way to specify the location of an assembly.
It differs from the <probing> element in that it can specify any directory on the
machine, as well as a location on a file or Web server across a network. The CLR uses
the URI information provided by this element to download an assembly the first time
it is requested by an application. Notice how <codebase> is used in the following
sample configuration file.
The <codebase> element, along with a companion <assemblyIdentity> ele-
ment, is placed inside the <dependentAssembly> block. The <assemblyIden-
tity> element provides information about the assembly the CLR is attempting to
locate: its simple name, public key token, and culture. The <codebase> element
provides the assembly’s version number and location where it can be found. If the
assembly is not strongly named, the version information is ignored.

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

The href can also refer to the local file system:

<codeBase version="1.0.0.0"
href="file:///e:\movieclass.dll" />
726 Chapter 15 ■ Code Refinement, Security, and Deployment

Using a Configuration File to Manage


Multiple Versions of an Assembly
One of the advantages of using a strongly named assembly is the version binding fea-
ture it offers. When Assembly A is compiled against Assembly B, A is bound to the
specific version of B used during the compilation. If Assembly B is replaced with a
new version, the CLR recognizes this at load time and throws an exception. It is a
good security feature, but can make updates to B more time-consuming because all
assemblies dependent on it must be recompiled.
A configuration file can be used to override version binding by instructing the
runtime to load a different version of the assembly than was originally bound to the
calling assembly. This redirection is achieved by adding a <bindingRedirect> ele-
ment that specifies the original version and the new version to be used in place of it.
Here is how the previous configuration file is altered to have a newer version of the
movieclass assembly loaded:

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

Use of the <bindingRedirect> element inside an application configuration file


offers a flexible and practical approach to matching applications with new or older
versions of a component. For example, both versions of the component can run side
by side on the same machine. One application can continue to use the older version,
whereas the configuration file of a second application is modified to direct it to the
newer component. Moreover, if the second application encounters problems with
the new component, the <bindingRedirect> element can be removed and the
application will revert to using the original component.
15.4 Application Deployment Considerations 727

Assembly Version and Product Information


As a final note on deployment, it is good practice to include assembly-level attributes
to your assembly to provide useful information to clients that want to examine the
properties of your .exe or .dll file. As an example, the attributes in the following
code yield the properties shown in Figure 15-11:

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

Figure 15-11 Attributes provide information identifying an assembly


728 Chapter 15 ■ Code Refinement, Security, and Deployment

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.

15.6 Test Your Understanding


1. Indicate whether the following are true or false:
a. A strongly named assembly must be stored in the GAC.
b. When compiling an application, the CLR automatically searches
the GAC for a referenced assembly.
c. When loading an assembly, the CLR automatically searches the
GAC for a referenced assembly.
d. A public key token must exist in the manifest of an assembly
referencing a strongly named assembly.
15.6 Test Your Understanding 729

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"

3. What are the four parts of an assembly’s strong name?

4. How do you assign a version number to an assembly? How are default


values for the build number and revision determined?

5. What three methods are used to modify a stack walk? Describe the
role of each.

6. What is delayed signing, and why is it used?

7. Identify each of the following: predefined permission, named permis-


sion set, and security zone.

8. Indicate whether the following statements about assembly deploy-


ment are true or false:
a. Only a strongly named assembly can be placed in the GAC.
b. The <probing> element must specify a directory below the
application’s base directory.
c. The <codeBase> element is used to redirect the CLR to use a
newer version of an assembly.
d. XCOPY deployment can be used to install shared assemblies.

9. What element or elements need to be added to this application config-


uration file so that the application will access the new version 2.0.0.0
of this component, rather than the previous 1.0.0.0 version? Both the
application and component reside in c:\data\.
<dependentAssembly>
<assemblyIdentity name="movieclass"
publicKeyToken="1F081C4BA0EEB6DB"
culture="neutral" />

---> Insert code here


</dependentAssembly>
PROGRAMMING
FOR THE
INTERNET
IV

■ 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

Topics in This Chapter

• Web Client-Server Communications: Web page requests and


responses rely on HTML to represent content, and HTTP to define
the protocol that governs client-server interaction. JavaScript, ASP,
and ASP.NET are compared as ways to develop an application.
• Structure of an .aspx Page: An .aspx Web page is compiled as
a class. It contains a variety of directives that control the page’s
behavior and can link it to external code. This section looks at the
basic elements that comprise an .aspx file, including how
Viewstate is used to maintain state information for the form.
• Inline and Code-Behind Pages: ASP.NET offers three models for
designing a Web application: inline code, code-behind, and
code-behind with partial classes. The latter two permit the page
interface to be separated from the program logic. The
implementation code is placed in a code-behind file that is
referenced by an .aspx page.
• HTML Server-Side Controls and Web Controls: ASP.NET provides
controls that correspond to HTML tags and are accessed as
classes in the server-side code. It also provides a number of native
“Web” controls that include features not found on standard HTML
controls. These include validation controls, data source controls,
and sophisticated data display controls such as the DataList,
GridView, and TreeView.
• Master and Content Pages: ASP.NET supports the creation of a
master Web page that serves as a template for other “content”
pages that visually inherit from it. This chapter explains how to
create master pages containing special “place holder” controls
that define where derived content pages may insert their custom
content.
16

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

16.1 Client-Server Interaction


over the Internet
At its core, the Internet consists of resources and users who want to access
Web-based resources. Three mechanisms are required to enable this access: a nam-
ing scheme (URI) to locate the resources, a protocol (HTTP) defining how the
request/response process works, and a language (HTML) for publishing information
and providing a means to navigate to the resources. This environment is quite differ-
ent than that facing the Windows Forms programmer and presents the Web applica-
tion developer with three major challenges:

• HTML. The developer must be familiar with the Hypertext Markup


Language in order to create Web Forms. Certainly, HTML generators
are a useful accessory, but at some point, the developer has to under-
stand the raw HTML.
• HTTP. The primary task of a Web application is to respond to HTTP
requests. These requests may be sent by either an HTTP GET or
POST method and may contain headers in addition to the main mes-
sage body. The language used by the Web developer must provide
access to the full HTTP message. For example, to process a received
form, a Web application must be able to extract data from fields on the
form. Header information can be equally important. It can be used to
control caching, identify the source of the request, and pass cookies
between the client and server.
• State. There is no intrinsic feature in HTML or HTTP that maintains
the state of variables during the request/response operation. Figure
16-1 illustrates this. The client receives an empty survey form, fills it
out, and posts it back to the server. The server detects an error and
returns the same Web page—which is blank again. Preserving data
(state) over the round trip(s) between client and server is, perhaps, the
biggest challenge facing the Web programmer.

One of the best ways to understand and appreciate a technology is to compare it


with other technologies that perform a similar task. With that in mind, this section
takes a simple Web application and implements it in JavaScript, ASP, and ASP.NET.
The objectives are twofold: to understand the problems associated with implement-
ing a Web Forms application, and to illustrate the evolutionary approach that
ASP.NET uses to meet these challenges. You do not need experience with ASP or
JavaScript to understand the examples, but it helps. Their function is to demonstrate
how the HTTP GET and POST methods send data to a server and maintain state
information. The examples could just as easily use Perl or PHP scripting.
16.1 Client-Server Interaction over the Internet 735

Client

Receive Web Server


Empty survey.htm
Form
1

POST process.htm
Filled
Form 2
3

Receive survey.htm
Empty
Form

Figure 16-1 Request/response process

Web Application Example:


Implementing a BMI Calculator
This application calculates the Body Mass Index (BMI) for a given height and weight.
The user enters this information (see Figure 16-2) into a form and then clicks Submit
Form to send the request to a server. The server calculates the BMI and returns a
Web page that includes the BMI and original data entered.

Figure 16-2 BMI Web calculator


736 Chapter 16 ■ ASP.NET Web Forms and Controls

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.

A JavaScript BMI Calculator


JavaScript remains the primary language used for client-side coding on a Web appli-
cation. It is platform and browser independent (although there are a few exceptions).
As we will see later, ASP.NET automatically generates JavaScript for its client-side
processing.
The first example uses JavaScript on both the client and server side. Listing 16-1
contains the HTML and JavaScript that comprise the client-side page. For brevity,
some of the less important code is excluded.
The HTML in the <body> defines the fields and buttons on a form bmi_input.
This form defines GET as the HTML method for delivering the contents of the form
to the server. When the button is clicked, control passes to the JavaScript function
post that verifies the content of the form and sends it to the server. We’ll get to the
other JavaScript code after we look at the server side.

JavaScript/HTML to Display a BMI Calculator—


Listing 16-1
bmi.htm

<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

JavaScript/HTML to Display a BMI Calculator—


Listing 16-1
bmi.htm (continued)

{
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>&nbsp;</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>&nbsp;</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

Figure 16-3 Passing data in a query string

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.

An ASP (Active Server Pages) BMI Calculator


JavaScript is intended primarily for client-side scripting and cannot process data sent
by a POST method. This example replaces the JavaScript server code with an ASP
file that handles the POST request. We change one statement in the client code to
indicate that POST is being used and to provide the name of the ASP server page:

<FORM NAME="bmi_input" method=POST action=bmicalculator.asp>

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.

<script language="VBScript" runat="server">


function getBMI(inch,feet,pounds)
totinches = feet*12 + inch
h2 = totinches * totinches
getBMI= (pounds * 703 * 10/ h2)/10
end function
</script>
<%
' ... POST data is available in request object
inch = request("hti")
feet = request("htf")
pounds = request("wt")
bmi= left(cstr(getBMI(inch,feet,pounds)),4)
740 Chapter 16 ■ ASP.NET Web Forms and Controls

' ... return value and original parameters as a query string


hrefstr = "&htf=" +cstr(feet) + "&hti=" + cstr(inch) +
"&wt="&cstr(pounds)
response.redirect ("bmi3.htm?bmi="+bmi+hrefstr)
%>

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.

Using ASP.NET to Implement a BMI Calculator


Before building an ASP.NET solution for the calculator, let’s first take a general look
at the features of ASP.NET and how they affect Web Forms programming. One way
to understand the Web Forms model is to look at the requirements faced by the
ASP.NET designers—and how they met them.

• Integration with the .NET environment. Web Forms are built on


the Common Language Runtime (CLR) and have access to all types in
the Framework Class Library, as well as special namespaces, such as
System.Web and System.Web.UI that support Web applications. An
important aspect of .NET integration is that it is required only on the
server side; users are not required to install the .NET Framework on
their computers. The emphasis is on rendering code that runs unaided
on a browser. In fact, the emitted code is tailored to recognize the
browser it is running on, so it can take full advantage of its features.
• Linking client-side HTML controls with server-side controls. All
controls in ASP.NET are classes. Controls that appear on a browser win-
dow are renderings based on HTML tags. To reconcile this differing
technology, ASP.NET includes server-side controls that map directly to
HTML tags. For example, the <input type = text> tags we saw in
the earlier examples are represented by the HtmlInputText class
found in the System.Web.UI.HtmlControls namespace. When
information on a Web page is sent to a server, all HTML tags designated
to run on the server are converted into their corresponding .NET class
and compiled as members of the Web page. We’ll see how HTML tags
are mapped to server controls in the next example.
• Compiled rather than interpreted Web pages. Traditionally, Web
pages have been rendered by JavaScript, VBScript, Perl, and other
scripting interpreters. In contrast, an ASP.NET Web page consists of a
user interface defined by HTML, and interface logic written in a
.NET language such as C# or VB.NET. The first request for an ASP
16.1 Client-Server Interaction over the Internet 741

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.

ASP.NET offers three models for implementing a Web application:

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

Inline Code Model


The code for the BMI Web Form application is shown in Listing 16-2. Although it
resembles an HTML page, an .aspx page is actually an XML-formatted page. It is
processed on the server and used to generate a mixture of HTML and JavaScript that
is sent to the browser.
Notice that this example contains actual C# code—between the <script>
</script> tags—that calculates the BMI value. This method is executed on the
server and is not visible as source code to the client.

ASP.NET Inline Code Implementation of a BMI


Listing 16-2
Calculator
<%@ Page Language="C#" %>
<HTML>
<HEAD><TITLE>BMI Calculator</TITLE>
<script runat="Server">
// Calculate BMI from values on form
742 Chapter 16 ■ ASP.NET Web Forms and Controls

ASP.NET Inline Code Implementation of a BMI


Listing 16-2
Calculator (continued)
private void getBMI(object sender, System.EventArgs e)
{
try
{
decimal f = Convert.ToDecimal(htf.Value);
decimal inch = Convert.ToDecimal(hti.Value);
decimal w = Convert.ToDecimal(wt.Value);
decimal totinches = f * 12 + inch;
decimal h2 = totinches * totinches;
decimal massIndex = (w * 703 * 10/ h2)/10;
bmi.Value = massIndex.ToString("##.##");
}catch (Exception ex)
{ bmi.Value=" "; }
}
</script>
</HEAD>
<BODY bgcolor=#ffffff>
<FORM NAME="bmi_input" runat=server>
<table border=0 cellpadding=2 cellspacing=0 width= 180
bgcolor=#cccccc>
<tr><td colspan=3 align=center><font size=2>
<b>BMI Calculator</b>
</td></tr>
<tr><td><font size=2 ><b>BMI:</b></td>
<td colspan=2 ><input type=text size=5 id=bmi
runat=server></td></tr>
<tr><td colspan=3><hr size=1></td>
</tr>
<tr><td><font size=2 >Height:</td>
<td><input type=text size=3 id=htf maxlength=1
runat=server></td>
<td><input type=text size=3 id=hti maxlength=2
runat=server></td>
</tr>
<tr><td>&nbsp;</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 id=wt maxlength=3
runat=server></td>
</tr>
<tr><td colspan=3 align=center>
16.1 Client-Server Interaction over the Internet 743

ASP.NET Inline Code Implementation of a BMI


Listing 16-2
Calculator (continued)
<INPUT TYPE="button" VALUE="Submit Form"
OnServerClick="getBMI"
id=bmiButton runat=server>
</td>
</tr>
<tr><td colspan=3>&nbsp;</td>
</tr>
</table>
</FORM>
</body>
</html>

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.

• The most obvious difference is the addition of the runat=server


attribute in the <Form> and <Input> tags. This designation converts
any HTML elements to HTML server controls that can be processed
by the server-side code prior to sending them to the browser. The fol-
lowing code illustrates how an <input> tag is transformed before it
emitted to the client’s browser:
ASP.NET statement:
<input type=text size=3 id=wt maxlength=3 runat=server>
HTML statement emitted:
<input name="wt" id="wt" type="text" size="3"
maxlength="3" value="168" />
• The <Form> tag does not include the method or action attribute. By
default, the POST method is used. As the following code shows, the
page is self-referencing, which means the page sends the form to
itself.
ASP.NET statement:
<FORM NAME="bmi_input" runat=server>
HTML statement emitted:
<form name="_ctl0" method="post" action="bmi.aspx"
id="_ctl0">
• The <button> tag contains an OnServerClick event delegate. This
indicates that a method on the server will handle the event. The
744 Chapter 16 ■ ASP.NET Web Forms and Controls

resulting HTML references a JavaScript function that has been added


to post the form when the button is clicked.

HTML statement emitted:


<input language="javascript" onclick =
"__doPostBack('_ctl1','')" name="_ctl1"
type="button" value="Submit Form" />

ASP.NET also adds three hidden fields to the HTML page it returns to the
browser:

<input type="hidden" name="__EVENTTARGET" value="" />


<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE"
value="dDwxMzc5NjU4NTAwOzs+iIczTTLHA74jT/02tIwU9FRx5uc=" />

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

By default, a machine-specific authentication code is calculated on the data and


appended to the view state string. The full string is then Base64 encoded. It is possi-
ble to decode the string, but the difficulty in doing so will thwart most casual efforts.
Tampering with the string can also be detected by the server and results in a security
exception being thrown. As always, use Secure Sockets Layer (SSL) to ensure abso-
lute security for Internet communications.

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:

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

Then, to enable view state for an individual control, apply the attribute as shown
in the following code:

<input type=text size=3 id=wt maxlength=3 EnableViewState=true


runat=server >

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.

The @Page Directive


The last element to discuss in Listing 16-2 is the @Page directive. An .aspx file can
contain only one @Page directive, and it is typically—although not required—the
first statement in the file. The purpose of this directive is to assign attribute values
that control the overall behavior of the Web page, as well as specify how the page is
assembled and compiled on the server. Table 16-1 lists selected attributes for the
@Page directive.

Table 16-1 Attributes for the @Page Directive

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.

SmartNavigation = bool Setting this to true can improve the rendering of


pages for users of Internet Explorer 5.0 and later. Its
improvements include
• Eliminating flickering when a page loads.
• Retaining the input focus on the field last having it.
• Preserving the scroll position on pages longer than
one screen.

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

Table 16-1 Attributes for the @Page Directive (continued)

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.

UICulture = id Specifies the user interface culture for this page.


Example: UICulture="de"

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.

Language = language Specifies the language for inline code.

Codebehind = *.dll Specifies the name of a compiled code-behind file.


This file must be in the \bin subdirectory of the
application.

Codefile = *.cs Specifies a code-behind file containing a partial class.


Introduced with 2.0.

Src = path Specifies a code-behind file containing source code.


748 Chapter 16 ■ ASP.NET Web Forms and Controls

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.

<%@ Import namespace="System.Net" %>

Several namespaces are automatically imported in a page, making this directive


unnecessary in most cases. These namespaces include

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

<%@ Assembly Name="webfunctions" %>


<%@ Assembly Src="webfunctions.cs" %>

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:

<%@ Register Tagprefix="tagprefix" Namespace="namespace"


Assembly="assembly" %>
<%@ Register Tagprefix="tagprefix" Tagname="tagname"
Src="pathname" %>

Attributes:

Tagprefix Alias for a namespace.


Namespace The namespace to associate with Tagprefix.
Assembly Assembly in which namespace resides.
Tagname Alias to associate with a class.
Src The file containing the user control

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.

<%@ Register TagPrefix="uc1" TagName="hdr" Src="hdr.ascx" %>

<form id="Form1" method="post" runat="server">


<uc1:hdr id="Hdr1" runat="server"></uc1:hdr>
</form>

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.

The Code-Behind Model


The example in Listing 16-2 contains both C# to implement program logic and
HTML to render the user interface. A Web page can also be configured as an .aspx
file, containing only the interface code and a separate code-behind file that contains
the program logic and serves as the base class for compiling the .aspx file (see Fig-
ure 16-4). This code-behind file takes the .cs extension.
The code-behind page is linked to the .aspx file as an assembly or source file using
the Codebehind or Src attributes of the @Page directive. If the Codebehind
attribute is used, the assembly must be stored in the \bin directory of the application.
750 Chapter 16 ■ ASP.NET Web Forms and Controls

Page Request
Web Server Compile
Client
Software .aspx Page
HTML

.aspx Load
Page Compiled
Class

Render HTML

Code- Create Code-


Behind Behind
Object Object

Figure 16-4 How ASP.NET responds to a Web page request

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:

<%@ Page Language="C#" Src="bmicb.cs" Inherits="BMI" %>

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.

Code-Behind File for BMI Calculator—


Listing 16-3
bmicb.cs

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

Code-Behind File for BMI Calculator—


Listing 16-3
bmicb.cs (continued)

// <input type=text id=bmi runat=server>


protected HtmlInputText bmi;
// <input type="button" VALUE="Submit Form" id=bmiButton
// runat=server>
protected HtmlInputButton bmiButton;

override protected void OnInit(EventArgs e)


{
// Delegate to handle button click on client
bmiButton.ServerClick += new EventHandler(getBMI);
}
protected void getBMI(object sender, System.EventArgs e)
{
decimal f = Convert.ToDecimal(htf.Value);
decimal inch = Convert.ToDecimal(hti.Value);
decimal w = Convert.ToDecimal(wt.Value);
decimal totinches = f * 12 + inch;
decimal h2 = totinches * totinches;
decimal massIndex = (w * 703 * 10/ h2)/10;
bmi.Value = massIndex.ToString("##.##");
}
}

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

<%@ Page Language="C#" using System;


Src="bmicb.cs" using System.Web.UI.HtmlControls;
Inherits="BMI" %> public class BMI : SystemWeb.UI.Page
{
<td> protected HtmlInputText htf;
<input type=text id=htf protected HtmlInputText hti;
runat=server></td> protected HtmlInputText wt;
<td> protected HtmlInputText bmi;
<input type=text id=hti protected HtmlInputButton bmiButton;
runat=server></td>

Figure 16-5 Binding between code-behind page and .aspx page

Table 16-2 HTML Controls and Their Tags

Control HTML Tag

HtmlAnchor <a>

HtmlSelect <select>

HtmlTextArea <textarea>

HtmlButton <input type=button> <input type=submit>

HtmlCheckBox <input type=checkbox>

HtmlRadio <input type=radio>

HtmlHidden <input type=hidden>

HtmlInputText <input type=text>

HtmlInputFile <input type=file>

HtmlForm <form>

HtmlImage <img>

HtmlTable <table>

HtmlTableRow <tr>
HtmlTableCell <td>

HtmlGenericControl All other unmapped tags such as <div> and <p>


16.1 Client-Server Interaction over the Internet 753

Handling Events on the Server


In our example, clicking the Submit button sends a request to the server to calculate
and return a BMI value based on the form’s content. The .aspx code for the button
looks like this:

<INPUT TYPE="button" VALUE="Submit Form"


id=bmiButton runat=server>

Compare this with the tag defining the button in Listing 16-2:

<INPUT TYPE="button" VALUE="Submit Form" OnServerClick="getBMI"


id=bmiButton runat=server>

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:

bmiButton.ServerClick += new EventHandler(getBMI);

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.

Code-Behind with Partial Classes


The problem with the preceding code-behind model is that each control in the
markup page must be explicitly mapped to a protected member in the code page.
Changes to the markup page require that the code page members be kept in sync.
The use of a partial class in the code-behind page eliminates the need for the pro-
tected class member. The partial class is compiled with the markup page, which per-
mits the markup and code sections to directly access each other’s members. The
effect is the same as using the inline model. To demonstrate this, extract the getBMI
method from the inline code (refer to Listing 16-2) and place it in its own file inside
a partial class. The result is a code-behind partial class as shown in Listing 16-4.
754 Chapter 16 ■ ASP.NET Web Forms and Controls

Listing 16-4 Code-Behind Partial Class File for BMI Calculator


//file: bmi.aspx.cs
using System;
partial class BMICalc: System.Web.UI.Page{}
{
void getBMI (Object sender, EventArgs e)
{
try
{
decimal f = Convert.ToDecimal(htf.Value);
decimal inch = Convert.ToDecimal(hti.Value);
decimal w = Convert.ToDecimal(wt.Value);
decimal totinches = f * 12 + inch;
decimal h2 = totinches * totinches;
decimal massIndex = (w * 703 * 10/ h2)/10;
bmi.Value = massIndex.ToString("##.##");
} catch (Exception ex)
{ bmi.Value=" "; }
}
}

The client markup page links to this code by specifying the file name and the par-
tial class name in its @Page declaration:

<%@ Page codefile="bmi.aspx.cs" inherits="BMICalc" %>

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.

Table 16-3 Selected Properties of the Page Class

Control HTML Tag

Application Returns the HttpApplicationState object that contains informa-


tion about the executing application.

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.

Response Gets the HttpResponse object that is used to programmatically send


HTTP responses to the client.

Server Gets the HttpServerUtility object provided by the HTTP runtime.

Session Gets the HttpSessionState object, which provides information


about the state of the current session.

The Application and Session properties provide state information for a Web
application and are discussed in the next chapter.

HttpRequest and HttpResponse Objects


The Request and Response properties expose underlying objects (HttpRequest and
HttpResponse) that are used to interact with the incoming HTTP request and the
outgoing HTTP response. These classes include properties and methods to read cook-
ies from the client’s machine, receive uploaded files, identify the browser, get the IP
address of the client, and insert custom content into the outgoing HTTP body. Chapter
17 discusses these in detail, but as an introduction, let’s look a simple code example that
illustrates the fundamentals of accessing these objects within an .aspx page.
The next code segment uses the Response.Output.Write method to write infor-
mation into the HTTP content returned to a browser. The information comes from
Request.Browser properties that indicate the type of client. Note that by using <%
%> tags in an .aspx file, we can intersperse these statements with the HTML code. Do
756 Chapter 16 ■ ASP.NET Web Forms and Controls

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.

<table border=0 cellpadding=0 cellspacing=0>


<%
Response.Output.Write(@"<tr><td>Browser Version: {0}
</td></tr>", Request.Browser.Type); // IE6
%>
</table>

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

<FORM NAME="bmi_input" runat=server>


<span id=sessionstart runat=server/><br>

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:

void Page_Load(object sender, EventArgs e)


{
if(!this.IsPostBack) sessionstart.InnerText =
DateTime.Now.ToString();
}

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.

Occurs before server-side controls


Init have been restored.

Controls have been initialized and


Load their values are accessible.

Server-side events have been fired,


PreRender
but no HTML has been rendered.

Occurs after page rendering


UnLoad
has been completed.
Figure 16-6 System.Web.UI.Page events

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

16.2 Web Forms Controls


The designer of an ASP.NET Web page can choose from three types of controls for
the GUI design: client-side HTML controls, HTML server controls, and Web Forms
controls. Client-side controls are the traditional controls defined by HTML tags
(<table>, <button>, <input>, and others). These controls are still useful for cli-
ent-side form validation and creating hyperlinks.
HTML server controls are server-side versions of the standard HTML controls
(see Listing 16-2). As we have seen, they’re created by adding a runat = server
16.2 Web Forms Controls 759

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 Overview


Web controls behave much like the HTML server controls: Each has a correspond-
ing class; those with a user interface render themselves in HTML understandable by
a browser; and most expose events that are handled on the server. A significant dif-
ference from the HTML controls is that Web controls provide a richer object model.
From the WebControl class, they inherit properties that affect their appearance:
ForeColor, BackColor, BorderColor, BorderStyle, Height, and Width. In
addition, many can be bound to a data source that provides their content.
A Web control is declared using the asp: prefix. For example, a TextBox and
Button are declared as

<asp:TextBox id="First Name" type="text" runat="server" />


<asp:Button id="Save" Text="Save Data" runat="server"
OnClick="SaveBtn_Click" />

ASP.NET Web controls are defined in the System.Web.UI.WebControls


namespace. There are a lot of them—more than 70 with the release of ASP.NET 2.0.
They naturally fall into functional categories that provide a convenient way to present
them (see Figure 16-7):

• Simple controls. Enhanced alternatives to the HTML Server con-


trols and standard HTML tags.
• List controls. These inherit directly from the System.Web.UI.Web-
Controls.ListControl class and are populated by ListItem
objects.
• Data Display controls. These complex controls are designed to dis-
play multiple rows of data (GridView, DataList, Repeater) or mul-
tiple fields for a single row of data (FormView, DetailsView).
760 Chapter 16 ■ ASP.NET Web Forms and Controls

• Data Source controls. Introduced with ADO.NET 2.0, these con-


trols serve as source of data for other controls—primarily the data dis-
play controls—that bind to them. As their names imply, these controls
provide data from a variety of sources.
• Validation controls. These helper controls perform predefined and
custom data validation on other controls.
• Login controls. A set of controls designed to control the login pro-
cess by managing and authenticating users.

Web Controls

Visual Controls Components

Simple List Login Data Source


Button CheckBoxList Login AccessDataSource
ImageButton DropDownList LoginView DataSetDataSource
RadioButton ListBox ObjectDataSource
LinkButton RadioButtonList Special SiteMapDataSource
CheckBox Data Display Calendar SqlDataSource
Label DataGrid AdRotator XmlDataSource
TextBox DataList FileUpload
Image GridView Validation
ImageMap DetailsView RequiredFieldValidator
BulletedList FormView RangeValidator
HiddenField Repeater CompareValidator
Table TreeView RegularExpressionValidator
Panel MultiView CustomValidator
ValidationSummaryControl
Figure 16-7 ASP.NET Web controls

In addition to these, there are a few of highly specialized controls: Calendar,


AdRotator, and FileUpload. This section provides examples of commonly used
visual controls, as well Validation controls and the new (.NET 2.0) Data Source con-
trols. Before examining specific controls, let’s look at the properties shared by all
Web controls that govern their appearance.

Specifying the Appearance of a Web Control


All Web controls inherit numerous properties from the base WebControl class that
can be used to set their appearance and behavior. The properties are most effective
when used to decorate simple controls where their effect is more pronounced than
with complex controls. Table 16-4 demonstrates how these properties can be used to
alter the appearance of a simple Button control.
16.2 Web Forms Controls 761

Table 16-4 Properties That Affect the Appearance of a Web Control

Property Button1 Button2 Button3

Width 80 100 100

Height 20 20 24

BackColor #ffffff #efefe4 #cccccc

BorderStyle Double Dotted None

BorderWidth 1 1 1

BorderColor Red Black Black

Font-Names Sans-Serif Sans-Serif Courier

Font-Size 8pt 10pt 11pt

Font-Bold true true true

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:

• It eliminates the need for multiple Web pages. Because ASP.NET is


designed around posting back to the calling Web page, it’s an easy way
for a single Web page to present multiple interfaces by simply toggling
panels’ visibility on or off.
• Controls on a panel maintain data that can be referenced even if the
panel is not visible. This eliminates the use of multiple hidden fields.
762 Chapter 16 ■ ASP.NET Web Forms and Controls

• It serves to unify the appearance of grouped controls by providing a


common background or border. A screen is easily sectioned into mul-
tiple panels.

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.

Figure 16-8 Using panels to manage simple controls

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.

Using the Page_Load Event to Initialize the Screen


When the page is loaded for the first time, the panel that accepts name fields,
pnlName, is made visible, whereas pnlAddress has its visibility turned off.

<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

<asp:Button ID="btnName" CommandName="name"


OnCommand="Button_Command" text="Name" Runat="server" />
<asp:Button ID="btnAddress" CommandName="address"
OnCommand="Button_Command" text="Address" Runat="server" />

The buttons specify Button_Command as an event handler to be called when each


button is clicked. As shown in the event handler code, the buttons’ CommandName
property identifies the button raising the event. This enables a single method to han-
dle multiple button clicks, which is a useful way to group code performing similar
tasks. It’s most commonly used with buttons that control column sorting in a grid.

void Button_Command(Object sender, CommandEventArgs e) {


// CommandName identifies control invoking event
switch(e.CommandName)
{
case "address":
pnlName.Visible = false;
pnlAddress.Visible = true;
break;
case "name":
pnlAddress.Visible= false;
pnlName.Visible = true;
break;
default:
break;
}
}

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

&nbsp;
<asp:Button id="btnClear" Text="Clear" OnClick="clear_Form"
runat="server" />
&nbsp;&nbsp;
<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.

private void clear_Form(object sender, System.EventArgs e)


{
if(pnlName.Visible)
{
txtFirstName.Text ="";
txtLastName.Text ="";
txtMiddleName.Text ="";
} else
{
// Clear fields on pnlAddress
}
}
private void store_Form(object sender, System.EventArgs e)
{
// Code to verify and store Form
}

Core Note

ASP.NET 2.0 adds an OnClientClick property to the Button,


ImageButton, and LinkButton controls, which can be used to execute
client-side script. The following code causes a JavaScript function to be
executed when the button is clicked.
<asp:Button id=btn text="Client Click" OnClientClick="popup()"
runat="server" />

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

<asp:Panel id="pnlName" runat="server"


style="Z-INDEX: 101; LEFT: 20px;
POSITION: absolute; TOP: 64px"
BackColor = "LightGreen" Height="160px" Width="278px">
<TABLE>
<TR>
<TD><asp:Label id="lblFirstName" Runat="server"
text="First Name:"></asp:Label></TD>
<TD><asp:TextBox id="txtFirstName" MaxLength=30
Runat="server"></asp:TextBox></TD></TR>
<TR>
<TD><asp:Label id="lblMiddleName" Runat="server"
text="Middle Name:"></asp:Label></TD>
<TD><asp:TextBox id="txtMiddleName" MaxLength=30
Runat="server"></asp:TextBox></TD></TR>
<TR>
<TD><asp:Label id="lblLastName" Runat="server"
text="Last Name:"></asp:Label></TD>
<TD><asp:TextBox id="txtLastName" MaxLength=30
Runat="server"></asp:TextBox></TD></TR>
</TABLE>
</asp:Panel>

It is interesting to note how ASP.NET tailors the HTML code to capabilities of


the client browser. The HTML returned to Internet Explorer renders the panel as a
<div> element.

<div id="pnlName" style="background-color:LightGreen;


height:160px;width:278px;Z-INDEX: 103; LEFT: 20px;
POSITION: absolute; TOP: 64px">

The Firefox and Netscape browsers receive HTML in which the panel is ren-
dered as a table:

<table id="pnlName" cellpadding="0" cellspacing="0" border="0"


bgcolor="LightGreen" height="160" width="278"
style="Z-INDEX: 101; LEFT: 20px; POSITION: absolute;
TOP: 64px"><tr><td>

Core Note

ASP.NET 2.0 adds a ScrollBars property to the panel control. It can be


set to Vertical, Horizontal, or Both. Beware that not all browsers
support this feature.
766 Chapter 16 ■ ASP.NET Web Forms and Controls

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

Filling the Control with Data


Individual data items within a list control are represented as a ListItem control.
There are several ways to specify the text displayed for an item in the list control. The
most common method is by placing text between the opening and closing tags of the
ListItem control—referred to as the inner HTML content. You can also use the
Text property to specify the text displayed in the list control for the item.

<asp:RadioButtonList id="RadioButtonList" BackColor="LightBlue"


RepeatColumns=1 RepeatDirection="Vertical" runat="server">
<asp:ListItem Value="1">Rembrandt />
<asp:ListItem Value="2">Courbet />
<asp:ListItem Value="3" Text="Manet" />
<asp:ListItem Value="4">Degas />
</asp:RadioButtonList>

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:

<asp:ListBox id="ListBox" runat="server" Rows=4 Width=150px>


</asp:ListBox>

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.

<asp:ListBox id="ListBox" runat="server"


SelectionMode="Multiple" Rows=4
AutoPostBack="true"
OnSelectedIndexChanged="ShowSelections"
Width=150px>
</asp:ListBox>

The event handler declaration has the familiar parameters of the EventHandler
delegate declaration.

public void ShowSelections(Object sender, EventArgs e) {


Label1.Text="";
foreach (ListItem item in ListBox.Items)
{
if(item.Selected)
{
Label1.Text += item.Text + "<br>";
}
}
}

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.

The DataList Control


The DataList control makes it easy to display data in a repeating pattern. The idea
is to create a template that defines the layout for items it contains. Each “item” is
defined as a mixture of visual Web controls and HTML. Item content comes from
one or more fields in a data source. When an instance of the DataList control is set
16.2 Web Forms Controls 769

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:

<ASP:DataList id="MyDataList" RepeatColumns="2"


RepeatDirection="Horizontal" OnItemCommand="Item_Command"
runat="server">

The RepeatColumns property specifies the number of columns displayed, and


RepeatDirection indicates the direction in which items are displayed. OnItem-
Command specifies the method to be called when a button in the DataList control
is clicked. In this example, the ItemCommand event fires when the user adds an item
to the cart. Here is an example of the code to handle this event:

private void Item_Command(object source,


System.Web.UI.WebControls.DataListCommandEventArgs e)
{
// (1) Crucial: select an item in the DataList
MyDataList.SelectedIndex = e.Item.ItemIndex;
// (2) Get the value of a control in the selected item
string id= ((Label)
MyDataList.SelectedItem.FindControl("movID")).Text;
}

An item selected in the DataList is indexed by the SelectedIndex property.


After an item is selected, the FindControl method can be used to obtain a refer-
ence to any control in the item definition. In this case, the value of the Label con-
taining the movie’s ID is assigned to a variable.
The ItemTemplate shown in Listing 16-5 describes the appearance of each item.
The most important thing to note in this code is the use of DataBinder.Eval to
bind to a property or column in the data source. As shown here, the method has two
overloads, one of which takes a formatting parameter.

<%# DataBinder.Eval(Container.DataItem, "movie_ID") %>


<%# DataBinder.Eval(Container.DataItem,"r_price", " {0:c2}") %>

Note that ASP.NET 2.0 offers a simpler, but equivalent version:

<%# Eval("movie_ID") %>

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

Figure 16-10 DataList control is used to display items in repeated format

Defining an ItemTemplate for a DataList


Listing 16-5
Control
<ItemTemplate>
<table cellpadding=10 style="font: 10pt verdana" width=380>
<tr>
<td width=1 bgcolor="00000"/>
<td valign="top">
<a href=showdvd.aspx?id=<%# DataBinder.Eval(
Container.DataItem, "movie_ID") %>>
<img align="top" border=0
src='./images/<%# DataBinder.Eval(
Container.DataItem, "movie_ImgID") %>' ></a>
</td>
<td valign="top">
<b>Title: </b><%# DataBinder.Eval(
Container.DataItem, "movie_title") %><br>
<b>ID: </b>
<asp:Label id="movID"
Text=<%# DataBinder.Eval(Container.DataItem,
"movie_ID") %>
runat="server" >
</asp:Label></b><br>
16.2 Web Forms Controls 771

Defining an ItemTemplate for a DataList


Listing 16-5
Control (continued)
<b>Year: </b><%# DataBinder.Eval(
Container.DataItem, "movie_Year") %><br>
<b>Price: </b><%# DataBinder.Eval(Container.DataItem,
"r_price", " {0:c2}") %><br>
<%# DataBinder.Eval(Container.DataItem,
"movie_shortdesc") %> <br>
<asp:Button Value="Select" Text="Add to Cart"
BackColor="#bda563"
CommandName="cart"
runat=server>
</asp:Button> <br>
</td>
</tr>
</table>
</ItemTemplate>

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

DataBinder.Eval and Eval use reflection to perform late-bound


evaluation. The advantage of this approach is that it can determine the
data type (making casting unnecessary) and has a simple formatting
syntax. The disadvantage is that it is much slower than an early-bound
approach. As an alternative—particularly when formatting is not
needed—consider this early-bound syntax:
<%# ((IDataRecord)Container.DataItem)["movie_ID"] %>
IDataRecord casting is used when a DataReader is the data source.
Cast with DataRowView when the data source is a DataSet.
772 Chapter 16 ■ ASP.NET Web Forms and Controls

In summary, the DataList acts as composite control that can be configured to


display data in just about any format. In addition to the ItemTemplate, it supports
header, footer, edit, and select templates that can be used to expand its capabilities
beyond those described in this section.

16.3 Data Binding and Data


Source Controls
Data binding enables the contents of a control to be populated by data from a desig-
nated data source. Technically speaking, the data source is any collection of data that
implements the IEnumerable interface. In simple cases, the source may be an
object such as an Array, ArrayList, Hashtable, or SortedList. More often, the
data comes from an ADO.NET DataTable, DataSet, or IDataReader object. The
control may bind directly to one of these objects, or indirectly, using the special data
source controls introduced with ASP.NET 2.0.

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.

Listing 16-6 Data Binding a List Box to a Data Reader


<%@ Page Language="C#" %>
<%@Import namespace="System.Data.SqlClient" %>
<%@Import namespace="System.Data" %>
<html>
<body>
<head><TITLE>Bind Films data to a ListBox</TITLE>
16.3 Data Binding and Data Source Controls 773

Listing 16-6 Data Binding a List Box to a Data Reader (continued)


<script runat="Server">
void Page_Load(object sender, EventArgs e) {
if(!this.IsPostBack) {
getMovies();
}
}
private void getMovies()
{
string cnstr=GetConnString(); // Get a connection string
SqlConnection conn = new SqlConnection(cnstr);
IDataReader rdr=null;
IDbCommand cmd = new SqlCommand();
cmd.Connection= conn;
conn.Open();
cmd.Connection=conn;
cmd.CommandText="SELECT movie_id, movie_title FROM movies
ORDER BY AFIRank";
conn.Open();
rdr = cmd.ExecuteReader();
// Bind DataReader to ListBox
ListBoxMovie.DataSource = rdr;
ListBoxMovie.DataBind();
conn.Close();
rdr.Close();
}
</script>
</head>
<FORM NAME="FORM1" runat=server>
<asp:ListBox
id="ListBoxMovie"
dataValueField = "movie_ID"
dataTextField = "movie_title"
AppendDataBoundItems=true
Rows="10"
BackColor=#efefe4
font-size=9pt
runat="server" >
<asp:ListItem Value=-1 Text="Select Movie" />
</asp:ListBox>
</FORM>
</body>
</html>
774 Chapter 16 ■ ASP.NET Web Forms and Controls

Observe the presence of the AppendDataBoundItems property in the ListBox


declaration. This property, a new feature added by ASP.NET 2.0, provides a way to
specify whether data binding should overwrite any preexisting items in a list control.
This is particularly useful for placing an entry in a list that precedes the actual data.
In the preceding example, the ListBox declaration includes a ListItem that causes
Select Movie to be placed in it first row.

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.

Figure 16-11 DataReader versus DataSet as a control’s DataSource

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

string sql ="SELECT movie_id, movie_title,movie_year FROM movies


ORDER BY movie_year";
SqlDataAdapter da = new SqlDataAdapter(sql,conn);
DataSet ds = new DataSet();
da.Fill(ds,"Movies"); // DataSet and DataTable
DataView dview = new DataView(ds.Tables["Movies"]);
dview.RowFilter = "movie_year < 1951";
// List box containing movies before 1951
ListBoxMovie.DataSource= dview;
ListBoxMovie.DataBind();
// List box containing movies produced after 1950
dview.RowFilter = "vendor_name > 1950";
ListBoxMovie2.DataSource= dview;
ListBoxMovie2.DataBind();

A DataSet is also useful when the Web application is designed as a three-tier


Web site in which the presentation layer accesses a database through an intermediate
data access layer. The data access layer contains a method or methods that return a
DataSet in response to the call.
Let’s look at how the .aspx file in Listing 16-6 can be converted from its current
two-tier structure to a three-tier design. The first step is to remove the getMovies
method and place it in a separate assembly. In the following code, the method is part
of the DataMethods class and rewritten to return a data set containing the vendor
data. This code is compiled into a DLL file and placed in the bin subdirectory below
the Web page.

// datalayer.dll – place in \bin subdirectory below application


using System.Data.SqlClient;
using System.Data;
namespace myUtil{
public class DataMethods{
public DataSet getMovies(){
string cnstr= GetConnString(); // Get a connection string
SqlConnection conn = new SqlConnection(cnstr);
IDbCommand cmd = new SqlCommand();
cmd.Connection= conn;
string sql="SELECT movie_id, movie_title FROM
movies ORDER BY AFIRank";
SqlDataAdapter da = new SqlDataAdapter(sql,conn);
DataSet ds = new DataSet();
da.Fill(ds,"Movies");
return (ds);
}
}
}
776 Chapter 16 ■ ASP.NET Web Forms and Controls

The code changes in the .aspx file are minimal. An @Import directive is added
so that the namespace in datalayer.dll can be accessed.

<%@Import namespace="myUtil" %>

The getMovies call is replaced with the following:

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:

1. Create a data connection.


2. Create a Command object.
3. Build a query and use the Command object to retrieve data into a data
reader.

Data source controls encapsulate the functionality required to perform these


operations—eliminating the need for coding by the developer. The data-bound con-
trol is no longer bound to the data collection, but to a data source control. To illus-
trate, Figure 16-12 shows how a grid can be populated by binding it to a data reader
or a SqlDataSource control.
Data controls are not limited to database access. In fact, ASP.NET 2.0 supports
data source controls that attach to six types of data:

• AccessDataSource. Binds to a Microsoft Access database.


• DataSetDataSource. Binds to non-hierarchical XML data.
• ObjectDataSource. Binds to data through custom classes imple-
mented in a data access layer.
• SiteMapdataSource. Binds to XML site maps.
• SqlDataSource. Binds to a SQL database.
• XmlDataSource. Binds to XML documents.
16.3 Data Binding and Data Source Controls 777

We’ll look at the SqlDataSource, ObjectDataSource, and XmlDataSource


controls in this section.

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

IDbConnection conn; <asp:SqlDataSource


conn = new SqlConnection(connstr); Runat="server"
IDbCommand cmd = new IDbCommand(); ID="dsActors";
cmd.Connection = conn; . . .
. . .
string sql = "Select * from Actors"; . . .
cmd.CommandText = sql;
conn.Open(); </asp:SqlDataSource>
IDataReader rd = cmd.ExecuteReader();
DataSource Control

Data
Source

Figure 16-12 Comparison of data binding using


ADO.NET code versus DataSource control

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:

<asp:sqldatasource runat="server" id="controlID"

Properties:

ConnectionString Connection string to access database.


ProviderName Managed provider. Default is SqlClient.
778 Chapter 16 ■ ASP.NET Web Forms and Controls

DataSourceMode Controls how the select command retrieves data.


Is a SqlDataSourceMode enumeration: DataSet or
DataReader. Default is DataSet.
EnableCaching True or false. Default is false. Can only be used if
DataSourceMode is DataSet.
CacheDuration How long the contents of the data source are maintained
in memory. Value is in seconds.
SelectCommand SQL statement that retrieves data from associated data store.
DeleteCommand SQL statement to delete row(s) from data store.
InsertCommand SQL statement to insert row(s) into data store.
UpdateCommand SQL statement to update row(s) in data store.

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.

Binding a ListBox to a SqlDataSource


Listing 16-7
Control
<%@ Page Language="C#" %>
<%@Import namespace="System.Data.SqlClient" %>
<%@Import namespace="System.Data" %>
<HTML>
<HEAD><TITLE>Using a DataSource Control</TITLE>
</HEAD>
<body>
<form id="Form1" runat="server">
<asp:sqldatasource runat="server" id="SqlDataSource1"
connectionstring="SERVER=(local);DATABASE=FILMS;
Integrated Security=SSPI; "
providername = "System.Data.SqlClient"
16.3 Data Binding and Data Source Controls 779

Binding a ListBox to a SqlDataSource


Listing 16-7
Control (continued)
selectCommand= "SELECT movie_ID, movie_Title FROM movies
ORDER BY AFIRank" >
</asp:sqldatasource>
<table border=0>
<tr><td>
<asp:ListBox runat="server" id="ListBoxMovie"
dataSourceid = "SqlDataSource1"
dataValueField = "movie_ID"
dataTtextField = "movie_Title" />
</td></tr>
</table>
</form>
</body>
</html>

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.

Figure 16-13 Using data source controls to depict a parent-child relationship

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 query assigned to the SelectCommand property contains a parameter


(@movieID) that serves as a placeholder for the actual movie ID. Within the data
source control is another control, ControlParameter, that has the same name as the
parameter in our query. It links the data source control to the ListBox via two proper-
ties: ControlID, which specifies the ListBox ID, and PropertyName, which speci-
fies the value to assign to the query parameter. When an item in the ListBox is
selected, ASP.NET replaces the parameter with the current SelectedValue of List-
BoxMovie (which is a movie ID) and executes the query to retrieve the actor data.

<asp:SqlDataSource ID="ActorSource" RunAt="server"


connectionstring="SERVER=(local);DATABASE=FILMS;
Integrated Security=SSPI; "
SelectCommand= "SELECT actor_first, actor_last, actor_sex FROM
actor_movie LEFT JOIN actors ON actor_movie.actor_ID=
actors.actor_ID WHERE movie_ID=@movieID">
<SelectParameters>
<asp:ControlParameter Name="movieID"
ControlID="ListBoxMovie"
PropertyName="SelectedValue"
</asp:ControlParameter>
</SelectParameters>
</asp:SqlDataSource>

The GridView control contains three columns that are bound to the data source
control:

<asp:GridView ID="MovieGridView" DataSourceID="ActorSource"


Width="100%" runat="server" AutoGenerateColumns="false"
SelectedIndex="0" AutoGenerateSelectButton="true" >
<Columns>
<asp:BoundField HeaderText="First Name"
DataField="actor_first" />
<asp:BoundField HeaderText="Last Name"
DataField="actor_last" />
<asp:BoundField HeaderText="Sex"
DataField="actor_sex" />
</Columns>
</asp:GridView>

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.

<%# XPath("xpath-expression"[, "format"]) %>


<%# XPathSelect("xpath-expression") %>

Displaying RSS Feed with a DataList and


Listing 16-8
XmlDataSource Control

<asp:DataList ID="DataList1" Runat="server"


RepeatColumns=1
RepeatDirection="Horizontal"
GridLines="Horizontal"
BorderWidth="1px" BackColor="White" CellPadding="2"
BorderStyle="None" BorderColor="#E7E7FF"
DataSourceID="XmlDataSource1">
<ItemTemplate>
<asp:HyperLink ID="HyperLink1" Runat="server"
Text=<%# XPath("title") %>
NavigateUrl=<%# XPath("link") %>
Target="_blank" Font-Names="Sans-Serif"
Font-Size="X-Small">
</asp:HyperLink><br/>
<i><%# XPath("description")%></i><br /><br />
</ItemTemplate>
<AlternatingItemStyle BackColor="#F7F7F7">
</AlternatingItemStyle>
<ItemStyle ForeColor="#4A3C8C"
Font-Size=9pt BackColor="#E7E7FF">
</ItemStyle>
<HeaderTemplate>BBC RSS Feed: Nature</HeaderTemplate>
<HeaderStyle ForeColor="#F7F7F7"
Font-Bold="True" BackColor="#4A3C8C">
</HeaderStyle>
</asp:DataList>
784 Chapter 16 ■ ASP.NET Web Forms and Controls

Displaying RSS Feed with a DataList and


Listing 16-8
XmlDataSource Control (continued)

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

By binding a data component to a sophisticated visual data control, we are able to


create an application in which all data binding is specified through declarations. This
can eliminate the need for code to input data, iterate through it, and parse it into a
format that can be displayed. In addition, data components have built-in caching fea-
tures that improve the efficiency of accessing data and eliminate code-managed
caching. We look at caching in the next chapter.

16.4 Validation Controls


One of the more frustrating Internet experiences is to fill in a long form, submit it,
and—after a lengthy wait—have it rejected due to an invalid field entry. Any
well-designed form should attempt to avoid this by including client-side verification
to check fields before the form is submitted. Validation is typically used to ensure
that a field is not empty, that a field contains a numeric value only, that a phone num-
ber or credit card has the correct format, and that an e-mail address contains the @
character. JavaScript is traditionally used for this purpose.
ASP.NET offers validation controls as a flexible alternative to implementing your
own client-side JavaScript functions. The purpose of these controls is to perform a
specific type of validation on an associated control. For example, a RequiredField-
Validator control checks an input control to ensure it is not empty.
Table 16-5 lists the six built-in validation controls along with their unique prop-
erties and values.
16.4 Validation Controls 785

Table 16-5 Validation Controls

Control Properties Description and Possible Values

RequiredField Checks whether input control field contains a value.


Validator

CompareValidator Compares the value of an input control with a constant or other control.

Operator Has value of:


Equal, NotEqual, GreaterThan, Greater-
ThanEqual, LessThan, LessThanEqual,
DataTypeCheck

Type Currency, Date, Double, Integer, String.

ValueToCompare Constant value used for comparison.

ControlToCompare Other control to compare value with.

RangeValidator Checks the value of the input control against a range of values.

MaximumValue Constant value that represents upper value.

MinimumValue Constant value that represents lowest value.

Type Currency, Date, Double, Integer, String.

RegularExpression Matches the value of the input control against a regular expression.
Validator
ValidationExpression Regex to match input control’s value against.

CustomValidator Checks a field’s value against custom validation logic.

ClientValidation- JavaScript client-side function to perform


Function validation.

OnServerValidate Server-side method to perform validation.

ValidationSummary Collects and lists all the error messages from the form validation process.

DisplayMode Format of error messages:


BulletList, List, SingleParagraph

HeaderText Title for error message summary.

ShowMessageBox Display errors in pop-up box: true or false.

ShowSummary Display errors on control: true or false.


786 Chapter 16 ■ ASP.NET Web Forms and Controls

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.

Using Validation Controls


Validation controls are used only with controls that have a single input field type.
These include the HTMLInputText, HTMLSelect, TextBox, DropDownList, and
ListBox controls. ASP.NET implements the validation function on both the server
and client side. To handle the client side, it includes a .js file containing validation
code in the response to the browser. Note, however, that if scripting is disabled on
the browser, only client-side checking will occur.
To illustrate the mechanics of using a validation control, here is the code to associate
a RequiredFieldValidator and RangeValidator control with a TextBox control.
The range validator control uses its MaximumValue and MinimumValue properties to
ensure that the value entered in the text box is a numeric value from 0 to 12.

<td><asp:TextBox Width=30 id=hti maxlength=2


runat=server></td>

<asp:RequiredFieldValidator id="htivalidator" runat=server


ControlToValidate="hti"
ErrorMessage="Must enter height value."
Display="dynamic">
</asp:RequiredFieldValidator>

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

• ControlToValidate. Set to the identifier of the control to be


validated.
• Display. static, dynamic, or none. This specifies how the error
message takes up space on the form: static reserves space and
16.4 Validation Controls 787

makes the message visible when it is needed; dynamic uses no space


on the form until it is displayed; none is used when the error message
is to be displayed by the ValidationSummary control.
• ForeColor. Sets the color of the error message text. Red is the
default.
• ErrorMessage. The message displayed when a validation exception is
detected.
• ValidationGroup. A string value that enables validation controls
to be grouped by assigning the same value to this property. When a
Button control that has its ValidationGroup property set to a group
value submits a form, only controls with validators in the group are
validated. This allows sections of a page to be validated separately.
This property is introduced in ASP.NET 2.0.

Of the controls listed in Table 16-4, the CustomValidator and Validation-


Summary exhibit unique behavior that requires further explanation.

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.

<input type=password id="pw" runat="server" />


<br>
<asp:CustomValidator id="pwvalidator"
ControlToValidate="pw"
ClientValidationFunction="checkPWClient"
OnServerValidate="checkPW"
Display=dynamic
ErrorMessage="A password must be between 8 and 12 characters."
runat="server"/>

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.

<asp:ValidationSummary id=validsumm runat="server"


ShowMessageBox=true
DisplayMode=List
ShowSummary=false>
</asp:ValidationSummary>

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.

16.5 Master and Content Pages


A principal design objective when creating a multi-page Web site is visual and func-
tional consistency. Headers and footers should look the same, and the layout and use of
controls should be similar from page to page. One way to achieve this is to drag and
drop common controls on to each new Web page; but in many cases, a better approach
is to create a template from which new pages can be derived. In Windows Forms pro-
gramming, a form can be filled with controls and used as a base class to create other
interfaces. ASP.NET has a similar feature known as master pages. The idea is to create
a template, or master page, containing visual elements that will be common to other
pages, as well as placeholders that will be filled in with the unique content from other
pages. Pages that provide content for the master page are referred to as content pages.
The major advantages to this approach are that any changes made to a master
page are automatically reflected in the content pages and that the act of creating con-
tent pages is limited to providing statements that specify the content to be associated
with the placeholders in the master page.
To illustrate the fundamentals of using master pages, we’ll create a master page that
defines the layout for the Web page shown in Figure 16-15. In addition, we’ll create two
content pages that correspond to the menu items shown on the left side of the page.

Figure 16-15 Web page derived from a master page


790 Chapter 16 ■ ASP.NET Web Forms and Controls

Creating a Master Page


Master pages are so similar to regular .aspx pages that converting an .aspx page to
a master page requires only three changes: the file must have the .master extension;
the @Page directive is replaced with the @Master directive; and one or more Con-
tentPlaceHolder controls are added to serve as containers that will be filled at
runtime with content from content pages.
Listing 16-9 shows the master page used by content pages to create the Web page
in Figure 16-15. The two menu items are implemented as HyperLink controls that
reference the two content pages. Two ContentPlaceHolder server controls desig-
nate the area in the page where a content page’s title and main body are placed.

Listing 16-9 Master Page Definition—shell.master


<%@ Master %>
<html>
<head>
<title>Master Page for Shell Design Studio</title>
</head>
<body bgcolor=#ffffff link=#ffffff alink=#ffffff vlink=#ffffff>
<FORM id="mainform" runat="server">
<table width="500" cellpadding="0" cellspacing="0">
<tr><td bgcolor=black align=center>
<img src=./images/sdslogo.gif>
</td><td>&nbsp;&nbsp;
</td><td>
<font size=4 face=Verdana> <b>SHELL</b> DESIGN STUDIO
</td></tr>
<tr>
<td width=120 height=300 bgcolor=red valign=top>
<asp:HyperLink id="homepage" NavigateUrl="home.aspx"
Text="Home Page"
Font-Bold="true" Font-Size=9pt
Font-Names="Verdana"
runat=server />
<br><br>
<asp:HyperLink id="clients" NavigateUrl="clients.aspx"
Text="Our Clients"
Font-Bold="true" Font-Size=9pt
Font-Names="Verdana"
runat=server />
</td>
<td>&nbsp;&nbsp;</td>
16.5 Master and Content Pages 791

Listing 16-9 Master Page Definition—shell.master (continued)


<td valign=top>
<hr size=1 color=red>
<asp:contentplaceholder id="Header" runat="Server">
<b>Introduction</b>
</asp:contentplaceholder>
<hr size=1 color=red>
<asp:contentplaceholder id="PageBody" runat="Server">
This is Default Content to be overridden
by content pages
</asp:contentplaceholder>
</td></tr>
</table>
</FORM>
</body>
</html>

Creating a Content Page


A content page is an .aspx file containing <asp:Content> tags (instances of Con-
tent controls) that override corresponding <asp:contentplaceholder> tags in
the master page. The ContentPlaceHolderID property of the content tag matches
the ID of the placeholder where the content is to be inserted. The home.aspx con-
tent page in our example illustrates this. It contains two content tags that define the
content for the Header and PageBody placeholders, respectively. The master-
pagefile attribute specifies the master page from which this page inherits.

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

<asp:content id="MainBody" runat="server"


contentplaceholderid="PageBody">
<font face=Verdana size=2>
Shell Design Studios specializes in interior decorating for
homes and offices. Our staff contains experts in art, color
theory, architectural design and home technology.
</asp:content>
792 Chapter 16 ■ ASP.NET Web Forms and Controls

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>

<asp:content id="MainBody" runat="server"


contentplaceholderid="PageBody">
<font face=Verdana size=2>
<ul>
<li>Swanburg Medical </li>
<li>Lombard & Gable Law </li>
<li>Coble Architectural Design</li>
</ul>
</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.

Accessing the Master Page from a Content Page


Recall that .aspx files are compiled at runtime into (System.UI.Web) Page objects.
The Page object serves as the naming container for all objects on the page. When a
master page is involved, one of the objects included is a MasterPage object that, in
turn, serves as a container for the ContentPlaceHolder and Content objects. This
hierarchy of objects comprises the Web page that is rendered to the client.
To support master pages, the Page object includes a Master property that can be
used to reference objects in a master page’s control collection. Content pages have
access to this property and can thus alter the appearance of the master page template
when they are loaded. As an example of how this can be used, consider the menu on
our sample Web page (see Figure 16-15). The items have the same appearance no
16.6 Building and Using Custom Web Controls 793

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:

<%@ Import namespace=System.Drawing %>


<script runat="Server">
// Highlight link for home.aspx page
void Page_Load(object sender, EventArgs e)
{
// Change color of link to indicate current page
HyperLink h = (HyperLink)Master.FindControl("homepage");
h.BackColor=Color.Goldenrod; // highlight menu item
}
</script>

The Master.FindControl method is used to reference the link pointing to the


current page. In this case, the returned object’s BackColor property is set to high-
light the link.

16.6 Building and Using


Custom Web Controls
This section provides an overview of how to write and consume a custom Web con-
trol. Its objective is twofold: to introduce the fundamentals of implementing a con-
trol and, in the process, provide insight into the architecture of the intrinsic .NET
controls.
In its simplest form, a custom control is a class that inherits from the Sys-
tem.Web.UI.Control class, implements (overrides) a Render method, and uses its
HtmlTextWriter parameter to emit the HTML code that represents the control.
Add some public properties to define the control’s behavior, and you have a custom
control that functions like the built-in ASP.NET controls. More complex controls
may require features such as data binding and caching support, which are not cov-
ered in this chapter. For a full understanding of those topics, refer to a good
ASP.NET book.1

1. Essential ASP.NET by Fritz Onion (Addison-Wesley, 2003) is a good choice.


794 Chapter 16 ■ ASP.NET Web Forms and Controls

A Custom Control Example


Listing 16-10 defines a custom control that we will use to illustrate the basics of con-
trol creation. The purpose of this control is to display a large or small version of a
company logo, along with the company’s name. Two properties determine its appear-
ance and behavior: LogoType takes a value of small or large to indicate the size of
the image to be displayed, and Link specifies the URL to navigate to when the small
logo is clicked.

Listing 16-10 A Custom Control—logocontrol.cs


using System;
using System.Web;
using System.Web.UI;
namespace CompanyControls
{
// (1) Inherit from the System.Web.UI.Control class
public class CompanyLogo : Control
{
// Custom control to display large or small company logo
private string logo_sz; // "small" or "large"
// Page to go to when logo is clicked
private string myLink;
public string LogoType
{
get {return logo_sz;}
set {logo_sz = value;}
}
public string Link
{
get {return myLink;}
set {myLink = value;}
}
// (2) Override the Render method
protected override void Render(HtmlTextWriter output)
{
// (3) Emit HTML to the browser
if (LogoType == "large"){
output.Write("<a href="+Link+">");
output.Write("<img src=./images/logo_big.gif
align=middle border=0>");
output.WriteLine("</a>");
output.Write("&nbsp;&nbsp;");
output.Write("<b style=font-style:24;");
16.6 Building and Using Custom Web Controls 795

Listing 16-10 A Custom Control—logocontrol.cs (continued)


output.Write("font-family:arial;color:#333333;>");
output.Write("STC Software</b>");
} else {
output.Write("<a href="+Link+">");
output.Write("<img src=./images/logo_small.gif
align=middle border=0>");
output.WriteLine("</a>");
output.Write<br>");
output.Write("<b style=font-style:12;");
output.Write("font-family:arial;color:#333333;>");
output.Write("Shell Design Studio</b>");
}
}
}
}

Let’s examine the three distinguishing features of a custom control class:

1. Inherits from System.Web.UI.Control.

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.

2. Overrides the Render method.

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.

3. Uses HtmlTextWriter object to generate HTML code.

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.

//Following yields: <table border=0>


Output.WriteBeginTag("table")
Output.WriteAttribute("border","0");
Output.WriteEndTag("table")

A third approach uses “stack-based” methods to render code. It uses AddAt-


tribute methods to define attributes for a tag that is then created with a Render-
BeginTag method call and closed with a RenderEndTag call. Although the
approach is verbose, it has the advantage of automatically detecting which version of
HTML a browser supports and emitting code for that version.
The following code generates the same HTML as is in Listing 16-7 for the large
image. It relies on a mixture of HtmlTextWriter methods and special tag, attribute,
and style enumerations. Refer to the documentation of HtmlTextWriter for the
lengthy list of methods and enumerations.

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 (" &nbsp;&nbsp;");
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();

Using a Custom Control


The key to using a custom control is the @Register directive, which was discussed
in Section 16.1. Its Assembly and Namespace attributes identify the assembly and
namespace of the custom control. Its TagPrefix attribute notifies the ASP.NET
runtime that any tag containing this prefix value refers to the control specified in the
directive. Here is a Web page that includes the custom CompanyLogo control:

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


<%@ Register Namespace="CompanyControls" TagPrefix="logo"
Assembly="logocontrol" %>
16.6 Building and Using Custom Web Controls 797

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

Control State Management


Let’s change the preceding code to set the LogoType property when the page is first
loaded, rather than within the body of the code. We use the IsPostBack property
for this purpose:

protected void Page_Load(object src, EventArgs e)


{
if (!IsPostBack) {
lgc.LogoType="large";
}
}

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

public class CompanyLogo : Control


{
// Custom control to display large or small company logo
public CompanyLogo() {
ViewState["logo_sz"] = "small";
ViewState["myLink"] = "";
}
public string LogoType
{
get {return (string) ViewState["logo_sz"]; }
set {ViewState["logo_sz"]= value; }
}
public string Link
{
get {return (string) ViewState["myLink"]; }
set {ViewState["myLink"]= value; }
}
// Rest of class code is here...

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:

• The composite does not have to override the Control.Render


method to display controls. Instead, it must override the Con-
trol.CreateChildControls method to add existing controls to the
collection of controls making up the composite control.
16.6 Building and Using Custom Web Controls 799

• The custom control class should inherit from the INamingContainer


interface. Its purpose is to indicate to ASP.NET that child controls
exist and they should be placed in a separate namespace. This pre-
vents name collision problems when a page contains more than one
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.

Listing 16-11 A Composite Control—bmicompos.cs


using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace CompositionControls {
public class BMIComposition : Control, INamingContainer {
TextBox htf;
TextBox hti;
TextBox wt;
Label bmi;
private void getBMI(object sender, System.EventArgs e)
{
if (Page.IsValid) {
decimal f = Convert.ToDecimal(htf.Text);
decimal inch = Convert.ToDecimal(hti.Text);
decimal w = Convert.ToDecimal(wt.Text);
decimal totinches = f * 12 + inch;
decimal h2 = totinches * totinches;
decimal massIndex = (w * 703 * 10/ h2)/10;
bmi.Text = massIndex.ToString("##.##");
}
}
protected override void CreateChildControls() {
htf = new TextBox();
hti = new TextBox();
wt = new TextBox();
bmi = new Label();
800 Chapter 16 ■ ASP.NET Web Forms and Controls

Listing 16-11 A Composite Control—bmicompos.cs (continued)


bmi.Width= 50;
bmi.BorderStyle= BorderStyle.Solid;
bmi.BorderWidth=2;
htf.Width= 30;
hti.Width= 30;
hti.ID = "hti";
wt.Width = 40;
// Display calculator interface
Controls.Add(new LiteralControl
("&nbsp;&nbsp;<b>BMI Calculator</b>"));
Controls.Add(new LiteralControl
("<br>BMI: &nbsp;&nbsp;"));
Controls.Add(bmi);
Controls.Add(new LiteralControl("<br>Height: &nbsp;"));
Controls.Add(htf);
Controls.Add(new LiteralControl("&nbsp;&nbsp"));
Controls.Add(hti);
Controls.Add(new LiteralControl(" (feet/inches)"));
Controls.Add(new LiteralControl("<br>Weight: "));
Controls.Add(wt);
Controls.Add(new LiteralControl("<br>"));
// Validation control for inches accepted
RangeValidator rv = new RangeValidator();
rv.ControlToValidate="hti";
rv.MaximumValue="12";
rv.MinimumValue="0";
rv.Type=ValidationDataType.Integer;
rv.ErrorMessage="Inches must be 1-12";
Controls.Add(rv);
// Button to invoke BMI calculation routine
Button calcBMI = new Button();
calcBMI.Text = "Submit Form";
calcBMI.Click += new EventHandler(this.getBMI);
this.Controls.Add(calcBMI);
}
}
}

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.

csc /t:library bmicompos.cs

The control is included in a page using the @Register directive as described


earlier:

<%@ Register Namespace="CompositionControls" TagPrefix="bmicon"


Assembly="bmicompos" %>
<HTML>
<table border=0 color=#cccccc>
<tr><td>
<bmicon:BMIComposition runat="server" id="bmic" />
</td></tr></table>

Figure 16-16 BMI composite control

16.7 Selecting a Web Control


to Display Data
ASP.NET developers have an overflowing toolbox of controls to choose from—many
with overlapping functionality. Table 16-6 offers some recommendations for select-
ing a control, or combination of controls, to handle frequently encountered Web
page tasks. Some of the suggested controls have been introduced in this chapter; but
for other important ones, such as the GridView, DetailsView, and FormView, you
should refer to an in-depth ASP.NET 2.0 book or reference source.
802 Chapter 16 ■ ASP.NET Web Forms and Controls

Table 16-6 Controls Recommended for Typical Web Page Applications

Page Requirement Controls

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.

Display a parent-child relationship ListBox or DropDownList and a GridView. See


between data. Figure 16-13 on page 779.

Display a hierarchical view, such as a TreeView. Allows nodes to be expanded to display


directory or XML elements. data on lower level.

View, edit, or delete data, one record DetailsView or FormView.


at a time.

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.

16.9 Test Your Understanding


1. What are the two standard HTTP methods of sending data to a Web
host? Which does ASP.NET use as its default?

2. Indicate whether the following statements are true or false.


a. All controls in ASP.NET are classes.
b. An .aspx page must be compiled before it can respond to
a request.
c. A user must have the .NET runtime installed to take advantage of
ASP.NET Web pages.
d. ASP.NET may render different HTML code for different browsers.
e. A Web page may contain both HTML Server and Web controls.

3. Which collection class contains the data for List controls?

4. You set up an event handler to be called when the contents of a Text-


Box change. If the contents are changed, when is the event handler
code executed?

5. Which property enables the controls on a master page to be program-


matically accessed?

6. Which property must be set and which method executed to bind a


control to a data source?

7. Indicate whether the following statements are true or false with


regard to a data-bound control.
a. It can bind directly to a DataAdapter.
b. It can bind directly to a DataReader.
c. The control is populated with data when its Fill method is
invoked.
d. Its DataSource property can specify a DataSet or a data source
control.
804 Chapter 16 ■ ASP.NET Web Forms and Controls

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?

9. Which directive must a Web page include to use a custom control on


the page?
a. @Page
b. @Import
c. @Register
d. @Assembly

10. What role does the HtmlTextWriter class play in the implementa-
tion of a custom control?

11. What are the advantages of creating a composite control versus a


non-composite custom control?
This page intentionally left blank
THE ASP.NET
APPLICATION
ENVIRONMENT

Topics in This Chapter

• HTTP Request and Response Objects: These ASP.NET classes


correspond closely to the HTTP request and response
specifications, and expose information about a request or
response through class properties.
• Configuration Files: Configuration files provide an easy way to
specify the behavior and performance of a Web application.
Focus is on the web.config file.
• ASP.NET Security: Forms Authentication is a platform-neutral
technique that can be used to control who can access a Web
page. An example demonstrates the use of Forms Authentication
to manage authentication and authorization.
• State Management: Both the Application and Session
classes can be used to maintain information during a Web session
or during the life of a Web application.
• Caching: Data caching and output caching can be used to
improve Web application performance.
• Accessing Web Resources: The WebRequest and WebResponse
classes provide a simple way to request and process Web pages
for the purpose of extracting page content.
• HTTP Pipeline: The HTTP pipeline is the combination of events,
HTTP modules, and HTTP handlers that affect how requests and
responses are handled as they pass between a client and server.
17

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.

17.1 HTTP Request and


Response Classes
HTTP specifications1 concisely define messages as “requests from client to server
and responses from server to client.” At its most elemental level, the role of
ASP.NET is to enable server-based applications to handle HTTP messages. The pri-
mary way it does this is through HTTPRequest and HttpResponse classes. The
request class contains the HTTP values sent by a client during a Web request; the
response class contains the values returned to the client.

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.

HTTP Request Message Structure


Figure 17-1 represents the general structure of the HTTP request message as
defined by the HTTP/1.1 specifications.

1. RFC 2616—Hypertext Transport Protocol—HTTP/1.1.


17.1 HTTP Request and Response Classes 809

Request-line Get /products/dvd.htm HTTP/1.1

General Header Host:www.videoequip.com


Cache-Control:no-cache
Connection:Keep-Alive

Request Header Content-Length:133


Accept-Language:en-us
. . .
Entity Header Content-Length:133
Content-Language:en
. . .
Body

Figure 17-1 Structure of a request message

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:

• The Request-Line consists of three parts: the method token, the


Request-URI, and the protocol version. Several methods are available,
the most common being the POST and GET methods described in
Chapter 16. The Uniform Resource Identifier (URI) specifies the
resource being requested. This most commonly takes the form of a
Uniform Resource Locator (URL), but can be a file or other resource.
The protocol version closes out the Request-Line.
• The general-header is used to pass fields that apply to both
requests and responses. The most important of these is the
cache-control field that specifies, among other things, what can be
cached and how long it can be cached.
• The request-header is used by the client to pass additional informa-
tion about the request, as well as the client. Most of the HttpRequest
object’s properties correspond to values in this header.

Viewing the Request Message


For debugging—or simply out of curiosity—you may want to view the raw contents
of the HTTP request. A simple way to do this is to use the SaveAs method of the
HttpRequest class to write the request to a file where it can be viewed with a text
editor. The method, as shown here, takes two parameters: the name of the output file
and a bool type switch that indicates whether HTTP headers are to be included in
the output.
810 Chapter 17 ■ The ASP.NET Application Environment

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.

POST /ideas/panel.aspx HTTP/1.1


Cache-Control: no-cache
Connection: Keep-Alive
Content-Length: 158
Content-Type: application/x-www-form-urlencoded
Accept: image/gif, image/x-xbitmap, image/jpeg, */*
Accept-Encoding: gzip, deflate
Accept-Language: en-us
Cookie: ASP.NET_SessionId=uszrfs45z4f20y45v0wyyp45
Host: localhost
Referer: http://localhost/ideas/panel.aspx
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0;
.NET CLR 1.0.3705; .NET CLR 1.1.4322; .NET CLR 2.0.40607)

__VIEWSTATE=%2FwEPDwULLTEwODExMTgwOTAPZBYCA ...
&txtFirstName=joanna&txtLastName=larson&btnSubmit=Submit

HttpRequest Class Structure


Table 17-1 summarizes selected properties of the HttpRequest class. Where applica-
ble, it includes the underlying header and message field on which its content is based.

Table 17-1 HttpRequest Properties

HttpRequest Request
Property Message Field Description

AcceptTypes — String array of client-supported MIME


accept types.

Browser (request-header) Identifies software program making


User-Agent request.

ClientCertificate — Returns the client’s security certificate


as an HttpClientCertificate
object. Used with SSL when a client is
configured with a personal certificate.

ContentEncoding (entity-header) Character set of the entity body.


Content-Type
17.1 HTTP Request and Response Classes 811

Table 17-1 HttpRequest Properties (continued)

HttpRequest Request
Property Message Field Description

ContentLength (entity-header) Size of client request.


Content-Length

ContentType (entity-header) MIME content type of request.


Content-Type

Cookies Not defined in HTTP Collection of client’s cookie variables.


protocol

FilePath Request-Line URI The virtual path of the currently


requested page.

Files — Files uploaded by the client.

Form Body The collection of form variables


including ViewState.

Headers general and Collection containing content of head-


request-header fields ers contained in request message.

HttpMethod Request-Line method HTTP data transfer method.

IsAuthenticated (request-header) Has user been authenticated. True or


Authorization False.

IsSecureConnection Request-Line True if HTTPS used.

Path Request-Line URI Virtual path of current request.

PhysicalPath — Physical path of current page.

QueryString Request-Line URI Query string arguments.

RawUrl Request-Line URI Part of a URL following the domain


name.

RequestType Request-Line method HTTP data transfer method (GET,


field POST).

TotalBytes (entity-header) Number of bytes in input stream.


Content-Length

Url Request-Line URI URL of current request.


field and Host field of
header
812 Chapter 17 ■ The ASP.NET Application Environment

Table 17-1 HttpRequest Properties (continued)

HttpRequest Request
Property Message Field Description

UrlReferrer (request-header) Information about the URL of the cli-


Referer field ent’s previous request that linked to the
current URL.

UserAgent (request-header) String containing raw information about


User-Agent the client software used for request—
usually a browser.

UserHostAddress — IP host address of the remote client.

The built-in features of ASP.NET reduce the amount of direct interaction


required between an application and the HttpRequest object. For example, we saw
in Section 16.2, “Web Forms Controls,” that ASP.NET Web controls generate code
that is automatically tailored to the client’s browser—eliminating the need for the
application code to implement logic to identify the browser. There are, however,
some cases where an application must access the object fields directly. Logging Web
statistics is one; another is working with cookies.
Cookies are used to store information on the computer of a client that has cookies
enabled on his browser. The browser is responsible for passing the cookie value as
part of the request and handling any cookies returned from the server. The Cookies
attribute references the collection of cookies returned by the browser. The following
code loops through the collection displaying the name, value, and expiration date of
cookies in the collection.

// Read cookies returned by browser


foreach(string cookieName in Request.Cookies.AllKeys){
HttpCookie cookie = Request.Cookies[cookieName];
Response.Write(cookie.Name+" = "+cookie.Value
+"<br>Expires: "+cookie.Expires);
}

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

HTTP Response Message Structure


The server responds with a status line that includes the message’s protocol version, a
success or error code, and a textual description of the error code. This is followed by
the general header and the response header that provides information about the
server. The entity header provides metainformation about the body contents or the
resource requested.

Status-line HTTP/1.1 503 Service Unavailable

General Header
Response Header Content-Length:133
Accept-Language:en-us
. . .
Entity Header Server: Apache/1.3.27 (Unix)
Allow: GET, HEAD, PUT
. . .
Body

Figure 17-2 Structure of a response message

Viewing an HTTP Response Message


ASP.NET provides the HttpWebRequest and HttpWebResponse classes for work-
ing with HTTP requests and responses, respectively. They are discussed in detail
later in the chapter, but let’s take a preliminary look at how they can be used to dis-
play portions of a response message.
Listing 17-1 contains a simple application that sends a request to a user-entered
URL, receives the response, and extracts the status code and server description. Run
the program from the command line by typing in the program name and domain
name you want to contact:
814 Chapter 17 ■ The ASP.NET Application Environment

Example: showserver www.addison-wesley.de/


Output: Web Host: Microsoft-IIS/5.0
Response Status: OK

Getting Status and Server Information


Listing 17-1
from a Response Message
//File: showserver.cs
using System;
using System.Net;
class WebClient
{
// To run, type in: showserver <domain name>
public static void Main(string[] args)
{
HttpWebRequest request;
HttpWebResponse response;
if(args.Length>0)
{
string url="http://"+args[0];
// Create a request to the URL
request = (HttpWebRequest) WebRequest.Create(url);
try
{
response = (HttpWebResponse) request.GetResponse();
Console.WriteLine("Web Host: "+response.Server);
Console.WriteLine("Response Status: "+
response.StatusCode);
} catch ( Exception ex)
{
Console.Write(ex.Message);
}
} else
{
Console.Write("You must enter a domain name.");
}
}
}

HttpResponse Class Properties


Table 17-2 lists selected properties of the HttpResponse class. Some of those excluded
exist only for ASP compatibility and have been deprecated by newer properties.
17.1 HTTP Request and Response Classes 815

Table 17-2 Selected HttpResponse Properties

Property Description

BufferOutput Set to true or false to indicate whether output is buffered and


sent only when a page has been completely processed. Default is
true.

Cache HttpCachePolicy object that describes the caching policy fea-


tures such as expiration time and privacy.
Example: Response.Cache.SetExpires(
DateTime.Parse("6:00:00PM"));

CharSet Gets or sets the character set of the output stream.

ContentEncoding An Encoding object that contains information about the character


set of the current response. Encoding type includes ASCII, UTF-7,
UTF-8, and others.

ContentType String value containing MIME type of the output—for example,


“text/html”.

Cookies Collection of cookies sent to the client.

Filter A developer-written Stream object that filters all data being sent to
the client.

IsClientConnected Indicates whether client is still connected to the server.

Output TextWriter object that sends text output to the client software.
Example: Response.Output.Write("{0} Years Old", age);

StatusCode Status code returned in the response message status line.

StatusDescription Status code description returned in the response message status


line.

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

myCookie.Expires = DateTime.Now.Add(new TimeSpan(0,0,10,0));


// Add to collection
Response.Cookies.Add(myCookie);
// Later... Read specific cookie
myCookie = Request.Cookies["userid"];
if(myCookie !=null) Response.Write("userid: "+myCookie.Value);

Using HttpResponse Methods


The Response.Write method, which we have used in numerous examples to emit
output into the response stream, is the most commonly used method. Other useful
methods include the following:

• Redirect. Redirects the client’s request to another URL.


• AppendHeader. Adds an HTTP header to the response stream.
• ClearContent. Clears the contents of the response stream.
• End. Stops page execution and sends buffered output to the client.
• WriteFile. Places the contents of a file directly into the output
stream.

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.

Listing 17-2 Using HttpResponse to Download a File


//File: requestfile.aspx
<%@ Page Language="C#" %>
<script Language="C#" runat="Server">
private void Page_Load(object sender, EventArgs e)
{
//http://localserver/ideas/requestfile.aspx?file=notes.txt
string fileRequest = Request.QueryString["file"];
if(fileRequest!=null) {
// File is store in directory of application
string path = Server.MapPath(fileRequest);
System.IO.FileInfo fi = new System.IO.FileInfo(path);
if (fi.Exists) {
Response.ClearContent(); // Clear the response stream
17.2 ASP.NET and Configuration Files 817

Listing 17-2 Using HttpResponse to Download a File (continued)


// Add a header to indicate attachment type
Response.AppendHeader("Content-Disposition",
"attachment;filename="+fi.Name);
Response.AppendHeader("Content-Length",
fi.Length.ToString());
// Use octet-stream to indicate unknown media type
Response.ContentType="application/octet-stream";
// Write file to output stream
Response.WriteFile(fi.FullName);
Response.End(); // Flush buffer output to the client
} else {
Response.Write(path+" does not exist.");
}
} else {
Response.Write("No Download file was specified.");
}
}
</script>

17.2 ASP.NET and Configuration Files


One of the advantages of working with ASP.NET, as a developer or administrator, is
the ease of configuring and customizing the settings. The use of XML-based configu-
ration files replaces the previous ASP approach that required shutting down and
restarting server software to change a setting or replace DLLs. Now, changes that
affect the underlying behavior of a Web page can be made dynamically. The beauty
of this approach is that changes can be made using a simple text editor—although an
XML editor is preferable—and that ASP.NET automatically detects and applies
these changes when they occur.
Configuration information is stored in a required machine.config file and
optional web.config files. The format of these files is the same; the difference is
their scope.
As shown in Figure 17-3, configuration files can be placed in a hierarchy on the
Web server. Each file lower down in the hierarchy overrides the settings (with a few
exceptions) of files above it. The machine.config file is at the top of the hierarchy,
and its settings apply to all applications running on the machine. Web.config files
placed in application directories and subdirectories apply their settings to a specific
application or pages in an application.
818 Chapter 17 ■ The ASP.NET Application Environment

Machine Level machine.config


c:\winnt\microsoft.net\..\..\config\

Web Site Root Directory


web.config
c:\inetpub\wwwroot

Virtual Directory
web.config
c:\calculators\

Subdirectory web.config
c:\calculators\bmi

Figure 17-3 Hierarchy of configuration files

Core Note

As an alternative to a text editor, ASP.NET includes a configuration


editor with the IIS management console. It is accessed from the
Properties menu of a virtual directory.

A Look Inside web.config


The web.config file is an XML-formatted text file comprising numerous sections
and subsections. Listing 17-3 offers a sampling of sections you’re likely to find most
useful: <configSections>, <appSettings>, <location>, <system.web>, and
<connectionStrings>. After summarizing key features of these predefined sec-
tions, we’ll look at how to create a custom configuration section.

Listing 17-3 Sample web.config File


<configuration>
<!-- (1) Define custom configurations -->
<configSections>
<section name="RewriterConfig"
type="RewriteSectionHandler, URLRewriter" />
</configSections>
<RewriterConfig>
<!-- Contents of custom configuration section -->
</RewriterConfig>
17.2 ASP.NET and Configuration Files 819

Listing 17-3 Sample web.config File (continued)


<!-- (2) Place application data in here -->
<appSettings>
<add key="mainFont" value="arial" />
<add key="fontSize" value="2" />
</appSettings>
<!-- (3) Define system.web settings for a directory -->
<location path="calculators\bmi">
<system.web>
<trace enabled="true"
pageOutput="true" />
</system.web>
</location>
<!-- (4) Main ASP.NET Application settings -->
<system.web>
<sessionState
cookieless="false"
timeout=20 />
...
</system.web>
<!-- (5) Connection string for database -->
<connectionStrings>
<!-- connection string description -->
</connectionStrings>
</configuration>

<appSettings> Configuration Section


This area is used by developers to hold constant data values required by an applica-
tion. These values typically include preference settings that define the appearance of
a Web page. This entry illustrates how font settings are added using an <add> ele-
ment and a key-value pair of attributes:

<add key="mainFont" value="arial" />

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


As mentioned previously, web.config files can be placed hierarchically within a
directory structure, with files at lower levels overriding the settings of files in parent
directories. This approach permits you to tailor the actions of ASP.NET down to the
application and page level. However, this flexibility brings with it the potential prob-
lem of keeping track of the settings in multiple configuration files. As an alternative,
ASP.NET provides a way to configure the settings for multiple directories in a single
web.config file by using <location> elements. The <location> section operates
as a configuration file within a configuration file. The element has a path attribute
that takes the value of a virtual directory name. The settings within the contained
<system.web> block apply to the virtual directory.
As an example, suppose that during application development we want to view the
trace information as we test our applications. Because we don’t want this voluminous
output in the final product, we can set up a virtual “test” directory and enable tracing
for that directory only.

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

<system.web> Configuration Section


This is the area in web.config where an administrator can configure just about any
aspect of the ASP.NET environment. It can be used to set session time-out length,
user authentication and authorization rules, the type of session state management
used, and the default culture used for processing requests. Table 17-3 summarizes
the more important elements in this section.

Table 17-3 Selected system.web Elements

Element Use

<compilation> Sets default language and debug option.

<customErrors> Defines custom errors and error pages.

<trace> Enables or disables the trace feature for an application.

<pages> Sets default Web page attributes.


17.2 ASP.NET and Configuration Files 821

Table 17-3 Selected system.web Elements (continued)

Element Use

<globalization> Defines response/request encoding and culture-specific setting for


Web pages.

<processModel> Used to configure process setting for an IIS Web server.

<authentication> Specifies authentication mode for a client: Windows, Forms,


Passport, None.

<authorization> Allows or denies access to resources.

<membership> Defines how the Membership class manages user credentials and
authentication.

<identity> Specifies whether client impersonation is used on a Web page


request.

<sessionState> Configures the session state features of ASP.NET.

Of particular interest are the authentication, authorization, and member-


ship elements that are discussed in Section 17.3, “ASP.NET Application Security.”
Other useful sections are summarized here.

The <compilation> Section


Use the debug attribute to indicate whether debugging symbols will be included in
the compiled code. The defaultLanguage attribute sets the default language used
in a Web page’s script blocks. Multiple languages can be selected by using a semico-
lon to separate them.

<compilation debug="true" defaultLanguage="C#" />

The <customErrors> Section


The primary use of this section is to redirect errors to custom Web pages. Here is an
entry that establishes a default error page, and a page to handle the HTTP 404 status
code that arises when a resource cannot be found:

<customErrors defaultRedirect = "defaultError.aspx" mode="On" >


<error statusCode="404" redirect="Error404.aspx" />
</customErrors>

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.

The <trace> Section


The <trace> element is used to enable application-level trace logging. This is in
contrast to the trace attribute of the <%@Page> directive that provides only
page-level tracing. Here is an example that illustrates the use of the <trace> ele-
ment and its associated attributes:

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

• pageOutput specifies whether the trace log is appended to the Web


page for display on the client’s browser. If set to false, the output can
be viewed in the trace.axd page of the application’s root. Here is an
example:
Application URL: http://localhost/calculation
Trace log: http://localhost/calculation/trace.axd

• The requestLimit attribute sets the maximum number of trace logs


maintained.
• localOnly indicates whether tracing is enabled for localhost users
only or all users. The default is true.

The <sessionState> Section


One of the more powerful features of ASP.NET is the ability to maintain state infor-
mation for a session; and the <sessionState> element plays a definitive role in
selecting how it is implemented. By setting its mode attribute to one of four values,
the software architect can specify where the session state information is stored.

• off disables the use of session state management.


• Inproc enables in-process state management (aspnet_state.exe).
17.2 ASP.NET and Configuration Files 823

• StateServer state is stored by a surrogate process running on a


selected server.
• SqlServer state is maintained in a temporary SQL Server table.

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.

The <connectionStrings> Section


Prior to .NET version 2.0, developers usually resorted to storing connection strings
as a key-value pair in the <appSettings> section. ASP.NET 2.0 now provides a pre-
defined <connectionStrings> section for this purpose. As shown in this example,
the section takes three attributes: name, connectionString, and providerName.

<connectionStrings>
<add name="movies"
connectionString=
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=/movies.mdb;"
providerName="System.Data.OleDb"/>
</connectionStrings>

The connection string is retrieved using the ConfigurationSettings class,


which retrieves the string associated with a specified name attribute. Note that multi-
ple strings may be defined.

string cs=
ConfigurationSettings.ConnectionStrings["movies"].ConnectionString;
824 Chapter 17 ■ The ASP.NET Application Environment

Adding a Custom Configuration Section


The web.config file can be a convenient place to stash information needed by an
application. We saw earlier how simple name/value pairs can be stored in the <app-
Settings> section. Suppose, however, your application requires data represented
by a more complex XML structure. The solution is to add a custom configuration sec-
tion to web.config. However, it is not as simple as choosing a configuration name
and inserting appropriately formatted code into the configuration file. When you try
to access a Web application that uses the configuration file, you’ll receive an Unrec-
ognized Configuration Section error.
It turns out that each section in web.config has a special handler that parses
the section and makes its content available to an application. This handler is a class
that implements the System.Configuration.IConfigurationSectionHandler
interface and is declared in the <configSections> section of the web.config file.

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>

<!-- Custom Configuration Section for URL Rewriting -->


<RewriterConfig>
<RewriterRules>
<Rule>
<OldPath>/ideas/calculator.aspx</OldPath>
<NewPath>/utilities/calculator.aspx</NewPath>
</Rule>
<Rule>
17.2 ASP.NET and Configuration Files 825

<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

Listing 17-4 Configuration Section Handler


//File: RedirectConfigHandler.cs
using System.Xml;
using System.Configuration;
using System.Collections;
namespace ConfigRewriter
{
public class RulesHandler: IConfigurationSectionHandler
{
public object Create(object parent, object input,
XmlNode section)
{
Hashtable paths = new Hashtable();
string oldUrl="";
XmlElement root = (XmlElement) section;
foreach (XmlNode node in root.ChildNodes){
foreach(XmlNode child in node.ChildNodes)
{
if(child.Name=="OldPath") oldUrl= child.InnerText;
if(child.Name=="NewPath")
{
paths[oldUrl]= child.InnerText;
}
}
}
RulesData urlRules = new RulesData(paths);
return urlRules;
}
}
}

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.

// Retrieve object returned by section handler


// and use it to access content of section
RulesData r;
r = ConfigurationSettings.GetConfig(
"RewriterConfig/RewriterRules")
as RulesData;
if(r!=null)
// Pass old path to method and receive redirected path
string newURL = r.GetNewPath("/ideas/bmi.aspx");
17.3 ASP.NET Application Security 827

17.3 ASP.NET Application Security


Web-based applications must address many of the same security issues that are faced
by applications situated on a local network. The most important of these—and the
subject of this section—is client authentication. By this, we mean the capability to
identify the user attempting to access a Web resource. Unlike traditional static Web
sites that permit anonymous user access, an application that wants to tailor its con-
tent for the user, or restrict access based on authorization rights, must first authenti-
cate the user.
ASP.NET offers three forms of client authentication:

• Windows Authentication. Relies on the relationship between IIS


(Internet Information Server) and the underlying operating system’s
directory security. Its options include using the native Microsoft Win-
dows login authentication.
• Microsoft Passport Service. Microsoft’s proprietary “single sign-on”
authentication service. Requires subscribing to the Passport network
and installing the Passport SDK to link .NET to the service.
• Forms Authentication. Allows an application to define user creden-
tials and handle authentication based on it own requirements. This
approach relies on .NET classes to create and manage a cookie that
maintains user authentication information during the lifetime of a
user’s session.

The most flexible—and least proprietary—of these techniques is Forms Authenti-


cation. It represents the natural evolution from a do-it-yourself approach that
requires creating a login screen, writing code to match user credentials against a
database or file, and building a cookie to maintain client data during a session.
ASP.NET offers substitutes for each of these tasks, which can be easily plugged into a
developer’s custom solution. The remainder of this section examines this form of
authentication.

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

user is authenticated and ASP.NET methods are used to create a FormsAuthenti-


cationTicket that contains the user’s security information. The ticket is encrypted
and stored as a cookie. The cookie may be configured to expire at the end of the ses-
sion, or remain on the client’s machine so that the user can be automatically identi-
fied in future sessions. An optional step in this process is to add authorization
information about a client to the ticket. For example, users may be assigned to
groups that define their access rights. The code to add this role information can be
placed in the global.asax file to handle the application’s AuthenticateRequest
event.
After these steps are complete and the user is authenticated, control passes to the
Web page originally requested. Subsequent session authentication uses information
in the authentication cookie and avoids these steps.

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

Security Settings in web.config


for Forms Authentication
The web.config file plays an important role in supporting authentication and
authorization in ASP.NET security. Listing 17-5 presents many of its elements and
optional attribute values that are used to select and implement a security method.
17.3 ASP.NET Application Security 829

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.

Authentication and Authorization Configuration


Listing 17-5
Sections
<Configuration>
<system.web>
<authentication mode="{Windows|Forms|Passport|None}">
<forms name="Cookiename"
loginurl="path to login file"
timeout="minutes"
protection="{None|All|Encryption|Validation}"
path="Cookiepath" >
<credentials passwordFormat="Clear|MDS|SHA1}">
<user name="User Name" password="password" />
</credentials>
</forms>
</authentication>
830 Chapter 17 ■ The ASP.NET Application Environment

Authentication and Authorization Configuration


Listing 17-5
Sections (continued)
<authorization>
<allow users="comma-separated list"
roles="roles list" />
<deny users="comma-separated list"
roles="roles list />
</authorization>
...
</system.web>
...
</configuration>

An Example of Forms Authentication


We’ll now create a simple Web page that can only be accessed by users who provide a
valid name and password. The example is then extended to demonstrate how client
role information can be incorporated in the authorization process. As you work
through the example, the most important things to observe are

• The interrelationship between the web.config file and authentica-


tion methods used in the login screen code.
• How roles can be assigned to users by the use of global.asax.
• How to access authentication ticket information through the underly-
ing .NET classes that manage authentication.

Using web.config for


Authentication and Authorization
The configuration file segment in this example is used for both user authentication
and authorization. The <credentials> element contains the names and passwords
of three users who may access the system. The <authorization> element contains
rules that deny login access to all anonymous users as well as one individual user.
Only users Joanna and Ruth are permitted to access the Web page.

<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

<user name="ruth" password="raleigh" />


<user name="kim" password="newyork" />
</credentials>
</forms>
</authentication>
<authorization>
<deny users="?,kim" />
</authorization>

The same authorization rights can be granted by a combination of allow/deny


rules:

<allow users="joanna,ruth" />


<deny users="*" />

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.

The Login Screen—Using the


FormsAuthentication Class
The web.config file used in this example redirects the initial request for a Web
page to applogin.aspx, which accepts login credentials and performs user authen-
tication. The login part is easy—two text boxes are used to accept a user name and
password. The technique for authenticating the user is more interesting.
Traditionally, login screens validate a user-supplied name and password against a
database or file containing valid users. You are still free to do this in ASP.NET, but
there are other options. These include storing names and passwords in the
web.config file, or using the ASP.NET Membership class and its preconfigured
database to manage user credentials. For demonstration purposes, this example uses
web.config as the data store. Keep in mind that this is not the recommended
approach if there are more than a few users, or multiple servers are used, which
would require synchronizing the configuration file across machines.
As Listing 17-6 illustrates, the FormsAuthentication class plays the key role in
authenticating a user and creating the authentication ticket that identifies the user.
This important class provides static properties and methods that are used to manipu-
late and manage authentication information. Two of its methods are used here:
Authenticate accepts a name and password, which it attempts to validate against
the data in web.config; RedirectFromLoginPage redirects the user to the page
originally requested, and creates an authentication cookie for the user. The second
parameter to this method indicates whether the cookie should persist on the user’s
machine beyond the lifetime of the session.
832 Chapter 17 ■ The ASP.NET Application Environment

Listing 17-6 Login Screen Using ASP.NET Authentication


<%@ Page Language="C#" %>
<html>
<head>
<title>login</title>
<script runat="Server">
void verify_Form(object sender, System.EventArgs e)
{
// Code to verify user against a database goes here...
// or use .NET to verify user using web.config info.
// Name and password are compared against web.config content
if( FormsAuthentication.Authenticate(
txtName.Text,txtPW.Text))
{
// Redirect to original form and
// create authentication ticket
bool persistCookie=false;
FormsAuthentication.RedirectFromLoginPage(txtName.Text,
persistCookie);
}
else
{ errmsg.Text="Cannot log in user."; }
}
</script>
</head>
<body >
<form id=Form1 method=post runat="server">
<asp:Panel id="pnlName"
style="Z-INDEX: 101; LEFT: 20px; POSITION: absolute;
TOP: 64px"
BackColor = "#efefe4"
Height="120px" Width="278px"
runat="server" >
<TABLE>
<TR>
<TD><asp:Label id="lblName" Runat="server"
text="User ID:"></asp:Label></TD>
<TD><asp:TextBox id="txtName" Runat="server">
</asp:TextBox></TD></TR>
<TR>
<TD><asp:Label id="lblPW" Runat="server"
text="Password:"></asp:Label></TD>
<TD><asp:TextBox id="txtPW" TextMode="Password"
Runat="server"> </asp:TextBox></TD></TR>
17.3 ASP.NET Application Security 833

Listing 17-6 Login Screen Using ASP.NET Authentication (continued)


<TR>
<TD colspan=2 align=center>
<asp:Button ID="btnSubmit"
Text="Submit" Font-Bold="true"
OnClick="verify_Form" Runat="server" /></TD></TR>
<TR>
<TD colspan=2 align=center>
<asp:Label id=errmsg runat="server" /></TD>
</TR>
</TABLE>
</asp:Panel>
</FORM>
</body>
</html>

Adding Role-Based Authorization with global.asax


Authentication is often only the first step in permitting a user to access Web
resources; the second step is authorization—the process of determining what
resources the user is authorized to access. If all users have full access, this step can be
ignored. However, if a user’s access rights, or role, are determined by membership in
a group, this group must be identified and associated with the user.
After a user is created in ASP.NET, information about that user is available
through the Page.User property. This property returns a principal object, so named
because it implements the IPrincipal interface. This interface has two important
members (see Figure 17-5) that the principal object implements: Identity, which
can be used to get the name of the user, and the IsInRole method that is used to
check a user’s group membership.
The IsInRole method is passed a role name and returns a bool value indicating
whether the user is in that role. It is the developer’s task to assign the roles to the
authenticated user object so that this method will have an internal list to check
against. Looking at Figure 17-5, you would expect this list to be part of the Gener-
icPrincipal object—and you would be right. The constructor for this class accepts
as one of its parameters a string array of roles to be associated with the user. To
assign these roles, the application can take advantage of the AuthenticateRequest
event that occurs when the identity of a user is established. A handler for this event is
placed in the global.asax file.
Listing 17-7 shows how code in the event handler assigns roles to the current user. It
determines the user roles—in this case, calling GetRoles—and places them in a string
array. This array, along with the current Identity, is passed to the GenericPrinci-
pal constructor to create a new object that updates the value of the User property.
834 Chapter 17 ■ The ASP.NET Application Environment

<<interface>> <<interface>>
IPrincipal IIdentity

Identity AuthenticationType
IsInRole() IsAuthenticated
Name

GenericPrincipal Page.User

Figure 17-5 User information is encapsulated in a GenericPrincipal object

Listing 17-7 Using global.asax to Add Role Information for a User


<%! file:global.asax %>
<%@ Import Namespace="System.Security" %>
<%@ Import Namespace="System.Security.Principal" %>
<%@ Application %>
<script language = "C#" runat=server>
protected void Application_AuthenticateRequest(
object src, EventArgs e)
{
if (Request.IsAuthenticated)
{
string currUser= Context.User.Identity.Name;
string roles= GetRoles(currUser);
string[] appRoles = roles.Split(new char[]{'|'});
// Create GenericPrincipal class and add roles to it
// GenericPrincipal(IIdentity identity, string[] roles)
Context.User = new GenericPrincipal(
Context.User.Identity, appRoles);
}
}
private string GetRoles( string username )
{
// Code here would query database for user's role(s).
// Return role as delimited string that can split into array.
return "Administrator|Operator";
}
17.4 Maintaining State 835

Viewing Authentication Information


After the authentication steps are complete, an application may want to log informa-
tion or make decisions based on information about the user. The User property
exposes all of the members shown in Figure 17-5. Further authentication details,
such as when the authentication cookie expires or when it was issued, can be
obtained from properties exposed by the authentication ticket. As shown in Listing
17-8, an instance of the cookie is created and then converted to a FormsAuthenti-
cationTicket object by using the Decrypt method of the FormsAuthentica-
tion class. The properties of this object are then displayed.

Listing 17-8 Displaying Authentication Details


// Display authentication details
Response.Write("Client: "+ User.Identity.Name+"<br>");
if(User.IsInRole("Operator")) Response.Write(
"Role: Operator<br>");
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if(authCookie !=null)
{
// Create ticket from cookie and display properties
FormsAuthenticationTicket authTicket = null;
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
Response.Write("Issued: "+authTicket.IssueDate+"<br>");
Response.Write("Expiration: "+authTicket.Expiration+"<br>");
Response.Write("Persistent: "+authTicket.IsPersistent);
}

17.4 Maintaining State


The standard for the HTTP protocol is contained in a document known as RFC
2616. In printed form, it is more than 120 pages in length. If you search this docu-
ment, you’ll find that not one page, section, or sentence discusses a method for main-
taining state information between HTTP requests. The term cookie is completely
absent. So, unlike human memory where “our thoughts are linked by many a hidden
chain” (Alexander Pope), the Internet protocol is designed to be stateless—each
request unlinked to the preceding one.
Yet, for Internet applications to flourish, a means has to exist to identify specific
users. Online purchasing, surveys, and the need to recognize user preferences
depend on it. The first efforts to maintain state information relied on using features
836 Chapter 17 ■ The ASP.NET Application Environment

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.

Table 17-4 State Management Techniques for Web Applications

Web Farm
Technique Compatible Description

Query String Yes Data passed as part of URL.

View State Yes ASP.NET hidden View_State field used.


Cookie Yes Information passed to/from browser in a cookie.

Application No An Application object maintains information


available to all sessions accessing an application.

Session: In-Process No Session state is stored and managed using the


Aspnet_wp.exe process.

Session: State Server Yes Session state is stored in the Aspnet_state.exe


process.

Session: SQL Server Yes State information is maintained in a temporary


table in a database.
17.4 Maintaining State 837

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.

protected void Application_Start(object sender, EventArgs e)


{
Application["targetBMI"]= 21;
Application["obeseBMI"] = 26;
}

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

Accessing and Updating Application State Data


All sessions using the application may access the Application object through the
Application property of the System.Web.UI.Page type. The only thing to note
about accessing data is that it must be cast, because the information is stored as an
object. Here is a segment that illustrates the primary properties and methods for
working with the Application object:

int target = (int) Application["targetBMI"];


// Change value
Application["targetBMI"] = 22;
// List all HttpApplicationState values
foreach( string s in Application.AllKeys){
Response.Output.Write("<br>{0} = {1}",s,Application[s]);
}
// Remove Application item
Application.Remove("obeseBMI");

Not illustrated are the Lock and UnLock methods that allow application variables
to be updated in a thread-safe manner.

Considerations in Using Application State


The variables contained in application state have two primary uses: as read-only data
globally available across all of the application’s sessions, and as variables that are
838 Chapter 17 ■ The ASP.NET Application Environment

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

Application recycling refers to shutting down and restarting an


application based on criteria such as time, memory usage, and number
of client requests. It is set using the <processModel> tag in the
web.config file. The following setting recycles the application every
two hours.
<processModel timeout="120">

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:

int ict= (int)Session["QuestionsAnswered"]+1;


Session["QuestionsAnswered"]=ict;

The HttpSessionState class contains several members that are useful in


manipulating and interrogating the Session object. The following code demon-
strates their use:

// Session ID is unique to each session


Response.Write("<br>Session ID: "+Session.SessionID);
// Mode may be InProc, StateServer, SqlServer, Off
Response.Write("<br>Processing Mode: "+Session.Mode);
// Session times out after 20 minutes (default) of inactivity
Response.Write("<br> Timeout: "+Session.Timeout);
// Number of items in session state
Response.Write("<br>Items: "+Session.Count);
// List key-values in session state
foreach( string key in Session.Keys)
Response.Output.Write("<br>{0} = {1}",key, Session[key]);

The most interesting of these properties, by far, is Session.Mode, which indicates


where the Session object is configured to store its data. Session state information can
be stored using one of three methods: InProc, StateServer, or SqlServer.
Although the choice does not affect the way information is accessed through the Ses-
sion object, it does affect application performance, manageability, and extensibility.
Let’s look at the details of using all three backing stores (see Figure 17-6).

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

In-Process Session State


By default, ASP.NET stores session state in-process using the aspnet_wp.exe pro-
cess. This means that the information is managed in memory space where the appli-
cation is running. This has the advantage of providing fast access to the data. The
drawbacks are that it must run on a single server—precluding its use with Web
farms—and that all session data is lost if the server reboots. Given this, it is recom-
mended for single-server sites that need to maintain moderate amounts of data in
state.
To select in-process session state management, set the mode attribute to InProc
in the <sessionState> element of the web.config file. As shown here, you can
also specify a time limit specifying how long the process may be idle before the ses-
sion data is discarded.

<configuration>
<system.web>
<sessionState mode="InProc" timeout="30"/>
</system.web>
</configuration>

Out-of-Process Session Using a State Server Process


The concept of out-of-process session state management entails the use of a mecha-
nism outside of the application’s process to store state data. ASP.NET provides two
such mechanisms: a separate process running on a selected state server and the use
of a data server to store state information.
When session state mode is used, session state is stored in the Aspnet_
state.exe process. This allows an application running on multiple servers to share
state information. Because the server running this process is accessed via a network
on the Internet, performance is slower than using the in-process approach. Also, the
data is susceptible to a server reboot.
To access a state server, make sure the machine selected is running the
Aspnet_state.exe process. Any server accessing the state server must set the
<sessionState> element in its web.config file to point to the state server. The
default port used is 42424. On a Windows-based operating system, this can be
changed through the registry.

<configuration>
<system.web>
<sessionState mode="StateServer"
stateConnectionString="192.168.1.109:42424"
/>
</system.web>
</configuration>
17.5 Caching 841

Out-of-Process Session Using SQL Server


Using a SQL Server database to store session state data also offers the capability of
sharing data among machines. In addition, SQL Server security features can be used
to selectively provide access to the data; and, of course, the data is not transient
because it exists in tables. The disadvantages of this approach are that it only works
with Microsoft SQL Server and it is the slowest of the three session state modes.
To prepare a machine running SQL Server to handle session state requires no
more than creating the tables that ASP.NET expects. The InstallPersistSql-
State.sql script takes care of this. By default, the script is located in the \system-
root\Microsoft.NET\Framework\version folder. It creates a database named
ASPState that contains the state tables.
Web.config is used to tell ASP.NET that session information for the applica-
tion(s) is stored on a SQL Server. The <sessionState> element is set to specify the
mode and connection string.

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

Page Output Caching


ASP.NET permits a developer to indicate whether all or part of a Web Form should
be cached, where it should be cached, and how long it should remain in the cache
before it expires and must be refreshed. The key to cache control for a Web Form is
the @OutputCache directive:

<%@OutputCache Duration="30" Location="Any"


VaryByParam="none" %>

Its attributes declare the specific caching policies in effect for that Web page.
Let’s look at how these attributes are used.

Core Note

ASP.NET processes the @OutputCache directive by translating its


attributes into HTTPCachePolicy method calls.

Specifying Cache Duration


The mandatory Duration attribute specifies the length, in seconds, that the page or
control is cached. Setting this to 20 seconds generates the underlying statement:

Response.Cache.SetExpires(DateTime.Now.AddSeconds(20));

Specifying the Caching Location


The Location attribute specifies where the caching can occur. In general, caching
occurs on a server, browser, or somewhere in between, such as on a proxy server. The
values of this attribute correspond to these locations: Any, Client, DownStream,
Server, ServerAndClient, or None. DownStream refers to a cache-capable device
other than the origin server. None disables caching everywhere.
17.5 Caching 843

Conditional Caching with VaryByParam,


VaryByHeader, and VaryByCustom
In Chapter 16, we developed a BMI calculator Web page in which the user enters a
height and weight value and the BMI value is returned. Because there are thousands
of combinations of height (htf, hti) and weight (wt) parameters, several slightly dif-
ferent versions of the same page are likely to be created. The VaryByParam attribute
is available for cases such as this. Its value indicates the parameter(s) (from a POST or
query string) that should be considered when selecting a page to cache. For example,
the following statement causes a page to be cached for each unique combination of
wt, hti, and htf values:

<%@ OutputCache Duration="60" VaryByParam="wt;hti;htf" %>

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

Figure 17-7 Cached page: VaryByParam="wt;hti;htf"

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

<%@ OutputCache Duration="60" VaryByParam="*"


VaryByCustom="Browser" %>
844 Chapter 17 ■ The ASP.NET Application Environment

This attribute also can be set to recognize custom values that your program gener-
ates. Please refer to caching documentation for details on this.

Caching a Partial Page (Fragment Caching)


All parts of a Web page are not created equally. Some, such as headers and links,
rarely change, whereas other sections, such as the latest stock exchange quotes,
change by the minute. A desirable caching strategy is to identify the static objects on
the page and cache only those. This can be done in ASP.NET by creating custom
controls (.ascx files) for the sections of the Web page to be cached. Custom controls
have their own OutputCache directive that determines how they are cached, irre-
spective of the form containing them.
To illustrate the concept, let’s create a simple control to be embedded within a
Web page (see Figure 17-8). This segment simply contains a couple of links to other
Web sites.

<!-- File: CacheFrag.ascx -->


<%@ OutputCache Duration="30" VaryByParam="None" %>
<b>My Links</b><br>
<a href=http://www.moviesites.org>Movies</a><br>
<a href=http://www.imdb.com>Movie Reviews</a>
<br>

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.

<!-- File: CacheMain -->


<%@ Page Language="C#" %>
<%@ OutputCache Location="None" VaryByParam="*" %>
<%@ Register TagPrefix="frag" TagName="fraglinks"
Src="CacheFrag.ascx" %>
<HTML>
<HEAD><TITLE>Sample Web Site</TITLE>
<body>
<center> <b>Fragment Sample</b>
<br>
<% Response.Output.Write(DateTime.Now.ToString("f")); %>
</center>
<frag:fraglinks id="fragmentlink" runat="server" />
</body>
</html>
17.5 Caching 845

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.

Adding Items to a Data Cache


The simplest way to add data to a data cache is to use the key-value syntax of the
Cache property of the Page class:

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:

public void Insert( Cache.Insert (


string key, "userid",
object value, "rigoletto",
CacheDependency dependencies, null,
DateTime absoluteExpiration, DateTime.Now.AddMinutes(20),
TimeSpan slidingExpiration, TimeSpan.Zero,
CacheItemPriority priority, CacheItemPriority.Normal,
CacheItemRemovedCallBack cb null
); );
846 Chapter 17 ■ The ASP.NET Application Environment

Either the absoluteExpiration or slidingExpiration parameter can be


used to determine when the cached data expires. The former sets a specific date and
time; the latter sets the expiration to occur a specified amount of time after the value
is last accessed. To disable sliding expiration, assign the value Cache.NoSlidingEx-
piration.
The dependencies parameter allows cached data to be tied to files, directories,
or other objects in the cache. Establishing this dependency causes the cached data to
be removed when the object it is dependent upon changes. Thus, if cached data
comes from a file and the file contents are modified, the data in the cache is
removed.

string filename = "actors";


CacheDependency dep = new CacheDependency(fileName);
cache.Insert("key", "value", dep);

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.

Retrieving Items from the Data Cache


To retrieve data from the cache, simply specify the key (case is ignored) of the
desired data:

String user = (string) Cache["userid"];

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.

Data Caching as a Substitute for ViewState


The true value of data caching comes into play when large amounts of data need to
be saved to restore a page’s state. As was explained in the previous chapter, View-
State automatically saves data and state information for Web controls on a page as
long as the page posts to itself. Figure 17-9 illustrates this. In it, we have Web page A
containing a list box populated with several hundred items from a database. In steps
1 and 2, a user performs some action that results in a modified version of the same
page being returned to the client. State is retained automatically because the con-
tents of the page’s list box—and any other fields—are passed in the ViewState field.
17.5 Caching 847

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

// Step 3: Save listbox data before making server request


Cache["listbox"]= actors.Items;

Client Web Server

Request ="A" ViewState holds


A 1 2
State information.
"A"

Request ="B" DataCache holds


A 3 4
ListBox items.
"B"

Request ="A" DataCache is used


B 5 6
to restore ListBox data.
"A"

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.

// Step 6: Fill list box from cache


if(Cache["listbox"] !=null){
ListItemCollection li= (ListItemCollection)Cache["listbox"];
for(int i=0;i< li.Count; i++){
ListItem a= li[i];
actor.Items.Add(a);
}
}
Cache["listbox"] = null; // Clear cache

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

Data caching should be considered as a substitute for ViewState when


data is read-only, or there is a large amount of it, or its update frequency
is in minutes or hours rather than seconds, or when the data is not
unique to individual users.

17.6 Creating a Web Client with


WebRequest and WebResponse
As you would expect in a chapter on ASP.NET, the role of the Web client does not
receive much attention. It’s usually assumed to be a browser that requests a Web
page specified by the user, and renders the returned HTML content. Despite the
evolution of browser features, its primary role remains to display what it receives. It
is of less use if you want to parse and extract portions of the received content—or
want to examine the HTTP message headers. For this, you need a Web client—a
program that communicates with an HTTP server and includes custom methods to
perform operations on the response object. This section demonstrates how easy it is
to write a Web client using .NET classes to handle the HTTP request/response
duties.

WebRequest and WebResponse Classes


The System.Net namespace contains classes that are intended for applications
involved with network communications. Included in these are the abstract WebRe-
quest and WebResponse classes that contain generic properties to upload and
download data given a specific Uniform Resource Identifier (URI). These classes are
designed to support the response/request model—not a specific protocol. The details
of that are left to descendant classes. To handle the HTTP protocol, we call on the
HttpWebRequest and HttpWebResponse classes. Note that other classes are avail-
able to support the file:// URI scheme and FTP operations.

Web Client Example


This example accepts a URL, sends a request for its Web page, and uses the response
object to display the server description, IP address(es) of the server, and source code
for the requested Web page. Figure 17-10 shows the interface for the application.
17.6 Creating a Web Client with WebRequest and WebResponse 849

Figure 17-10 Example using HttpWebRequest and HttpWebResponse classes

The basic steps for communicating synchronously with the HTTP server are quite
simple:

1. Create an HttpWebRequest object that identifies the URL using the


Create method:
request = (HttpWebRequest) WebRequest.Create(url);
2. Get a response from the Internet resource using the WebRe-
quest.GetResponse method:
response = (HttpWebResponse) request.GetResponse();
3. Get the Stream used to read the body of the response and read its
contents.
Stream s = response.GetResponseStream();
string strContents = new StreamReader(s).ReadToEnd();

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

Using WebRequest and WebResponse to Scrape


Listing 17-9
a Web Page
private void btnURL_Click(object sender, System.EventArgs e)
{
// Fetch web page for requested URL
HttpWebRequest request;
HttpWebResponse response;
if(txt_URL.Text.Length>0)
{
lblServer.Text="";
tbIP.Text="";
string serverPath= txt_URL.Text;
string url="http://"+serverPath;
// create a request to the url
request = (HttpWebRequest) WebRequest.Create(url);
request.Timeout= 7000; // timeout after 7 seconds
try
{
response = (HttpWebResponse)
request.GetResponse();
lblServer.Text= response.Server;
// Get a stream to send the web page source
Stream s = response.GetResponseStream();
string strContents = new
StreamReader(s).ReadToEnd();
// Place Web page source in text box
HTMLViewer.Text= strContents;
s.Close();
ListIP(serverPath); // List IP address(es)
}
catch ( Exception ex)
{
lblServer.Text= ex.Message;
}
}
else
{
lblServer.Text= "Please enter a domain name.";
}
}
private void ListIP(string uri)
{
// List IP addresses for this domain
17.7 HTTP Pipeline 851

Using WebRequest and WebResponse to Scrape


Listing 17-9
a Web Page (continued)
// Use only server name part of URI for IP resolution
int ndx= uri.IndexOf("/");
if(ndx>0) uri= uri.Substring(0,ndx);
string ips="";
// Get a list of IP addresses for the URI
// Dns contacts the Internet Domain Name System
IPHostEntry IPHost = Dns.GetHostByName(uri);
foreach(IPAddress addr in IPHost.AddressList)
ips+= addr.ToString()+"\r\n";
tbIP.Text= ips;
}

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.

17.7 HTTP Pipeline


The purpose of this section is to provide background information about the compo-
nents and classes that come into play between the time a Web server receives a
request and the time it finishes crafting a response. An understanding of this HTTP
pipeline can be crucial to improving Web application performance, because it
enables a developer to create components that affect how ASP.NET responds to
requests. The references to HTTP modules and handler factories can make the sub-
ject appear daunting, but underneath there is a simple logic at work that is rooted in
event handling.

Processing a Request in the Pipeline


As a request makes its way to a server, several events occur. ASP.NET permits a
developer to interact with the request or response at the point of these events—
through special code referred to as an HTTP module or by script in a global.asax
file. Both the module and file function as event handlers, and the nature of what goes
on in these event handlers is the topic of this section. Figure 17-11 depicts how a
request flows from the beginning of the HTTP pipeline to the endpoint, where a
handler provides a response.
852 Chapter 17 ■ The ASP.NET Application Environment

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

Figure 17-11 Handling a request within the HTTP pipeline

Let’s take a simplified look at the sequence of events that occur.

1. Request is received by Web server. The HTTP request for the


bmi.aspx resource is passed to the ASP.NET Worker Process
(aspnet_wp.exe). It creates an instance of the HttpRuntime class,
which initiates processing of the request.
The processing begins with the creation of an HttpContext
object. You can think of this as the central repository of all information
pertaining to the request. When another class needs information
about the request, it can find it in an HttpContext field or property.
In addition to the HttpRequest and HttpResponse fields, the class
also exposes state information through HttpSessionState and
HttpApplicationState properties.
2. HttpApplicationFactory provides the HttpApplication
object to process the request. After the HttpContext object is
created, the HttpRuntime calls on the static factory method Http-
ApplicationFactory.GetApplicationInstance to create or
find an HttpApplication object to process the request.
3. HttpApplication is initialized with HTTP modules that filter
request and response. HTTP modules are used to process a request
and response as they pass through the pipeline. Predefined ASP.NET
modules provide authentication, caching, and session state services.
4. Locate handler factory. After the HttpApplication object takes
over the processing from the HttpRuntime, it locates the Handler-
Factory that finds or creates the HTTP handler object. It does this
by examining the type of request (.aspx) and searching the
machine.config file or web.config for the name of the handler
17.7 HTTP Pipeline 853

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

Table 17-5 HttpApplication Events

Event Description

BeginRequest Fires when ASP.NET receives a request. A request can


be modified here before it reaches the page.

AuthenticateRequest The identity of the user has been established.

AuthorizeRequest User authorization has been verified.

ResolveRequestCache Fires when ASP.NET determines whether to handle


request from a cached page or pass request to a handler.

AcquireRequestState Acquires the state of the current session.

PreRequestHandlerExecute Fires before the request is sent to a handler. In the case


of a request for an .aspx file, the handler is the page
itself.

PostRequestHandlerExecute Fires after the handler has packaged a response. The


response object contains the text returned to the client.

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.

EndRequest Occurs when request has been processed.

Error Occurs in response to unhandled application exception.

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.

<%! file:global.asax %>


<%@ Application Language="C#" %>
<script runat=server>
protected void Application_EndRequest(object sender, EventArgs e)
{
this.Context.Response.Output.Write(
"<br>&copy2005 BMI Software");
}
</script>

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.

Listing 17-10 Using global.asax to Count Sessions


<%! global.asax %>
<%@ Application Language="C#" %>
<script language = "C#" runat=server>
// Can also use Application_OnStart syntax
void Application_Start(Object Sender, EventArgs e)
{
// Initialize counters with application scope
Application["Sessions"] = 0;
Application["TerminatedSessions"] = 0;
}
856 Chapter 17 ■ The ASP.NET Application Environment

Listing 17-10 Using global.asax to Count Sessions (continued)


void Session_Start(Object Sender, EventArgs e)
{
Application.Lock();
Application["Sessions"]=(int) Application["Sessions"] + 1;
Application.UnLock();
}
void Session_End(Object Sender, EventArgs e)
{
Application.Lock();
Application["TerminatedSessions"] =
(int) Application["TerminatedSessions"] + 1;
Application.UnLock();
}
protected void Application_EndRequest(object sender,
EventArgs e)
{
string sessionCt= "Session #:
"+Application["Sessions"].ToString();
// Display copyright and current session number
this.Context.Response.Output.Write(
"<br>&copy2005 BMI Software<br>"+sessionCt);
}
</script>

Using a Code-Behind File with global.asax


As with the .aspx file, code can be separated from the global.asax file and placed
in a code-behind file. The code is compiled into a DLL and placed it in the \bin
subdirectory of the application. The Inherits attribute of the Application direc-
tive points to the class implemented in the DLL.
To convert the preceding example to use a code-behind file, we remove code from
the global.asax file, leaving only the Application directive:

<%! file:global.asax %>


<%@ Application Inherits="sessionctr" %>

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

public class sessionctr: HttpApplication


{
void Application_OnStart(Object Sender, EventArgs e)
{
Application["Sessions"] = 0;
Application["TerminatedSessions"] = 0;
}
// Remainder of assembly code goes here
}

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.

Listing 17-11 HTTP Modules in machine.config File


<httpModules>
<add name="OutputCache"
type="System.Web.Caching.OutputCacheModule" />
<add name="Session"
type="System.Web.SessionState.SessionStateModule" />
<add name="WindowsAuthentication"
type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication"
type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication"
type="System.Web.Security.PassportAuthenticationModule" />
<add name="UrlAuthorization"
type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization"
type="System.Web.Security.FileAuthorizationModule" />
858 Chapter 17 ■ The ASP.NET Application Environment

Listing 17-11 HTTP Modules in machine.config File (continued)


<add name="ErrorHandlerModule"
type="System.Web.Mobile.ErrorHandlerModule,
System.Web.Mobile, Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
</httpModules>

Implementing a Custom HttpModule


There are two minimal requirements for building a functioning HttpModule: It
must contain a class that implements the IHttpModule interface and it must register
an event handler to process one of the HttpApplication events. An example of this
is shown in Listing 17-12. The code adds copyright information to the bottom of all
pages returned by the application—exactly the same function performed by the ear-
lier global.asax file.
The IHttpModule interface consists of two methods—Init and Dispose—that
must be accounted for. We use Init to register the event handler that will be called
when the EndRequest event occurs. Note that access to the events is provided
through the HttpApplication object that Init receives as a parameter.

Listing 17-12 Module to Append Copyright to Web Pages


using System;
using System.Web;
namespace CustomModules{
public class FooterModule: IHttpModule
{
public void Init(HttpApplication httpApp)
{
httpApp.EndRequest += new EventHandler(this.OnEndRequest);
}
public void Dispose() {}
//
public void OnEndRequest (Object obj, EventArgs e)
{
HttpApplication httpApp = (HttpApplication) obj;
httpApp.Context.Response.Output.Write(
"<br>&copy2005 BMI Software");
}
}
}
17.7 HTTP Pipeline 859

Deploying a Custom HttpModule


To use the module with a Web application, compile it and place it in the \bin subdi-
rectory of the application or in the Global Assembly Cache (GAC). Next, the assem-
bly must be registered in either the web.config or machine.config file. In this
example, we use a web.config file, which is placed in the root of the application’s
virtual directory. A portion of the file is shown here:

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

<add name=friendly name type=class name, assembly />

This element is enclosed by the <httpModules> elements. Multiple <add> ele-


ments can be used to specify multiple modules.

URL Rewriting with a Custom Module


URL rewriting is the process of changing a requested URL so that it is redirected to
another resource. This is commonly used to ensure that bookmarked links continue
to work when directories and resource names are changed on a Web server.
An HTTP module provides a convenient and easy-to-use approach for rewriting a
URL. The implementation logic in the module is straightforward:

1. An HttpApplication event invokes event handler code.


2. The event handler examines the Context.Request.Path property
for the requested path.
3. If the path URL needs to be redirected, the Context.RewritePath
method is called with the substitute path.

The most important issue to be resolved in developing a rewriter is selecting an


event in the request’s lifecycle where the URL checking and modification should
occur. The three HttpAppliction event candidates are shown in Table 17-6, along
with the criteria for selecting them. (Authentication is discussed in Section 17.5.)
In general, if no authentication is being used, place the rewrite logic in an event
handler for the BeginRequest event.
860 Chapter 17 ■ The ASP.NET Application Environment

Table 17-6 Event Choices for Implementing a URL Rewriter Module

Event Criteria for Selecting Event

BeginRequest Use unless forms authentications is also used.

AuthenticateRequest Use if Windows authentication is used.

AuthorizeRequest Use if Forms Authentication is used and Windows authen-


tication is not.

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.

Listing 17-13 Custom HTTP Module to Rewrite a URL


using System;
using System.Web;
using System.Collections;
namespace CustomModules
{
public class ReWriteModule: IHttpModule
{
public void Init(HttpApplication httpApp)
{
httpApp.AuthorizeRequest += new
EventHandler(this.OnAuthorizeRequest);
}
public void Dispose() {}
// Determine if requested URL needs to be rewritten
public void OnAuthorizeRequest( Object obj, EventArgs e)
{
// Get hash table containing old and replacement URLs
Hashtable urls = Rewrites.getRewrites();
HttpApplication httpApp = (HttpApplication) obj;
// Get path of requested URL
string path = httpApp.Context.Request.Path.ToUpper();
17.7 HTTP Pipeline 861

Listing 17-13 Custom HTTP Module to Rewrite a URL (continued)


// See if path is in hash table
if(urls.ContainsKey(path))
path= (string)urls[path];
httpApp.Context.RewritePath(path); // Rewrite URL
}
}
}

The module is registered by placing the following entry in the web.config file:

<add name="redirector"
type="CustomModules.ReWriteModule, rewritemodule" />

The process of identifying URLs to be rewritten can be implemented in various


ways. This example stores the old and replacement URLs in an XML file using the
following format:

<!-- File: redirector.xml -->


<RewriterConfig>
<RewriterRules>
<Rule>
<OldPath>/ideas/calculator.aspx</OldPath>
<NewPath>/utilities/calculator.aspx</NewPath>
</Rule>
<Rule>
<OldPath>/ideas/bmi.aspx</OldPath>
<NewPath>/utilities/bminew.aspx</NewPath>
</Rule>
</RewriterRules>
</RewriterConfig>

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.

Choosing Between an HTTP Module and global.asax


In many cases, the functionality you want to add to a Web server environment can be
implemented using HTTP modules or the global.asax file. As a guide to making
the choice, let’s review their features:
862 Chapter 17 ■ The ASP.NET Application Environment

• Both can contain handlers to service HttpApplication events.


• Global.asax can receive notification of session and application
start and end events. An HTTP module cannot service these events.
• Modules are deployed at the machine (machine.config) or virtual
directory (web.config) level; global.asax operates at a directory
level only.

Either can be used to handle HttpApplication events. If the feature being


added applies to all applications on the server, a module is the better choice; if the
feature is specific to an application, use a global.asax file.

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>

An HTTP handler is a component that implements the System.Web.IhttpHan-


dler interface. Unlike an HTTP module, only one handler is called to process a
request. ASP.NET maps a request to the target handler based on information in a
machine.config or web.config file. The request can be mapped directly to a
handler or to a handler factory that creates or retrieves the appropriate handler. As
shown in the preceding machine.config file, .aspx requests are handled by a
PageHandlerFactory class. We’ll look at both techniques.

Implementing a Custom HTTP Handler


ASP.NET provides handlers for .aspx, .soap, .asmx (Web services), and other
standard ASP.NET file types. So why create a custom handler? The primary motiva-
tion is to support new file extensions or existing file extensions that require added
17.7 HTTP Pipeline 863

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.

Listing 17-14 Custom HTTP Handler to Display a Text File


// file: txthandler.cs
using System;
using System.Web;
using System.IO;
using System.Text.RegularExpressions;
public class TextHandler: IHttpHandler
{
static int wordCt=0;
static string BoldWord(Match m)
{
// Get the matched string and place bold tags around it
string kwd = m.ToString();
kwd="<b>"+kwd+"</b>";
wordCt += 1;
return kwd;
}
public void ProcessRequest(HttpContext ctx)
{
864 Chapter 17 ■ The ASP.NET Application Environment

Listing 17-14 Custom HTTP Handler to Display a Text File (continued)


// Get file to be opened
string filePath= ctx.Request.PhysicalPath;
int ndx= filePath.LastIndexOf(@"\");
string fileName = filePath.Substring(ndx+1);
// Keyword to search for in file
string keyWord = ctx.Request["kwd"];
// Create HTML response
ctx.Response.Output.Write("<html><body >");
ctx.Response.Output.Write("File: "+fileName+
" &nbsp;Keyword: <b>"+keyWord+"</b><hr size=1>");
string line;
try {
StreamReader reader= new StreamReader(filePath);
// Read lines of file and display in response
while ((line = reader.ReadLine()) != null)
{
if (keyWord!= null) {
// search for keyword and highlight
string newLine = Regex.Replace(line, keyWord,
new MatchEvaluator(TextHandler.BoldWord),
RegexOptions.IgnoreCase);
line= newLine;
}
ctx.Response.Output.Write("<br>"+line);
}
reader.Close();
} catch (Exception e)
{
ctx.Response.Output.Write("<br>"+e.Message);
}
// Display number of matches
ctx.Response.Output.Write("<br><br>Word Matches: " +
wordCt.ToString());
ctx.Response.Output.Write("</body></html>");
wordCt=0; // reset since it is static
}
public bool IsReusable
{
get {return false;}
}
}

The final code is compiled and placed in the \bin subdirectory of the application.
17.7 HTTP Pipeline 865

Deploying a Custom HTTP Handler


For ASP.NET to be aware of the handler, an entry is made in the machine.config
or application’s web.config file. In this example, we place an <add> element in the
web.config file. This element contains three attributes that define the handler and
the conditions for using it. Verb specifies the type of request to be handled, such as
GET or POST. The * is a wildcard character that represents any type request. The
path attribute indicates the requested resource name that is mapped to the handler;
and type specifies the handler class or handler factory and its containing assembly.

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

1. Invoke Internet Services Manager and right-click Default Web Site.


2. Select Properties – Home Directory – Configuration.
3. Click Add and a pop-up window appears.
4. Fill in the Executable field with the path to the aspnet_isapi.dll
file. This file should be in the version directory of the Framework
installation.
5. Fill in the Extension field with *.txt.

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

If an extension used by an HTTP handler is not mapped in IIS to


ASP.NET, IIS attempts to return the contents of any file requested having
that extension. ASP.NET never sees the request.

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

17.9 Test Your Understanding


1. True or False: A Web application requires at least one web.config
file in its directory tree.

2. Name the two places that trace output can be viewed.

3. Regarding ASP.NET security:


a. List the three types of .NET authentication available.
b. Where is the authentication type specified?
c. What is the difference between authentication and authorization?
d. Which property of the Page class provides information about an
authenticated user?

4. Which mechanism(s) for storing session state information supports


multiple servers?

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?

6. What factors influence whether state data is maintained in a data


cache or an application state variable?

7. What is resource scavenging, and how can a data cache prevent it from
occurring?

8. Fill in the blanks:


As a _________ or __________ passes through the HTTP pipeline, an
__________ object fires a series of events that may be handled by a/an
___________ or in the _________ file.
XML
WEB SERVICES

Topics in This Chapter

• Web Service Overview: The popularity of Web Services stems


from the fact that they are implemented using the non-proprietary
technologies of HTTP, XML, and SOAP. For them to become truly
useful, users must have a way to locate them on the Internet.
UDDI offers one such discovery service.
• Creating a Web Service: In C#, a Web Service is created by
implementing a method that is exposed to clients across the
Internet. To indicate a method is available, a WebService
directive is required on the Web page, and a WebMethod
attribute is attached to the Web Service method.
• Building an XML Web Service Client: The easiest way to create a
Web Service client is to build a proxy using the Web Services
Description Language (WSDL) provided by the Web Service. The
proxy handles the physical connection to the Web Service and
provides asynchronous and synchronous access.
• WSDL and SOAP: WSDL provides the information that describes
a Web Service; Simple Object Access Protocol (SOAP) is the
format in which the requests and responses are delivered. An
understanding of both offers added insight into how Web Services
operate.
• Web Service Examples: To satisfy real-world business needs, Web
Services must deliver more than simple data types. Examples in
this section show how to build a service that delivers images and
how to build a client to retrieve datasets from the publicly
available Amazon Web Services.
18

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

We begin the exploration of the .NET Web Services architecture by creating a


simple Web Service and Web Service consumer. Our first examples are created “by
hand” to emphasize the underlying principles. After these are understood, we see
how Visual Studio.NET simplifies the development. The chapter also demonstrates
how to use the wsdl.exe tool and .NET classes that are integral to the development
process. Closing out the chapter is a sample application to access the Amazon Web
Services.

18.1 Introduction to Web Services


The Web Service architecture is a service-oriented architecture that enables applica-
tions to be distributed across a network or the Internet to clients using any language
or operating system. As shown in Figure 18-1, Web Service communications are
implemented using existing technology and standards that require no proprietary
vendor support. This technology either formed the basis of the Internet or evolved
from it. HTTP and XML have been discussed in earlier chapters, but let’s take a brief
look at them—from a Web Services perspective—along with TCP/IP and SOAP:

• TCP/IP (Transmission Control Protocol/Internet Protocol). A


communications protocol suite that forms the basis of the Internet. It’s
an open system that governs the flow of data between computers by
breaking data into chunks that are easily routed over a network. A
Web Service user or developer rarely has direct contact with this layer.
• HTTP (Hypertext Transfer Protocol). Technically, this text-based
protocol is a Remote Procedure Call (RPC) protocol that supports
request/response communications. The .NET Framework as well as
most production Web Services use it because it has wide support and
generally allows information to sail unimpeded through firewalls. Both
the HTTP POST and HTTP GET methods are supported in .NET as a
way to call a Web Service. Most applications use POST, because it
packages the request inside the request body (in a SOAP envelope),
rather than as part of a less secure query string.

Core Note

The rules that describe how a message is carried within or on top of a


protocol is referred to as a binding. The default protocol binding used by
.NET for Web Services is HttpSoap, which should be used to access
Web Services that understand SOAP. It is specified inside the
System.Web section of the machine.config file.
18.1 Introduction to Web Services 871

• XML (Extended Markup Language). We have seen in earlier chap-


ters how data can be serialized into XML format for storage or trans-
mission. The fact that it is text based makes it easy to work with. Just
about every programming environment supports tools for encoding
and decoding XML formatted data. Its inherent flexibility, extensibility,
and validity checking make it attractive to Web Services that must deal
with simple data types such as strings, as well as more complex data
structures. There are currently two XML-based protocols used for
delivering Web Services: XML-RPC and SOAP. Because .NET sup-
ports SOAP, this chapter focuses on it. After you understand SOAP, you
should have no difficulty with XML-RPC if you encounter it.
• SOAP (Simple Object Access Protocol). SOAP is defined as “a
lightweight protocol for exchange of information in a decentralized,
distributed environment.”1 It is not designed specifically for Web
Services, nor restricted to HTTP; but its RPC specifications define a
model for invoking methods on a specified target machine, passing
parameters, handling faults, and receiving a method response. We will
look at SOAP in detail later in the chapter. For now, keep in mind that
the details of XML and SOAP are typically handled transparently by
.NET. The provider code only needs to focus on implementing the
method that returns the desired data; the requestor code simply
places a method call and processes the returned data.

Data

SOAP/XML

HTTP

TCP/IP

Figure 18-1 Web Service transport

Discovering and Using a Web Service


To use a Web Service, a client must have a description of how to access the service.
This information is provided by a Web Services Description Language (WSDL) doc-
ument that provides the name of the service, the signature of the method(s) that can

1. Simple Object Access Protocol (SOAP) 1.1—W3C Note, May 8, 2000.


872 Chapter 18 ■ XML Web Services

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

Figure 18-2 Discovering and accessing a Web Service


18.1 Introduction to Web Services 873

UDDI Discovery Example


To demonstrate how to use UDDI, w‘e’ll look at the SOAP messages sent between
client and server as we seek to discover a Web Service that can provide a stock quote.
A Web-based UDDI browser (described shortly) sends and receives the messages.

Step 1: Send Discovery Request


For our example, we’ll search the UDDI registry provided by Microsoft:

http://uddi.microsoft.com/inquire

An inquiry may request business information or tModel information. The former


contains information about a business, including contact information and a descrip-
tion of services. The tModel structure is much simpler: It consists of a unique key,
optional description, and a URL or pointer to a Web page where information for
using the service is described. The following message requests a list of tModel keys
for companies whose service includes providing a stock quote.

<Envelope>
<Body>
<find_tModel generic="1.0" maxRows="100">
<findQualifiers/>
<name>stock quote</name>
</find_tModel>
</Body>
</Envelope>

Step 2: Registry Service Responds


with List of Services Matching Request
A list of tModel keys is returned. These keys are used for the subsequent query:

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

Step 3: Retrieve Overview Document Containing WSDL


Send a request for tModel details for the service with the specified tModelKey:

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

How to Communicate with a UDDI Service Registry


To interact with a UDDI registry—whether to publish or inquire—you need a way to
generate SOAP requests and interpret responses. One option is to write your own
application using the Microsoft UDDI 2.0 SDK. It contains excellent C# examples
that demonstrate the API and can be run against a test UDDI registry hosted by
Microsoft.
For making registry inquires, Web-based UDDI browsers are available. A publicly
available one—as well as a wealth of accompanying UDDI information—can be
found at

http://www.soapclient.com/UDDISearch.html
18.2 Building an XML Web Service 875

18.2 Building an XML Web Service


In this section, we demonstrate how to build a Web Service by hand and then access
it using a browser. We also show how to create the same Web Service using Visual
Studio.NET. Although IIS is used, the examples run on any Web server.

Creating a Web Service by Hand


The first step is to select or create a virtual directory under IIS that will hold the Web
Service source code file(s). Any physical directory can be mapped to an IIS virtual
directory. You can use the Internet Service Manager or simply right-click the direc-
tory and select Sharing-Web Sharing. Then, assign it an alias that will be used in its
URL. In our example, we will place the service in the \ws subdirectory.
After the directory has been set up, the next step is to use a text editor to create a
file in this directory with the .asmx extension to contain the Web Service code. List-
ing 18-1 contains the code for our simple Web Service. This service exposes a
method GetDayBorn that accepts three integer parameters that represent the
month, day, and year for birth date. A string value containing the day of the week
(Monday, Tuesday, and so on) for this date is returned. The service performs rudi-
mentary error checking and returns an error message if the date is invalid.

Web Service to Return a Date’s Day of Week—


Listing 18-1
BirthDayWS.asmx

<%@ WebService Language="C#" Class="BirthDayWS.BirthDay" %>


using System;
namespace BirthDayWS
{
public class BirthDay
{
[System.Web.Services.WebMethod
(Description="Return day of week for a date")]
public string GetDayBorn(int mo, int day, int yr)
{
bool err = false;
string dob;
if (mo <1 || mo >12) err=true;
if (day < 1 || day > 31) err=true;
if (err)
dob = "Invalid Date";
876 Chapter 18 ■ XML Web Services

Web Service to Return a Date’s Day of Week—


Listing 18-1
BirthDayWS.asmx (continued)

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

<%@ WebService Language="C#" Class="BirthDayWS.BirthDay" %>

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:

<%@ WebService Language="C#"


Class="BirthDayWS.BirthDay, bdAssembly" %>

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.

Testing the Web Service


A quick way to test the newly developed Web Service is to point a browser to its loca-
tion. For this example, we enter the address:

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.

Figure 18-3 Using a browser to access the BirthDay Web Service

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

Figure 18-4 BirthDayWS output

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

It is unnecessary to compile the .asmx file containing the Web Service


in order to deploy it. On a Microsoft Windows server, the ASP.NET
runtime automatically compiles the file the first time it is requested and
places it in a subdirectory—named after the virtual directory—on the
following path:
<%windir%>\Microsoft.NET\Framework\<version>\
Temporary ASP.NET Files\

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

Creating a Web Service Using VS.NET


Aside from the usual advantages of IntelliSense and single key compilation (F5), the
major advantage of VS.NET over a manual approach is that it automatically creates a
new virtual directory under IIS to host the Web Service. This directory takes the
same name as the project.
To create a Web Service with VS.NET, open it up and select ASP.NET Web Ser-
vice as the project type. Assign it a project name (BirthDayWS, in this case) that
reflects the purpose of the Web Service. When the project opens, select View Code
and you will find that the following template class is predefined:

namespace BirthDayWS
{
18.2 Building an XML Web Service 879

public class Service1 : System.Web.Services.WebService


{
public Service1()
{ InitializeComponent(); }
private IContainer components = null;
private void InitializeComponent() { }
protected override void Dispose( bool disposing )
{
if(disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}
// --> Place BirthDayWS code here
}
}

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

• Application. Provides access to an Application object that can be


used to maintain state information for all sessions accessing the Web
Service. For example, it can be used to keep track of how many times
the service is called.
• Session. Provides access to the HttpSessionState object that
maintains session state information. It can be used, for example, to
track how many times a service is called while the current session is
alive. This property is available only if the EnableSession property
of the WebMethodAttribute is set to true.
880 Chapter 18 ■ XML Web Services

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

dob = dt.ToString("dddd"); // extracts day


this.Application["count"] = GetCount()+1;

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:

private int GetCount()


{
object ob = this.Application["count"];
if (ob == null) {
return(0);
} else {
return((int)ob);
}
}
[WebMethod(Description="Number of times Web Service called.")]
public int GetVisitors()
{
return (GetCount());
}

Extending the Web Service with the


WebService and WebMethod Attributes
The BirthDayWS example demonstrates that a functioning Web Service can be cre-
ated with a minimal use of .NET classes. However, before releasing a Web Service to
the Internet, there are additional features that should be considered. For instance,
each XML Web Service should be given a unique namespace rather than relying on
the default <http://tempuria.org.>, caching can be implemented to improve
performance, and overloaded methods can be implemented using the WebMethod
attribute.
18.2 Building an XML Web Service 881

The WebService Attribute


The optional WebService attribute (not to be confused with the WebService direc-
tive) is applied to the class implementing the XML Web Service. It has two useful
properties: Description, which describes the overall Web Service, and
Namespace, which sets the default XML namespace for the service. In general
terms, namespaces are used to avoid naming conflicts among XML elements and
attributes. In this case, it is used to make the Web Service name unique.
Let’s add a WebService attribute containing a namespace and description to the
class in our BirthDayWS example:

[WebService(Namespace="http://www.dateservices.com/ws/",
Description="<b>Web Service that Provides Date
Functions.</b>")]
public class BirthDay : System.Web.Services.WebService

It is important to note that the namespace is intended to be a unique identifier,


and does not need to actually point to anything. Domains are typically used to take
advantage of their uniqueness.
Figure 18-5 shows the page that is returned when we call the Web Service that is
now updated with the WebService attribute and the GetVisitors method.

Figure 18-5 Updated BirthDayWS Web Service

The WebMethod Attribute


As mentioned previously, the WebMethod attribute is required to expose a method as
part of a Web Service. Its properties include Description, which we have already
used, EnableSession, MessageName, CacheDuration, and TransactionOp-
tion. The latter property applies to applications that call transactional COM+ com-
ponents—a topic not discussed in this book. The other three properties are useful for
developing general Web Services. Let’s look at the role of each.
882 Chapter 18 ■ XML Web Services

EnableSession: Activate the Use


of Session State Information
This property is used with Web Services that inherit from the WebService class.
Turning it on allows a Web Service method to access session information through
WebService.Session or HttpContext.Current.Session. By default, this is set
to false, because maintaining session data increases memory consumption and
reduces performance.

Example: [WebMethod(EnableSession=true)] // Default is false

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.

MessageName: Create Overloaded Web Operations


Suppose we want to add a method to our Web Service that accepts the month as a
string rather than an integer. Because the signature is different than the original
GetDayBorn method, we can give it the same method name and C# will compile it as
an overloaded method. However, when we try to access it from a browser, we receive
a system exception indicating that the two methods have the same name.
The problem lies in the WSDL, which requires that each Web method in its XML
elements be uniquely named, irrespective of its signature. By default, the routine in
ASP.NET that generates WSDL code uses the name for both. The solution—aside
from renaming the method—is to use the MessageName property to indicate a surro-
gate name.

[WebMethod(Description="Return day of week for any date",


MessageName="GetDayBorn2")]
public string GetDayBorn(string month, int day, int yr){
// Code to convert string month to int value
string allMos= "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC";
month = month.Substring(0,3).ToUpper();
int ndx = allMos.IndexOf(month);
if(ndx <0) err=true; else mo = ndx/3 +1;
// Remainder of code goes here...

CacheDuration: Caching Output from a Web Operation


Just like ASP.NET Web pages, output from a Web method can be cached to obviate
executing it each time the method is called. The CacheDuration property specifies
how long (in seconds) the output is to be cached. For methods that accept argu-
ments, cached output is saved for each unique set of arguments.
18.2 Building an XML Web Service 883

Example: [WebMethod(CacheDuration=1800)] // Value is in seconds

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.

Using web.config to Configure


Web Service Options
.NET Web Services support four protocols: HttpGet, HttpPost, HttpSoap, and
HttpLocalHost. Of these, HttpPost and HttpGet are disabled for security rea-
sons. The effect of this is to limit Web Service access to URLs located on
http://localhost; an attempt by a remote machine to access a local Web Service
results in this message being displayed:

The test form is only available for requests from the local
machine

To make a Web Service available to remote users, it is necessary to enable the


HttpPost and HttpGet protocols. This is easily done by configuring the <proto-
col> section of the web.config file to “add” the two protocols.

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

<remove name="Documentation" />


884 Chapter 18 ■ XML Web Services

18.3 Building an XML Web Service Client


This section describes how to create a client application that consumes a Web Ser-
vice. The objective is to make the client’s call to the remote Web method as simple as
calling a method in its own code. Although the actual implementation does not reach
quite that level of simplicity, the code is straightforward, and much of it can be gen-
erated automatically using .NET tools.
Before delving into details, let’s first take a high-level view of how a .NET Web
Services client interacts with a Web Service.
The most important thing to observe in Figure 18-6 is that the client does not
directly invoke the Web Service method. Instead, it calls a proxy object that performs
this task. The proxy class is created from the WSDL information provided by the
Web Service. We’ll see how to do this shortly. The proxy code is a class that has the
same class name and method names as the Web Service class does. It contains the
transport logic that allows it to make the actual connection to the Web Service. It
may do this either synchronously (without receiving confirmation) or asynchronously.
The messages exchanged between the proxy and server are bundled within an HTTP
request and transmitted using either the HTTP or the SOAP wire protocol.

Web Service Client Web Service

Web Class
Call to Web Method 1

Proxy Class Web Method 1


P
Synchronous SOA
Web Method 2
Asynchronous

Figure 18-6 Overview of how a client accesses a Web Service

Creating a Simple Client to


Access the Web Service Class
To demonstrate the basic principles involved in creating a Web Service client, let’s
develop a console client application to access the BirthDayWS Web Service (refer to
18.3 Building an XML Web Service Client 885

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:

public class BirthDay


{
[System.Web.Services.WebMethod
(Description="Return day of week for a date")]
public string GetDayBorn(int mo, int day, int yr)

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.

Using wsdl.exe to Create a Proxy


The wsdl.exe utility reads the WSDL describing a Web Service and generates the
source code for a proxy class to access the service; it can also use the information to
create the skeleton code for a Web Service. This latter feature is designed for devel-
opers who prefer to design the WSDL as the first step in creating a Web Service. We
do not take that approach in this chapter, but you should be aware that there are
WSDL editors available for that task.
The wsdl.exe utility is run from the command line and has numerous flags or
options to govern its execution. Table 18-1 lists those most commonly used.
886 Chapter 18 ■ XML Web Services

Table 18-1 wsdl.exe Command-Line Options

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.

/namespace Specifies the namespace for the proxy.


/n:

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

/protocol The wire protocol to use within the proxy code:


/protocol:SOAP SOAP 1.1 is generated
/protocol:SOAP12 SOAP 1.2 is generated
/protocol:HttpGet or /protocol:HttpPost

/server Generates an abstract class for the Web Service. This is used when
the WSDL document is used to create a Web Service.

/serverinterface Generates interfaces—rather than abstract classes—for the Web


Service. Available only in 2.0 and later.

The following statement creates a proxy class from the BirthDayWS.asmx Web
Service and places it in c:\BDProxy.cs:

wsdl.exe /out:c:\BDProxy.cs http://localhost/ws/


BirthDayWS.asmx?WSDL

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:

csc /t:library /r:system.web.services.dll /r:system.xml.dll


BDProxy.cs

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

csc /r:BDProxy.dll bdClient.cs

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

When wsdl.exe creates proxy code, it checks the associated Web


Service for compliance with the WS-I Basic Profile 1.0 standards and
emits a warning if the service is noncompliant. For information on this
standard, refer to the Web Services Interoperability Organization’s Web
site at http://www.ws-i.org.

Listing 18-2 examines the proxy source code to understand how it converts a cli-
ent’s call into a remote Web Service invocation.

Proxy Source Code to Access BirthDayWS.asmx


Listing 18-2
Web Service
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
//
// Auto-generated by wsdl, Version=2.0.40607.16.
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(
Name="BirthDaySoap",
Namespace="http://tempuri.org/")]
public class BirthDay : SoapHttpClientProtocol {
private System.Threading.SendOrPostCallback
GetDayBornOperationCompleted;
public BirthDay() {
this.Url = "http://localhost/ws/BirthDayWS.asmx";
}
public event GetDayBornCompletedEventHandler
GetDayBornCompleted;
888 Chapter 18 ■ XML Web Services

Proxy Source Code to Access BirthDayWS.asmx


Listing 18-2
Web Service (continued)
// (1) Synchronous Call to Web Service
[SoapDocumentMethodAttribute(
"http://tempuri.org/GetDayBorn",
RequestNamespace="http://tempuri.org/",
ResponseNamespace="http://tempuri.org/",
Use=
System.Web.Services.Description.SoapBindingUse.Literal,
ParameterStyle=
System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public string GetDayBorn(int mo, int day, int yr)
{
object[] results = this.Invoke("GetDayBorn", new object[] {
mo,
day,
yr});
return ((string)(results[0]));
}
// (2) Asynchronous call to Web Service
public System.IAsyncResult BeginGetDayBorn(int mo, int day,
int yr, System.AsyncCallback callback,
object asyncState) {
return this.BeginInvoke("GetDayBorn", new object[] {
mo,
day,
yr}, callback, asyncState);
}

public string EndGetDayBorn(System.IAsyncResult asyncResult) {


object[] results = this.EndInvoke(asyncResult);
return ((string)(results[0]));
}
// (3) Call this for event-based asynchronous handling
public void GetDayBornAsync(int mo, int day, int yr) {
this.GetDayBornAsync(mo, day, yr, null);
}

public void GetDayBornAsync(int mo, int day, int yr,


object userState) {
if ((this.GetDayBornOperationCompleted == null)) {
this.GetDayBornOperationCompleted = new
System.Threading.SendOrPostCallback(
this.OnGetDayBornOperationCompleted);
18.3 Building an XML Web Service Client 889

Proxy Source Code to Access BirthDayWS.asmx


Listing 18-2
Web Service (continued)
}
this.InvokeAsync("GetDayBorn", new object[] {
mo,
day,
yr}, this.GetDayBornOperationCompleted,
userState);
}

private void OnGetDayBornOperationCompleted(object arg) {


if ((this.GetDayBornCompleted != null)) {
InvokeCompletedEventArgs invokeArgs =
((InvokeCompletedEventArgs)(arg));
this.GetDayBornCompleted(this, new
GetDayBornCompletedEventArgs(invokeArgs.Results,
invokeArgs.Error, invokeArgs.Cancelled,
invokeArgs.UserState));
}
}
public new void CancelAsync(object userState) {
base.CancelAsync(userState);
}
}
public delegate void GetDayBornCompletedEventHandler(
object sender, GetDayBornCompletedEventArgs args);
public class
GetDayBornCompletedEventArgs :AsyncCompletedEventArgs
{
private object[] results;
internal GetDayBornCompletedEventArgs(object[] results,
System.Exception exception, bool cancelled,
object userState) : base(exception, cancelled, userState) {
this.results = results;
}
// Results are available as a property
public string Result {
get {
this.RaiseExceptionIfNecessary();
return ((string)(this.results[0]));
}
}
}
890 Chapter 18 ■ XML Web Services

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.

Table 18-2 Selected Members of SoapHttpClientProtocol

Member Description

BeginInvoke() Begins an asynchronous invocation of the Web method.

EndInvoke() Ends an asynchronous invocation of the Web method.

Invoke() Invokes the Web method synchronously.

CookieContainer Gets or sets the collection of cookies. This permits a proxy-based


client to accept cookies and allow a server to maintain state
information.

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.

Let’s look at how these members are used in a proxy class.

Synchronous Calls to a Web Service Method


A proxy provides the capability to invoke a Web Service method synchronously or
asynchronously. Recall from Chapter 13, “Asynchronous Programming and Multi-
threading,” that a synchronous operation uses only one thread, which is blocked until
control returns from the called method. An asynchronous call, on the other hand,
creates a worker thread that handles the operations of the called method. Control
returns immediately to the main thread while a separate thread handles the auxiliary
task. The calling routine is notified when the task is completed.
Synchronous communication is implemented with a proxy method that has the
same name as the Web method and uses the Invoke method to request the service.
18.3 Building an XML Web Service Client 891

public string GetDayBorn(int mo, int day, int yr) {


object[] results = this.Invoke("GetDayBorn", new object[] {
mo, day, yr});
return ((string)(results[0]));
}

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.

// Must add using System.Net;


BirthDay bd = new BirthDay();
bd.Timeout= 4000; // Set timeout to 4 seconds
try
{
string dayOfWeek = bd.GetDayBorn(12,20,1963);
Console.WriteLine(dayOfWeek);
}
catch (WebException ex)
{
Console.WriteLine(ex.Message); // Will report timeout
}
catch (Exception ex)
{
Console.WriteLine("Unknown error occurred.");
}

Core Note

An easy way to test the timeout exception is to add a Thread.Sleep()


call within the Web method being called on the Web server. This
suspends thread execution for a specified amount of time. For example:
System.Threading.Thread.Sleep(3000); // Sleep 3 seconds

Asynchronous Calls to a Web Service Method


Asynchronous communication is performed using the Beginmethodname and
Endmethodname methods supplied by the proxy. Internally, these call the Begin-
Invoke and EndInvoke methods, respectively. BeginInvoke queues the method
892 Chapter 18 ■ XML Web Services

to be run on a separate (worker) thread. It returns an IAsyncResult type that is


subsequently passed to the EndInvoke method to retrieve the results. As shown in
the following code, the IAsyncResult type is also used to determine if the call has
been completed.

BirthDay bd = new BirthDay();


// Pass null callback argument since we will poll status
IAsyncResult dayOfWeek =bd.BeginGetDay-
Born(12,20,1963,null,null);
// Perform other operations here while waiting for response
If (dayOfWeek.IsCompleted)
Console.Write(bd.EndGetDayBorn(dayOfWeek));

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.

// Create delegate that specifies method to be called back


AsyncCallback wscb = new AsyncCallback(DayReturned);
BirthDay bd = new BirthDay();
IAsyncResult dayOfWeek =
bd.BeginGetDayBorn(12,20,1963,wscb,null);
// This method is called back when the web method completes
public static void DayReturned(IAsyncResult result)
{
BirthDay bd = new BirthDay();
Console.WriteLine(bd.EndGetDayBorn(result));
}

As with polling, the method implementing EndInvoke (EndGetDayBorn, in this


example) is called to return the result.

Event-Based Asynchronous Calls


If you take a close look at the proxy code in Listing 18-2, you’ll notice that it includes
a delegate and corresponding event declaration:

public event GetDayBornCompletedEventHandler


GetDayBornCompleted;

public delegate void GetDayBornCompletedEventHandler(


object sender, GetDayBornCompletedEventArgs args);
18.3 Building an XML Web Service Client 893

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:

Birthday service = new Birthday();


// (1) Associate event handler with event
service.GetDayBornCompleted += new
GetDayBornCompletedEventHandler(this.ShowOutput);
// (2) Invoke service asynchronously
service.GetDayBornAsync();
// (3) Event handler called when service returns value
private void ShowOutput(object sender,
GetDayBornCompletedEventArgs args)
{
Console.WriteLine(args.Result);
}

The Web Service is invoked asynchronously by calling the proxy provided


method GetDayBornAsync (<web service name>Async). When the Web Service
finishes execution, .NET returns the results to the event handler through the args
parameter.

Using the CookieContainer


Property to Maintain Session State
Setting a Web Service method’s EnableSession attribute to true enables the
method to maintain state information. However, this is only effective if the client can
accept cookies. This is not usually a problem with browsers, but Windows Forms
(WinForms) applications do not store cookies by default. The CookieContainer
property is a way to permit WinForms clients to accept cookies.
To permit the client to accept cookies, set the CookieContainer property of the
proxy class instance to a CookieContainer object. The code shown here permits
the Web Service to maintain state information over all queries made by this client.

// BirthDay bd = new BirthDay() .. must have class level scope


if (bd.CookieContainer == null)
bd.CookieContainer = new CookieContainer();
IAsyncResult dayOfWeek = bd.BeginGetDay-
Born(mo,dy,yr,null,null);
Console.WriteLine(bd.EndGetDayBorn(dayOfWeek));
// List all cookies in CookieContainer collection
// Create a Uniform Resource Identifier object
Uri hostURL = new Uri("http://localhost");
foreach (Cookie ck in bd.CookieContainer.GetCookies(hostURL))
Console.WriteLine(ck.Name+" "+ck.TimeStamp.ToString());
894 Chapter 18 ■ XML Web Services

Notice that the CookieContainer class has a method GetCookies that


returns a collection of cookies stored in the container. In the example, foreach is
used to enumerate the collection and list the name of each cookie along with the
date it was created.

Creating a Proxy with


Visual Studio.NET
We have shown how the proxy code can be generated using wsdl.exe and compiled
into a DLL or used as source code within the client application. If you use VS.NET
to create your Web Service client, you have no need for the wsdl.exe utility.
Instead, VS.NET lets you select a Web Service for which you want to generate the
proxy code. It adds the code to the project, and you can use the techniques described
in the preceding examples to access it. Here are the details:

1. Open up .NET and select a Windows application.


2. Select Add Web Reference under the Project menu tab. The subse-
quent screen contains a text box at the top in which you enter the
URL of the Web Service. For example:
http://localhost/ws/BirthDayWS.asmx
3. Press the Enter key and a screen appears that includes a Web Service
Help page for the selected service. Click the Add Reference button
and the source code containing the proxy is added to the project.
4. The proxy code has its own namespace that should be added to the cli-
ent code. In this example, wsdlclient is the name of the VS.NET
project.
using wsdlclient.localhost;
You will see that a Web References folder has been added to the directory of the
project. To view the proxy source, click the Show All Files buttons in the Solution
Explorer and open the Reference.cs file beneath the localhost node. Notice
that in addition to the proxy code, the directory includes the WSDL file from which
the proxy was generated. We’ll look at this file in detail in the next section to gain an
understanding of how WSDL describes a Web Service. In addition, we will look at
how SOAP is used to define the format of messages transported between the Web
Service consumer and provider.
18.4 Understanding WSDL and SOAP 895

18.4 Understanding WSDL and SOAP


The purpose of this section is to add some depth to the topic of Web Services—to
look underneath at the XML grammar that is used to describe the service (WSDL)
and the protocol (SOAP) used for transporting data between the service and client.
Be aware that both of these have been submitted to the World Wide Web Consor-
tium (W3C), but neither is an official standard.

Web Services Description Language (WSDL)


WSDL is broadly defined as “an XML format for describing network services as a set
of endpoints operating on messages containing either document-oriented or proce-
dure-oriented information.”2 In our case, the endpoints are the client and a Web Ser-
vice, and WSDL defines how the client interacts with the service.
When developing a Web Service from the ground up, it is good practice to
develop the interface definition first—in the form of WSDL—and then map it to the
implementation code. Although the sheer complexity of WSDL demands that
WSDL editors be used for the task, there is still a need for the developer to have a
general understanding of the WSDL structure. The same is true even if you work
only on the client side and use wsdl.exe to create source code directly from the
WSDL. Problems can arise using utilities and editors that require a familiarity with
the format in order to perform troubleshooting. For example, a well-designed
WSDL interface is often built from multiple documents tied together by an XML
import element. Being familiar with the semantics and existence of the import ele-
ment goes a long way toward solving import issues—a not uncommon source of
WSDL errors.
This section introduces the basic structure of WSDL, which should satisfy the
curiosity of the occasional Web Service consumer or developer. Those interested in
learning more should refer to the specification that is published by W3C at
http://www.w3.org/TR/wsdl.

The WSDL Structure


Specifications for the WSDL grammar define six major elements: definitions, types,
message, port type, binding, and service. Let’s discuss these within the context of the
WSDL file that describes the sample BirthDayWS Web Service.

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

Namespaces are used to distinguish elements because it is possible that elements


from different namespaces could have the same name.

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

<binding name="BirthDaySoap" type="s0:BirthDaySoap">


<soap:binding transport=
"http://schemas.xmlsoap.org/soap/http"
style="document" />
<operation name="GetDayBorn">
<soap:operation soapAction="http://tempuri.org/GetDayBorn"
style="document" />
898 Chapter 18 ■ XML Web Services

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

Simple Object Access Protocol (SOAP)


SOAP is a platform-neutral protocol for exchanging information. Its cross-platform
capabilities are attributable to its use of XML to define the data being passed and
support for HTTP as a communications protocol. SOAP is the most popular and flex-
ible protocol for the exchange of information between Web Service consumers and
providers. Its format allows it to define complex data structures not supported by the
competing HTTP GET and POST protocols.
Our discussion of SOAP follows the same approach used with WSDL: We exam-
ine the basic features of SOAP using the request/response messages generated from
18.4 Understanding WSDL and SOAP 899

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.

A SOAP Request Message


The header for a SOAP request reveals that the SOAP request is packaged as an
HTTP POST request to the server designated by the Host field. The length field
specifies the number of characters in the body of the POST, and SOAPAction indi-
cates the namespace and method to be contacted.

POST /ws/BirthDayWS.asmx HTTP/1.1


Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/GetDayBorn"

Listing 18-3 shows the XML template for the SOAP message that is sent to the
server.

Listing 18-3 GetDayBorn SOAP Request Content

<?xml version="1.0" encoding="utf-8"?>


<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap-
enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://tempuri.org/" xmlns:types="http://tem-
puri.org/encodedTypes" xmlns:soap="http://schemas.xml-
soap.org/soap/envelope/">
<soap:Body
soap:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/">
<tns:GetDayBorn>
<mo xsi:type="xsd:int">int</mo>
<day xsi:type="xsd:int">int</day>
<yr xsi:type="xsd:int">int</yr>
</tns:GetDayBorn>
</soap:Body>
</soap:Envelope>

The overall structure of a SOAP message is not complex. It is an XML document


that has a mandatory root element, <Envelope>, an optional <Header> element,
and a mandatory <Body>.
900 Chapter 18 ■ XML Web Services

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:

string dayOfWeek = bd.GetDayBorn(12,20,1963);

A SOAP Response Message


The SOAP body of the response includes a <GetDayBornResult> element (see
Listing 18-4) that contains the response from the Web Service and identifies it as a
string type.

Listing 18-4 GetDayBorn SOAP Response Content

<?xml version="1.0" encoding="utf-8"?>


<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap-
enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://tempuri.org/" xmlns:types="http://tem-
puri.org/encodedTypes" xmlns:soap="http://schemas.xml-
soap.org/soap/envelope/">
<soap:Body
soap:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/">
<tns:GetDayBornResponse>
<GetDayBornResult
xsi:type="xsd:string">string</GetDayBornResult>
</tns:GetDayBornResponse>
</soap:Body>
</soap:Envelope>

Using the SOAP Header for User Authentication


The optional SOAP header is available for adding miscellaneous information about
its associated SOAP message. One popular use for this header is to include identifica-
tion information about the user making the request. This enables user authentication
to be performed by the methods within the Web Service.
18.4 Understanding WSDL and SOAP 901

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.

Authenticate Web Service User by Checking SOAP


Listing 18-5
Header
using System;
using System.Web.Services;
// Required for SoapException
using System.Web.Services.Protocols;
namespace BirthDayWS
{
// (1) Class to hold data passed in the header
public class SOAPHeaderContent : SoapHeader
{
public string UserName;
public string PassWord;
}
public class BirthDay : System.Web.Services.WebService
{
// (2) Member class accessing header data
public SOAPHeaderContent headerInfo;
// (3) Add SoapHeader attribute
[WebMethod(Description="Return day of week for any date"),
SoapHeader("headerInfo")]
public string GetDayBorn(int mo, int day, int yr)
{
902 Chapter 18 ■ XML Web Services

Authenticate Web Service User by Checking SOAP


Listing 18-5
Header (continued)
if (!Verify())
{
throw new SoapException(
"Valid User info not included.",
SoapException.ClientFaultCode);
} else {
bool err = false;
string dob;
if (mo <1 || mo >12) err=true;
if (day < 1 || day > 31) err=true;
if ( ! err)
{
DateTime dt = new DateTime(yr,mo,day);
dob = dt.ToString("dddd"); // extracts day
} else {
dob = "Invalid Date";
}
return(dob);
}
}
// Method to check password and ID in SOAP header
private bool Verify()
{
// (4) Access data in header
if (headerInfo.UserName != "Vincent" ||
headerInfo.PassWord != "arles")
{ return(false);
} else { return(true);}
}
}
}

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

3. Apply a SoapHeader attribute that references the member created in


Step 2. Applying this to a method makes the information in the header
available to the method.
4. Process the header data by accessing the members of the class created
in Step 1. The Verify method contains the simple logic to check
PassWord and UserName. In reality, the comparison information
would come from a database rather than being hardcoded.

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

Handling SOAP Exceptions


As shown in Listing 18-5, the Web Service throws an exception if the user name and
password cannot be authenticated:

throw new SoapException("Valid User info not included.",


SoapException.ClientFaultCode);

The exception is rendered as a SoapException object. In this example, its con-


structor receives a message and a SOAP fault code that signifies the type of error.
This information is returned to the client as a <Fault> element in the SOAP body
and is converted by the .NET Framework back to a SoapException object that can
be processed by the client’s code.
The SoapException object includes four properties that provide information
about the exception:

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

• VersionMismatchFaultCode. The SOAP envelope has an invalid


namespace.
• ClientFaultCode. The message sent by the client is incorrectly for-
matted or contains incorrect information.
• ServerFaultCode. The error occurs on the server and is not related
to the SOAP message. An example would be a network or hardware
problem.
• MustUnderstandFaultCode. A SOAP element marked with the
MustUnderstand attribute set to true is not processed. This is an
attribute that indicates the particular element must be processed.
18.4 Understanding WSDL and SOAP 905

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:

• Authentication. How a SOAP message expresses the identity of its


sender.
• Integrity. How to verify that a SOAP message has not been tampered
with.
• Confidentiality. What protects the contents of a SOAP message from
being read by an intermediary.

3. http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-soap-message-security-1.0.pdf
906 Chapter 18 ■ XML Web Services

In support of this specification, Microsoft provides an add-on to the .NET Frame-


work known as Web Services Enhancements 2.0 (WSE 2.0). This tool set comprises a
.NET class library that can be used to integrate the WS-Security specification into
Web Service applications. Note that the use of these enhancements requires .NET
on both the client and service provider endpoints, so it’s not yet a generic Web Ser-
vice security solution. See the MSDN Web site, msdn.microsoft.com/webser-
vices/webservices/building/wse/default.aspx, for whitepapers and a free
download of the enhancements library

18.5 Using Web Services with


Complex Data Types
The BirthDayWS Web Service used throughout this chapter accepts integers as
input and returns a string value. This is useful for introducing Web Service princi-
ples because HTTP GET, HTTP POST, and SOAP all support it. However, Web Ser-
vices also have the capability of serving up more complex data types such as data sets,
hash tables, images, and custom objects.
Before data can be sent to or from a Web Service, it is serialized using XML seri-
alization. Conversely, it is deserialized on the receiving end so it can be restored to its
original type. As we saw in Chapter 4, “Working with Objects in C#,” not all data can
be serialized. Thus, when designing a Web Service, it is important to understand
restrictions that apply to serialization:

• 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

A Web Service to Return Images


Image manipulation usually requires representing an image as a Bitmap object.
However, because bitmaps cannot be serialized and transferred directly, we must
find an indirect way to transport an image. The not-so-difficult solution is to break
the image into bytes and return a byte stream to the client, who is responsible for
transforming the stream to an image.
The logic on the server side is straightforward: a FileStream is opened and asso-
ciated with the image file. Its contents are read into memory and converted to a byte
array using

tempStream.ToArray()

This byte array is then sent to the Web client (see Listing 18-6).

Listing 18-6 Web Service to Return an Image as a String of Bytes


<%@ WebService Language="C#" Class="WSImages" %>
using System;
using System.Web.Services;
using System.IO;
using System.Web.Services.Protocols;
public class WSImages: System.Web.Services.WebService {
[WebMethod(Description="Request an Image")]
public byte[] GetImage(string imgName) {
byte[] imgArray;
imgArray = getBinaryFile("c:\\"+imgName+".gif");
if (imgArray.Length <2)
{
throw new SoapException(
"Could not open image on server.",
SoapException.ServerFaultCode);
} else
{
return(imgArray);
}
}
public byte[] getBinaryFile(string filename)
{
if(File.Exists(filename)) {
try {
FileStream s = File.OpenRead(filename);
return ConvertStreamToByteBuffer(s);
}
908 Chapter 18 ■ XML Web Services

Listing 18-6 Web Service to Return an Image as a String of Bytes


catch(Exception e)
{
return new byte[0];
}
} else { return new byte[0]; }
}
// Write image to memory as a stream of bytes
public byte[] ConvertStreamToByteBuffer(Stream imgStream) {
int imgByte;
MemoryStream tempStream = new MemoryStream();
while((imgByte=imgStream.ReadByte())!=-1) {
tempStream.WriteByte(((byte)imgByte));
}
return tempStream.ToArray(); // Convert to array of bytes
}
}

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.

WSImages myImage = new WSImages();


try {
// Request an image from the Web Service
byte[] image = myImage.GetImage("stanwyck");
MemoryStream memStream = new MemoryStream(image);
Console.WriteLine(memStream.Length);
// Convert memory stream to a Bitmap
Bitmap bm = new Bitmap(memStream);
// Save image returned to local disk
bm.Save("c:\\bstanwyck.jpg",
System.Drawing.Imaging.ImageFormat.Jpeg);
}
catch (WebException ex)
{
Console.WriteLine(ex.Message);
}
18.5 Using Web Services with Complex Data Types 909

Using Amazon Web Services


To use the Amazon E-Commerce Service, you must register for a developer’s token,
which is required as part of all requests made to the Web Services. In addition, you
should download (http://www.amazon.com/webservices) the developer’s kit that
contains the latest documentation, examples, and—most importantly—a WSDL file
defining all the services.
An examination of the WSDL file reveals that AmazonSearchService is the
Web Service class that contains the numerous search methods available to clients.
These methods provide the ability to search the Amazon product database by key-
word, author, artist, ISBN number, manufacturer, actor, and a number of other crite-
ria. Each search method takes a search object as an argument that describes the
request to the server and returns a ProductInfo object. For example, a request to
search by keywords looks like this:

AmazonSearchService amazon = new AmazonSearchService();


KeywordRequest kwRequest = new KeywordRequest();
// Set fields for kwRequest
ProductInfo products = amazon.KeywordSearchRequest(kwRequest);

Sending a Request with the


AmazonSearchService Class
Table 18-3 contains a sampling of the methods available for searching Amazon prod-
ucts. These methods are for accessing the Web Service synchronously. An asynchro-
nous form of each method is also available that can be accessed using the techniques
discussed earlier in this chapter.

Table 18-3 Selected Methods of AmazonSearchService Class

Method Description

ProductInfo KeyWordSearchRequest Method to return items that contain one or


(KeywordRequest req) more keywords provided in request.

ProductInfo AsinSearchRequest Method to return a book having a requested


(AsinRequest req) Amazon Standard Identification Number
(ASIN) that is the same as the book’s ISBN.
Represented as a 10-digit string.

ProductInfo AuthorSearchRequest Method to return names of all books by


(AuthorRequest req) requested author.
910 Chapter 18 ■ XML Web Services

Table 18-3 Selected Methods of AmazonSearchService Class (continued)

Method Description

ProductInfo ActorSearchRequest Method to return video titles of movies in which


(ActorRequest req) a specified actor or actress was a cast member.

ProductInfo PowerSearchRequest Method to retrieve book information based on a


(PowerRequest req) Boolean query that may include a combination
of title, subject, author, keyword, ISBN, pub-
lisher, language, and publication date (pubdate).

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

string page Page of results to display.

string mode Type of products being searched—for example, "books".

string tag Amazon associate’s ID. Use "webservices-20" as default.

string type "lite" or "heavy". Determines how much XML data is


returned.

string devtag Developer’s token assigned to you by Amazon.


18.5 Using Web Services with Complex Data Types 911

Using the ProductInfo Class to


Process the Web Service Response
The Web Service responds to the search request with a ProductInfo object con-
taining results from the search. This object exposes three important fields: a Total-
Results string contains the number of products retrieved by the request, a
TotalPages string that indicates how many pages these results are displayed in, and
the important Details array that contains a detailed description of products that
constitute one returned page of results. This array is of the Details type. Table 18-5
shows the fields that are related to books.

Table 18-5 Selected Fields of the Details Class

Field Description

string ProductName Name of a single product.

string SalesRank Ranking of product based on sales of items of its type.

string Publisher Publisher of book.

String ListPrice List and sales price of book.


string OurPrice

Reviews[] Reviews The Reviews class contains several fields relating to reviews
of the book:
string AvgCustomerRating
string TotalCustomerReviews
CustomerReview CustomerReviews
string Comment
string Rating

String[] Authors One or more authors for the book.

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.

Creating a Proxy for the Amazon Web Services


Our first step is to create a proxy class from the Amazon WSDL information. The
downloadable kit includes a WSDL file, and it can also be retrieved from the Inter-
net, as we do here. Using the VS.NET command line, we place the proxy source
code in the file AZProxy.cs.
912 Chapter 18 ■ XML Web Services

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.

csc/t:library /r:system.web.services.dll /r:system.xml.dll


AZProxy.cs

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

Compile and execute this from the command line:

csc /r:AZProxy.dll azclient.cs


azclient

When azclient.exe is executed, it should print the number of matching results.


18.5 Using Web Services with Complex Data Types 913

Building a WinForms Web Service Client


Let’s design a Windows Forms application that permits a user to perform searches on
books using multiple search options. Open VS.NET and select a Windows applica-
tion. After this is open, we need to add a reference to the proxy assembly
AZProxy.dll. From the menu, select Project – Add Reference – Browse. Click the
assembly when it is located and then click OK to add it as a reference. You also need
to add a reference to System.Web.Services.dll, which contains the required
Web Service namespaces.
The purpose of the application is to permit a user to search the Amazon book
database by keyword, author, or title. The search can be on a single field on a combi-
nation of fields. Figure 18-7 shows the interface for entering the search values and
viewing the results. The Search buttons submit a search request based on the value
in their corresponding text box. The Power Search button creates a query that logi-
cally “ands” any values in the text boxes and submits it.
A single page of results is displayed in a ListView control. Beneath the control
are buttons that can be used to navigate backward and forward through the results
pages.

Figure 18-7 Overview of how a client accesses a Web Service


914 Chapter 18 ■ XML Web Services

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.

Client Code to Display Results of Author Search—


Listing 18-7
azwsclient.cs

// Fields having class-wide scope


int CurrPg; // Current page being displayed
string SearchMode = ""; // Current search mode
int MaxPages =1; // Number of pages available
// This method is called when author Search button is clicked
private bool AuthorReq()
{
AmazonSearchService amazon = new AmazonSearchService();
AuthorRequest auRequest = new AuthorRequest();
auRequest.author = textBox2.Text; // Get author from GUI
auRequest.type = "heavy";
auRequest.devtag= "****KLMJFLGV9"; // Developer token
auRequest.mode = "books";
auRequest.tag = "webservices-20";
auRequest.page = CurrPg.ToString();
try
{
// Call Web Service with author query
ProductInfo products =
amazon.AuthorSearchRequest(auRequest);
FillListView(products);
return(true);
}
catch (SoapException ex)
{
MessageBox.Show(ex.Message);
return(false);
}
}
private void FillListView(ProductInfo products)
listView1.Items.Clear(); // Remove current entries
label6.Text=""; // Clear any title
label4.Text = products.TotalResults;
label5.Text = CurrPg.ToString()+" of "+products.TotalPages;
18.5 Using Web Services with Complex Data Types 915

Client Code to Display Results of Author Search—


Listing 18-7
azwsclient.cs (continued)

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

"author:hemingway AND keywords:Kilimanjaro"

This application was designed as a Windows Forms application. It could just as


easily be set up as Web page under ASP.NET. The code to reference the assembly
AZProxy.dll is identical. The ListView control is not supported on a Web Form,
but you could easily substitute a DataGrid for it.
916 Chapter 18 ■ XML Web Services

18.6 Web Services Performance


The performance of a Web Service from both the client and server side is affected by a
variety of factors. Some are .NET related and others are inherent in the technology.
The solutions for improving Web Services performance range from shaving a few milli-
seconds off the way .NET sends a request to simply eschewing Web Services for an
alternate protocol when transferring large amounts of data. We’ll look at all of these.

Configuring the HTTP Connection


Connections (HTTP) to Internet resources are managed in .NET by the Service-
Point class. This class includes properties to specify a connection’s timeout interval,
set its security protocol, and manage the use of server security certificates. It also
includes properties that directly affect how much delay is incurred before a Web Ser-
vice request is transmitted over a network: UseNagleAlgorithm and Expect100-
Continue. Despite the awkward sounding names, setting their properties is as easy
as assigning a true or false value to them. The default for both is true.

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


One way to improve network efficiency is to reduce the number of small data packets
sent across a network. To accomplish this, the software layer controlling the underly-
ing TCP (Transmission Control Protocol) connection attempts to accumulate, or
buffer, small messages into a larger TCP segment before they are sent. The tech-
nique to do this is based on the Nagle algorithm.4

4. RFC 896, “Congestion Control in IP/TCP Internetworks,” by John Nagle, 1984.


18.6 Web Services Performance 917

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:

// Using System.Net must be added


protected override WebRequest GetWebRequest( Uri uri)
{
// Locate ServicePoint object used by this application
ServicePoint sp =
ServicePointManager.FindServicePoint(uri);
sp.Expect100Continue = false;
sp.UseNagleAlgorithm = false;
return WebRequest.Create(uri);
}

Working with Large Amounts of Data


Although the XML format offers a great deal of flexibility in representing data, it can
place a potentially large burden on a network because of the large files that can be
generated. Moreover, processing XML data can require extensive memory resources
on the client and server. Finally, there is the nature of a Web Service: If the transmis-
sion fails at any point, the entire response must be resent. This is in contrast to FTP
and the HTTP GET verb that allow partial data transmission.
For large amounts of data, consider these options:

• Use FTP or a Web client as described in Section 17.6, “Creating a


Web Client with WebRequest and WebResponse.”
• Avoid calls as much as possible by caching data rather re-requesting it.
• Look at compression techniques such as HTTP transport compression
or the SOAP extensions that can compress part of a Web Service
message.
918 Chapter 18 ■ XML Web Services

• Wait for new Web Service standards. Of particular note is Message


Transmission Optimization Mechanism (MTOM), a new W3C recom-
mendation—pushed by Microsoft—that details a method for attach-
ing large binary data to a SOAP message.

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.

18.8 Test Your Understanding


1. Which base class must the Web Service class inherit from for the Web
Service to use the ASP.NET Session and Application objects?

2. What is the difference between a WebService attribute and a


WebService directive?
18.8 Test Your Understanding 919

3. How can we create a Web Service that exposes two methods having
the same name?

4. Which SoapHttpClientProtocol member is used to make a


synchronous call to a Web method? An asynchronous call?

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>

7. Which attribute must be applied to a Web method for the method to


access a SOAP header?

8. What three ways can a proxy be used to make asynchronous calls to a


Web Service?
FEATURES SPECIFIC
TO .NET 2.0
AND C# 2.0

In This Appendix

Table A-1 provides a shortcut to reference the types and classes


described in this book that are supported only in .NET 2.0 and
C# 2.0.
A

Table A-1 .NET 2.0 and C# 2.0 Features Index

Chapter.
Section Feature Description

2.2 float type Now referred to as a single.

2.2 TryParse method Has been added to return a Boolean value if a


string can be successfully converted into the target
type:
int iParse =
Int32.TryParse("100", out result);

3.2 Static class A class containing only static members can be


declared.

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.

3.10 Generics syntax New syntax that enables collection objects to be


made type-safe. An application specifies the type
of data a class can hold in a parameter:
myStack<T> = new myStack<T>();

921
922 Appendix A ■ Features Specific to .NET 2.0 and C# 2.0

Table A-1 .NET 2.0 and C# 2.0 Features Index (continued)

Chapter.
Section Feature Description

4.4 Iterators An easier way to implement enumeration on a custom class.

4.4 System.Collection. Holds a generic version of the collection classes. New classes
Generic namespace include List<>, Dictionary<>, and Stack<>.

7.1 DataGridView and Some new WinForms controls.


MenuStrip controls

7.6 StatusStrip and StatusStrip control supersedes StatusBar.


ToolStripProgressBar ToolStripProgressBar can be displayed on a StatusStrip.
controls

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.

10.4 XmlReader.Create New method that returns an instance of an XmlReader.


method

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();

11.4 DataTable.Load() Accepts a DataReader object as a parameter and fills a table


method based on DataReader.

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.2 GZipStream class Class to perform text compression.

13.3 ParameterizedThread- Permits data to be passed to a thread.


Start delegate

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

Table A-1 .NET 2.0 and C# 2.0 Features Index (continued)

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.4 ValidationGroup Allows validation controls to be grouped so that when a page is


property submitted, only controls whose validators are in a specific group
are validated.

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.

// Use reflection to extract DataGridView events


using System;
using System.Reflection;
using System.Collections;
using System.IO;
class MyApp
{
public static void Main()
{
// This DLL is found in the Framework version folder
// You will need to specify the path
Assembly myassembly =
Assembly.LoadFrom("System.windows.forms.dll");
Type grid = myassembly.GetType(
"System.Windows.Forms.DataGridView");
Hashtable events = new Hashtable();
StreamWriter sw = new StreamWriter("c:\\events.txt");
foreach(EventInfo gev in grid.GetEvents())
{
events.Add(gev.Name, gev.EventHandlerType);
}

925
926 Appendix B ■ DataGridView Events and Delegates

// Tab allows columns to be imported into table


string mytab= ((char)9).ToString();
// Write event and delegate info to file
foreach (DictionaryEntry de in events)
sw.WriteLine(de.Key+mytab+de.Value);
sw.Close();
}
}

Table B-1 DataGridView Events and Delegates

Note: System.Windows.Forms has been truncated to Forms in the Delegate description.

DataGridView Event Delegate

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

Table B-1 DataGridView Events and Delegates (continued)

DataGridView Event Delegate

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

Table B-1 DataGridView Events and Delegates (continued)

DataGridView Event Delegate

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

Table B-1 DataGridView Events and Delegates (continued)

DataGridView Event Delegate

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

Table B-1 DataGridView Events and Delegates (continued)

DataGridView Event Delegate

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

Table B-1 DataGridView Events and Delegates (continued)

DataGridView Event Delegate

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

Table B-1 DataGridView Events and Delegates (continued)

DataGridView Event Delegate

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

Table B-1 DataGridView Events and Delegates (continued)

DataGridView Event Delegate

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.

Hashtable signature = new Hashtable(); // Holds signatures

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

Table B-2 DataGridView Delegates and Parameters

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

Table B-2 DataGridView Delegates and Parameters (continued)

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

Table B-2 DataGridView Delegates and Parameters (continued)

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

1. The .NET Framework Redistributable is required by a client to run a .NET applica-


tion and is available as a free download on the Internet.
2. Managed code is produced by a compiler that meets the Common Type System
(CTS) requirements that are necessary before code produced by it can be run by the
Common Language Runtime (CLR). Unmanaged code does not meet the CTS stan-
dard. The Windows API and COM objects are examples of unmanaged code.
3. The Common Type System defines the types and their members that must be used
by compilers that create code to run on the Common Language Runtime. The Com-
mon Language Specification provides stricter requirements that ensure interopera-
bility between languages.
4. All .NET compilers generate an Intermediate Language (IL) code. Since IL is com-
patible, irrespective of its source, the CLR doesn’t care which compiler produces it.
5. The Global Assembly Cache (GAC) holds shared assemblies—assemblies that can
be used by more than one application. Assemblies in the GAC have a digital signa-
ture that uniquely identifies them, even if they have the same file name.
6. A strong name consists of an assembly name, a version, a culture setting, and a pub-
lic key token that is required by a client to use the assembly.
Answers to Chapter Exercises 939

7. A namespace usually identifies a group of types that provide related services. An


assembly may contain one or more namespaces. Also, a namespace may contain
types in more than one assembly.
8. CLR—Common Language Runtime
FCL—Framework Class Library
GAC—Global Assembly Cache
IL—Intermediate Language (also, CIL or MSIL)

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

3. a. ShowName is static and cannot be referenced from a class instance.


b. ShowName.ShowMe("My Name is Ishmael");
4. An abstract class may contain both abstract and non-abstract methods.
5. new is used to replace (not override) an inherited method with one of the same
name.
6. (a) x=60 y=40. x is passed by reference; y by value.
7. Include a method that performs the same operation as the operator overloading.
8. A class cannot be instantiated if it is abstract or if it has a private
constructor.
9. Example event handler:
private void PrintSale(object sender, SaleEvArgs e)
{
decimal prc= (decimal)e.saleprice;
DateTime dt = (DateTime)e.date;
int itemNum = (int)e.itemnum;
// Now print the values
}
10. (a) Compilation error indicating that keyword new is required on
Child.amethod(int, string) because it hides the inherited method. If new
is used, the code prints “Base Constructor”.

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

6. The IDisposable interface is a convenient way to notify a client that an object’s


Dispose method should be called. The client only needs to check for the exist-
ence of the interface.
7. a. false
b. true
c. false
d. true
The Clone method creates a copy of an object at a new address.
8. Objects with a Finalize method implemented are placed in a special queue so
that Finalize can be executed before Garbage Collection occurs. This delays
the actual Garbage Collection by one cycle.

Chapter 5

1. CultureInfo represents information about a culture. Here is an example of its


use:
CultureInfo ci = new CultureInfo("de-AT"); // German-Austria
2. CultureInfo() with no parameters and Thread.CurrentThread.Current-
Culture return objects with current culture.
3. a. 2
b. 3
c. 1
4. Use the instance method when the expression is used repeatedly, because,
unlike the static approach, it does not have to recompile the expression each
time.
5. Equals() is used to check the memory location. If different, a character-
by-character comparison is performed.
6. a. 2
b. 1
c. 3
7. a. FileInfo is not created from a FileStream.
8. a
9. a. true
b. true
c. false
d. false
942 Answers to Chapter Exercises

Chapter 6

1. System.Windows.Forms.Form is the required base class for a Windows appli-


cation.
2. Docking attaches a control to an edge of its container; anchoring places a control
in a fixed position relative to the edge(s) of a container. As the container is
resized, the control remains a fixed distance from the edges.
3. x and y yield the coordinates. The Button property is set to a MouseButtons
enumeration value.
4. The Form.TransparencyKey permits part of a form to appear transparent.
Any part of the Form have the color assigned to this property becomes transpar-
ent. For example, to set the red portion of myForm to red, set the Transparen-
cyKey as follows:
myForm.TransparencyKey = Color.Red;
5. A modal form maintains focus until it is closed. A modeless form allows the par-
ent form to regain focus.
6. The owned form is always on top, and closing or minimizing the parent also
closes or minimizes the owned form.
7. Minimize and Maximize properties are set to false; the HelpButton prop-
erty is set to true.
8. A MouseOver displays information associated with a control’s ToolTip. Help
text is enabled by pressing F1 or selecting the Help button and clicking a control
to show context-sensitive Help.
9. Have the original event handler in the base Form call a virtual method to deal
with the event. The inheriting Form then overrides the virtual method.

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

7. DragEnter and DragDrop must be supported by a destination control for drag


and drop to work.
8. Assign Browsable, Category, and Description attributes.
9. The ResourceManager class is used to access resources. Its Get-
String(name) method returns the value associated with a name in the text file.

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

4. The third column begins at x coordinate of 310 (usually in 1/100ths of an inch).


The tab numbers are cumulative.
5. The BeginPrint event is fired when PrintDocument.Print is executed.
6. The PrintPageEventArgs parameter has a MarginBounds property that rep-
resents the margins.
7. To preview a document on the screen, you must create a PrintPreviewDia-
log object, set its Document property to the PrintDocument, and then call
ShowDialog to display the preview:
PrintPreviewDialog prvDialog = new PrintPreviewDialog();
PrvDialog.Document = pd;
PrvDialog.ShowDialog(); // Show preview

Chapter 10

1. Use its static Create method to create an instance.


2. XmlReaderSettings defines how an XmlReader processes the input stream.
It can specify node types to be ignored and a schema to be used for validation.
3. An XPathDocument is used for reading only.
4. a. Two movies nodes are retrieved: Raging Bull and Taxi Driver.
b. One movies node is retrieved: Taxi Driver.
c. One movies node is retrieved: Raging Bull.
d. Two movie_Title nodes are retrieved: Raging Bull and Taxi Driver.
5. Two validation techniques were discussed in this chapter. One approach is to use
the XmlReader class directly by setting the Schemas and XsdValidate
properties to enable validation. The other approach is to use the
XmlValidatingReader class.

Chapter 11

1. A .NET data provider must supply a connection, command, DataReader, and


DataAdapter object to provide access to its data source.
2. b is the correct choice. a and c are invalid statements.
3. ExecuteNonQuery() executes a command but does not return a resultset.
ExecuteReader() returns a resultset in response to a query.
ExecuteScalar() returns a single value—or null—if the query generates no
value.
4. The DataReader remains connected to a data source and returns data in a for-
ward, read-only cursor. A DataAdapter is a bridge between a data source and
Answers to Chapter Exercises 945

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

1. False. An asynchronous delegate may have a return value.


946 Answers to Chapter Exercises

2. ia is an IAsyncResult object returned by BeginInvoke that is later passed to


EndInvoke.
p1 is a string parameter defined in the delegate’s signature.
p2 is an optional callback method.
p3 is a value that can be passed to the callback method when the thread ends.
3. Thread Local Storage (TLS) holds state information about a thread. This is
required when a thread is swapped out before completion.
4. The default number of threads in a thread pool is 25.
5. ThreadStart and ParameterizedThreadStart delegates are used to create
a thread. The latter permits parameters to be passed to a thread.
6. lock (this) { } expands into an identical construct.
7. a. The message is never printed because the semaphore is created with zero ini-
tial threads, and then goes into a wait state.
8. b. It prints "Primary Thread" followed by "Worker Thread".
The mutex in the main thread is created with ownership. The spawned thread’s
mutex must wait for the original one to finish before its thread continues pro-
cessing.
9. There are many solutions. Here is one based on not grabbing a chopstick until
both are available:
using System;
using System.Threading;
class Stick {
//Sticks available are designated as true
bool[] chopStick = {true, true, true, true, true};
// Attempt to pick up left and right chopstick
public void GetSticks(int left, int right)
{
lock (this)
{
// Release lock and wait until both chopsticks are free
while (!chopStick[left] && !chopStick[right])
Monitor.Wait(this);
chopStick[right] = false; chopStick[left] = false;
}
}

// Put chopsticks down


public void FreeSticks(int left, int right)
{
lock(this)
{
chopStick[right] = true;
Answers to Chapter Exercises 947

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();
}

public void Run()


{
while(true)
{
try
{
// Philosopher thinks for random amount of time
Thread.Sleep(thinkDelay);
chopSticks.GetSticks(left-1, right-1);
Console.WriteLine("Philosopher {0} is eating for
[1} ms ",n, eatDelay);
Thread.Sleep(eatDelay);
chopSticks.FreeSticks(left-1, right-1);
} catch { return; }
}
}
} // End of class Philosopher

public class Diners


{
public static void Main()
948 Answers to Chapter Exercises

{
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

1. a. True. A process may contain multiple AppDomains.


b. True. An AppDomain may contain multiple assemblies.
c. True. An AppDomain may contain .dll and .exe files.
d. True. An AppDomain can be unloaded from a process.
2. Three activation modes: client activation, server activation (single call), and
server activation (singleton). HTTP or TCP can be used with all.
3. Single call server activation creates a new object on each call; single call single-
ton creates an object on the first call only. The object is then used for other calls.
4. CurrentLeaseTime—Amount of time until object is available for Garbage
Collection.
InitialLeaseTime—Initial lifetime of a lease.
RenewalOnCallTime—Amount that CurrentLeaseTime is increased on
called object.
5. Set the LeaseManagerPoolTime in a configuration file to specify how
frequently the lease manager checks for expirations.
6. Client-activated and server-activated singleton use leases.
7. c. A server-activated singleton object is created when the first call is made to the
host object.
8. Channel registration indicates the type of protocol to be used—usually TCP or
HTTP—in the remoting and the port number to be used. Type registration
specifies the available remote object and the activation mode type.
9. An interface or SoapSuds is used to prevent having to place a full assembly on
the remoting client’s machine. The assembly provides metadata, but can also
expose proprietary code. An interface provides only the metadata a client
requires. SoapSuds extracts the metadata and places it in a file that is deployed
on the client’s machine.
Answers to Chapter Exercises 949

Chapter 15

1. a. False. Only a strongly named assembly may be placed in the GAC—but it


does not have to be there.
b. False. The compiler does not automatically check the GAC for references.
c. True. The CLR checks the GAC for assembly references.
d. True. A client must have a valid public key token to use an assembly in the
GAC.
2. A Permission attribute should be added to the assembly, specifying the
permission(s) required by the assembly.
3. A strong name consists of a simple name, version number, culture info, and
public key token.
4. Assign version: [assembly: AssemblyVersion("1.1.0.1")]
Assign culture: [assembly: AssemblyCulture("fr-CA")]
The default build number is the number of days since 1/1/2000; the default revi-
sion number is the number of seconds past midnight divided by 2.
5. The IStackWalk interface defines the Assert, Deny, and PermitOnly meth-
ods to modify a stack walk. Assert stops the walk; Deny specifies permissions
that cause the walk to fail; and PermitOnly specifies the only permissions that
do not cause the stack walk to fail..
6. Delayed signing refers to using a public key to create a digital signature, as
opposed to using the private key. When the final assembly is ready, the private
key replaces the public key for encryption. This reduces the need to expose the
private key during development.
7. A predefined permission is an individual permission class provided by .NET. A
named permission set is a predefined set of permission objects. A security zone
classifies where an assembly originates: Internet, Intranet, MyComputer,
NoZone, Trusted, Untrusted.
8. a. True.
b. True.
c. False. <Binding Redirect /> is used to redirect CLR to newer version.
d. False.
9. Add to the configuration:
<binding Redirect OldVersion="1.0.0.0"
NewVersion="2.0.0.0" />
950 Answers to Chapter Exercises

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

1. False. A web.config file is not required.


2. pageOutput in the web.config file specifies whether the trace log is
appended to the Web page for display on the client’s browser. If set to false,
the output can be viewed in the trace.axd file in the application’s root.
3. a. Windows, Forms, and Microsoft Passport authentication are offered by
.NET.
b. Authentication is specified using the <authentication> element of the
web.config file.
Answers to Chapter Exercises 951

c. Authentication verifies that a user is who he says he is; authorization


determines what actions the user may perform or what resources he may
access.
d. The Page.User property provides information about a user.
4. Out-of-Process Session management supports storing state information on a
server on process on another server.
5. <%@ OutputCache Duration="180" VaryByParam="*"
VaryByCustom="Browser"
%>
6. A data cache is read-only versus read/write for application state data. However, a
data cache is much more flexible: An expiration can be assigned to it, and it can
be tied to other objects, causing the cache data to be removed if the objects’
value changes.
7. ASP.NET periodically invokes a resource scavenging process that removes less
important data from a data cache. Importance is determined by priority. Thus,
setting a high priority reduces the chance of the data cache being removed.
8. As a request or response passes through the HTTP pipeline, an Application
object fires a series of events that may be handled by an HTTP module or in the
global.asax file.

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

char type, 48 DefaultPageSettings, 441


Char variables, assigning, 205–206 Delegate, 597–599
CheckBox class, 324–325 Directory, 256–260
CheckedListBox class, 341–342 DirectoryInfo, 256–260
Checked property, 306 DrawImage, 410
CIL (Common Intermediate Language), 7 events, 118–119
classes exceptions, 155–157
AmazonSearchService, 909–910 FCL, 6, 18–22
AppDomain, 640–643 File, 261–263
ArrayList, 179–180 FileInfo, 261–263
binding, 558–560 FileStream, 245–247
base, 87 FileSystemInfo, 256
BindingManagerBase, 553–554 Font, 430–433
BufferedStream, 248–249 FontFamily, 428–429
built-in permission, 696–697 Form, 285–305
Button, 323–324 FormsAuthentication, 831–833
C# generics, 132
constants, 89–97 Graphics
constructors, 106–112 GDI+, 380–384
defining, 82–87 methods, 383–384
delegates, 112–123 GraphicsPath, 390–391
fields, 89–97 GroupBox, 327
generics, 130–133 HatchBrush, 396–397
interfaces, 126–130 HttpApplication, 853–857
members, 88–89 HttpRequest objects, 808–812
methods, 97–106 HttpResponse
operator overloading, 123–126 objects, 813–817
properties, 89–97 properties, 814–816
structures, 134–139 identifiers, 86–87
CheckBox, 324–325 identity permissions, 698
CheckedListBox, 341–342 instances, 92
ComboBox, 341–342 Label, 330–331
connections (ADO.NET), 506–510 LinearGradientBrush, 397–400
ConnectionStringBuilder, 508 ListBox, 335–341
ContextMenu, 307 ListView, 342–349
CryptoStream, 252–255 MemoryStream, 247–248
DataAdapter, 504–506, 525–529 MenuItem properties, 306
DataGridView MessageBox, 299–300
configuring master-detail, 576–579 Monitor, 623–625
data binding, 563–584 Mutex, 625–626
delegates, 924–936 .NET collections, 167–187
events, 571–576, 924–936 NumberFormatInfo, 230
parameters, 934–936 operators, 123–126
properties, 564–571 Page (ASP.NET), 754–758
virtual mode, 579–584 PageSettings, 445–446
DataReader, 503–504, 516–518 Panel, 328–330
binding, 772–774 Path, 256–260
DataRow, 521–523 Pen, 393–395
DataSet, 504–506, 518–519 PictureBox, 331–333
binding, 556–557, 774–776 PrintDocument, 439–456
defining relationships, 530–532 customizing, 454–456
loading data into, 523–525 events, 440, 446–448
XML, 534–540 ProductInfo, 911
DataTables, 519–523, 555–557 ProgressBar, 355–358
DateTimeFormatInfo, 230 ProviderFactory, 500
DbCommand, 511 proxy
DbProviderFactories, 500 Amazon Web services, 911–912
956 Index

classes, proxy (continued) assemblies, 658–660, 666–669


creating with Visual Studio.NET, 894 remoting, 643–671
wsdl.exe utility, 885–890 servers (Internet interaction), 734–758
Queue, 177–179 UDDI, 872–874
RadioButton, 325–327 Web services
Regex, 232–237 building, 884–894
ResourceManager, 371–372 Windows Forms, 913–915
ResourceWriter, 371 Clone method, 73
ResXResourceReader, 372–373 cloning objects, 165
ResXResourceWriter, 372 closing forms, 294
Semaphore, 627–628 CLR (Common Language Runtime), 9–18
ShowDialog, 300–301 .NET, 10–11
Stack, 177–179 reference/value type support, 73–77
StatusStrip, 355–358 CLS (Common Language Specification), 12
Stream, 244–245 code
StringBuilder, 220–223 design, 682–686
StringFormat, 435 exception handling 151–153
StringReader, 251–252 forms, 296–298
StringWriter, 251–252 groups, 692
structures, 137–139 creating, 708–709
synchronization, 621–622 testing, 709
System.Array, 71 inline code models (ASP.NET), 741–749
System.Collections, 168 isolation, 638
generic namespaces, 184–187 libraries, 34
namespaces, 177 managed, 10
System.Exception, 150–151 .NET, 10–11
properties, 150–151 regions, 61
System.Object Code Access Security model, 692. See also security
customizing, 160–167 CodeBase configuration, 725
System.String, 209–215 code-behind models
System.Timers.Timer, 618–619 ASP.NET, 749–754
System.Web.Services.WebService, 879–880 global.asax files, 856–857
System.Windows.Forms.Timer, 619 code points (Unicode identifiers), 205
TextBox, 333–335 collections, 167–187
Thread, 592 bindings, 547
Timer, 355–358 enumeration, 172
TreeNode, 349–352 members (iterators), 172–174
TreeView, 349–355 colors, 400–402
WebRequest, 848–851 multi-color gradients, 400
WebResponse, 848–851 objects, 401
Windows.Forms.Form control classes, 271–284 viewers, 402–407
XmlDataDocument, 491–493 wheels, 400
XmlDocument, 489 columns
XmlNodeReader, 477–479 DataTable.Columns property, 519–521
XmlReader, 472–477 headers, 344–345
XmlReaderSettings, 489–490 recognizing, 574–575
XmlSerializer, 463–465 tab stops, 436–437
XmlWriter, 482–484 types, 570–571
XPathDocument, 490 value access, 517–518
XslTransform, 469–471 ComboBox class, 341–342
click event buttons, 271 CommandBuilder object, 525–526
CLI (Common Language Infrastructure), 6 command line, compiling from the, 32–35
standards, 7–9 commands
client-activated objects. See CAO objects, 511–515
clients SQL, 512–513
ASP.NET, 848–851 comments, embedding, 43–45
Index 957

Common Intermediate Language. See CIL ConnectionStringBuilder class, 508


Common Language Infrastructure. See CLI <connectionStrings> configuration file section, 823
Common Language Runtime. See CLR constants, C# classes, 89–91
Common Language Specification. See CLS constraints, relationships, 531–532
Common Type System. See CTS constructors
Compact Profile, 8 C# classes, 106–112
comparing Delegate class, 598
abstract classes and interfaces, 130 Font class, 430
classes and structures, 137–139 inheritance, 107
strings, 212–213 instances, 106–110
<compilation> configuration file section, 821 private, 110–111
compiling static, 111–112
C#, 31–35 StringBuilder class, 221
code (.NET), 10–11 content pages (ASP.NET), 789–793
from the command line, 32–35 context
multiple files, 34 devices, 380
Web pages, 740 menus, 307–308
complex data types, Web Services, 906–915 ContextMenu class, 307
components control flow statements (C#), 52–53
HelpProvider, 312–313 controls. See also user controls
services, 623 anchoring, 275–276
composite controls, 798–801 arrays, 558–559
compressing files, 607–608 buttons, 270
concatenation classes, 271–284
StringBuilder class, 222 composite, 798–801
strings, 63–64 CreateGraphics method, 381
conditional attributes, 84–85 CustomValidator, 787–788
conditional caching, 843–844 data binding, 546–554
conditional compilation, 60 data display, 759
conditional operators, 51–52 DataList, 768–772
configuration DataSource property, 776–784
application configuration files, 23 data sources, 551, 760
assemblies, 29 docking, 275–276
configuration files dragging and dropping, 363–368
ASP.NET, 817–826 events, 279–284
assembly management, 726 extending, 358–359
customizing, 824–826 filling, 767–768
message servers, 660–662 FlowLayoutPanel, 328–329
configuring forms, 319–323
CodeBase configuration, 725 buttons, 323–331
column headers, 344–345 CheckedListBox, 341–342
DataGridViews class, 576–579 ComboBox, 341–342
integrated help systems, 308–313 customizing, 358–363
leasing, 673–674 dragging and dropping, 363–368
.NET Framework Configuration tool security, 704–706 group boxes, 323–331
permissions, 708 iterating, 278
printer resolution, 444 ListBox, 335–341
security, 702–704, 706–711 ListView, 342–349
strongly named assemblies, 687–688 panels, 323–331
Web services (Web.config file), 883 PictureBox, 331–333
connected models populating, 550
ADO.NET, 502–504, 506–518 ProgressBar, 355–358
selecting, 532–533 resources, 369–376
connections StatusStrip, 355–358
classes (ADO.NET), 506–510 TextBox, 333–335
pooling, 510 Timer, 355–358
strings, 507–509 TreeView, 349–355
958 Index

controls (continued) PrintDocument class, 454–456


interfaces, 605–606 System.Object class, 160–167
lists, 549–550, 759, 766–768 CustomValidator control, 787–788
login, 760
ObjectDataSource, 781 D
positioning, 274–275
properties, 272–274 DashStyle property, 394
servers (View state), 744–746 data access models (ADO.NET), 502–506
sizing, 274–275 DataAdapter class, 504–506, 525–529
SqlDataSource, 777–780 databases
state management, 797–798 SQL, 507. See also SQL
TableLayoutPanel, 329–330 synchronizing, 526–529
ToolTip, 309–310 updating, 525–529, 562–563
validating, 760, 784–789 data binding. See also binding
ValidationSummary, 787–789 applications, 555–563
values, 550–551 DataGridView class, 563–584
Web overview of, 546–554
customizing ASP.NET, 793–801 DataBindings.Add method, 547
selecting ASP.NET, 801–802 data caching, 845–848
Web Forms, 758–772 data display controls, 759
Web pages, 802 DataError event, 575
XmlDataSource, 781–784 data error handling, 575–576
conventions, naming, 42–43 DataGridView class
converting data binding, 563–584
Char values, 206 delegates, 924–936
narrowing, 46 events, 571–576, 924–936
numeric types, 49–50 master-detail, 576–579
resource files, 373 parameters, 934–936
widening, 46 properties, 564–571
CookieContainer property, 893–894 virtual mode, 579–584
copying images, 418 DataList controls, 768–772
Copy method, 73 data providers
CreateGraphics method, 381 factories, 500–502
creational patterns, 145 .NET, 499–502
cross-page posting, 758 DataReader class, 503–504, 516–518
CryptoStream class, 252–255 binding, 772–774
CTS (Common Type System), 11–13 DataRow class, 521–523
cultures DataSet class, 504–506, 518–519
assembly settings, 17 binding, 556–557, 774–776
dates, 230 defining relationships, 530–532
strings, 213–215 loading data into, 523–525
CurrentThread property, 609 XML, 534–540
custom Web controls, 796–797 DataSource property controls, 776–784
custom date formats, 228–229 data sources
<customErrors> configuration file section, 821–822 controls, 551, 760
customizing customizing, 559–560
ADO.NET Web controls, 793–801 items, 560–561
class exceptions, 155–157 for reports, 450
configuration files, 824–826 DataTable.Columns property, 519–521
controls, 358–363 DataTables class, 519–523, 555–557
data sources, 559–560 data types
events, 118–119 C# primitive, 45–46
HTTP Web Services, 906–915
handlers, 862–864 dates, 230
modules, 858 DateTime class, formatting, 223–231
interfaces, 127–129 DateTimeFormatInfo class, 230
list boxes, 339–341 DbCommand class, 511
Index 959

DbProviderFactories class, 500 docking controls, 275–276


deactivating forms, 294 documenting XML tags, 44
deadlock, avoiding, 628–630 Document Object Model. See DOM
decimal type, 47 documents
declarative security, 711 fragments, 474
permission attributes, 721–722 printing, 441. See also printing
decoding, 219. See also encoding XML, 463. See also XML
<Definitions>, 896 DoDragDrop method, 363
delayed signing, 688–689 do loops, 56
Delegate class, 597–599 domains
delegates AppDomain class, 640–643
anonymous methods, 117–118 applications, 638–643
asynchronous invocation, 596 assemblies, 639–640
C# classes, 112–123 DOM (Document Object Model), 485
DataGridView class, 924–936 double type, 49
events downloading files, 816–817
defining, 119–120 dragging and dropping controls, 363–368
handling, 115–123 DrawImage class, 410
MethodInvoker, 606–607 drawing, 380. See also GDI+
multicasting, 114 multi-line text, 434–435
deleting rectangles, 417–419
nodes, 351, 491–493 text strings, 433–439
subdirectories, 258 DrawRectangle method, 382
deploying duration of caching, 842
applications, 722–727
CAO applications, 669 E
custom HTTP handlers, 865
custom HTTP modules, 859 ECMA International, 6
GAC, 723–724 embedding comments, 43–45
private assemblies, 724–725 Enabled property, 306
XCOPY, 722–723 EnableSession property, 882
derived classes, 87 encapsulation, monitoring, 624–625
DES algorithm, 253 encoding
devices, context, 380 strings, 219–220
2-D graphics, 388–393 Unicode, 204–208
diagnostic directives, 60–61 encryption, 252–255. See also security
dialog boxes, forms, 298–301 EndCap property, 395
Dijkstra, Edsger, 633 EndInvoke method, 599
directives EndPrint event, 447
@Assembly, 748 enumerated types
diagnostic, 60–61 bit flags and, 69
@Import, 748 C#, 66–69
@Page, 746–748 enumeration
preprocessing C#, 59–61 collections, 172
@Register, 748–749 iterators, 173
WebService, 876 System.Enum methods, 68
directories XML, 476
members, 257 environments
System.IO namespace, 255–263 IDE, 31
Directory class, 256–260 .NET Framework. See .NET Framework
DirectoryInfo class, 256–260 equality (==) operator, 65
DISconnected models, selecting, 532–533 Equals( ) method, 163–165
disconnected models (ADO.NET), 504–506, 518–533 errors. See also troubleshooting
discovery data error handling, 575–576
UDDI. See UDDI updating, 528
Web services, 871–874 escape characters, 62
Dispose method, 195–198, 382 escape sequences, 237
960 Index

events familles of fonts, 428


asynchronous calls, 892–893 FCL (Framework Class Library), 6, 18–22
BeginPrint, 446–447 factory creational patterns, 145
binary serialization, 190–191 namespaces, 20–22
built-in, 115–116 fields
buttons, 271, 324 C# classes, 91–93
controls, 279–284 initializing, 135
customizing, 118–119 static read-only, 92
DataError, 575 File class, 261–263
DataGridView class, 571–576, 924–936 FileInfo class, 261–263
declaring, 118 files. See also directories
delegates, 119–120 application configuration, 23
dragging and dropping, 365–368 .asmx, 877
EndPrint, 447 code-behind models, 856–857
handling, 115–123 compressing, 607–608
keyboard, 282–284 configuration
mouse, 279–282 ASP.NET, 817–826
overriding, 314–315 customizing, 824–826
Page_Load, 762–763 managing assemblies, 726
pages, 757–758 message servers, 660–662
Paint (GDI+), 384–388 downloading, 816–817
PrintDocument class, 440, 446–448 global.asax, 854–856
PrintPage, 448–449 listing files and directories, 259–260
propertyChanged, 551 manifests, 14
QueryPageSettingsEvent, 447–448 multi-file assemblies, 15
servers, 753 multiple, 34
Everything permission, 694 opening files, 261–263
evidence, 692, 698–701 resources, 369–373
exceptions schemas, 534–536
classes, 155–157 System.IO namespace, 255–263
handling, 149–160 text
guidelines, 159–160 creating resource strings from, 380–371
SEH, 149 reading from, 250–251
writing code to, 151–153 writing to, 249–250
SOAP, 904–905 web.config, 818–823
System.Exception class, 150–151 authorization, 830–831
unhandled, 157–159 configuring Web services, 883
ExecuteReader method, 512, 772 forms authentication, 828–830
Executexxx methods, 512 XML, 463. See also XML
executing FileStream class, 245–247
SQL commands, 512–513 FileSystemInfo class, 256
stored procedures, 513–515 finalization, 194–195
Execution permission, 694 Finalize method, 196–198
Expect100Continue property, 916 finally block, 153
Extended Markup Language. See XML F1 keys, invoking help, 311–312
Extended Style Language Transformation. See XSLT flags, bit and enumerated types, 69
extending FlowLayoutPanel control, 328–329
controls, 358–359 Focus and tab order, 277
Web services, 880–883 Font class, 430–433
extracting substrings, 64–65 FontFamily class, 428–429
fonts
F formatting, 430–431
GDI+, 428–433
factories metrics, 431–433
creational patterns, 145 foreach loops, 57
data providers, 500–502 foreground threads, 593–594
multiple, 148–149 foreign keys, 532
Index 961

for loops, 56–57 Timer, 355–358


formatters, remoting, 646 TreeView, 349–355
formatting creating, 270
BinaryFormatter object, 188 deactivating, 294
buttons, 323–324 dialog boxes, 298–301
cells, 573–574 formatting, 286–290, 293
checkboxes, 325 Help, 308–313
content pages, 791–792 inheritance, 313–315
control buttons, 270 interaction, 294–298
DateTime values, 223–231 life cycles, 292–294
files (XML), 463 locations, 290–291
fonts, 430–431 MDI, 301–306
forms, 270, 293 menus, 306–308
appearance, 286–290 messages, 298–301
localized, 373–376 opacity, 287
locations, 290–291 owned, 298–299
MDI menus, 303–305 owner, 298–299
items (ListView), 345–346 sizing, 290–291
master pages, 790–791 transparency, 287–290
numeric values, 223–231 viewing, 292, 293
objects, 145–149 FormsAuthentication class, 831–833
colors, 401 fragments, 474, 844–845
FontFamily, 429 Framework Class Library. See FCL
pages for printing, 445–446 Framework Configuration tool, 29–30
regular expressions, 237–238 frozen columns, 570
schemas, 534–536 FullTrust permission, 694
String.Format method, 223 functions (XPath), 488–489
strings, 209–211, 435 FxCop tool, 683–686
tab stops, 436–437
text, 333–335 G
Web controls, 760–761
XML, 917–918 GAC (Global Assembly Cache), 16, 689–670
Form class, 285–305 deploying, 723–724
forms Garbage Collection (.NET), 192–198
activating, 294 GDI+, 379-423
ASP.NET. See ASP.NET fonts, 428–433
authentication, 827–835 Graphics class, 380–384
closing, 294 Graphics object, 388–407
code, 296–298 images, 407–422
controls, 319–323 Paint events, 384–388
buttons, 323–331 printing, 439–456
CheckedListBox, 341–342 text, 433–439
ComboBox, 341–342 Windows, 421–422
customizing, 358–363 GDI (Graphical Device Interface), 379
dragging and dropping, 363–368 generics
group boxes, 323–331 C# classes, 131–133
iterating, 278 classes, 132
labels, 323–331 collection class example, 184-187
ListBox, 335–341 GetHashCode method, overriding, 164–165
ListView, 342–349 GIF (Graphics Interchange Format), 408
panels, 323–331 global.asax files, 854–856
PictureBox, 331–333 code-behind models, 856–857
populating, 550 Global Assembly Cache. See GAC
ProgressBar, 355–358 gradients, creating, 400
resources, 369–376 granting permissions, 709–710
StatusStrip, 355–358 Graphical Device Interface. See GDI
TextBox, 333–335 Graphical User Interface. See GUI
962 Index

graphics. See also GDI+; images inner content, 767


2-D, 388–393 tags, 752
GDI+, 379. See also GDI+ XML style sheets, 470–471
Graphics class HttpApplication class, 853–857
GDI+, 380–384 HTTP (Hypertext Transfer Protocol), 734, 870
methods, 383–384 ASP.NET, 808–817
Graphics.DrawString method, 433 handlers, 862–866
Graphics Interchange Format (GIF), 408 modules, 857–862
Graphics object (GDI+), 388–407 pipelines, 851–866
GraphicsPath class, 390–391 HttpRequest class objects, 755–756, 808–812
GroupBox class, 327 HttpResponse class
guidelines object, 755–756, 813–817
code design, 682–686 properties, 814–816
connection pooling, 510 Hypertext Markup Language. See HTML
handling exceptions, 159–160 Hypertext Transfer Protocol. See HTTP
GUI (Graphical User Interface), 267
I
H
ICollection interfaces, 169–170
handlers (HTTP), 862–866 IComparable interfaces, sorting, 175–176
handling IComparer interfaces, sorting, 175–176
data errors, 575–576 icons, specifying, 345–346
events, 115–123 IDbCommand interface, 511–515
buttons, 324 IDbConnection interface, 506
on servers, 753 IDE (Integrated Development Environment), 31, 267
exceptions, 149–160 identifiers, classes, 86–87
guidelines, 159–160 identifying updating, 561–562
SEH, 149 identity permissions, 697–698
SOAP, 904–905 IDictionaryEnumerator interfaces, 182
writing code to, 151–153 IDictionary interfaces, 181
keyboard events, 282–284 IEnumerable interfaces, 170–172
mouse events, 279–282 IEnumerator interfaces, 170–172
Hashtable if-else statements, 53–54
creating, 182–183 IHashCodeProvider interfaces, 170
interfaces, 181-184 Ildasm.exe, 25–28
keys, 183, 210 IL (Intermediate Language), 10
serialization, 189 hiding, 27–28
values, 183 viewing, 26
HatchBrush class, 396–397 IList interfaces, 179–180
headers images
columns, 344–345 buffering, 413–414
SOAP, 900–903 copying, 418
heaps, 11, 74 creating, 414–421
height GDI+, 407–422
fonts, 431–433 loading, 408–411
rows in DataGridView, 569–570 menus, 415–417
Help, adding forms, 308–313 mirroring, 412–413
HelpProvider component, 312–313 modifying, 411–414, 420–421
hiding IL, 27–28 Ngen, 17
high surrogates, 205 PictureBox class, 331–333
hit testing shapes, 391–393 rectangles, 417–419
hosts, assemblies, 657–658, 665 returning, 907–908
hot keys, 306 rotating, 412–413
HSB (hue/saturation/brightness), 400 saving, 408–411
HSL (hue/saturation/luminosity), 400 servers, 664–669
HTML (Hypertext Markup Language), 734 ToolTip controls, 310
code-behind models (ASP.NET), 749–754 @Import directive, 748
Index 963

indexers (C# classes), 95–97 Internet client-server interaction, 734–758


IndexOf method, 64 Internet permission, 694
infrastructure (CLI), 6 interning strings, 209–211
inheritance Interop, 8
classes int type, 48
C#, 87 Invalidate method, 381, 384–385
defining, 82 Invoke method, 598
constructors, 107 I/O (Input/Output), asynchronous calls, 607–608
forms, 313–315 isolation, code, 638
methods, 100–101 IsPostBack property, 756
structures, 138 items
Initialization Vector (IV), 253 data caches
initializers, 108–110 adding to, 845–846
initializing retrieving from, 846
fields, 135 data sources, 560–561
interfaces, 762–763 detecting, 347–348
virtual DataGridView, 581 iterating, 347
inline code models (ASP.NET), 741–749 ListView, 345–346
inner HTML content, 767 selecting, 768
in-process session state, 840 sorting, 348–349
Input/Output. See I/O iterating
inserting. See adding controls on forms, 278
instances items, 347
class references, 92 nodes, 352
constructors, 106–110 iterators, 172–174
Integrated Development Environment. See IDE IV (Initialization Vector), 253
integrated help systems, configuring, 308–313
interaction, forms, 294–298 J
interfaces
abstract classes, 130 JavaScript, BMI calculators, 736–739
assemblies (SAO), 662–664 JIT (Just-in-Time), 10
C# classes, 87, 126–130 JPEG (Joint Photographics Experts Group), 408
controls, 605–606 Just-in-Time. See JIT
customizing, 127–129
GDI, 379. See also GDI K
GUI, 267
Hashtable, 181–184 Kernel Profile, 8
ICollection, 169–170 keyboard events, handling, 282–284
IComparable, 175–176 keys
IComparer, 175–176 F1 (help), 311–312
IDbCommand, 511–515 foreign, 532
IDbConnection, 506 hashtables, 183, 210
IDictionary, 181 hot, 306
IDictionaryEnumerator, 182 primary, 521
IEnumerable, 170–172 private, 253, 688
IEnumerator, 170–172 public, 17, 253, 687
IHashCodeProvider, 170
IList, 179–180 L
initializing, 762–763
MDI, 301–306 Label class, 330–331
members, 129–130 leasing, 671–675
methods, 130 objects, 650
.NET collections, 167–187 libraries
OLE DB, 499 code, 34
simple controls, 761–766 FCL, 6, 18–22
Intermediate Language. See IL forms, 313–314
964 Index

life cycles marshaling by value (MBV), 648–649


forms, 292–294 master-detail views, configuring, 576–579
object management, 192–198 master pages (ASP.NET), 789–793
lifetimes of leases, 674–675 Matches() method, 235–237
LinearGradientBrush class, 397–400 matching characters, 237
lines Match() method, 235–237
DrawLine method, 382 MBR (marshaling by reference), 649–650
multi-line text, 434–435 MBV (marshaling by value), 648–649
linking, 546. See also binding MDI (Multiple Document Interface), 301–306
ASP.NET, 740 memory
ListBox class, 335–341 allocating, 74–75, 77
listing intern pools, 209
files, 259–260 releasing, 77
font families, 429 MemoryStream class, 247–248
Hashtable keys and values, 183 MenuItem class properties, 306
lists menus
binding, 547–548 buttons (ASP.NET), 763–764
controls, 549–550, 759, 766–768 context, 307–308
navigating, 553–554 forms, 306–308
ListView class, 342–349 images, 415–417
literals, strings, 61–63 MDI forms, 303–305
loading <Message>, 896
data MessageBox class, 299–300
ADO.NET, 505 MessageName property, 882
into DataSet class, 523–525 messages
images, 408–411 HTTP response, 813–814
LocalIntranet permission, 694 request, 809–810
localization metadata, 10, 14
characters, 206–207 MethodInvoker delegate, 606–607
forms, 373–376 methods
<location> configuration file section, 820 accessor, 93
location anonymous, 117–118
Control property, 272 asynchronous, 598-607
form location, 290-291 C# classes, 97–106
locking ExecuteReader, 512, 772
deadlock, 628–630 inheritance, 100–101
objects, 624 interfaces, 130
login modifiers, 98–103
controls, 760 multicasting, 114
FormsAuthentication class, 831–833 Parse, TryParse, 49–50
long type, 48 ShowHelp, 312
loops (C#), 55–59 structures, 136–137
do, 56 methods, .NET Framework
for, 56–57 ControlBindingsCollection, 547
foreach, 57 System.Array, 71-73
transferring control within, 58–59 System.Char, 206-208
while, 55 System.Enum, 68
low surrogates, 205 System.Drawing.Graphics, 388–391, 433
System.IO.DirectoryInfo, FileSystem-
M Info, 256-260
System.Object, 160-167,195-198
Main() method, 41 System.RegularExpressions.Regex, 233-237
maintenance System.String, 212-219,223
CookieContainer property, 893–894 System.Text.StringBuilder, 221-222
state (ASP.NET), 835–841 System.Web.HttpResponse, 816
manifests, 14, 686 System.Windows.Forms.Control, 381
marshaling by reference (MBR), 649–650 System.Xml.XmlWriter, 482-484
Index 965

System.Xml.Xsl.XslTransform, 471 Native Image Generator. See Ngen


metrics, sizing fonts, 431–433 navigating
Microsoft CIL standards, 7 lists, 553–554
Microsoft Intermediate Language. See MSIL XPath, 485
mirroring images, 412–413 .NET
MMC (Microsoft Management Console) ADO.NET. See ADO.NET
Framework Configuration tool, 29 ASP.NET. See ASP.NET
modeless forms, life cycles of, 292 assemblies, 13–18
modifiers built-in security permissions, 695–697
abstract, 86, 102–103 compiling code, 10–11
access, 85–86, 89 CTS, 11–13
fields, 91 data providers, 499–502
methods, 98–103 FCL, 6, 18–22
override, 100–101 features specific to, 920–923
sealed, 86, 102–103 Framework
stack walk, 719–720 Configuration tool, 704–706
versioning, 101–102 overview of, 6–9
virtual, 100–101 SDK, 22–30
modules (HTTP), 857–862 standards, 7–9
Monitor class, 623–625 tools, 23–30
monitoring encapsulation, 624–625 updating, 23
mouse events, handling, 279–282 Garbage Collection, 192–198
moving controls, 363–368 generics, 131–133
MSIL (Microsoft Intermediate Language), 10 OLE DB providers in, 498–499
multicasting delegates, 114 operator overloading, 125
multi-color gradients, creating, 400 security policies, 701–704
multi-file assemblies, 15 Visual Studio.NET. See Visual Studio.NET
multi-line text, drawing, 434–435 Ngen (Native Image Generator), 17
Multiple Document Interface. See MDI nodes
multiple files, compiling, 34 adding, deleting, iterating TreeView, 351-352
multithreading, 592–595 trees
applying, 613–617 adding on, 491–493
user interface controls, 605–606 deleting on, 491–493
Mutex class, 625–626 XmlNodeReader class, 477–479
Nothing permission, 694
N NumberFormatInfo class, 230
numeric types, primitives, 49–50
Nagle algorithm, 916–917 numeric values, formatting, 223–231
namespaces
FCL, 20–22 O
System.Collections class, 177, 184–187
System.Drawing.Drawing2D, 390 obfuscation and Ildasm.exe, 27–28
System.IO ObjectDataSource control, 781
directories, 255–263 objects
files, 255–263 ArrayList, 69
streams, 244–255 BinaryFormatter, 188
System.Web.UI.WebControls, 759 C#
using statements, 40 creating, 145–149
naming exception handling, 149–160
assemblies, 16 implementing System.Object methods, 160–
channels, 647–648 167
conventions, 42–43 life cycle management, 192–196
identifiers, 86–87 .NET collection classes and interfaces, 167–187
paths, 260 serialization, 187–192
permission sets, 694–695 CAO, 649–650, 664–669
strongly named assemblies, 686–691 cloning, 165
narrowing conversion, 46 CommandBuilder, 525–526
966 Index

objects (continued) parsing numbers, 243


commands, 511–515 partial classes, code-behind with, 753–754
finalization, 194–195 partial pages, caching, 844–845
locking, 624 Path class, 256–260
polling, 600–603 paths
SAO, 649–652, 654–664 naming, 260
SOAP, 643 synchronization, 620
synchronization, 600–603 patterns
WKO, 649 creational, 145
OLE DB providers in .NET, 498–499 custom date formatting, 228–229
one-way data binding, 550–551 regular expressions, 237–238
opacity of forms, 287 Pen class, 393–395
opening files, 261–263 performance
OpenType fonts, 428. See also fonts AppDomain, 639
operators View state, 745
C#, 50 Web services, 916–918
arithmetic, 50–51 permissions, 693–698
conditional, 51–52 assembly requests, 711–714
equality (==), 65 attributes, 695, 698
relational, 51–52 applying, 712–713
overloading, 123–126 declarative security, 721–722
XPath, 487–489 testing, 713–714
optimistic concurrency, 526 built-in security, 695–697
ordering tabs, 277 creating, 708
out-of-process sessions, 840–841 granting, 709–710
output caching, pages, 842–845 identity, 697–698
overloading PictureBox class, 331–333
operators, 123–126 pipelines (HTTP), 851–866
strings, 65–66 PNG (Portable Network Graphics), 408
override modifiers, 100–101 policies
overriding binding, 29
events, 314–315 default configuration, 705–706
GetHashCode method, 164–165 security, 701–704
owned and owner forms, 298–299 polling objects, 600–603
polymorphism, 98
P pooling
connections, 510
Page class (ASP.NET), 754–758 threads, 596, 617–618
@Page directives, 746–748 populating controls in forms, 550
Page_Load event, 762–763 positioning
pages. See also Web pages controls, 274–275
events, 757–758 tab stops, 436–437
output caching, 842–845 postbacks, 756
PageSettings class, 445–446 posting, cross-page, 758
pages to print, selecting, 443–444 precompiling. See also compiling
Paint events (GDI+), 384–388 assemblies, 17–18
Panel class, 328–330 preprocessing directives (C#), 59–61
panels previewing printing, 449–454
ASP.NET, 764–765 primary keys, 521
images, 420–421 primary threads, 592
parameters primitives (C#), 45–50
commands without stored procedures, 515 bool type, 47
DataGridView class, 934–936 byte type, 48
passing, 103–106 char type, 48
passing to threads, 610–611 decimal type, 47
types, 184 double type, 49
Parse method, 49–50 int type, 48
Index 967

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

remoting applications (continued) sealed modifiers, 86, 102–103


SAO, 650–652, 654–664 searching. See also queries
types of, 648–650 strings, 216–217
reports substrings, 64–65
code for, 451–454 XML with XPath, 484–493
data sources for, 450 Secure Sockets Layer. See SSL
previewing printing, 449–454 security, 692–722
requests AppDomain, 639
Amazon Web services, 909–910 ASP.NET, 827–835
HTTP, 808–817, 851–853 configuring, 706–711
permissions, 711–714 CryptoStream class, 252–255
SOAP, 899–900 declarative, 711
UDDI discovery, 873–874 evidence, 698–701
Web (ASP.NET), 848–851 .NET Framework Configuration tool, 704–706
resolution, configuring printers, 444 permissions, 693–698
ResourceManager class, 371–372 attributes, 721–722
resources requesting for assemblies, 711–714
files, 369–373 policies, 701–704
forms, 369–376 programmatic, 715–722
localization, 375 SOAP, 905–906
scavenging, 846 stack walk, 715–720
ResourceWriter class, 371 viewing, 29
responses SEH (structured exception handling), 149
HTTP, 808–817 selecting
SOAP, 900 ADO.NET access models, 532–533
Web (ASP.NET), 848–851 ASP.NET Web controls, 801–802
ResXResourceReader class, 372–373 list boxes, 338–339
ResXResourceWriter class, 372 pages to print, 443–444
retrieving items from data caches, 846 printers, 443
reversing text, 242 semantics values, 134
rewriting URLs, 859 Semaphore class, 627–628
RGB (red/green/blue), 400 send and forget, 590
role-based authorization, adding, 833–834 sequences, escape, 237
rolling back updates, 527 serialization
rotating images, 412–413 attributes, 465–466
rows binary, 188–192
DataGridView class, 566–568 events, 190–191
DataRow class, 521–523 objects, 187–192
height, 569–570 XML, 462–466
recognizing, 574–575 server-activated objects. See SAO
updating, 528 server-activated single calls, 651–652
RPC (Remote Procedure Call), 870 server-activated singleton, 651
runtime, localization resources, 375 servers
assemblies, 656–657, 665–666
S clients, 734–758
configuration files, 660–662
safety threads, 620 event handling, 753
SAO (server-activated objects), 649–652 remoting, 643–671
satellite assemblies, 369, 374 UDDI, 872–874
Visual Studio.NET, 375–376 <Service>, 898
sbyte type, 48 sessions
schemas CookieContainer property, 893–894
creating, 534–536 global.asax files, 854–856
XML, 480–481 state, 838–841
XSD, 466–468 Web services, 882
scraping Web pages, 850 <sessionState> configuration file section, 822–823
SDK (Software Development Kit), 22–30 shallow copies, creating, 166–167
Index 969

shapes sessions, 838–841


drawing, 380. See also GDI+ threads, 594–595
DrawLine method, 382 View, 744–746
DrawRectangle method, 382 statements
hit testing, 391–393 C#
shared assemblies, 16–17 control flow, 52–53
short circuit evaluation, 52 if-else, 53–54
Shortcut property, 306 switch, 54–55
short type, 48 using, 40
ShowDialog class, 300–301 static constructors, 111–112
ShowHelp method, 312 static method modifiers, 99–100
signatures, methods, 97 static read-only fields, 92
signing, 17 StatusStrip class, 355–358
simple controls, 759, 761–766 stops, tags, 436–437
Simple Object Access Protocol. See SOAP stored procedures, executing, 513–515
single type, 49 Stream class, 244–245
sink chains, 644 streams
site evidence, 692 members, 245
sizing System.IO namespace, 244–255
arrays, 70 StringBuilder class, 220–223
controls, 274–275 String.Compare method, 213–215
fonts, 431–433 String.CompareOrdinal method, 215
forms, 290–291 StringFormat class, 435
rows, 569–570 String.Format method, 223
SOAP (Simple Object Access Protocol), 643, 870–871, StringReader class, 251–252
898–906 strings
Software Development Kit. See SDK aligning, 438–439
sorting C#, 61–66
columns, 570 comparing, 212–213
IComparable interfaces, 175–176 concatenation, 63–64
IComparer interfaces, 175–176 connections, 507–509
items, 348–349 creating, 209–211
Sort method, 73 encoding, 219–220
source control responsibilities, 365–366 formatting, 435
special characters, carriage returns, 334–335 interning, 209–211
specifications of CLS, 12 literals, 61–63
sponsorship, 675–677 methods, 211–212
SqlDataSource control, 777–780 modifying, 63–66
SQL (Structured Query Language) overloading, 65–66
commands, 512–513 resources, 370–371
connection strings, 507–509 searching, 216–217
stored procedures, 513–515 text, 433–439
SSL (Secure Sockets Layer), 905 transformations, 212, 217–219
Stack class, 177–179 trimming, 438–439
stacks wrapping, 438–439
runtime, 74 StringWriter class, 251–252
walk, 711, 715–720 strongly named assemblies, 14, 686–691
standards structured exception handling. See SEH
CLI, 7–9 Structured Query Language. See SQL
ECMA International, 6 structures
.NET Framework, 7–9 C# classes, 134–137
StartCap property, 395 comparing, 137–139
state, 734 defining, 134–135
applications, 837–838 inheritance, 138
ASP.NET, 835–841 methods, 136–137
CookieContainer property, 893–894 properties, 136–137
management of controls, 797–798 style sheets (XSLT), 468–471
970 Index

subdirectories. See also directories formatting DateTime/numeric values, 223–231


creating, 258 modifying strings, 216–220
deleting, 258 regular expressions, 232–243
substrings. See also strings StringBuilder class, 220–223
extracting, 64–65 System.IO namespace, 244–263
searching, 64–65 System.String class, 209–212
surrogates Unicode, 204–209
pairs, 205 files, 380–371
strings, 217 formatting, 333–335
switch statements, 54–55 GDI+, 433–439
synchronization patterns, 240–242
databases, 526–529 printing, 441. See also printing
objects, 600–603 reading, 249–251
threads, 620–630 reversing, 242
Synchronization attribute, 622–623 strings, 433–439
synchronous calls to Web service methods, 890–891 writing, 249–251
System.Array class, 71 TextBox class, 333–335
System.Collections class, 168 Thread class, 592
generic namespaces, 184–187 Thread Local Storage. See TLS
namespaces, 177 threads, 590. See also asynchronous programming
System.Drawing.Drawing2D namespace, 390 aborting, 611–613
System.Enum methods, 68 applying, 609–619
System.Exception class, 150–151 creating, 606–607, 610–611
System.IO namespace CurrentThread property, 609
directories, 255–263 multithreading, 592–595
files, 255–263 overview of, 592–595
streams, 244–255 parameters, 610–611
System.Object class, customizing, 160–167 pooling, 596, 617–618
System.String class, 209–215 priority, 592
System.Timers.Timer class, 618–619 safety, 620
<system.web> configuration file section, 820–823 synchronization, 620–630
System.Web.Services.WebService class, timers, 618–619
879– 880 throwing exceptions, 149
System.Web.UI.WebControls namespace, 759 TIFF (Tag Image File Format), 408
System.Windows.Forms.Timer class, 619 time, formatting, 227–231
Timer class, 355–358
T timer threads, 618–619
time slices, 592
TableLayoutPanel control, 329–330 time-to-live (TTL), 671
tables TLS (Thread Local Storage), 592
keys, 210 tokens, public keys, 17, 687
master-detail, 577 tools
relationships FxCop, 683–686
creating, 521 .NET Framework, 23–25
defining, 530–532 Framework Configuration tool, 29–30, 704–706
tabs, ordering, 277 Ildasm.exe, 25–28
Tag Image File Format (TIFF), 408 wincv.exe, 28
tags SDK, 22–30
HTML, 752 wsdl.exe utility, 885–890
XML documentation, 44 ToolTip controls, 309–310
target control responsibilities, 366–368 ToString( ) method, 161–162
TCP/IP (Transmission Control Protocol/Internet <trace> configuration file section, 822
Protocol), 870 transactions, rolling back updates, 527
text transferring control within loops, 58–59
boxes, 766 transforming strings, 212, 217–219
C# Transmission Control Protocol/Internet Protocol. See
comparing strings, 212–215 TCP/IP
Index 971

transparency of forms, 287–290 V


transparent proxies, 645
transport sinks, 644 validation
TreeNode class, 349–352 XML, 480–481
tree nodes XSD, 467–468
adding on, 491–493 Validation controls, 784-789
deleting on, 491–493 CustomValidator control, 787-788
TreeView class, 349–355 ValidationSummary control, 788–789
trimming strings, 438–439 Value types
TrueType fonts, 428. See also fonts support for CLR, 73–77
try block, 151 types, 11
TryParse method, 49–50 variables
TTL (time-to-live), 671 assigning, 77
two-way data binding, 550–551 Char, 205–206
typefaces. See fonts VaryByCustom attribute, 843–844
types VaryByHeader attribute, 843–844
CLS, 12 VaryByParam attribute, 843–844
CTS, 11–13 verbatim strings, 62
enumeration (C#), 66–69 versioning
generics, 131–133 assemblies, 16, 690–691, 727
numeric, 49–50 modifiers, 101–102
references, 11 serialized objects, 191–192
classes, 138 View state, 744–746
support for CLR, 73–77 data caching as substitute for, 846–847
registering, 652–654 virtual mode, DataGridView class, 579–584
<Types>, 896 virtual modifiers, 100–101
Visual Studio.NET
U inherited forms
creating, 315
UDDI (Universal Description Discovery and Integration), MDI menus, 305–306
872 proxy classes, 894
unhandled exceptions, 157–159. See also handling resource localization, 374
exceptions satellite assembles, 375–376
Unicode standards, 204–208 Web services, 878–880
Uniform Resource Identifier. See URI
Uniform Resource Locator. See URL W
Universal Description Discovery and Integration. See
UDDI walking the call stack, 693
updating Web clients, creating, 848–851
application state data, 837 web.config file, 818–823
controls authorization, 830–831
data sources, 551 forms authentication, 828–830
values, 550–551 Web services, 883
databases, 525–529, 562–563 Web controls, customizing, 793–801
errors, 528 Web Form controls, 758–772
identifying, 561–562 WebMethod attribute, 876–877, 881–883
.NET Framework, 23 Web pages
URI (Uniform Resource Identifier), 734 compiling, 740
URL (Uniform Resource Locator), 859 controls, 802
user controls scraping, 850
applying, 361–362 WebRequest class, 848–851
building, 359–361 WebResponse class, 848–851
at design time, 362–363 WebService attribute, 881
user interfaces. See also interfaces WebService directive, 876
multithreading, 605–606 Web services
using statements, namespaces, 40 access, 877
UTF-8, UTF-16, 219 Amazon, 909–912
972 Index

Web services (continued) XmlDataSource control, 781–784


applying, 871–874 XmlDocument class (XPath), 489
building, 875–894 XML (Extended Markup Language)
clients (Windows Forms), 913–915 ADO.NET, 533–540
complex data types, 906–915 applying, 462–471
extending, 880–883 attributes, 465–466
overview of, 870–874 DataSet class, 534–540
performance, 916–918 documentation tags, 44
sessions, 882 enumeration, 476
SOAP, 898–906 formatting, 917–918
testing, 877–878 overview of, 461–462
Web.config file, 883 reading, 472–482, 537–540
Web Services Description Language. See WSDL resource files, 369–373
Web Services Security. See WS-Security schemas, 480–481
well known objects. See WKO searching with XPath, 484–493
while loop, 55 serialization, 462–466
widening conversion, 46 Web services, 870. See also Web services
wincv.exe, 28 writing, 482–484
Windows XmlNodeReader class, 477–479
application unhandled exceptions, 158–159 XmlReader class, 472–477
Bit Block Transfer, 421–422 XmlReaderSettings class, 489–480
Forms, 268–271. See also forms XML Schema Definition. See XSD
GDI+, 421–422 XmlSerializer class, 463–465
Windows Forms XmlWriter class, 482–484
Web services clients, 913–915 XPath
Windows.Forms.Form class, 271–284 functions, 488–489
WKO (well known objects), 649 navigating, 485
wrappers, HelpProvider, 312–313 operators, 487–489
writing queries, 486–489
resource files, 372 searching XML with, 484–493
XML, 482–484 XmlDataDocument class, 491–493
wsdl.exe utility, 885–890 XmlDocument class, 489
WSDL (Web Services Description Language), 871, 895– XPathDocument class, 490
898 XSD (XML Schema Definition), 466–468
WS-Security (Web Services Security), 905 XSLT (Extended Style Language Transformation), 468–
471
X XslTransform class, 469–471

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?

They have. Introducing


InformIT Online Books
powered by Safari.

■ Specific answers to specific questions.


InformIT Online Books’ powerful search engine gives you

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.

■ Cut, paste and annotate.


Paste code to save time and eliminate typographical
errors. Make notes on the material you find useful and
choose whether or not to share them with your work
group.
Online Books
■ Customized for your enterprise.
Customize a library for you, your department or your entire
organization. You only pay for what you need.

Get your first 14 days FREE!


For a limited time, InformIT Online Books is offering
its members a 10 book subscription risk-free for
14 days. Visit http://www.informit.com/online-
books for details.
Keep Up to Date with

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:

What’s new at PHPTR? We don’t just publish books for the


professional community, we’re a part of it. Check out our convention
schedule, keep up with your favorite authors, and get the latest reviews
and press releases on topics of interest to you.

Special interest areas offering our latest books, book series,


features of the month, related links, and other useful information to help
you get the job done.

User Groups Prentice Hall Professional Technical Reference’s User


Group Program helps volunteer, not-for-profit user groups provide their
members with training and information about cutting-edge technology.

Companion Websites Our Companion Websites provide


valuable solutions beyond the book. Here you can download the source
code, get updates and corrections, chat with other users and the author
about the book, or discover links to other websites on this topic.

Need to find a bookstore? Chances are, there’s a bookseller


near you that carries a broad selection of PTR titles. Locate a Magnet
bookstore near you at www.phptr.com.

Subscribe today! Join PHPTR’s monthly email newsletter!


Want to be kept up-to-date on your area of interest? Choose a targeted
category on our website, and we’ll keep you informed of the latest PHPTR
products, author events, reviews and conferences in your interest area.

Visit our mailroom to subscribe today! http://www.phptr.com/mail_lists


www.informit.com

YOUR GUIDE TO IT REFERENCE

Articles

Keep your edge with thousands of free articles, in-


depth features, interviews, and IT reference recommen-
dations – all written by experts you know and trust.

Online Books

Answers in an instant from InformIT Online Book’s 600+


fully searchable on line books. For a limited time, you can
get your first 14 days free.

Catalog

Review online sample chapters, author biographies


and customer rankings and choose exactly the right book
from a selection of over 5,000 titles.

You might also like