advanced c sharp programming

579

Click here to load reader

Upload: mohammed-ali

Post on 28-May-2015

874 views

Category:

Technology


44 download

DESCRIPTION

Advanced c sharp programming

TRANSCRIPT

Page 1: Advanced c sharp programming
Page 2: Advanced c sharp programming

AdvancedC# Programming

Page 3: Advanced c sharp programming

About the AuthorPaul Kimmel founded Software Conceptions, Inc., in Okemos, Michigan in 1990. Paulspecializes in object-oriented architecture and software design. He is the frameworks columnistfor Windows Developer Magazine, a bimonthly columnist for codeguru.com, and a contributingcolumnist to InformIT. Paul has written several books on object-oriented programming. He iscurrently the lead architect for a C# and ASP.NET enterprise application for managing thecorrections division of the sherrif’s office in Oregon, and he has successfully implementeddozens of corporate and commercial applications in the telecom, insurance, and financialindustries and for law enforcement and government agencies, with customers throughout theUnited States, Canada, and Puerto Rico.

Page 4: Advanced c sharp programming

AdvancedC# Programming

Paul Kimmel

McGraw-Hill/OsborneNew York Chicago San Francisco

Lisbon London Madrid Mexico CityMilan New Delhi San Juan Seoul

Singapore Sydney Toronto

Page 5: Advanced c sharp programming

Copyright © 2002 by The McGraw-HIll Companies, Inc. All rights reserved. Manufactured in the United States ofAmerica. Except as permitted under the United States Copyright Act of 1976, no part of this publication may bereproduced or distributed in any form or by any means, or stored in a database or retrieval system, without the priorwritten permission of the publisher.

0-07-222828-8

The material in this eBook also appears in the print version of this title: 0-07-222417-7

All trademarks are trademarks of their respective owners. Rather than put a trademark symbol after every occur-rence of a trademarked name, we use names in an editorial fashion only, and to the benefit of the trademarkowner, with no intention of infringement of the trademark. Where such designations appear in this book, theyhave been printed with initial caps.

McGraw-Hill eBooks are available at special quantity discounts to use as premiums and sales promotions, or foruse in corporate training programs. For more information, please contact George Hoare, Special Sales, [email protected] or (212) 904-4069.

TERMS OF USEThis is a copyrighted work and The McGraw-Hill Companies, Inc. (“McGraw-Hill”) and its licensors reserve allrights in and to the work. Use of this work is subject to these terms. Except as permitted under the Copyright Actof 1976 and the right to store and retrieve one copy of the work, you may not decompile, disassemble, reverseengineer, reproduce, modify, create derivative works based upon, transmit, distribute, disseminate, sell, publishor sublicense the work or any part of it without McGraw-Hill’s prior consent. You may use the work for yourown noncommercial and personal use; any other use of the work is strictly prohibited. Your right to use the workmay be terminated if you fail to comply with these terms.

THE WORK IS PROVIDED “AS IS”. McGRAW-HILL AND ITS LICENSORS MAKE NO GUARANTEESOR WARRANTIES AS TO THE ACCURACY, ADEQUACY OR COMPLETENESS OF OR RESULTS TO BEOBTAINED FROM USING THE WORK, INCLUDING ANY INFORMATION THAT CAN BE ACCESSEDTHROUGH THE WORK VIA HYPERLINK OR OTHERWISE, AND EXPRESSLY DISCLAIM ANY WAR-RANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OFMERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. McGraw-Hill and its licensors do notwarrant or guarantee that the functions contained in the work will meet your requirements or that its operationwill be uninterrupted or error free. Neither McGraw-Hill nor its licensors shall be liable to you or anyone else forany inaccuracy, error or omission, regardless of cause, in the work or for any damages resulting therefrom.McGraw-Hill has no responsibility for the content of any information accessed through the work. Under no cir-cumstances shall McGraw-Hill and/or its licensors be liable for any indirect, incidental, special, punitive, conse-quential or similar damages that result from the use of or inability to use the work, even if any of them has beenadvised of the possibility of such damages. This limitation of liability shall apply to any claim or cause whatso-ever whether such claim or cause arises in contract, tort or otherwise.

DOI: 10.1036/0072228288

Page 6: Advanced c sharp programming

Want to learn more?

We hope you enjoy this McGraw-Hill eBook! If you d like

and websites, please click here.more information about this book, its author, or related books

,

Page 7: Advanced c sharp programming

For Trevor, Douglas, and Noah,I love you for the good-natured, loving boys that you are

and the fine men I know you will be.Love, Fat Daddy

Page 8: Advanced c sharp programming

This page intentionally left blank.

Page 9: Advanced c sharp programming

Contents at a Glance

Part I Windows Applications

Chapter 1 Language Foundations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

Chapter 2 Assembly Viewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

Chapter 3 Video Kiosk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

Chapter 4 Terrarium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

Chapter 5 Building Database Applications with ADO.NET . . . . . . . . . . . . . . . 133

Part II Tools and Components

Chapter 6 Customizing Visual Studio .NET . . . . . . . . . . . . . . . . . . . . . . . . 169

Chapter 7 Event Logger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201

Chapter 8 Creating UserControls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231

Chapter 9 Special Effects Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261

Chapter 10 Creating Custom Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . 301

Chapter 11 Practical Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319

Part III Web Applications—IBUYSPY Portal

Chapter 12 Implementing Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . 371

Chapter 13 IBuySpy and Dynamic User Interfaces in ASP.NET . . . . . . . . . . . . . 399

Chapter 14 Creating Custom Web Controls . . . . . . . . . . . . . . . . . . . . . . . . 435

vii

For more information about this title, click here.

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 10: Advanced c sharp programming

Chapter 15 Output Caching and Persisting State Information . . . . . . . . . . . . . . 469

Chapter 16 Security and Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . 505

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 529

v i i i A d v a n c e d C # P r o g r a m m i n g

Page 11: Advanced c sharp programming

Contents

Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiIntroduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii

Part I Windows Applications

Chapter 1 Language Foundations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3Object-Oriented Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

Defining Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5Creating Instances of Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22Defining Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23Implementing Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25Encapsulation and Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . 28Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29Using Modifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

Operator Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31Coding Operator Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31Overloading Operator Guidelines and Limitations . . . . . . . . . . . . . . . . . . 32

Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

Chapter 2 Assembly Viewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36Code Listing for the Assembly Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

using Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39Defining Your Namespace Name . . . . . . . . . . . . . . . . . . . . . . . . . . 40Defining Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41Walking the Assembly Using Reflection . . . . . . . . . . . . . . . . . . . . . . . 43Broadcasting Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

ix

For more information about this title, click here.

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 12: Advanced c sharp programming

Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49Value Types Versus Reference Types . . . . . . . . . . . . . . . . . . . . . . . . 49Windows Forms Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52Basic Printing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57Embedded Resource File Management . . . . . . . . . . . . . . . . . . . . . . . 57Multithreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

Chapter 3 Video Kiosk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64What Is GDI+? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

GDI+ Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65Understanding the GDI+ Programming Model . . . . . . . . . . . . . . . . . . . . 65

Examining the PlayControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67Implementing the PlayControl . . . . . . . . . . . . . . . . . . . . . . . . . . . 67Creating the Graphical User Interface for the PlayControl . . . . . . . . . . . . . . 71Implementing the Tracker Control . . . . . . . . . . . . . . . . . . . . . . . . . . 87

Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93Implementing the Elapsed Time Clock . . . . . . . . . . . . . . . . . . . . . . . . 93Using the ToolTip Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94Adding Controls to the Toolbox . . . . . . . . . . . . . . . . . . . . . . . . . . . 95Catching and Handling Specific Exceptions . . . . . . . . . . . . . . . . . . . . . 95Adding TODO Items to the Task List . . . . . . . . . . . . . . . . . . . . . . . . . 96Using the Process Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99COM Interop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

Chapter 4 Terrarium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102Downloading, Installing, and Configuring Terrarium . . . . . . . . . . . . . . . . . . . . . 103

Configuring the Terrarium Client . . . . . . . . . . . . . . . . . . . . . . . . . . 104Running the Terrarium Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105Configuring the Terrarium Server . . . . . . . . . . . . . . . . . . . . . . . . . . 106

Playing Terrarium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107Environment Reporting Every Six Minutes . . . . . . . . . . . . . . . . . . . . . . 107Critter Timing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111

x A d v a n c e d C # P r o g r a m m i n g

Page 13: Advanced c sharp programming

Teleporter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112Smart Client Updates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112Peer-to-Peer Computing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115Support for Multiple Programming Languages . . . . . . . . . . . . . . . . . . . 115Code Access Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

Reviewing the Terrarium Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116Creating Plants and Critters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116

Creating a Plant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117Creating Critters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

Introducing Plants and Critters to the Terrarium . . . . . . . . . . . . . . . . . . . . . . . 128Introducing Code Access Security . . . . . . . . . . . . . . . . . . . . . . . . . . 129

Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129Serializing Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

Serializing Organisms Using MemoryStream Objects . . . . . . . . . . . . . . . . . 130Writing to a FileStream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132

Chapter 5 Building Database Applications with ADO.NET . . . . . . . . . . . . . . . 133Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134A Quick Review of ADO.NET Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . 135Connecting to DataSources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135

Connecting to an OLE DB Data Store . . . . . . . . . . . . . . . . . . . . . . . . 136Connecting to an MS SQL Server Data Store . . . . . . . . . . . . . . . . . . . . . 139Using ADO.NET Interfaces to Declare Types . . . . . . . . . . . . . . . . . . . . . 139

Understanding the Role of the Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . 143Initializing an Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144Invoking the Adapter Fill Method . . . . . . . . . . . . . . . . . . . . . . . . . . 146Invoking the Adapter FillSchema Method . . . . . . . . . . . . . . . . . . . . . . 148Updating Changes to Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148

Working with the DataSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150Adding DataTable Objects to a DataSet . . . . . . . . . . . . . . . . . . . . . . . 150Creating Master-Detail Relationships . . . . . . . . . . . . . . . . . . . . . . . . 151Creating Data Column Mappings . . . . . . . . . . . . . . . . . . . . . . . . . . 153

Using the DataTable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155Creating a DataTable Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

C o n t e n t s x i

Page 14: Advanced c sharp programming

Creating a Primary Key, Auto-Incremented Column . . . . . . . . . . . . . . . . . 156Walking the Properties of the PropertyInfo Class . . . . . . . . . . . . . . . . . . 157

Using the DataView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158Using the DataReader for Read-Only Data . . . . . . . . . . . . . . . . . . . . . . . . . 159Displaying Information in the DataGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . 159Using the Command Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161Generating SQL with the CommandBuilder . . . . . . . . . . . . . . . . . . . . . . . . . 162Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162Binding a DataSet to a DataGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163Returning a DataSet from a Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . 163Implementing a TraceListener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166

Part II Tools and Components

Chapter 6 Customizing Visual Studio .NET . . . . . . . . . . . . . . . . . . . . . . . . 169Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170Creating a Custom Wizard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170

Creating the Wizard Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172Implementing the IDTWizard Interface . . . . . . . . . . . . . . . . . . . . . . . 172Passing Arguments by Reference . . . . . . . . . . . . . . . . . . . . . . . . . . 173Testing the Wizard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

Creating a Project Template for Visual Studio .NET Wizards . . . . . . . . . . . . . . . . . 175Copying an Existing Project Template . . . . . . . . . . . . . . . . . . . . . . . . 176Adding Files for the Wizard Library Template . . . . . . . . . . . . . . . . . . . . 177Modifying the default.js Script . . . . . . . . . . . . . . . . . . . . . . . . . . . 178Modifying the common.js Script . . . . . . . . . . . . . . . . . . . . . . . . . . . 180Creating the Wizard Launch File . . . . . . . . . . . . . . . . . . . . . . . . . . 181Creating the VSDir File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182

Extending Visual Studio .NET with Wizards . . . . . . . . . . . . . . . . . . . . . . . . . 184Writing a Macro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185A Brief Introduction to the Common Environment Object Model . . . . . . . . . . . 186Writing Code with Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187Creating the Code Generator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188Implementing the Wizard User Interface . . . . . . . . . . . . . . . . . . . . . . 191Implementing the Wizard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194

x i i A d v a n c e d C # P r o g r a m m i n g

Page 15: Advanced c sharp programming

Completing the Sample Wizard Launch File . . . . . . . . . . . . . . . . . . . . . 196Registering Wizards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196Testing the Wizard with a Macro . . . . . . . . . . . . . . . . . . . . . . . . . . 197Running the Wizard from the Command Window . . . . . . . . . . . . . . . . . . 197

Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198Returning to Jscript .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198Using the regasm Utility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200

Chapter 7 Event Logger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202Exploring System.Diagnostics Namespace . . . . . . . . . . . . . . . . . . . . . . . . . . 203Using the EventLog Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203

Writing to the EventLog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204Handling EntryWritten Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205Logging Events to a Remote Machine . . . . . . . . . . . . . . . . . . . . . . . . 206

Tracing as a Debugging Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207Tracing to the Event Log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208

Dumping the Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209Using the StackTrace as a Diagnostic Tool . . . . . . . . . . . . . . . . . . . . . . 209Using the StackFrame as a Diagnostic Tool . . . . . . . . . . . . . . . . . . . . . 211

Managing Debug Code Automatically . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211Conditional Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212Using Switches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215

Implementing the Logger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216Creating an Event Source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219Determining if a Source Exists . . . . . . . . . . . . . . . . . . . . . . . . . . . 220Deleting an Event Source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220Deleting a Custom Log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220Adding a Trace Listener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221

Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221Creating Dialog Boxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221

Dialogs Return a DialogResult . . . . . . . . . . . . . . . . . . . . . . . . . . . 222Private Constructors for Dialogs . . . . . . . . . . . . . . . . . . . . . . . . . . . 222Passing Arguments by Reference in C# . . . . . . . . . . . . . . . . . . . . . . . 223

FileVersion Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224

C o n t e n t s x i i i

Page 16: Advanced c sharp programming

Using the Debug Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225Writing Debug Information to the Output Window . . . . . . . . . . . . . . . . . . 225Assert Everything . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225A Brief History of Debugging Tools . . . . . . . . . . . . . . . . . . . . . . . . . 226Enabling and Disabling the Debug Class . . . . . . . . . . . . . . . . . . . . . . . 227

Measuring Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230

Chapter 8 Creating UserControls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232Understanding UserControls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232

What Is a UserControl? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233GUI Is King . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233Creating a Control Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234

Creating a ButtonCluster Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234Designing the UserControl Visual Interface . . . . . . . . . . . . . . . . . . . . . 235Surfacing Constituent Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239

Creating a PickList Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244Adding and Removing Elements from ListBoxes . . . . . . . . . . . . . . . . . . . 244BeginUpdate and EndUpdate . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246

Implementing an AboutBox Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246Drawing and Coding the AboutBox . . . . . . . . . . . . . . . . . . . . . . . . . 247Surfacing Constituent Properties . . . . . . . . . . . . . . . . . . . . . . . . . . 247

Defining a Data Bound UserControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248Implementing the UserControl . . . . . . . . . . . . . . . . . . . . . . . . . . . 248Implementing the ContactInformation Class . . . . . . . . . . . . . . . . . . . . . 250Binding and Navigating . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252

Custom Painting in UserControls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253Transparent UserControl Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254Extending UserControls Through Inheritance . . . . . . . . . . . . . . . . . . . . . . . . 254Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255Loading ListBoxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255

Using BeginUpdate and EndUpdate . . . . . . . . . . . . . . . . . . . . . . . . . 256Using the ThreadPool to Load a ListBox . . . . . . . . . . . . . . . . . . . . . . . 256

Dynamically Positioning and Sizing Controls . . . . . . . . . . . . . . . . . . . . . . . . 258Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259

x i v A d v a n c e d C # P r o g r a m m i n g

Page 17: Advanced c sharp programming

Chapter 9 Special Effects Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262Rapid Control Prototyping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262

Implement a PaintEventHandler for Custom Painting . . . . . . . . . . . . . . . . 263Pass an Instance of the Control to Methods . . . . . . . . . . . . . . . . . . . . . 263Examining the Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263

Creating a Class Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268Abstract Generic Behavior . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269Define a New Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269Adding the Finishing Touch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272

Testing Your Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278Implementing a Test Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278Implementing Trap Behavior . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279

Adding the Component to the Toolbox . . . . . . . . . . . . . . . . . . . . . . . . . . . 280Creating a Merge Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282Creating a Type Converter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282

Implementing IConvertible . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283Implementing a TypeConverter . . . . . . . . . . . . . . . . . . . . . . . . . . . 287

Implementing a Type Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291Creating the GradientEditor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291Implementing the AngleEditor . . . . . . . . . . . . . . . . . . . . . . . . . . . 293

Applying the Custom Editor with the EditorAttribute . . . . . . . . . . . . . . . . . . . . . 296Defining a Windows Forms Designer . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298

Chapter 10 Creating Custom Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . 301Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302Defining the Custom Attribute Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303Implementing an Attribute Constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . 303

Positional Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303Named Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304

Adding Other Members to Custom Attributes . . . . . . . . . . . . . . . . . . . . . . . . 305Applying the AttributeUsageAttribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306

Specifying Attribute Targets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306Specifying If Your Attribute Is Inherited . . . . . . . . . . . . . . . . . . . . . . . 306

C o n t e n t s x v

Page 18: Advanced c sharp programming

Specifying If Your Attribute Can Be Applied Multiple Times . . . . . . . . . . . . . 306Reading Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309Commenting Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309Implementing Extender Provider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311

Specifying the Class Header for the HelpProvider . . . . . . . . . . . . . . . . . . 311Applying the ProviderPropertyAttribute . . . . . . . . . . . . . . . . . . . . . . . 312Implement the IExtenderProvider Interface . . . . . . . . . . . . . . . . . . . . . 312Implement the HelpProvider . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313Using the HelpProvider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314

Reviewing the EditorBrowsableAttribute . . . . . . . . . . . . . . . . . . . . . . . . . . . 315Reviewing the DesignerSerializationVisibilityAttribute . . . . . . . . . . . . . . . . . . . . 316Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318

Chapter 11 Practical Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320Discovering and Using Types Dynamically . . . . . . . . . . . . . . . . . . . . . . . . . . 320

Using Type Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321Instantiating Objects with Reflection . . . . . . . . . . . . . . . . . . . . . . . . . 322Dynamic Member Invocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323Loading Assemblies Using Reflection . . . . . . . . . . . . . . . . . . . . . . . . 331Generating User Interfaces with Reflected Objects . . . . . . . . . . . . . . . . . . 333

Exploring the .NET Framework with Reflection . . . . . . . . . . . . . . . . . . . . . . . 337Returning to the CLR Reference Application, Assembly Viewer 2 . . . . . . . . . . . 338Requesting Type Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338Reflecting Methods and Method Parameters . . . . . . . . . . . . . . . . . . . . . 341Accessing Properties and Fields Dynamically . . . . . . . . . . . . . . . . . . . . . 343Binding to Events Dynamically . . . . . . . . . . . . . . . . . . . . . . . . . . . 347

Emitting Dynamic Assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348Emitting Assemblies in Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . 349Saving Emitted Assemblies to File . . . . . . . . . . . . . . . . . . . . . . . . . . 352Using Builder Objects to Create IL . . . . . . . . . . . . . . . . . . . . . . . . . . 353Writing Lines of OpCodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355Loading and Executing Dynamic Assemblies . . . . . . . . . . . . . . . . . . . . . 362

Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363Reflection and Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363

x v i A d v a n c e d C # P r o g r a m m i n g

Page 19: Advanced c sharp programming

Implementing the Metaclass Idiom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364Serializing Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365Emitting Regular Expression Assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . 366Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367

Part III Web Applications—IBUYSPY Portal

Chapter 12 Implementing Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . 371Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372Web Services: Discovery and Description . . . . . . . . . . . . . . . . . . . . . . . . . . 372

DISCO Is Back . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374WSDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375Consuming Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376Reviewing Web Services Wire Formats . . . . . . . . . . . . . . . . . . . . . . . 378

Testing Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379Creating a Simple Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380

Returning Simple Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380Providing a Namespace and Description . . . . . . . . . . . . . . . . . . . . . . . 384Handling Web Service Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . 384Invoking Web Services Asynchronously . . . . . . . . . . . . . . . . . . . . . . . 385

Returning Complex Data from a Web Service . . . . . . . . . . . . . . . . . . . . . . . . 388Returning a DataSet from a Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . 392Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393

Using the SoapFormatter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393Implementing the ISerializable Interface . . . . . . . . . . . . . . . . . . . . . . 394

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397

Chapter 13 IBuySpy and Dynamic User Interfaces in ASP.NET . . . . . . . . . . . . . 399Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400Creating a Cascading Style Sheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401

Navigating Style Sheets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401Defining Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405Defining Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406Using Elements and Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406

Implementing the Portal Banner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408Designing User Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408Working with the Design View . . . . . . . . . . . . . . . . . . . . . . . . . . . 409

C o n t e n t s x v i i

Page 20: Advanced c sharp programming

Programming in the Code-Behind Module . . . . . . . . . . . . . . . . . . . . . . 415Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432Administrating the Portal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432Debugging the IBUYSPY Portal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434Introducing Mobile Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434

Chapter 14 Creating Custom Web Controls . . . . . . . . . . . . . . . . . . . . . . . . 435Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436Rendering Controls Dynamically . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436

Creating a Custom Server Control . . . . . . . . . . . . . . . . . . . . . . . . . . 437Incorporating the Custom Server Control into a Web Page . . . . . . . . . . . . . . 438Saving Control State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440Creating a Composite Custom Control . . . . . . . . . . . . . . . . . . . . . . . . 444Using the WebControl Library Project Template . . . . . . . . . . . . . . . . . . . 446Reviewing Custom Controls in the Portal . . . . . . . . . . . . . . . . . . . . . . 446

Reviewing the PortalModuleControl Base Control . . . . . . . . . . . . . . . . . . . . . . 446Exploring Base Control Properties . . . . . . . . . . . . . . . . . . . . . . . . . . 447Using Control Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447

Binding the Tabs Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447Visually Designing the SignIn Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449Creating the Image Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451Creating the Links Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452

Coding Script Blocks in the HTML Code . . . . . . . . . . . . . . . . . . . . . . . 453Creating Script Blocks Using the Template Editor . . . . . . . . . . . . . . . . . . 456

Implementing the XML/XSL Transform Module . . . . . . . . . . . . . . . . . . . . . . . 457Reviewing the XML Document . . . . . . . . . . . . . . . . . . . . . . . . . . . 457Reviewing the XSL Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . 459Loading the XML and XSL Documents with the Xml Control . . . . . . . . . . . . . 460

Creating a Custom Portal Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462Creating a Custom Portal Module . . . . . . . . . . . . . . . . . . . . . . . . . . 462Integrating the Custom Module into the IBuySpy Portal . . . . . . . . . . . . . . . 463

Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465Using the HttpServerUtility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466Using the Repeater Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468

x v i i i A d v a n c e d C # P r o g r a m m i n g

Page 21: Advanced c sharp programming

Chapter 15 Output Caching and Persisting State Information . . . . . . . . . . . . . . 469Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 470Output Caching Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471

Using Declarative Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471Caching Examples in the IBuySpy Portal . . . . . . . . . . . . . . . . . . . . . . 482Using Programmatic Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482

Caching Partial Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 484Caching Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 486

Portal Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487Adding Data to the Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493Creating a File Dependency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493

Using the Session Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495Caching Data in the Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496Caching Objects in the Session Cache . . . . . . . . . . . . . . . . . . . . . . . . 497Managing the Session Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500

Using the Application Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500Configuring the Session State Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501Configuring the SQL Server for Session Management . . . . . . . . . . . . . . . . . . . . 503Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 504

Chapter 16 Security and Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . 505Demonstrated Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506Using Windows NTLM Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507

Modifying the Web.config File for Windows Authentication . . . . . . . . . . . . . . 507Denying and Allowing Authorization . . . . . . . . . . . . . . . . . . . . . . . . 508

Implementing Forms Authentication with Cookies . . . . . . . . . . . . . . . . . . . . . . 509Modifying the Web.config File for Forms Authentication . . . . . . . . . . . . . . . 509Authenticating Users . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511Programming with Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516

Implementing the SignIn Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 520Administering Users . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 521Secondary Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 522Implementing Code Access Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523

Declarative Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525Imperative Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 529

C o n t e n t s x i x

Page 22: Advanced c sharp programming

This page intentionally left blank.

Page 23: Advanced c sharp programming

Acknowledgments

I would like to thank Wendy Rinaldi and Katie Conley at McGraw-Hill/Osborne. Thereis a tremendous amount of work that goes into a book, and even more that goes intobooks with lots of code. Without their vision and efforts, I wouldn’t have had the

opportunity to write this book. I hope I gave as much as I got.Thanks to David Fugate at Waterside for proactive stewardship and for taking care of the

nuts-and-bolts stuff that makes it possible for me to write. Special thanks to Mary Bonniciwho has taken professional care of all financial matters without fail for many years now.

Special thanks to Susan Warren and Rob Howard at Microsoft. Susan and Rob providedinvaluable assistance by reviewing the outline, providing timely and proactive assistance onthe tough questions, and coordinating all of the IBUYSPY coverage. Thank you Susan and Rob.

Thanks to Steve Balmer, who has made it a policy for all of the busy Microsoft employeesto drop what they are doing to help customers and authors. Clearly, the program managersand developers take Steve seriously. As far as I know, no one was directly compensated forhelping me with technical questions, and my total experience demonstrates Microsoft’s clearcommitment to developers, to Microsoft .NET, and to the authors and publishers trying tobring developers the highest quality of information possible. Thanks to all of the programmanagers and developers who answered dozens of questions cheerfully and quickly.

I would like to thank Lewis Gouge. Mr. Gouge played matchmaker between me andMultnomah County ISD. Building an enterprise system using .NET for the correctionsdepartment was a rewarding challenge. Thanks to Steve “Mr. Ed” Chennault, Mark “Ploki”Davis, Bill Arnold, Joe “Bilbo” Shook, Robert Phillips, Brook Riddick, Yvette Yutze, JeffBraunstein, Peggy Duerscherl, Richard “Jelly” Augelli, Eric “Hank Hill” Cotter, John“Ballpeen” Armitage, Kathy Erwin, Geoff Caylor, and Karen Britton. I enjoyed workingwith and learning from so many smart people in one place.

Thanks to Yonnie, Erin, and Larinda at Wynne’s in Portland, Oregon for food and adultbeverages.

Finally, thank you to my family—Lori, Alex, Douglas, Noah, and Trevor. The only thing Ienjoy more than building software and writing is spending time with you guys. Home truly iswhere my heart is.

xxiCopyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 24: Advanced c sharp programming

This page intentionally left blank.

Page 25: Advanced c sharp programming

Introduction

Advanced C# Programming is about programmers and code. This book wasconceived around the idea of providing a lot of code listings for programmerswho need to solve problems now.

In each chapter you will find a brief introduction presenting the program showcased inthe chapter and describing the kinds of problems that you may encounter and how the codepresented will help you tackle them. From there, you can go right to the complete code listingto find the solutions, then read the explanations that follow describing the technology thatsupports the code listing.

Because each code listing is a complete application, you will find several applications thatdemonstrate both primary and secondary capabilities of C# and Microsoft .NET.

Advanced C# Programming was written for developers who have an intermediate toadvanced level of experience with similar languages, such as C++, Delphi, or Visual Basic.NET, or who have read an introductory-level book explaining the fundamentals of object-oriented programming with C#.

As a special feature, Chapters 12 through 16 demonstrate the advanced aspects of ASP.NETprogramming for the Web with C#. I am pleased to tell you that these chapters of AdvancedC# Programming showcase the IBUYSPY.COM Web site as the application for demonstratingsoftware development concepts for the Web. We could not have done this without the blessingand able assistance of Microsoft and Susan Warren, program manager for ASP.NET.

The code examples in this book will demonstrate how to use the most beneficial andpowerful aspects of C# programming for Windows and the Web. Everything you need toknow about Reflection, Assemblies, object-oriented programming, security and authentication,and Web Services and Web applications for e-commerce sites can be found within this book.All you have to do is open it up and begin exploring.

xxiiiCopyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 26: Advanced c sharp programming

This page intentionally left blank.

Page 27: Advanced c sharp programming

PART

IWindows Applications

OBJECTIVES

� Learn advanced idioms in C#

� Learn fundamental Windows programmingskills for .NET

� Learn about custom control creation, includingtopics on shaped forms, control transparency,GraphicsPath objects, and linear gradient brushes

� Learn Peer-To-Peer programming and WebServices by participating in the .NETTerrarium game

� Learn all about ADO.NET

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 28: Advanced c sharp programming

This page intentionally left blank.

Page 29: Advanced c sharp programming

This page intentionally left blank.

Page 30: Advanced c sharp programming

CHAPTER

1Language Foundations

3

IN THIS CHAPTER:

Object-Oriented Basics

Operator Overloading

Attributes

Reflection

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 31: Advanced c sharp programming

This is the only chapter of its kind in C# Developer’s Guide. It provides a generalreview of advanced idioms before we embark on the journey of exploring codelistings. These advanced idioms are essential to implementing solutions used in C#,

and this review will help get us all on the same footing.For senior programmers with many years of experience in object-oriented programming,

this chapter may be a good review for you. For everyone, there are important aspects ofC# programming and .NET programming in general that will be new because they wereintroduced in .NET. Discussions of what is meant by “delegates,” by “Reflection,” and by“an assembly” will provide important foundations for building advanced applications in C#.Every chapter after this one builds on these advanced general concepts.

Object-Oriented BasicsObject-oriented basics are far from basic. Every object-oriented language implements asubset of those things that make a good object-oriented language. And C#, distinct from C++,Delphi, or Visual Basic 6 or Visual Basic.NET, has its own unique way of implementingthese aspects of an object-oriented programming language (OOPL).

The tenets of object-oriented programming are encapsulation, inheritance, aggregation,and polymorphism. Languages that support these basic tenets are considered object-orientedlanguages. There is a diverse set of idioms and constructs that facilitate each of these tenets,including things like templates, operator overloading, interfaces, multithreading, multipleinheritance, exception handling, pointers, and garbage collection. C# is a powerful OOPLthat supports the tenets of object-oriented programming by providing operator overloading,inheritance, interfaces, exception handling, garbage collection, multiple interface inheritance,reflection, and multithreading. C# does not support templates, raw pointers, or multiple classinheritance. There is a lot of debate over whether or not these latter three features introducemore problems than they solve; for this reason they were left out of C#.

Instead of multiple inheritance, pointers, and templates, you do get additional newfeatures that will help you build Web applications and Web services. C# supports COMInterop, multilanguage programming, and rapid application development. There are a fewtrade-offs.

C# is managed code. The benefit is that you don’t have to worry about the slicing problemcaused by bad pointers, and the garbage collector will help you avoid memory leaks. Thetrade-off is the relinquishment of raw pointers. Pointers support some advanced idioms, likereference-counted objects and access to all memory. This same access to any memory addressprovides ultimate control and responsibility. With power comes responsibility. (For rawpointers, you can still use unmanaged C++ code.)

C# is most like C++ in its grammar. Most of the everyday idioms you will use regularlyin C# have the same syntax as C++, making the learning curve for C++ programmers theshallowest. Delphi and Java programmers will also find the transition to C# relatively easy,and even VB programmers will find a transition from VB to C# easier than from VB to C++.

4 A d v a n c e d C # P r o g r a m m i n g

Page 32: Advanced c sharp programming

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 5

C++ and Java programmers will feel right at home with the C# syntax, and Visual C++programmers will appreciate the Visual Basic–like rapid application development supportedby C# for Windows and the Web.

Defining ClassesThe class construct has the same basic elements found in C++. To define a class in C#, youneed to add the following construct to a code unit.

Access-modifier class name

{

}

TIPC# is case-sensitive. Keywords are typed in the same case you will need to use in your code. C++ is alsocase-sensitive; other languages, like Delphi and Visual Basic .NET, are case-insensitive.

Access modifier describes the accessibility of the class. Classes for general consumptionwill be public. The name is any name that is unique by characters and case in the samenamespace. (We’ll get back to namespaces in a moment.)

Applying these basic rules to defining classes, the following example demonstrates a classdefined in C#.

public class MyFirstClass

{

}

By default, classes have public access in C#; and by convention, class names are initial-capped,as demonstrated. The M in “My,” the F in “First,” and the C in “Class” are all capitalized, aconvention referred to as Pascal casing. (Conventions are there to breed consistency, but areelections that you can choose to adopt or not. We will stick with conventions in this book.)

Implementing a Class in a Console ApplicationA console application is one that runs at a command prompt without a graphical user interface,or at least without the Windows Forms graphical user interface.

When you create a console application from the New Project dialog, you will get the basiccode shown here:

using System;

namespace HelloWorld

{

/// <summary>

/// Summary description for Class1.

/// </summary>

Page 33: Advanced c sharp programming

class Class1

{

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread]

static void Main(string[] args)

{

//

// TODO: Add code to start application here

//

}

}

}

The using statement at the beginning of the listing indicates that the console applicationis using the System.dll assembly installed with the Microsoft.NET framework. The Systemassembly contains the most fundamental elements of the Common Language Runtime. Fornow, think of an assembly as an application or DLL, and the using statement as an “include”in C++ or a “uses” in Delphi.

The namespace construct comes next. Namespaces are a higher level of conceptualizationthan classes. A namespace is a convenient way to organize elements together and to uniquelyidentify them. Two separate namespaces can contain the same class. By default, thenamespace given to the console application will be the name given to the project in theNew Project dialog.

The three slash marks (///) is a special comment used to support automatic documentationgeneration, and the // and /* */ comment styles are supported in C# too. The class definitionis provided next. By default, auto-generated code constructs are given the name typenumber—for example, Class1, where Class is derived from the type of construct, a class, andthe number is appended as a suffix to ensure a unique name. You are encouraged to provide adescriptive name for your classes.

Inside the basic console application, we have the startup procedure Main. Main is definedas a static member, which means we do not have to have an instance of the enclosing class tocall the method. The argument string[] args indicates that Main is passed an array of stringsrepresenting the command line arguments.

Finally, note that all constructs have an opening and closing bracket pair, {}, using thesame level of block indentation to facilitate readability.

Hello World Using our simple console application we can display some text to the consoleby adding the statement Console.WriteLine(“Hello World!”); the modified applicationfollows.

using System;

namespace HelloWorld

{

6 A d v a n c e d C # P r o g r a m m i n g

Page 34: Advanced c sharp programming

/// <summary>

/// Summary description for Class1.

/// </summary>

class Class1

{

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread]

static void Main(string[] args)

{

Console.WriteLine("Hello World!");

Console.ReadLine();

}

}

}

Using the Visual Studio 6 keyboard scheme—modifiable in My Profile in Visual Studio.NET—we can press F5 to run the application. Both WriteLine and ReadLine are staticmembers in the Console class, so we do not need to create an instance of a Console object tocall these methods.

Console.WriteLine writes a line to the command prompt, appending a new line to the endof the text, and the statement Console.ReadLine waits for a carriage return before continuing.Also, note that statements in C# are terminated with a semicolon.

Reading from the Command Prompt If you want to read arguments passed from the commandprompt, then you can get them out of the array of strings represented by the parameter args.By modifying the code as follows, we can echo the text entered at the command promptinstead of “Hello World”.

using System;

namespace HelloWorld

{

/// <summary>

/// Summary description for Class1.

/// </summary>

class Class1

{

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread]

static void Main(string[] args)

{

Console.WriteLine(args[0]);

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 7

Page 35: Advanced c sharp programming

Console.ReadLine();

}

}

}

The preceding code writes the value of the first argument passed on the command promptto the console.

To supply a command line argument, run the assembly from the command prompt orfollow these steps to supply a command line argument in the Visual Studio .NET IDE.

1. In Visual Studio .NET, click View | Solution Explorer.

2. In Solution Explorer, right-click over the project name and select the Propertiescontext menu.

3. In the Property Pages dialog, select the Configuration Properties folder followedby the Debugging page.

4. Enter a value for the Command Line Arguments (see Figure 1-1) in the StartOptions section.

5. Press F5 to re-run the application in the VS.NET IDE. You will see the value youentered in the Command Line Arguments field in Step 4.

8 A d v a n c e d C # P r o g r a m m i n g

Figure 1-1 Supplying command line arguments in the HelloWorld Property Pages

Page 36: Advanced c sharp programming

Implementing Fields and PropertiesFields are data in classes. By convention, fields are private; if you need to provide access toconsumers then you are encouraged to do so through property methods. Property methods, orproperties, look and act like data from the perspective of the consumer but act like methodsfrom the perspective of the producer.

NOTEConsumers are programmers who use the classes, and producers are programmers who create the classes.The same programmer can be both a producer and consumer of the same class.

The benefit of data, of course, is that it provides a way to maintain the state of an object.The benefit of properties is that they allow you to constrain the way data is accessed withcode, just as if access were limited to access via methods, but from the perspective of theconsumer the properties are used like data.

Defining Fields Fields can be any kind of data. By convention, fields use the private accessmodifier, allowing only the producer to manipulate the field directly. Consumers mustmanipulate fields using property methods.

NOTEBy convention, you should name fields using camel-casing. In camel-casing, the first letter of the first word islowercase and the first letter of subsequent words are uppercase. Do not use the Hungarian notation.

If fields are complex data types, then you can create instances of those types in theconstructor (see the section “Implementing Methods”) or upon demand. The following listingdemonstrates a simple field type that stores the value of args[0] as a string type.

using System;

namespace HelloWorld

{

/// <summary>

/// Summary description for Class1.

/// </summary>

class Class1

{

private static string arg0 = null;

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread]

static void Main(string[] args)

{

arg0 = args[0];

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 9

Page 37: Advanced c sharp programming

1 0 A d v a n c e d C # P r o g r a m m i n g

Console.WriteLine(arg0);

Console.ReadLine();

}

}

}

The statement in boldface defines a static field. Because we are assigning the value ofargs[0] to the private field arg0, and there is no instance of Class1.

The Main method is a starting point for a console application. We could use it for thatpurpose only and revise the class accordingly to perform the same task but while using aninstance of the class. The code so revised follows:

using System;

namespace HelloWorld

{

/// <summary>

/// Summary description for Class1.

/// </summary>

class Class1

{

private string arg0 = null;

public void WriteCommandLine( string arg )

{

Console.WriteLine(arg);

Console.ReadLine();

}

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread]

static void Main(string[] args)

{

Class1 class1 = new Class1();

class1.arg0 = args[0];

class1.WriteCommandLine(class1.arg0);

}

}

}

In the preceding example, the Main method plays the role of startup method only, and aninstance of the class is used to perform the work. The Main method creates an instance of

Page 38: Advanced c sharp programming

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 1 1

Class1. The value of args[0] is assigned to the private field arg0; we can do this becauseMain is also a member of Class1. The statement

class1.WriteCommandLine(class1.arg0)

writes the contents of the command line to the console.

Defining Properties If you are reasonably comfortable with console applications, staticmembers, and creating objects, then this code is ultra trivial. Let’s step it up a little bit.We’ll leave the example and look at the mechanics of using properties.

Properties are effectively methods. Properties can have a getter and a setter. The getter isthe property method that is called when a property is used as a right-hand-side value. Thesetter is called when the property is used as a left-hand-side value.

TIPYou do not have to have an underlying field associated with a property. The value of the property can bederived rather than stored in a field.

Properties, like any member, can have access modifiers, which are usually public forproperties. The basic syntax of a readable and writable property follows.

Access-Modifier Type Property-Name

{

get

{

return field;}

set

{

field = value;

}

}

The Access-Modifier is usually public but can be any of the allowable access modifiers(see the section “Using Modifiers”). Type is the type of the field that the property represents.The Property-Name is any valid name. Usually there is a relationship between a property andits underlying field. One convention is to name the field with an underscore prefix and theproperty without the underscore. For example, a field myValue would be associated with aproperty MyValue. This convention is used to make it easy to associate properties with theirfield values. You don’t want to spend your time fishing for fields.

The getter is defined by the get{} part of the property. This is a code block, and you canput as much or as little code as you’d like to in this code block. Ultimately, you want toreturn the value that represents the property value.

Page 39: Advanced c sharp programming

The setter is defined by the set{} part of the property. This is a code block, too, and youcan put as much code as you’d like in the setter. The part that is not intuitive is that the valuepassed to the setter is represented by the argument value. The argument is implicit; you willjust have to know it is there.

Property Naming ConventionsMicrosoft offers some naming guidelines. Instead of inventing your own, you are gentlyencouraged to adopt these guidelines for consistency. Consistency among disparate developersis the reason to have guidelines. The bulleted list reflects the naming conventions proposedby Microsoft.

� Use a descriptive noun to name your property.

� Use the Pascal case naming convention. As mentioned earlier, Pascal case names havethe initial letters of each word in the name uppercased. For example, MyPropertyshows the M in “My” and the P in “Property” uppercased.

� Do not use Hungarian notation (surprise!).

� Name the property with the same name as its underlying field. Use camel-casingfor the field and Pascal casing for the property. (Camel-casing lowercases the firstletter of the first word and uppercases the first letter of subsequent words. For example,“camelCased” demonstrates camel-casing.)

Read-Only PropertiesRead-only properties are those property statements that only have a getter. Use read-onlyproperties when the consumer cannot change the value of the property. Suppose we have atemperature class. The temperature class has two modes—Celsius and Fahrenheit. Based onthe temperature mode, we will return one of two possible values for the temperature value.The listing demonstrates the read-only temperature property Value.

public enum TemperatureMode

{

fahrenheit, celsius

}

class Temperature

{

private TemperatureMode mode = TemperatureMode.fahrenheit;

private double celsius = 0;

private double fahrenheit = 0;

public Temperature( double aTemperature, TemperatureMode aMode )

{

mode = aMode;

SetTemperature(aTemperature, aMode);

}

1 2 A d v a n c e d C # P r o g r a m m i n g

Page 40: Advanced c sharp programming

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 1 3

private void SetTemperature(double aTemperature, TemperatureMode aMode)

{

if( aMode == TemperatureMode.fahrenheit )

{

fahrenheit = aTemperature;

celsius = FahrenheitToCelsius(aTemperature);

}

else

{

celsius = aTemperature;

fahrenheit = CelsiusToFahrenheit(aTemperature);

}

}

public static double CelsiusToFahrenheit(double celsius)

{

return celsius * (9.0/5) + 32;

}

public static double FahrenheitToCelsius(double fahrenheit)

{

return (fahrenheit - 32) * 5.0/9;

}

public TemperatureMode Mode

{

get

{

return mode;

}

set

{

mode = value;

}

}

public double Value

{

get

{

return mode == TemperatureMode.fahrenheit ?

fahrenheit : celsius;

}

}

public bool Test()

Page 41: Advanced c sharp programming

1 4 A d v a n c e d C # P r o g r a m m i n g

{

return fahrenheit == CelsiusToFahrenheit(celsius)

&& celsius == FahrenheitToCelsius(fahrenheit);

}

}

The code listing defines an enumeration to represent the mode that the temperature will bedisplayed in when it is requested through the read-only property Value. (The Value propertyis shown in boldface font.)

There are a couple of other interesting highlights in the preceding listing. The Valueproperty demonstrates the ternary operator (? :) found in C, C++, and C#. The test precedesthe question mark (?). The true value follows the question mark (?), and the false valuefollows the colon (:). The test in the temperature property is

mode == TemperatureMode.fahrenheit

and the true result will be the fahrenheit field. False will yield the celsius field value. Themode property demonstrates a readable and writable property statement. CelsiusToFahrenheitand FahrenheitToCelsius were implemented as static methods, since it is reasonable toassume that these two methods will be useful to other elements of code without aTemperature object.

Write-Only PropertiesA write-only property statement would have a setter only. Write-only properties are lesscommon than read-only properties because it is more difficult to cause harm by reading aproperty than it is by writing a property.

One instance where you may want to use writable properties is when you query for apassword. You may not want to let consumers ask what password was supplied after thepassword is supplied. Presumably, some mischievous programmer could snoop objectscontaining passwords.

The help documentation in Visual Studio .NET states not to use write-only properties.Semantically, write-only properties are methods that modify an object without a method toread the modified value. You might contrive an idiom for write-only properties that is useful.There is no technical prohibition against write-only properties.

Indexed PropertiesAn indexed property is used to represent a list of items contained in a class. Indexedproperties support the notation object[index], where the object looks like an array to theconsumer. Indexed properties have a special notation and some guidelines for their use.

� Use one indexed property per class, making it the default indexed property.

� Avoid nondefault indexed properties.

� Name the indexed property “Item” as a general rule.

Page 42: Advanced c sharp programming

� Use indexed properties for classes that contain an array of elements.

� Do not use an indexed property and method that are semantically equivalent. Use oneor the other.

The following code demonstrates a class named IndexedProperty. IndexedPropertycontains a cached copy of the command line arguments (or any array of strings) and allowsconsumers to index any element in the underlying array. For all intents and purposes,instances of the class IndexedProperty can be used just like an array.

class IndexedProperty

{

private string[] args = null;

public IndexedProperty(string[] args)

{

this.args = new string[args.Length];

args.CopyTo(this.args, 0);

}

[System.Runtime.CompilerServices.IndexerName("Command")]

public string this [int index]

{

get

{

return args[index];

}

set

{

args[index] = value;

}

}

}

The indexed property begins on the line containing “this[int index].” Except for the propertymethod header, the getter and setter are implemented as are any other property. Indexedproperties are not referred to by name in C#; the indexer is implicit, and you simply addthe [] index operator after the object name. For example, if we created an instance of theIndexedProperty class, then we can index the field represented by the indexer.

In the preceding example, the indexer represents the args field. The following code fragmentdemonstrates creating an instance of the IndexedProperty object and indexing the args field.

IndexedProperty obj = new IndexedProperty(args);

Console.WriteLine( obj[0] );

Console.ReadLine();

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 1 5

Page 43: Advanced c sharp programming

The first statement creates an instance of the IndexedProperty class, and the second linedemonstrates using the indexer as if obj were an array.

TIPYou can use “this.” to get Intellisense to display a list of members of an object internally while you areprogramming in the code editor in Visual Studio .NET.

There are a couple of other points of interest in IndexedProperty. The constructordemonstrates how to distinguish between a field and a parameter that have the same name.Note that the parameter args and the field args are identical in the constructor. (You mightwant to rename the field of the parameter if this happens.) You can use the implicit referenceto self, “this”, to indicate when you are referring to the object’s field versus a parameter withthe same name when a name collision occurs.

TIPYou can insert an indexed property from the Class View window by right-clicking the class and clickingAdd | Add Indexer from the class’ context menu. Complete the wizard (shown in Figure 1-2) to insertthe indexer.

1 6 A d v a n c e d C # P r o g r a m m i n g

Figure 1-2 The C# Indexer Wizard filled out to add the indexer for the IndexedProperty class

Page 44: Advanced c sharp programming

The IndexerNameAttribute applied to the indexed property is used to provide a friendlyname for languages that don’t support indexers.

[System.Runtime.CompilerServices.IndexerName("Command")]

(Refer to the section “Attributes” for more information about attributes.)If we were to use the IndexedProperty class defined in C# in a Visual Basic .NET

program, then we could create an instance of the class and access the indexed propertythrough the name Command. Assuming we named the instance “obj,” as in the precedingexample, then obj.Command(0) would invoke the indexed property and return the 0th

element of args.

Implementing MethodsMethods in C# always have the same method heading options. The following grammarexample demonstrates all of the possible information you can supply when adding a methodto a class.

[Attribute][Modifiers]Return-Type Name( [parameters] )

All grammatical features in [] are optional. You do not have to use attributes whenimplementing methods (or other members); when you understand them, you will know whenand why to use attributes. Modifiers, such as public, describe accessibility. Modifiers relateto the information hiding. All methods require a return type. The equivalent of a procedure isa method that returns void, and functions return a non-void data type. The Name of a methodshould be a verb of a verb phrase. You can supply zero or more parameters. The signature ofa parameter is type name, followed by a comma if more than one parameter is used. Thebulleted list paraphrases the method name guidelines as suggested by Microsoft.

� Use verbs or phrases beginning with a verb for method names.

� Use Pascal case naming.

� Don’t use the Hungarian notation. (I threw this one in for consistency.)

� Provide a descriptive name that states the behavior the method performs (for example,CalculateBalance).

NOTEIf it isn’t clear by now, let me state that Microsoft is moving away from using the Hungarian notation. TheHungarian notation was adopted for the weakly typed C programming language and propagated to the VBlanguage. Strongly typed languages like C# do not need prefix notations. The compiler makes sure that youdon’t mix types inappropriately.

Several examples of methods have been demonstrated already in this chapter, and you willsee hundreds more throughout the book. Let’s continue with the next topic.

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 1 7

Page 45: Advanced c sharp programming

Constructors There are two special methods, the constructor and the destructor. Constructorshave exactly the same name as the class. No other members may have the same name—where same applies to case, too—as the class, other than the constructor. Constructors canhave zero or more parameters and do not have return types. (We’ll talk about the destructorin the next section.)

Constructors are overloaded simply by adding additional constructors with distinctarguments. The following bulleted list provides an overview of constructor guidelines.

� If a class has all static members, then implement a private constructor to preventinstances from being created.

� Keep constructors short, performing only simple tasks like parameter-to-fieldassignment in the constructor. This approach allows objects to be created very quickly.

� Provide a protected constructor for use by child classes.

� As a recommendation, do not implement an empty constructor for structs. If you don’tprovide the empty parameter constructor for structs, the compiler adds a defaultconstructor that initializes all members to their null equivalents.

� Use constructor parameters to set properties. Constructing classes with parameters orcreating objects with the default constructing and assigning values to properties shouldproduce the same result.

� Be consistent when naming parameters. A common technique is to add an increasingnumber of parameters for each overloaded constructor.

Destructors Destructors have the same name as constructors with the addition of thecomplement operator (~) affixed as the first character of the destructor name.

NOTEThe ~ character is used as the 2s complement operator in C++. It was someone’s clever idea to use thecomplement operator for destructors, implying the relationship between constructor and destructor. That is,destructors are the complement of constructors.

Implement a destructor when you have resources in your class that need to be cleaned up,as when closing a file. When implementing constructors, keep in mind that the name of theconstructor is constrained by the name of the class. Destructors cannot be overloaded orinherited. Destructors are invoked automatically, and destructors are never called directly.The CLR garbage collector invokes destructors. When a destructor is called, it implicitlycalls the Object.Finalize method. Finally, destructors have no modifiers or parameters.

Non-Deterministic Destruction and Garbage CollectionC# uses non-deterministic destruction. The CLR implements a garbage collector that cleansup memory when objects are no longer used. This means that you don’t have to worry asmuch about destructors in C# as you do in C++ or Delphi.

The garbage collector will call the destructor for your objects when they are no longerneeded. The destructor will call any Finalize method. You can force garbage collection by

1 8 A d v a n c e d C # P r o g r a m m i n g

Page 46: Advanced c sharp programming

calling GC.Collect, but this is not a recommended process. The garbage collector suspendsany running threads, which can cause performance problems. Besides, the garbage collectorwill do a good job of determining when to run.

Implementing a Dispose MethodIf you have unmanaged resources in your classes, then you can implement a Dispose methodto support deterministic cleanup. By allowing consumers to call a Dispose method, you canlet the consumer decide when they have finished with files or network connections.

Use a technique similar to the following to implement a Dispose method.

private bool disposed = false;

public void Dispose()

{

if (disposed) return;

// cleanup code here

GC.SuppressFinalize(this);

disposed = true;

}

First, you declare a private field, disposed, to be used to track whether the Dispose methodhas been called previously, initializing disposed to False. In the Dispose method, check to seeif Dispose has already been called and return if it has. Perform the cleanup and then call theGC.SupressFinalize() method, passing the reference to the object whose Finalize you want tosuppress. Finally, set the field disposed to True.

Much of the time you will not need to implement a destructor or a Dispose method.Managed objects in the CLR will be garbage collected automatically. Implement a destructorand Dispose method if you are sure you need them.

Implementing EventsEvents form the pinnacle of the Windows object-oriented triad, with properties and methodsforming the base. Events were designed to respond to occurrences in code. As with properties andmethods, there are two perspectives from which events can be viewed, that of the consumerand that of the producer.

When you are writing classes, you will want to anticipate internal occurrences that youwant to let consumers respond to. For those occurrences, you will want to introduce an eventand raise that event when the occurrence happens. When you are consuming classes, you willwant to write event handlers, wire them up to an object’s events, and write the code thatactually responds when the event is raised. (Wiring up an event simply means associating amethod with an event.)

In C++ we use the function pointer idiom and in Delphi we use the procedural type idiomto implement events and event handlers. C# has evolved what are effectively raw functionpointers. C# implements the Delegate idiom. Delegates are special classes that store thefunction pointer. The difference between a raw function pointer and a delegate is that adelegate can maintain more than one function pointer. Delegates containing more than onefunction pointer are referred to as multicast delegates.

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 1 9

Page 47: Advanced c sharp programming

2 0 A d v a n c e d C # P r o g r a m m i n g

Technically, the delegate statement describes the signature of a procedure as a type. Anymethod with a signature matching the signature of the delegate can be added to the invocationlist of a delegate object. Describing a delegate signature is consistent with describing afunction pointer in C++ or a procedural type in Delphi. The following three statementsdemonstrate the similarities.

typedef void (*func)(); // Visual C++ 6.0 function pointer definition

type TProc = procedure; // Delphi (Object Pascal) procedural type

delegate void Delegate(); // C# delegate

Each statement roughly demonstrates a function pointer declaration in the respectivelanguages. The difference is that only the C# delegate (or any .NET language delegate) canstore more than one address. Function pointers in C++ and procedural types in Delphi canrefer only to one function address. You would need to create an array of pointer types in C++and Delphi to refer to multiple function addresses. C# effectively contains the array in thetype, allowing one delegate to store multiple addresses. That is the big difference.

Of course, if you are a VB6 programmer, then you have to learn what a function pointer isand then learn what is different about a delegate. For C++ and Delphi professionals, thinkarray of function pointers with a single signature. Unfortunately, if you do not learn what afunction pointer is and how to use delegates, then you will be writing code that is theequivalent of two-legged stools.

Declaring Events Using Existing Delegates Event declarations are straightforward. For the mostpart, events are destined to be used by class consumers, so they are generally declared aspublic members. A syntax example is demonstrated next.

Access-Modifier event Delegate-Type event-name

The Access-Modifier is generally public. The event keyword is a literal that indicates thismember is an event. The Delegate-Type is a class that is described by a method signature anduses the keyword delegate. An example of an existing delegate is the EventHandler delegate.Event-name is the member name of the event; this is what consumers will use to refer to theevent in code. The following statement demonstrates an event using the EventHandlerdelegate.

public event EventHandler Changed;

The signature of EventHandler is a procedure that returns void and has two parameters,object and System.EventArgs. The declaration of the EventHandler delegate exists in theCLR as

public delegate void EventHandler(object sender, System.EventArgs e);

(This delegate already exists; thus, you do not have to define it yourself.) EventHandler isthe delegate used for many Windows Forms controls, like the Button control’s Click event.

Declaring New Delegate Types If you want to define a custom delegate, you can follow thebasic example provided in the previous section demonstrating the EventHandler delegate.

Page 48: Advanced c sharp programming

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 2 1

A delegate definition is exactly the same as a procedure header with the keyword delegateadded between the access modifier and the return type. The following example defines a newdelegate for a procedure that returns void and takes no arguments.

public delegate void Delegate();

Recall that C# is case-sensitive and note that the preceding example introduces a delegatenamed “Delegate.” Any procedure that returns void and takes no arguments can be assignedto events or fields that are declared as Delegate types.

Raising Events as a Producer Producers raise events. Raising an event in C# is performed bycalling a procedure using the event name. By convention, events are raised by protectedmembers with the same name as the event with “On” as a prefix.

Returning to our original example of the Changed event defined as an EventHandler type,we can pair the event with a protected method that raises the event.

public event EventHandler Changed;

protected void OnChanged()

{

if(Changed == null) return;

Changed(this, System.EventArgs.Empty);

}

The event is named “Changed.” Consumers can assign any number of methods that matchthe signature of the EventHandler delegate to this one event member. Inside the classcontaining the code, you would call OnChanged anywhere that you want to raise the Changeevent. OnChanged checks to make sure that Changed has been assigned to at least onedelegate; if it hasn’t, then OnChanged returns. Otherwise, Changed is treated like a methodby the producer and is called, with the producer passing it the necessary arguments.

When a producer raises an event with a sender parameter, that value is always satisfiedwith the internal reference to self: this. In our simple example, we pass a null System.EventArgsparameter because we are not using that value. If you need to pass specific values to aconsumer’s event handler, then you can create an instance of System.EventArgs or createa delegate that takes a specific subclass of EventArgs.

Handling Events as a Consumer Consumers handle events by wiring an event handler to anevent member of an object. The example program EventsDemo.sln contains the classesEventChanged and Form1. For our scenario, we assume that Form1 contains an EventChangedobject and wants to handle the EventChanged.Changed event. For this to work Form1 needsto create an instance of EventChanged and assign a method to EventChanged.Changed.The following example demonstrates code in Form1 that wires an event handler toEventChanged.Changed.

private void button1_Click(object sender, System.EventArgs e)

{

MessageBox.Show(sender.ToString());

}

Page 49: Advanced c sharp programming

private EventChanged obj = new EventChanged();

private void Form1_Load(object sender, System.EventArgs e)

{

obj.Changed += new System.EventHandler(button1_Click);

obj.Value = 5;

}

The EventChanged object is named “obj” and instantiated as a field in the form.Form1_Load (an event itself) wires the EventChanged.Changed event to Form1’sbutton1_click method. (The button click method was already available and happens tohave the same signature as the Changed event.) The += operator is used to assign the newSystem.EventHandler object to Changed. Doing this will append the event handler to theinvocation list.

Only the += and –= operators are overloaded to append and remove event handlers froman events invocation list. You cannot use the assignment operator to assign a delegate toan event.

How Does Multicasting Work? Think of an event declaration as a field whose type is anaggregate type: a delegate. When you assign a delegate to the event, you are inserting thedelegate into a list that is a member of the delegate type. When you raise the event byinvoking the event as a method call, you are actually interacting with an object. When theevent method is called internally, it iterates through all of the delegates appended to theinternal list and calls each one of them.

Returning to our example containing the public event EventHandler Changed, when weraise the event by calling Changed, every delegate assigned to Changed is called. This isreferred to as multicasting. Delegates can be bound to a single method or to multiplemethods.

It is possible to layer several pieces of an entire solution into one event by assigningseveral delegates to a single event. What you should not do is rely on delegates being calledin any specific order.

Creating Instances of ClassesObjects are instantiated by invoking the constructor using the new operator. You can createinstances of objects and assign them to variables whose type is the same type as the objectinstantiated or a type in the object’s ancestry. That is, instances of child types can be assignedto variables declared as a type of the object’s parent. The reverse is not true. (See the upcomingsection “Inheritance” for more information.)

Keep in mind that constructors have the same name as the class, can have public ornon-public access modifiers, and may have zero or more parameters. Constructors can beoverloaded. So, there may be more than one constructor that is suitable for you to call. Hereare several examples demonstrating object instantiation.

2 2 A d v a n c e d C # P r o g r a m m i n g

Page 50: Advanced c sharp programming

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 2 3

// simple object creation

FileStream fs = new FileStream("c:\\temp\\killme.txt", FileMode.CreateNew);

fs.Write(new Byte[]{65, 66, 67, 68, 69}, 0, 5);

fs.Close();

// array examples

string[] strings = new string[10];

strings[0] = "Some Text!";

int[] integers = new int[]{0,1,2,3,4,5};

The first statement creates an instance of the FileStream class. (You will need to add theSystem.IO namespace to your application with a using statement to use the FileStream class.)FileStream objects have several constructors. The example used takes a file path and aFileMode. Keep in mind that C# strings use the backslash (\) as an escape character; youwill need to use the \\ backslash pair when you mean backslash within a string. The secondstatement demonstrates an inline construction of an array of bytes with an initializer list.

The first array example demonstrates constructing an array of strings with storage roomfor 10 strings. Valid indexes for arrays in C# are 0 to n – 1, where n represents the number ofelements in the array. The second example constructs an array of integers using an initializerlist. The initializer list provides the values for the array and the extent. The size of integers is6, and valid indexes are 0 to 5. In C# you can specify both the number of elements and aninitializer list for arrays, but you don’t need both.

Defining InterfacesInterfaces allow you to define an adapter to your classes. In the object-oriented world,we have inheritance, aggregation, and association. Inheritance describes a relationshipwhere a new thing is a generalization of an existing thing. For example, a Jeep is ageneralization of an automobile and can be described using inheritance. Jeeps havetransmissions, demonstrating an aggregation relationship. A Jeep may also have a driver,which is an example of an association relationship. The transmission belongs to the Jeep,but the driver does not.

Interfaces describe relationships that can sometimes be defined in other ways, likeinheritance, aggregation, or association, but may best be described as supporting capabilities.For example, stereo equipment in a car may have knobs that allow you to tune radio stationsor pick tracks on a CD. But, what if that same stereo equipment allows you to tune an FMstation without touching the knobs directly on the device? We could say that stereo equip-ment that supports tuning in general has a tuning interface. As a result we can affix othercontrols that tap into the stereo interface. Perhaps an infrared device or controls on thesteering column could use the interface to perform the same function as the controls physicallyon the stereo equipment.

The knob on the stereo is a physical control on the stereo. The volume is an attribute. Theability to change the volume can be implemented as a method or property method, either ofwhich in turn could be used to support an interface for changing volume. Other devices could

Page 51: Advanced c sharp programming

implement the same interface, which we’ll call Audio. Other unrelated devices could supportthe Audio interface: televisions, CB radios, cell phones, telephone, stereo, or communicationsequipment in an airplane. Suppose we create a universal remote control that can talk to anydevice that implements the Audio interface. Any device that supported the audio interfacecould be adjusted—made louder or quieter—with the same remote control. Now, yoursurround sound, big screen TV, and tuner could be controlled by the same device.

The basic syntax for defining an interface is similar to that for defining a class. The nextcode fragment demonstrates the basic syntax by implementing the Audio interface. (Byconvention we will name the interface with an I prefix.)

public interface IAudio

{

void AdjustVolume( int value );

}

The interface definition has an access modifier; in the example the access modifier ispublic. The keyword interface follows the access modifier, and the name of the interfacecompletes the interface header. Members of an interface do not use an access modifier buthave a procedure heading for each member of the interface. Every class that implements aninterface will have to implement all of the methods defined by the interface.

Implementing InterfacesSuppose you have defined an interface or want to implement an existing interface. You willneed to indicate that the class will implement the interface. The following listing demonstratesan interface and a partially complete class that implements the interface.

public interface IAudio

{

void AdjustVolume(int value );

}

public class Radio : IAudio

{

private int volume = 0;

void IAudio.AdjustVolume(int value)

{

volume += value;

}

}

The class Radio indicates that it is implementing the IAudio interface using theinheritance syntax in the class header. (The syntax for inheritance and implementinginterfaces is the same: add a colon and the name of the interface in the class header.) Whenyou indicate that you are implementing an interface, you are entering a contract that states

2 4 A d v a n c e d C # P r o g r a m m i n g

Page 52: Advanced c sharp programming

that you will add methods defined by the interface. The notable difference between methodsand methods that implement an interface is that you must include the interface name andmember-of operator in the procedure header for all interface methods. The procedure headervoid IAudio.AdjustVolume(int value) indicates that this implementation of AdjustVolume issatisfying the contract between the IAudio interface and the Radio class.

If you are implementing more than one interface, then add the additional interfaces to theclass heading, delimiting each additional interface with a comma.

It is important to remember that when you implement an interface you aren’t getting anymethods or data from the interface. Also, keep in mind that only methods can be described inan interface; you may not describe fields or properties in interfaces.

InheritanceWhen you define an inheritance relationship there are two classes involved at a minimum.One class is the parent and the second is the child. (The relationship between parent and childis also called superclass and subclass, where superclass is synonymous with parent andsubclass is synonymous with child.) Such a parent-child relationship is referred to as an IsArelationship. For example, I am the offspring of my father (and mother), so I am a Kimmel(and Symons), inheriting the DNA goop that comes from Kimmel’s (and Symons’).

When you define an inheritance relationship in C#, the child class gets all of the membersdefined in the parent class. When you inherit, you get all of the fields, properties, andmethods defined in the parent. Unlike chromosomal inheritance, object-oriented inheritancemeans you get everything.

I think most of you understand inheritance, but there is one more description that I’d liketo introduce because inheritance is an important concept. If class B inherits from class A,then, logically, A intersection B would be class A.

The notation for inheritance is identical to that for interface inheritance. To indicate thatclass B inherits from class A, you would write the class header as follows:

Public class B : A

Unlike with interface inheritance, when you inherit from a class, you do not have toimplement any methods from the parent class because they are already implemented inthe parent class.

Inheritance TerminologyThere are a lot of terms associated with inheritance relationships. Since you will encounterthem in this book, I will review several of them here briefly.

� If class B inherits from A, then A is the parent and B is the child.

� If class B inherits from A, then A is also referred to as the superclass and B isthe subclass.

� Inheritance relationships satisfy the IsA test. In our example we would say that B IsA A(although it is grammatically horrendous).

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 2 5

Page 53: Advanced c sharp programming

� Inheriting is also referred to as generalizing.

� Siblings are classes that inherit from the same parent.

� Very seldom, but occasionally, you will hear people refer to grandparents, ancestors,and offspring when talking about inheritance relationships.

It is possible that all of these ways of referring to the same thing, inheritance, can lead toconfusion.

As far as I know, no one has referred to second cousins or great aunts when talking objectinheritance relationships, but these terms could be used and should be used in such a way asto describe the same relationships as when the terms are used to describe human kinship. Forexample, a cousin would be the child of your parent’s sibling.

General Guidelines for InheritanceYou’re the boss when it comes to inheritance. From experience, I have included someguidelines for using inheritance relationships, but ultimately you will have to decide on acase-by-case basis.

� If you define a class and later decide that it needs to be modified, then considerinheriting from the original class to add the new code.

� Try to keep your inheritance relationships shallow and broad. There is no perfect ratiobetween the numbers of siblings to children, but there usually are more siblings thanoffspring in established frameworks.

� Investigate Refactoring. Refactoring is the best way invented so far to guide programmersin simplifying existing code. Essentially, Refactoring is an organized approach to factoringout common code. (Unfortunately, we cannot elaborate on Refactoring here, but Refactoringswill be used to produce the code listings in this book.)

� Don’t be afraid to experiment and change your mind.

� Explore existing frameworks to get a sense of proportion. (Excellent frameworks toexplore include Delphi’s VCL and the .Net Framework.)

Inheritance ExampleYou will find dozens of examples of inheritance throughout this book, and you can look inthe CLR for many more examples. To be thorough, we will continue with our radio exampleto demonstrate the basic technical aspects of inheritance. The next listing demonstratesseveral classes that all have the same root, our Radio class.

public interface IAudio

{

void AdjustVolume(int value );

}

public class Radio : IAudio

{

2 6 A d v a n c e d C # P r o g r a m m i n g

Page 54: Advanced c sharp programming

private int volume = 0;

void IAudio.AdjustVolume(int value)

{

volume += value;

}

public int Volume

{

get

{

return volume;

}

set

{

volume = value;

}

}

private double station = 94.1;

public double Station

{

get

{

return station;

}

set

{

station = value;

}

}

public void Receive()

{

}

}

public enum RadioBand

{

AM, FM

}

public class AMFMRadio : Radio

{

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 2 7

Page 55: Advanced c sharp programming

private RadioBand band = RadioBand.AM;

public RadioBand Band

{

get

{

return band;

}

set

{

band = value;

}

}

}

public class CommunicationsRadio : Radio

{

public void Transmit()

{

}

}

The code listing provides a couple of examples of inheritance. CommunicationsRadio andAMFMRadio inherits from Radio. AMFMRadio introduces the notion of an AM and FMband, and the CommunicationsRadio introduces the ability to transmit as well as the inheritedability to receive.

Encapsulation and AggregationEncapsulation and aggregation refer to the ability of classes to contain members. Morespecifically, aggregation refers to the ability of classes (and structures) to contain memberswhose types are also classes (or structures).

My favorite example of encapsulation is the relationship between the human body andthe bladder. Thankfully, the bladder is encapsulated inside of the human body. Could youimagine a world where people could express their displeasure with you by squeezing yourbladder? “Ouch, that’s me foot!” Squeeze.

What a mess.Clearly, the bladder is a class. It performs a function and has state. We know what the

function is, and we often feel the state at the most inopportune times. Thankfully, the bladderis encapsulated behind skin and bone and an in-born sense of personal space.

This is how you should view encapsulation and aggregation. Encapsulation allows youto put data safely inside of classes, limiting access and avoiding embarrassing situations.Aggregation allows you to put complex types safely inside of classes, again limiting accessand avoiding embarrassing situations.

Figure out where a particular data element should go by its semantic meaning and placethat data inside of an appropriate class. A good way to think about where to put data is by

2 8 A d v a n c e d C # P r o g r a m m i n g

Page 56: Advanced c sharp programming

thinking about whose job it is to own and protect the data. (Don’t let your neighbor have yourbladder.) Think of aggregation as adding complex types—read classes—to new types. Thebladder is a complex type, and the human body has a member that is a bladder.

In an expert medical system, implementing a class that represents the human body andthe bladder might be appropriate. However, for convenience, we will expand our Radioexample to demonstrate aggregation and encapsulation. The listing demonstrates a newclass BoomBox that has an AMFMRadio. The AMFMRadio demonstrates encapsulationand aggregation.

public class BoomBox

{

private AMFMRadio radio;

public AMFMRadio Radio

{

get

{

return radio;

}

}

BoomBox()

{

radio = new AMFMRadio();

}

}

PolymorphismPolymorphism has been described in many ways. Several times I have seen reference to itsGreek roots, given as “many forms,” and sometimes the descriptions are long and complex.I am going to try something a little different. I am going to use an analogy that I hope makessense to you.

Suppose you have a household chore that is suitable for a child to do. I have four children.We start them on chores at eighteen months. Yes, a toddler can empty wastebaskets at eighteenmonths. Cruel, perhaps, but functional. Here is the analogy.

You are watching the Superbowl. It is the fourth quarter with two minutes to go, and yourteam has the ball but is down by 8 points. Not only do they have to make the touchdown, butyour team has to get the two-point conversion. To sweeten the tension, you have $500 betwith a bookie for your team to win. Now, you’re out of beer. You need a beer, but you can’tleave the game because by the time you get back, the game will be over. You need a kid tomake the beer run, and any kid will do.

That’s polymorphism.When you have a general problem that can be solved by any provider within a common

set of providers, then you need polymorphism.

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 2 9

Page 57: Advanced c sharp programming

3 0 A d v a n c e d C # P r o g r a m m i n g

If beer and football analogies don’t make sense to you, don’t worry; they don’t makemuch sense to me either. Let’s try a more technical approach (for people who watch TLCinstead of Monday Night Football).

Remember that I said that inheritance is also referred to as generalization? Well, ifyou declare something as the general (or parent) type and instantiate—create instancesof—specific types, then you have more options regarding the specific type that you create.A good example of this is demonstrated by the EventHandler delegate. The EventHandlerdelegate is defined to take an object as the first parameter. The object class is the root of allclasses. Effectively, this means that any object can be passed to satisfy the sender parameterof an EventHandler method. This is polymorphism: declare general types and actually usespecific types.

Using ModifiersModifiers address information hiding. Information hiding exemplifies the cliché “out ofsight, out of mind.” That is, as a producer, if you can conceal something from a consumer,then they don’t have to worry about it. Hence, out of mind.

Access modifiers allow you to put things out of mind when you are programming. Whenproducing classes, make only those things public that consumers need to interact with to use theclass. By limiting what you expose in the public interface, you are making it easier to consumeyour classes. This is true even if you are the first consumer of the classes you produce.

There are several access modifiers to choose from. C# supports public, protected, internal,protected internal, private, and read-only for fields. The bulleted list briefly describes thepurpose of each access modifier.

� The public access modifier provides unrestricted access to types and members.

� The protected access modifier provides unrestricted access internally and to childclasses and restricted access to consumers. The protected access modifier can beapplied to nested types.

� The private access modifier provides the most restricted access. Only the producer of aclass may use its private members. Consumers and children have no access to privatemembers. The private access modifier can be applied to nested types.

� The internal access modifier means that types and members are accessible only withinthe same assembly. The internal modifier is used to allow code to cooperate internallyacross class boundaries without exposing those elements to consumers outside ofthe assembly.

� The protected internal modifier limits access to the same assembly for nested types.

When you are declaring top-level types, you must use the public or internal accessmodifier. You may use private and protected access for types only when those types arenested. A nested type is a type definition that contains a type definition.

You may not combine access modifiers, except for the protected internal modifier. Youmay not use access modifiers on namespaces. If you don’t specify an access modifier, then a

Page 58: Advanced c sharp programming

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 3 1

default access level will be used. It is best to specifically indicate the level of access ratherthan relying on a default access.

Using Class ModifiersWhen you define classes and the enclosing element is a namespace, then the class can bedefined to have public or internal access. If you don’t specify a modifier for a top-level class,then the class will have public access.

Nested classes can be defined with any access modifier, but you may only use protected,protected internal, and private access modifiers on nested classes.

Class access modifier rules apply to any struct and enum types, too.

Using Member ModifiersYou may use any access modifier on members of types, too. If you define members withoutan access modifier, then members are private by default.

Operator OverloadingOperators are tokens that perform an operation on data. Data are referred to as operandswhen they are the subject of an operation. Common operators, like the addition operator, +,exist for value types. An example of a value type is an integer, like 5. It is intuitive toperform operations like 5 + 4 on integer types.

Unfortunately, all of the possible types that may exist cannot be anticipated; but when typesare invented, it may also be intuitive to perform simple operations on those types, too. Anexample is demonstrated by the string concatenation operator. When you encounter code like

string s = "overloaded operators " + "are cool"

it is intuitively understood that the statement is performing string concatenation.Overloaded operators are methods. Specifically, they are methods with a special syntax.

You can understand operators by thinking of overloaded operators as two parts. The first partimplements a regular named method that performs the task that the operator would perform.The second part defines the operator method using the special syntax and calls the namedmethod inside of the operator. (Of course, you could always skip overloading operators, butthat would be no fun.)

Coding Operator MethodsThe procedure heading for operator methods is very similar to regular methods. Specify areturn type and argument types and names; but, instead of a method name, you use thekeyword operator and the operator token where you would have used the method name.

Borrowing from the help file examples, we could implement a complex number class.(Complex numbers have a real and imaginary part.) We could implement an overloaded

Page 59: Advanced c sharp programming

3 2 A d v a n c e d C # P r o g r a m m i n g

addition operator that adds the real parts and the imaginary parts of two complex numbers,yielding a new complex number that is the sum of the two added complex numbers.

public static Complex operator +(Complex c1, Complex c2)

{

return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);

}

The code listing is extracted from a struct that defines a complex number as a number thatis initialized with a real number and scalar part of the imaginary number. For example, if youconstruct a complex number with 2 for the real argument and 3 for the imaginary part, thenyou get the complex number 2 + 3i. The overloaded operator in the preceding exampledefines the + operator for Complex types as a static type that takes two Complex objects andreturns a new Complex object that is the sum of the two arguments. In the example, youwould invoke this operator+ method by writing a statement that adds two complex numbers.

Complex c = new Complex(4, 3);

Complex d = new Complex(1, 2);

Complex e = c + d;

The first two statements create instances of two complex numbers. The final statementinvokes the operator+ method, returning a new object to the variable e. The best way to get ahandle on overloaded operators is to write some simple examples and place breakpoints onthe statements that invoke the operator and in the operator method. You will quickly seethat the operation results in a method invocation.

Overloading Operator Guidelines and LimitationsBe circumspect about defining overloaded operators. The most important thing to avoiddoing is creating operators that are counterintuitive. For example, overloading the additionoperator to perform subtraction. Don’t do this. Following these basic guidelines foroverloading operators will keep you out of trouble:

� Define methods that perform the operation using the op_ prefix followed by the nameof the operator to support languages like Visual Basic .NET that don’t support operatoroverloading.

� Implement the overloaded operator by calling the named method. (Why codify theoperator behavior twice?)

� Overload an operator only in the class the operator applies to.

� Use operator overloading when the operation is intuitive and the result seems logical.

� Overload operators symmetrically. If you implement addition, then implementsubtraction.

Table 1-1 lists operators and indicates whether they can be overloaded or not. Not alloperators can be overloaded.

Page 60: Advanced c sharp programming

C h a p t e r 1 : L a n g u a g e F o u n d a t i o n s 3 3

Overloaded operators allow you to define special methods that make code simpler to writeand more intuitive. You will not need to write overloaded operators frequently, but you willbe using them as a consumer of CLR code quite frequently.

AttributesIt is idioms like interface and attributes that get us into trouble as programmers. The reason isthat these terms do double duty. For years, “interface” meant simply the members declared ina class. When COM was introduced, interface took on another meaning; interface became aconstruct—a lot like a class. (For VB programmers, interfaces and classes blur a great deal.)

The same problem exists for the word “attribute” and the idiom Attribute. In the generalsense, “attribute” is synonymous with “property.” An attribute is a property of a type. The.NET Attribute itself is actually a class. (There are likely to be as many confusing discussionssurrounding attributes as there are about parent and child, superclass and subclass, and objectand class.)

In C# an Attribute with a capital A is a class that adds metadata to your assembly.Metadata, or additional data, was added to help eradicate “DLL Hell.” Historically externaldata that an application needed was stored in a registry or INI file. Attributes allow thissupporting cast of data to travel with the assembly. Attribute classes allow you to add themetadata to your assemblies, and Reflection allows you to extract that metadata when youneed it. This is analogous to reading and writing from the registry. As a result of having themetadata cleverly folded into the assembly, deploying .NET applications is simpler thandeploying COM-based applications.

Examples of attributes can be found in the AssemblyInfo.cs module. An example is theAssemblyTitleAttribute class.

[assembly: AssemblyTitle("")]

Operator Overloadable+, –, !, ~, ++, – –, True, False Unary operators that can be overloaded.

+, –, *, /, %, &, |, ^, <<, >> Binary operators that can be overloaded.

==, !=, <, >, <=, >= Comparison operators that can be overloaded but must beoverloaded in pairs. (You will get a compiler indicating whatthe pair operator is and reminding you that you need tooverload the pair.)

&&, || These conditional logic operators cannot be overloaded.

[] The array operator cannot be overloaded, but you can definean indexer. (Refer to the section “Indexed Properties” formore information.)

+=, –=, *=, /=, %=, &=, |=, ^=, Assignment operators cannot be overloaded.

<<=, >>= The left-shift and right-shift operators cannot be overloaded.

=, ., ?:, new, is, sizeof, typeof These operators cannot be overloaded.

Table 1-1 The Overloadability of Operators

Page 61: Advanced c sharp programming

By adding text between the parentheses in the AssemblyTitleAttribute, you can add a titlefor your assembly that will show up in the Properties dialog. By convention, attribute classeshave an Attribute suffix, but the suffix is left off when the attribute is used. The assembly:tag indicates that this is an assembly-level attribute.

Because attributes are implemented as classes, you generalize existing Attribute classes tocreate new attributes for new uses. Attributes are used for many things, from providing hintsfor component properties to requesting security permissions. Many new attributes will becreated, but the rhythm for using Attributes is similar to that for constructing an object. Placethe name of the Attribute class immediately before the entity the Attribute describes and pass thearguments defined by the Attribute’s constructor. Constructor arguments are referred to aspositional arguments. You can also initialize properties in an Attribute object by passingnamed arguments.

ReflectionReflection is a capability implemented in .NET that allows you to dynamically discoverinformation, such as the namespaces, interfaces, classes, methods, properties, and fields inan assembly. In addition to providing a way to request the value of metadata described byattributes, Reflection allows you to find out about the types and members in types definedin an assembly, and Reflection allows you to emit Intermediate Language (IL) code atrun-time. IL is similar in intent to Java byte code. IL is the code before it is compiled intomachine-readable code.

Reflection is powerful mojo. We will explore it in detail in this book. Chapter 2demonstrates how to discover information about assemblies using Reflection, andChapter 10 demonstrates how to create custom attributes that can be read using Reflection.

SummaryThis chapter has provided an overview of core language foundations that you will need inalmost every application that you create. There will be 16 chapters and their attendantapplications that demonstrate these concepts in context. While the examples are brief in thischapter, the topics are central to all C# programming and you will find many examples ofthem in upcoming chapters.

3 4 A d v a n c e d C # P r o g r a m m i n g

Page 62: Advanced c sharp programming

CHAPTER

2Assembly Viewer

35

IN THIS CHAPTER:

Demonstrated Topics

Code Listing for the Assembly Manager

Secondary Topics

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 63: Advanced c sharp programming

3 6 A d v a n c e d C # P r o g r a m m i n g

This chapter demonstrates how to load assemblies dynamically and explore thoseassemblies using Reflection. Working with assemblies and Reflection in just thesecond chapter is a pretty tall order, but the assembly viewer will help you master

these subjects as well as build Windows applications using controls in the Windows Formsnamespace and GDI+.

Demonstrated TopicsThis chapter’s primary topic is using dynamic discovery to explore assemblies using Reflection.If you are able to dynamically load and use assemblies, you can take advantage of thin clientprogramming by loading assemblies over HTTP connections, which supports automaticdeployment and updates of Windows Forms–based applications. There will also be ampleexamples of the following topics:

� Working with value types

� Using the StringBuilder class

� Defining static methods

� Defining classes and inheritance

� Defining and implementing interfaces

� Using the IEnumerator interface

� Multithreading safely in Windows Forms

� Programming with the Graphics object

� Debugging with System.Diagnostics

� Loading information from a resource, which facilitates internationalization

� Basic printing using PrintDocument

Code Listing for the Assembly ManagerIn this chapter we will be discussing at length the code for AssemblyViewer.sln, which isavailable on the McGraw-Hill/Osborne web site, www.osborne.com. The complete coderequires 43 pages to list completely, so to avoid padding the book, we will reproduce here inListing 2-1 just the salient portions. Fortunately, you will be able to easily copy the classesand methods that are listed, or you can cut and paste them from the source code postedon www.osborne.com.

The AssemblyManager.cs module contains the AssemblyManager class. This classdemonstrates how to load and explore assemblies using Reflection.

Page 64: Advanced c sharp programming

C h a p t e r 2 : A s s e m b l y V i e w e r 3 7

Listing 2-1 AssemblyManager.cs manages loading and reading the assembly.

1: using System;

2: using System.Reflection;

3: using System.Diagnostics;

4: using Diag = System.Diagnostics;

5: using System.IO;

6: using System.Collections;

7: using System.Text;

8: using System.Windows.Forms;

9:

10: namespace AssemblyViewer

11: {

12: /// <summary>

13: /// Summary description for AssemblyManager.

14: /// </summary>

15: public class AssemblyManager

16: {

17: private string name = "";

18: private Assembly assembly;

19:

20: public AssemblyManager(string assemblyName)

21: {

22: Debug.Assert(File.Exists(assemblyName));

23: Name = assemblyName;

24: }

25:

26: public Assembly Assembly

27: {

28: get

29: {

30: return assembly;

31: }

32: }

33:

34: public string Name

35: {

36: get

37: {

Page 65: Advanced c sharp programming

3 8 A d v a n c e d C # P r o g r a m m i n g

38: return name;

39: }

40: set

41: {

42: if(name.Equals(value)) return;

43: name = value;

44: Changed();

45: }

46: }

47:

48: private void Changed()

49: {

50: Debug.Assert(File.Exists(name));

51: assembly = null;

52: assembly = Assembly.LoadFrom(name);

53: }

54:

55: private string Formatted(Type type)

56: {

57: const string mask = "{0} \r\n";

58: string result = string.Format(mask, type.Name);

59: Broadcaster.Broadcast(result);

60: return result;

61: }

62:

63: private string Formatted(MemberInfo info)

64: {

65: const string mask = "\t{0} {1}\r\n";

66: string result = string.Format( mask,

67: info.MemberType.ToString(), info.Name );

68: Broadcaster.Broadcast(result);

69: return result;

70: }

71:

72: private string Load()

73: {

74: StringBuilder str = new StringBuilder();

75: IEnumerator outer = assembly.GetTypes().GetEnumerator();

76:

77: while(outer.MoveNext())

78: {

79: Type type = (Type)outer.Current;

Page 66: Advanced c sharp programming

80: str.Append(Formatted(type));

81:

82: IEnumerator inner = type.GetMembers().GetEnumerator();

83:

84: while(inner.MoveNext())

85: {

86: str.Append(Formatted((MemberInfo)inner.Current));

87: Application.DoEvents();

88: }

89: }

90:

91: return str.ToString();

92: }

93:

94: public string Text

95: {

96: get

97: {

98: return GetText();

99: }

100: }

101:

102: public string GetText()

103: {

104: return Load();

105: }

106:

107: }

108:}

using StatementsThere are two places you will see the using keyword. The using statement is employed toimport namespaces and as a block statement for managing objects.

Listing 2-1 demonstrates how to use namespaces. Think of a using statement as adding areference to a DLL. Assemblies in the framework are actually contained in DLL files; thus

using System.Reflection;

refers to a DLL that begins with the word system.Assemblies can contain multiple namespaces. For example, System.Reflection is contained

in the System.dll assembly. However, System.Windows.Forms is actually the name of theassembly containing the Windows Forms controls. You can explore assemblies in the .NETframework by navigating to C:\WINNT\Microsoft.NET\Framework\v1.0.3705\.

C h a p t e r 2 : A s s e m b l y V i e w e r 3 9

Page 67: Advanced c sharp programming

4 0 A d v a n c e d C # P r o g r a m m i n g

Creating an Alias with the using DirectiveYou can create an alias for namespaces with the using directive. Changing

using System.Diagnostics;

to

using Diag = System.Diagnostics;

creates an alias Diag for the System.Diagnostics namespace. Wherever you might use thefully qualified System.Diagnostics namespace, you can replace it with the alias Diag.

Defining a using ScopeThe second way using is used is similar to the with statement in VB.NET. When you employ theusing statement in code with an object, you are defining a scope that will implicitly disposeof the object at the end of the using block. The object will be disposed of even if an exceptionis thrown before the end of the using block, as long as the object created in the using blockimplements the IDisposable interface.

IDisposable is an interface that has a single method, the Dispose method. ImplementIDisposable if you need deterministic cleanup. For example, when you open a file, you willwant to close it as soon as you are finished with the file. The following fragment demonstrateshow to use a using block to define a block that will dispose of an object at the end of the block.

using (FormAbout form = new FormAbout())

{

form.ShowDialog();

}

The using statement declares and creates an instance of the object. The instance namereferenced in the using statement is used in the block, and when the block exits, the form’sDispose method is called. (To verify this, overload the inherited Dispose method and place abreakpoint in the Dispose method. If you are not sure how to overload methods, refer to thesection “Defining the Interface” later in this chapter.)

Defining Your Namespace NameA few years ago classes were the highest level of abstraction. Namespaces provide a way toorganize classes in higher level relationships and to create unique names. Namespaces arehousekeeping tools.

NOTEProgIds are used for purposes similar to those of namespaces. However, ProgIds are limited to 39 charactersand are not guaranteed to be unique. ProgIds, which may contain no punctuation except a period, are stillsupported for COM Interop.

Page 68: Advanced c sharp programming

When you create an application, the name you give it will be used as the default namespacename. Every module you add to that project will be given the same namespace as defined bythe project. If you add a new project to the solution, the new project will get a namespacename identical to the project name.

Namespaces are public, and you may not add an access modifier to namespaces. You candeclare class, interface, struct, enum, delegate, and other namespace types inside of a namespace.

Line 11 of Listing 2-1 indicates that the AssemblyManager is defined in the AssemblyViewernamespace. If you don’t specify a namespace, the default namespace is used. You can modifythe default namespace in the General Property Pages for the AssemblyViewer project (seeFigure 2-1).

Defining ClassesLine 15 of Listing 2-1 contains the class header for the AssemblyManager class.AssemblyManager is defined as a public class and is the only class in the listing. Mostof the time the classes you define will have public access. Line 108 defines the closingblock for the AssemblyManager. (Refer to Chapter 1 for general information on classes.)

15: public class AssemblyManager

108:}

C h a p t e r 2 : A s s e m b l y V i e w e r 4 1

Figure 2-1 Viewing the default namespace in Visual Studio .NET

Page 69: Advanced c sharp programming

Implementing ConstructorsConstructors are the only members of a class that can have the same name and case as theclass. Constructors use an access modifier and can have arguments to initialize an instancebut do not define a return type. As a general rule, keep constructors short and to the point.

Lines 19 through 23 of Listing 2-1 implement the constructor for the AssemblyManager.Line 21 ensures that the assembly file exists, and line 22 assigns the assemblyName argumentto the Name property. Two strategies you can employ to make your constructors behavereliably, just as we did in the AssemblyManager constructor, are discussed in the followingsub-sections.

Guarding Your Code with Assert Line 21 calls the static method File.Exists, returning aBoolean value indicating whether the file represented by assemblyName exists. The staticmethod Debug.Assert is defined in the System.Diagnostics namespace. Assert is a developmenttool that you can use as a sentinel. While the conditional compilation constant DEBUG isused, the Assert method is turned on. This means that you can leave debug code in place anduse conditional compilation to toggle debug code.

When the condition evaluated in the Assert method fails, the Call Stack is displayedin a dialog box, enabling you to continue to run the application or abort. The purposeof the Assert method is to provide the developer with an early warning that an assumptionmade about the state of the system failed. (Write a simple program that calls System.Diagnostics.Debug.Assert (false) to see the Call Stack Trace dialog.)

Initializing Object Properties The purpose of a constructor is to initialize the state of an object.The constructor on lines 19 through 23 of Listing 2-1 initializes the AssemblyManager. Line 22does a bit more than it seems to do. If you look closely, you will notice that Name is anattribute of the AssemblyManager class. If convention is followed, then we know that theuppercase attribute is a property.

Name = assemblyName invokes the property setter for the Name property on lines 33 to 44.If you look at the setter method, you will see that if the underlying field equals the valueparameter, then we exit the setter. The parameter value is implicit; you just need to knowvalue is the name of the parameter passed to setters and has the data type of the property.Because the setter does more than change the field value, we use the if-conditional as a sentinelto prevent unnecessary work.

If value is different—that is, if value represents a different assembly—then we invoke theChanged method. Instead of actually performing the work in the setter, we use a method. Whena new assembly name is provided, the Changed method loads the assembly.

The Changed method is defined on lines 47 to 52 of Listing 2-1. Again, we check to see ifthe assembly exists. We might change the name after the AssemblyManager is created. (Wecould get rid of the original check in the constructor, if we choose to.) The old assembly isassigned to null, and the new name is used to load the assembly. Assembly.LoadFrom is astatic method that returns an initialized instance of the assembly.

4 2 A d v a n c e d C # P r o g r a m m i n g

Page 70: Advanced c sharp programming

C h a p t e r 2 : A s s e m b l y V i e w e r 4 3

Walking the Assembly Using ReflectionThe workhorse of the AssemblyManager class is the Load function. Load uses enumeratorsto walk all of the types defined in the assembly and an enumerator for each of the types towalk the members of that type. Using the nested loops, we get all of the types and all of thosetypes’ members. This information is formatted and returned as a string, which allows us tocreate a simplified interface for the class (see the Text property and the GetText method inListing 2-1).

Load is defined on lines 69 to 90 in Listing 2-1. Line 70 instantiates a StringBuilderobject. (StringBuilder is defined in the System.Text namespace.) StringBuilder is used tofacilitate adding a lot of formatted string information into a single string buffer. You coulduse the string value type, but some simple comparative trials indicate the StringBuffer worksfaster for many string operations.

The reason StringBuilder is faster than String for many string concatenation operations isthat StringBuilder is mutable and String is immutable. That is, StringBuilder is designed forconcatenation, but when you concatenate a string, a new string object is created for eachoperation. Hence, the following

string str += "Hello, World!";

performs a String.Concat (static) operation that returns a new string instance, whereas theStringBuilder is just packing the additional string information into the StringBuilder buffer.No new StringBuilder objects are created.

What Is Reflection?Reflection is the ability of .NET to discover Run Time Type Information about classes. However,it is significantly much more than that. With Reflection, you can discover everything aboutthe types and members defined in assemblies, as well as load and run those assembliesdynamically. On top of all of this, you can use the System.Reflection.Emit namespace to emitIntermediate Language (IL) to build types at runtime.

Reflection is a capability whose flexibility and power have not been truly absorbed by theIT community at large. There will likely be many tools built on this technology when it iscompletely understood. For now, let’s leave the definition of Reflection to the descriptionprovided in the preceding paragraph and Chapter 1. Reflection will also be discussed inChapters 5 and 10.

Using ReflectionWhen you have an instance of an assembly object, you use Reflection by asking the assemblyfor collections that contain the members defined in an assembly. When you have an instanceof a type, you ask the type for collections representing the Method, Field, Event, Property,Constructor, and Parameter information. Each of these types—like FieldInfo—describes theelement of the type referred to.

Page 71: Advanced c sharp programming

There are two straightforward ways to get an instance of an assembly object. The first is tocall Assembly.LoadFrom, passing the name of an assembly, and the second is by calling thestatic method Reflection.Assembly.GetCallingAssembly. An example of the first method canbe found in the Changed method in Listing 2-1, and the second method returns the assemblycontaining the call GetCallingAssembly.

After you have a reference to the assembly you want to explore, you need to invoke themethod that contains the information you want to obtain. For example, if I want to get all of thetypes defined by an assembly, then I declare an array of Type and invoke assembly.GetTypes(),where assembly refers to any instance of an assembly. The AssemblyViewer application uses anenumerator to walk these types.

What Are Enumerators? It is important to understand what enumerators are and why youmight use them. When you write code that iterates over data in a collection, list, or array,you might usually use a for or while loop. However, neither the for or while construct is anobject; these are statements. Statements cannot be passed as parameters to methods, butenumerators, which are objects, can be passed as parameters to methods. Ah!

Implementing enumerators for things that are iterated—like the characters in a string, therows in a DataSet, or the members of an array—provides you with a single common way ofjust iterating. Using enumerators also means that you can pass an enumerator to a method. Whenmight you want to pass an enumerator to a method, you ask? A very insightful question,grasshopper. When you write an algorithm that is the same regardless of the data type.

Examples of algorithms that are the same regardless of data type are things like sortingalgorithms. If I pass an enumerator that allows me to iterate over data rather than an arrayof a specific type, then I write the algorithm once and pass various instances of enumerators.Using an enumerator is useful for more than just sorting algorithms. You can and should defineany method to take enumerators when the implementation of that method is data independent.

The .NET framework defines the IEnumerator interface. Any class that supports enumeratingits elements can implement the IEnumerator interface. IEnumerator requires that the consumerimplement MoveNext, Reset, and Current. MoveNext can be used as the loop control method,returning a Boolean, and the Current property returns the currently referenced object. Resetmoves the internal reference index to the first element.

Using Enumerators to Walk the Assembly Information Types that implement the IEnumeratorinterface usually have a GetEnumerator method. For example, Assembly.GetTypesreturns an array of Type. The System.Array implements IEnumerator and System.Array.GetEnumerator returns an IEnumerator.

To enumerate each type in an assembly, you would write code similar to the following:

IEnumerator enumerator = assembly.GetEnumerator();

While(enumerator.MoveNext())

{

// do something with enumerator.Current;

}

4 4 A d v a n c e d C # P r o g r a m m i n g

Page 72: Advanced c sharp programming

C h a p t e r 2 : A s s e m b l y V i e w e r 4 5

The Load method in Listing 2-1 demonstrates how an enumerator for the Types andMemberInfo array is returned by Type.GetMembers.

GetMembers returns an array of MemberInfo. Elements in the MemberInfo array includeproperties, fields, methods, and events. If you want to get an array of just the methods, thenyou could invoke Type.GetMethods. The same is true for other member types.

Executing Code Discovered by Reflection The power provided by Reflection is more than anability to find out what assemblies contain. You can actually invoke the types and membersdefined in assemblies and discovered by Reflection. The next listing is an example ofdynamically loading an assembly and invoking a static method by Reflection.

const string s =

"c:\\winnt\\Microsoft.NET\\Framework" +

"\\v1.0.3705\\System.Windows.Forms.dll";

Assembly assembly = Assembly.LoadFrom(s);

Type type = assembly.GetType(

"System.Windows.Forms.MessageBox", true);

type.InvokeMember("Show", BindingFlags.Public |

BindingFlags.InvokeMethod |

BindingFlags.Static, null, null,

new object[] {"Invoked by Reflection"});

The first statement defines a constant path to the System.Windows.Forms.dll assemblydefined by the framework. The second statement uses the Assembly.LoadFrom method toload the assembly—Listing 2-1 provides another example of this. The third retrieves the metatype of the MessageBox class, and the final statement invokes a member in the MessageBoxclass. The first argument is the name of the member to refer to. The third member Orsthree enumerated values together, indicating that Show is a Public, Static method that wewant to invoke. The first null argument can be replaced with a subclass that describes howto bind the method, and the second null is an instance of the type that you want to invoke themethod on. For example, if Show were a non-static method, then we would need to create aninstance of the MessageBox class and pass it for this argument value. The final argument isan array of parameters that will be passed to the invoked method.

Broadcasting OperationsListing 2-1 contains a statement Broadcaster.Broadcast. This is a technique I derived forsending internal information to a single location, allowing listeners who want to know whatis going on to tune into the broadcaster. As an example of this, we could designate the mainform as a listener and display the status of operations on the main form. The AssemblyViewerdoes exactly this; the main form has a status bar that is used to display the progress of theAssemblyManager during a load operation.

Page 73: Advanced c sharp programming

NOTEThere are existing facilities for writing to the Event Log using debug and trace listeners. However, these toolsare not shipped with an application when the conditional compilation directives DEBUG and TRACE are notdefined in the application. Usually you do not ship applications with these constants defined. (You can andshould write critical information to the EventLog in a shipped application; you just won’t use trace listenersto do so. Use the EventLog class for deployed event logging.)

Listing 2-2 shows the Broadcaster class and the IListener interface.

Listing 2-2 The implementation of the Broadcaster class and the IListener interface.

1: using System;

2: using System.Collections;

3:

4: namespace AssemblyViewer

5: {

6: /// <summary>

7: /// Summary description for Broadcaster.

8: /// </summary>

9: public class Broadcaster

10: {

11: private static Broadcaster instance = null;

12: private ArrayList listeners = null;

13: protected Broadcaster()

14: {

15: listeners = new ArrayList();

16: }

17:

18: static private Broadcaster Instance

19: {

20: get

21: {

22: if( instance == null )

23: instance = new Broadcaster();

24: return instance;

25: }

26: }

27:

28: public static void Add(IListener listener)

29: {

30: Instance.listeners.Add(listener);

31: }

32:

33: public static void Remove(IListener listener)

4 6 A d v a n c e d C # P r o g r a m m i n g

Page 74: Advanced c sharp programming

C h a p t e r 2 : A s s e m b l y V i e w e r 4 7

34: {

35: Instance.listeners.Remove(listener);

36: }

37:

38: public static void Broadcast(string message)

39: {

40: IEnumerator enumerator = Instance.listeners.GetEnumerator();

41: while(enumerator.MoveNext())

42: {

43: if(((IListener)enumerator.Current).Listening())

44: ((IListener)enumerator.Current).Listen(message);

45: }

46: }

47: }

48:

49: public interface IListener

50: {

51: bool Listening();

52: void Listen(string message);

53: }

54: }

The Broadcaster class and the IListener interface shown in Listing 2-2 are used to centralizethe sharing of information between objects and the GUI. Based on the implementation of theBroadcaster, any class that implements IListener and registers with the Broadcaster is capableof receiving string data sent to the Broadcaster. The class and the interface demonstrate a fewsalient techniques, so we will examine each.

Implementing the BroadcasterThe Broadcaster demonstrates the Singleton pattern. A Singleton is a class that you want toensure has only one instance. Often, this pattern is used to reflect the existence of a singleresource, like a single printer. A Singleton class is created by making the constructor protectedor private and providing access only through a static member. The Broadcaster does thisthrough the internal, private Instance property.

Every public method in Broadcaster is static. When you invoke a public member in theBroadcaster (or any Singleton), the method references the read only Instance property. Instancechecks to see if the single instance of the Broadcaster has been created. If the Singleton doesn’texist, then one instance is created and assigned to the private field instance. The Singletonobject is then returned. The result is that the Broadcaster contains a reference to an instanceof itself.

If you add an IListener to the ArrayList of listeners, then every time the static methodBroadcast is called, all listeners who are listening will receive the string content. This strategycan be employed to display internal status information, write to a log file, trace your deployed

Page 75: Advanced c sharp programming

application, or to perform some combination of these tasks. (It is recommended that you useDebug and Trace listeners in conjunction with the Broadcaster idiom.) Also, notice that theIEnumerator is used to iterate the ArrayList of listeners.

Defining the InterfaceThe IListener interface defines two methods: Listening and Listen. Listening is a functionthat returns a Boolean indicating whether the object implementing the IListener interfacewants to receive messages. This allows the object to metaphorically plug its ears withoutunregistering from the Broadcaster. The Listen method is the method that will receive thestring content when listening.

You can use the Broadcaster by implementing the IListener interface. The FormMainclass implements IListener to display broadcasted messages in the status bar. In the exampleof the main form, you can add the form to the list of listeners in the Form’s Load. Anothergood place to add a control to the list of listeners is in the constructor of a class. Considerimplementing IDisposable and Dispose to remove the object from the Broadcaster’s listenerslist when the object is disposed of.

The following code fragment demonstrates the pieces you might encounter in a class thatimplements the IListener interface and receives messages from the Broadcaster.

public class FormMain : System.Windows.Forms.Form, IListener

{

private void FormMain_Load(object sender, System.EventArgs e)

{

Broadcaster.Add(this);

}

protected override void Dispose( bool disposing )

{

if( disposing )

{

if(components != null)

{

components.Dispose();

}

}

Broadcaster.Remove(this);

base.Dispose( disposing );

}

void IListener.Listen(string message)

{

ChangeStatus(message.Trim());

}

4 8 A d v a n c e d C # P r o g r a m m i n g

Page 76: Advanced c sharp programming

bool IListener.Listening()

{

return true;

}

}

In the example, we inserted the code to remove the form from the Broadcaster’s listenerslist in the protected Dispose method already provided for us when the form was added to theproject in Visual Studio .NET.

Secondary TopicsThe AssemblyManager contains code that loaded and walked the types and members of anassembly. There was a lot of additional code in the AssemblyManager class, as there will bein any application, and this code was described, too. We will now move on to secondarytopics for this chapter.

In this section, let’s take a look at the kinds of things you will commonly find inWindows Forms–based applications.

Value Types Versus Reference TypesWhether you use a string type or the StringBuilder class, you are using objects. Every thing,including what appear to be native types, is a class in .NET, and every class has the sameroot: Object.

To easily demonstrate that types like the string type are classes in .NET, declare a stringvariable and begin typing a line of code using the string variable and the member of anoperator. Intellisense will display the list of members that you can invoke on the string type(see Figure 2-2). Only types can have members; hence, the list of members clearly indicatesthat what appear to be native types are actually aggregate types.

There is a difference between types that appear to be native types, like int and string, andtypes that are obviously classes, like StringBuilder and the AssemblyManager. Types thatyou use like native types are descended from the ValueType class. Classes that are not valuetypes are called reference types.

There are some notable differences between value types and reference types. Types likestring and int that inherit from ValueType are allocated on the stack, and reference types areallocated on the heap. Allocating value types on the stack allows them to be created faster thanif they were created on the heap. Value types are not garbage collected, reducing the amountof workload on the garbage collector. When you need to invoke members on value types, aheap-allocated object is created behind the scenes, and the internal value of the value type iscopied to the heap-allocated object. When you are finished invoking members on a valuetype, the values in the heap-allocated object are copied back to the value type and the heapallocated object is destroyed. These internal operations of copying and copying back arereferred to as boxing and unboxing, respectively.

C h a p t e r 2 : A s s e m b l y V i e w e r 4 9

Page 77: Advanced c sharp programming

5 0 A d v a n c e d C # P r o g r a m m i n g

NOTEThink of stack memory as the memory made available to methods, and think of heap memory as the globalmemory pool. Stack memory is literally a chunk of memory referred to by your CPUs stack register, andheap memory is referred to by your CPUs data register.

The BoxingDemo.sln is a console application that demonstrates a scenario where boxingoccurs. It is easy to pick out when boxing occurs if you look at the Intermediate Language (IL)code. There is an IL instruction called “box” that is invoked. You can see the IL (in Figure 2-3)for the console application listed next.

using System;

namespace BoxingDemo

{

/// <summary>

Figure 2-2 Intellisense illustrates that types like string are classes.

Page 78: Advanced c sharp programming

/// Summary description for Class1.

/// </summary>

class Class1

{

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread]

static void Main(string[] args)

{

int I = 5;

string s = I.GetType().Name;

Console.WriteLine(s);

}

}

}

ValueType objects like int types do not carry their type information around with them.This is one of the reasons they are lightweight with respect to reference types. When theI.GetType method is invoked, boxing occurs and the type information is available asrequested. Notice that no special code was required; boxing occurred automatically behindthe scenes as a function of the CLR.

C h a p t e r 2 : A s s e m b l y V i e w e r 5 1

Figure 2-3 Intermediate Language code for the BoxingDemo.exe.

Page 79: Advanced c sharp programming

Windows Forms ControlsThe AssemblyViewer demonstrates several Windows Forms Controls. (It is important tonote, though, that because the AssemblyManager exists, we could wrap another presentationlayer around the AssemblyManager and create a console application or Web application.)The main form demonstrates the Form, StatusBar, MainMenu, OpenFileDialog, andRichTextBox controls. We’ll take a look at the FormMain module next.

The FormMain.cs contains 400 lines of code. To prevent chapter bloat we will refer toexcerpts from the code listing rather than provide the whole listing for FormMain.cs here.

Working with the Form ControlWhen you add a form to your project, the default namespace is wrapped around the classdefining the form. The class heading follows the namespace in the file that contains thedefinition of the form. The class heading for a Windows Form will demonstrate the syntaxfor inheritance, as each form you create will inherit from System.Windows.Forms.Form.The class heading is shown next.

public class FormMain : System.Windows.Forms.Form, IListener

The statement is understood to mean that FormMain inherits from System.Windows.Forms.Formand, since IListener is an interface, implements the IListener interface.

When you work with a form, you already have all of the members inherited from the baseForm class, and the Visual Studio .NET IDE presents two views for forms. There is the codedesigner view where you write and edit code, and there is the form designer view where youvisually design your graphical user interface. This dual view of graphic designer and code viewis what makes the combination of C# and Visual Studio .NET a rapid-application environment.

Adding Controls to Forms You can add controls to forms by dragging and dropping a controlfrom the toolbox to the Form Designer view. Dragging and dropping controls to the form isreferred to as “painting the interface.” Alternatively, you can add controls programmaticallyby declaring a reference to a control and creating an instance.

When you paint a control on a form, a field is added to the form’s class. TheInitializeComponent method is called by the constructor. InitializeComponent creates aninstance of the control. All of this code is added for you by the Form Designer and is usefulfor demonstrating the steps you would need to take to add controls dynamically. (We willreturn to dynamic control creation later in the book.)

The RAD part of all of this is that the Form Designer is generating code based on yourinteraction with the IDE.

Code Outlining When you open a form it may be difficult to locate the InitializeComponentmethod, even though this method is usually quite long. Look for a box containing the text“Windows Form Designer Generated Code.” The IDE uses code outlining to allow you tocollapse and expand regions of code. Since the Form Designer code is managed by thedesigner, this block of code is collapsed by default.

5 2 A d v a n c e d C # P r o g r a m m i n g

Page 80: Advanced c sharp programming

The collapsed state of the Form Designer code is suggesting that you should leave thiscode alone. Of course, you are welcome to expand the code and examine how controls arecreated and managed.

Region Directive Code outlining introduces the new region directive. The region directiveallows you to manually create logical code blocks that can be expanded and collapsed tofacilitate code housekeeping. The region directive uses the pragma operator # and is a blockstatement.

#region some text#endregion

Code between the #region and #endregion can be collapsed and expanded to organize yourcode. For example, you could place all of your private methods inside of a region directiveand collapse the lines of code containing the private members. The value of some text is astatement indicating what is inside the region, providing a hint to readers when theregion is collapsed.

Use the + and − symbols in the code view or the EditOutlining menu item to expand andcollapse code regions.

Implementing Event Handlers in Forms The easiest way to implement event handlers for aWindows Form is to select the control of interest in the Form Designer view. Press F4 to focusthe Properties window, and click the button with the lightning bolt in the Properties window.Clicking the lightning bolt switches the Properties window view so that it shows the eventsfor the selected object. To select another object, click a control on the Form Designer or selectthe alternate control from the list of objects at the top of the Properties window.

To allow the Form Designer to generate the event handler for you, double-click theeditable space adjacent to the name of the event that you want to generate code for.

When you double-click on the editable part of the Properties window, an event handlerwith the correct signature is generated for you. Additionally, the code that wires the eventto the delegate property is inserted in the designer-managed region of the form’s code, theInitializeComponent method.

FormMain implements the Load event. The code for the Load event—both the wire-upcode and the event handler—is shown next.

private void InitializeComponent()

{

// an excerpt from InitializeComponent

this.Load += new System.EventHandler(this.FormMain_Load);

}

private void FormMain_Load(object sender, System.EventArgs e)

{

Initialize();

}

C h a p t e r 2 : A s s e m b l y V i e w e r 5 3

Page 81: Advanced c sharp programming

The InitializeComponent method adds the Form_Load method generated by the FormDesigner to the invocation list of the Load delegate. (Remember: delegates are multicast,which means that more than one handler can be assigned to a delegate like Load.) TheFormMain_Load handler is generated by the designer, too. Again, the RAD part of all of this isthat double-clicking on the event in the Properties window is a lot faster than writing thepreceding code from scratch. Of course, you can write this code from scratch too, and therewill be times that you want to.

A good strategy is to create a simple method named Initialize to perform your initializationcode. This keeps the Load handler concise. In fact, this is a good strategy for event handlersin general: write a well-named method to perform the work for the handler. Doing so resultsin self-documenting code.

Writing Code When it comes to writing code there is no difference between writing code in aclass and writing code in a form class. A class is a class. Add fields, properties, methods, andevents based on need rather than the type of the class.

Using the StatusBarThe StatusBar control is used to display the status of an application, usually at the bottom ofa form. In the AssemblyViewer application, we target the StatusBar as the display control forbroadcasted messages. (Remember that FormMain implements the IListener interface.)

private void Initialize()

{

openFileDialog1.InitialDirectory = GetInitialDirectory();

Broadcaster.Add(this);

}

private void ChangeStatus(string status)

{

statusBar1.Text = status;

statusBar1.Refresh();

}

void IListener.Listen(string message)

{

ChangeStatus(message.Trim());

}

In AssemblyViewer, we registered MainForm as a listener with the Broadcaster, as shownin the Initialize method. The IListener.Listen method implements the Listen method of theIListener interface. When the Broadcaster invokes Listen for registered listeners, FormMaindisplays the information on the status bar by calling the ChangeStatus method.

Of course, you could wire the main form directly to the AssemblyManager, but I prefernot to have classes wired directly to GUIs. By not having a reference to FormMain in theAssemblyManager, it is easier to use the AssemblyManager in a new context, like a consoleapplication. You can also achieve a similar result using events. Either of these techniquesillustrates the concept of loose coupling.

5 4 A d v a n c e d C # P r o g r a m m i n g

Page 82: Advanced c sharp programming

C h a p t e r 2 : A s s e m b l y V i e w e r 5 5

Using the MainMenuMenus are comprised of MainMenu and MenuItem objects. When you paint a main menuonto a form, the MainMenu control is added to the component tray (underneath the gray formcanvas). The presentation of the main menu is drawn on the form itself.

When you want to add MenuItem objects to the main menu, you do so by typing the nameof the menu item directly into the location where you want that menu item to be displayed.Available MenuItem locations are indicated by a dotted outline with the text “Type Here” init. When you type the caption for the menu, the Form Designer generates the field and codenecessary to wire the menu.

The most common event for menu items is the Click event. To generate a Click event handler,double-click on the menu item or use the Properties window technique described earlier.

Remember, MainMenu and MenuItem objects are controls. They receive the focus and areinteracted with just like any other control.

Using the OpenFileDialogThe OpenFileDialog control displays a Windows Explorer–style dialog, enabling the userto select a file based on a programmer-described file mask. The OpenFileDialog supports aprogrammer-configurable InitialDirectory and Title. The following method taken from theFormMain module demonstrates how to display the dialog and retrieve the selected FileName.

public void OpenAssembly()

{

if( openFileDialog1.ShowDialog() == DialogResult.OK)

OpenAssembly(openFileDialog1.FileName);

}

The call to OpenAssembly loads the selected assembly into the AssemblyViewer.If you wanted to specify a filter mask that is suitable for selecting assemblies, then you

could specify the mask using the Properties window or with the following statement:

openFileDialog1.Filter = "Class Library(*.dll)|*.dll|Executables

(*.exe)|*.exe";

Filter masks are specified in pairs. The first half is the displayed mask that is selectable fromthe File of type combobox, and the second half is the actual mask. For example, “Class Library(*.dll)|*.dll” will display “Class Library (*.dll)” in the File of type combobox, and when youselect this filter, only files matching *.dll will be displayed in the dialog.

Using RichTextBoxRich Text is a kind of markup language. Known as Rich Text Format, or RTF, Rich Textsupports embedded tags that describe the format of the text. The RichTextBox is similar tothe TextBox, but supports embedded RTF formatting. The primary purpose of the RichTextBoxis to display formatted text, but the RichTextBox can display plain text and has built-incapabilities that support loading and saving the content of the text property to a file.

Page 83: Advanced c sharp programming

5 6 A d v a n c e d C # P r o g r a m m i n g

Putting Text in the RichTextBox The Text can be plain text or Rich Text. To programmaticallyassign string data to a RichTextBox control, assign a string variable to the Text property.

richTextBox1.Text = "C# Developer's Guide";

Loading a Text File Load text from an external text file by calling the LoadFile method.LoadFile takes three forms, the easiest of which specifies the file name of a file containingRTF formatting:

richTextBox1.LoadFile("file.rtf");

A second form of the RichTextBox has the RichTextBoxStreamType enumeration thatallows you to express the type of formatting the file contains. For example, to load plain text,you could rewrite the LoadFile statement:

richTextBox1.LoadFile("file.rtf", RichTextBoxStreamType.PlainText );

RichTextBoxStreamType is an enumeration defined in the System.Windows.Forms namespace.You will need to use the fully qualified name or ensure there is a using statement that refersto the System.Windows.Forms namespace.

Saving a Text File RichTextBox.SaveFile performs the symmetric operation to LoadFile.SaveFile writes the contents of the RichTextBox control to an RTF or TXT file bycalling SaveFile and passing a file name.

Using the ZoomFactor RichTextBox.ZoomFactor is an interesting property. With ZoomFactoryou can express a percentage increase in the size of the text. Setting ZoomFactor to 2 willdouble the size of the text, and .64 will reduce the size of the text by approximately half.Acceptable values are from .64 to 64, the latter being 64 times the normal size. This propertyis an excellent feature for applications supporting people with disabilities and for presentations.Suppose, for example, that you are displaying the content of the RichTextBox on an overheadprojector. Setting the ZoomFactor to increase text size will make it easier for your audienceto see the content in the RichTextBox control.

Using the LinkLabelThe LinkLabel control represents a Windows Forms hyperlink, further blurring the linesbetween Windows and Web applications. Enter an URL in the LinkLabel.Text property and,when a user clicks on the LinkLabel control, the value of the Text property can be used toopen IE, for example.

The code listing that follows is taken from the About form in the AssemblyViewer. TheURL of the author of the application is expressed in a LinkLabel. When the user clicks thelink label, IE is started and the default page of www.softconcepts.com is opened.

private void linkLabel1_LinkClicked(object sender,

LinkLabelLinkClickedEventArgs e)

{

Page 84: Advanced c sharp programming

(sender as LinkLabel).LinkVisited = true;

Process.Start( (sender as LinkLabel).Text );

}

The Process class is defined in the System.Diagnostics namespace, and Start is a staticmethod. The Process class is useful for starting, stopping, controlling, and monitoringapplications. In this example, the http// moniker implicitly instructs Process.Start to starta new instance of IE, given the association between URLs and Internet Explorer.

Basic PrintingCrystal Reports, which is a popular tool from Seagate Software, is now Crystal Decisions.For advanced printing and reporting, you can integrate Crystal Decisions–based controlsinto your application, but basic printing is supported by the PrintDocument control.

To implement basic printing add a PrintDocument control to your application and invokethe Print method. When you call PrintDocument.Print, the PrintPage event is fired. PrintPageis passed a PrintPageEventArgs object that contains a Graphics object representing the DeviceContext (DC) of the printer. With this Graphics object, you have the methods supported bythe Graphics class, like DrawString. The following code is taken from the FormMain moduleand writes the contents of the RichTextBox to the printer.

public void printDocument1_PrintPage(

object sender, PrintPageEventArgs e)

{

e.Graphics.DrawString(richTextBox1.Text,

richTextBox1.Font, Brushes.Black, 0, 0);

}

You could use the exact same code to write the text content of the RichTextBox control tosome other Device Context, like a form itself.

If you want to support Printer Setup, then use the PageSetupDialog. The PageSetupDialogallows the user to modify the PageSettings and PrinterSettings for a given document. To usethe PageSetupDialog control, click and drag a PageSetupDialog control to the application andset the PageSetupDialog.Document property to a PrintDocument control, printDocument1in the AssemblyViewer.

To display the PageSetupDialog, invoke the ShowDialog method. The changes you makewill be applied to the PrintDocument associated with the PageSetupDialog. (Refer to theMainForm.cs module in the AssemblyViewer for an example that demonstrates thesetwo controls.)

Embedded Resource File ManagementResource files are used to externalize content such as string data. By placing your string datain a resource file, you can provide different language versions of your resource files andthereby more easily internationalize text.

C h a p t e r 2 : A s s e m b l y V i e w e r 5 7

Page 85: Advanced c sharp programming

To add a resource file to your application, right-click the project in the SolutionExplorer. From the project context menu, select AddAdd New Item. Within the Add NewItem dialog, select the Assembly Resource File template, as shown in Figure 2-4.

When you select the resource file from the Solution Explorer, a resource editor is displayed,as shown in Figure 2-5. You can add resource content to the resource file using the editor. Asshown in the figure, string resource entries are presented as a table of name, value, comment,type, and mimetype information. This is similar to resource files. The biggest distinction isthat resource files in .NET are stored as XML files, rather than plain text. You can view theXML by clicking the XML tab (shown in Figure 2-5).

The Utility.cs module demonstrates how to use a ResourceManager to read data fromthe resource (.resx) file. To read string information from a resource file, create an instanceof the ResourceManager class defined in the System.Resources namespace and invoke theGetString method. The next code listing demonstrates using the ResourceManager as it isimplemented in the Utility.cs module.

public class Utility

{

public static string GetResourceString(string resourceName)

{

ResourceManager manager = new

ResourceManager("AssemblyViewer.Resource1",

Assembly.GetExecutingAssembly());

return manager.GetString(resourceName);

}

}

The ResourceManager needs to know the resource file that you want to load and theassembly that contains that resource file. In the example, we are getting the default-namedresource file in the executing assembly.

MultithreadingFinally, we come to multithreading. Multithreading is a complex subject. There are severalfacets to multithreading, so we will limit our discussion to multithreading in this chapter tothe way it was used to help implement the AssemblyViewer.

The AssemblyViewer uses a separate thread to display the Splash screen while theapplication is loaded. A luxury to be sure, but what is the point of a splash screen if it isexecuted in a linear fashion? That is, while the splash screen is loading, no real initializationoccurs, and I never liked letting the splash screen contain the initialization code. (You arelikely to find, however, that many splash screens do actually contain initialization code.)

In our sample application, the splash screen is splashed on its own thread. There arefour ways to contrive seemingly asynchronous behavior in .NET. The Timer control andApplication.Idle event provide event-driven synchronous behavior. Controls support theasynchronous BeginInvoke and EndInvoke. The ThreadPool class provides a pool ofavailable threads, and the Thread class allows you to spin up and manage threads yourself.

5 8 A d v a n c e d C # P r o g r a m m i n g

Page 86: Advanced c sharp programming

C h a p t e r 2 : A s s e m b l y V i e w e r 5 9

Figure 2-4 Add New Item dialog allows you to add items to your project from the templates list.

Figure 2-5 Use the string resource editor to externalize text information, facilitating applicationinternationalization.

Page 87: Advanced c sharp programming

6 0 A d v a n c e d C # P r o g r a m m i n g

The AssemblyViewer uses a thread from the ThreadPool class; hence, that is the facet ofthreading introduced here.

ThreadPool ThreadingThe ThreadPool is a class that manages a pool of threads for you. Multithreading usingthreads in the ThreadPool is no different than creating a Thread object. However, your workload is lightened a bit, and grabbing a thread from the pool is often a bit faster than creatingan instance of the Thread class because the pool most likely has a thread waiting for you togive it work.

To use a thread in the ThreadPool, all you have to do is give the ThreadPool some work todo in the form of a Delegate. In this instance, the Delegate will act as a procedure representingwork for the thread to do. Every other aspect of threading is identical to spinning a Threadobject yourself. The most important thing to remember is that ThreadPool threads requirejust as much care as Thread instances, especially when it comes to Windows Forms controls.Windows Forms controls are not thread-safe; thus, you must take extra care when interactingwith Windows Forms controls from a thread.

The following excerpt in Listing 2-3 from FormSplash.cs implements the fade-in splashscreen in the AssemblyViewer. The form itself is simply a form with a PictureBox control onit. The PictureBox control contains a cool picture downloaded from www.digitalblasphemy.com.

Listing 2-3 FormSplash.cs (excerpt) demonstrates the multithreaded code that implements thefade-in splash screen.

1: bool done = false;

2: private void Increment()

3: {

4: Opacity += .05;

5: lock(this)

6: {

7: done = Opacity >= 1;

8: }

9: }

10:

11: private void Show(Object state)

12: {

13: try

14: {

15: while(!done)

16: {

17: this.Invoke(new MethodInvoker(Increment));

18: Thread.Sleep(100);

19: }

20: Thread.Sleep(1500);

Page 88: Advanced c sharp programming

21: }

22: finally

23: {

24: Close();

25: }

26: }

27:

28: public static void Splash()

29: {

30: #if !DEBUG

31: FormSplash form = new FormSplash();

32: form.Opacity = 0;

33: form.Show();

34: ThreadPool.QueueUserWorkItem(

35: new WaitCallback(form.Show));

36: #endif

37: }

The remainder of this chapter and section will refer to the code in Listing 2-3.

Queuing a Work Item in the ThreadPoolDelegates are so important that event handlers do not work without them, and you cannotuse multithreading in any .NET language unless you understand delegates. To recap, adelegate contains the address of one or more methods. Threading works by giving a threada delegate containing procedures representing work to be done by the thread.

In Listing 2-3, the static method Splash creates the Splash form with an Opacity of 0. WhenForm.Opacity is 0, the form is transparent; 1 represents an opaque form. With an Opacitypercentage between 0 and 1, you have a semi-transparent form. The Splash form is showntransparently, and the thread will slowly increase the Opacity value until the form is Opaque,at which point the thread is finished.

Lines 34 and 35 in Listing 2-3 demonstrate how to multithread using the ThreadPool class.The ThreadPool is defined in the System.Threading namespace, and QueueUserWorkItem isa static method.

QueueUserWorkItem takes a WaitCallback delegate. The signature of a WaitCallbackdelegate is a method that has a single object argument. (We won’t use the state object in thisexample, but we still have to provide the argument. The state argument facilitates passinginformation to worker methods.)

QueueUserWorkItem adds the method Show to a Queue in the ThreadPool, which will inturn use or create a thread to perform the work. You can assure yourself that multithreadingis occurring by opening the Threads window from the Debug | Window | Threads menu item.When a thread is assigned, it will invoke the Show method—the WaitCallback delegate—onthat thread.

C h a p t e r 2 : A s s e m b l y V i e w e r 6 1

Page 89: Advanced c sharp programming

Windows Forms Synchronous Method InvocationThe Show method is defined on lines 11 through 26 in Listing 2-3. This Show method slowlyfades in the form by adjusting the Opacity. Unfortunately, because Windows Forms controlsare not thread-safe, the Opacity must be changed on the same thread as the one it resides on.The Show method is not on that thread.

The way that we actually update the Opacity value is by calling the form’s Invoke method.Invoke is thread-safe; Invoke marshals the delegate argument into the same thread as the threadthat the calling object is on. (Again, without delegates safe threading would not be possible.)

Invoke requires a MethodInvoker delegate and executes the code inside the method usedto initialize the delegate. The Increment method updates the Opacity value. The done field isused to indicate that the form is opaque. We lock the Done method while we are updating it,blocking the thread running Show from examining the Done value until it has been updated.

Finally, Show uses the try finally construct to catch exceptions and close the form. Forexample, if a user closed the application before the Splash form was completely Opaque,Show would refer to a disposed form (this is on line 17 in Listing 2-3) and raise an exception.The exception handler responds to an immediate application shut-down.

SummaryPerhaps the reason many books do not show complete applications is that there are dozensof skills necessary to create even basic applications. As this chapter demonstrated, simpleapplications can draw on a wide range of programming skills.

This chapter demonstrated Windows Forms programming, multithreading, Reflection,enumerators, delegates, and resource file management. Chapter 3 implements a video kioskand explores GDI+ programming in further detail.

6 2 A d v a n c e d C # P r o g r a m m i n g

Page 90: Advanced c sharp programming

CHAPTER

3Video Kiosk

63

IN THIS CHAPTER:

Demonstrated Topics

What Is GDI+?

Examining the PlayControl

Secondary Topics

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 91: Advanced c sharp programming

6 4 A d v a n c e d C # P r o g r a m m i n g

The power of graphics programming in Windows has, thankfully, been encapsulatedin an object-oriented framework. A substantial measure of graphics programmingsupport has existed in Windows for some time, but it has been challenging to get at.

The Video Kiosk example program in this chapter demonstrates how to use the newframework, dubbed “GDI+.” Many clever and engaging applications use non-standard userinterfaces to convey meaning or encourage people to play with them. This chapter demonstratessome of the fundamental skills you will need to use GDI+. The skills demonstrated in thischapter will help you create professional applications that use graphics in traditional waysand perhaps clever new ways.

Demonstrated TopicsFrom the examples in this chapter you will be able to master the intricacies of GDI+ and findcode that demonstrates the following topics:

� Creating custom controls (an introduction)

� Creating a control library

� Using the ControlPaint class to paint standard controls

� Working with virtual methods

� Overriding events

� Exception handling, including catching specific exceptions

� Employing COM Interop (demonstrated by using the ActiveX Media Player)

� Implementing interfaces

� Adding items to your TODO lists with the TODO comments

� Working with the MessageBox and TimeSpan classes

What Is GDI+?In keeping with the spirit of .NET, perhaps GDI+ should be redubbed GDI.NET. The nameis not relevant to the technology though. (The asymmetry just bugs me.)

Windows has always supported pretty advanced graphics programming. This basictechnology is referred to as GDI (graphics device interface). The problem with GDI is thatit is not approachable.

Windows visual programming may be thought of, metaphorically, as painting on a canvas.If you think about it, with a steady hand and a bit of diligence you could draw a Windowsapplication form in a program like Paint. The painting-on-canvas metaphor is precisely likethat, except you have standardized chunks of code that do the drawing for you.

Page 92: Advanced c sharp programming

C h a p t e r 3 : V i d e o K i o s k 6 5

In Windows, a canvas is referred to as a device context (DC). If you have programmedwith GDI (pre-.NET Windows programming), then you have used API methods that requireda handle to a device context, usually abbreviated hDC. What makes GDI unapproachableis that GDI is comprised of a disparate bunch of structures and API methods that areunorganized and uncategorized; that is to say, GDI is procedural.

GDI+ is an organized and categorized object-oriented version of GDI. By encapsulatingthe notion of the canvas and other drawing capabilities into an object-oriented framework,we get the same benefits we get with any other object-oriented framework. We get reusableclasses and components that are more approachable and comprehensible. In short, GDI+wraps GDI into an object-oriented framework. Now, instead of hunting and pecking, we onlyhave to look in a general area—the namespaces that make up GDI+—and learn somethingabout the organization of the types and classes in those namespaces.

NOTEIt is important to note that the Microsoft developers were the first developers to benefit from theclassification of basic, disparate API graphics methods when they came up with new graphics features.New features found in GDI+ include gradient brushes, cardinal splines, independent path objects, scalableregions, and alpha blending as well as support for a wide variety of image formats. You will get anopportunity to work with gradient brushes, path objects, and scalable regions directly in this chapter.

GDI+ NamespacesGDI+ is comprised of classes in the System.Drawing.dll, which includes the System.Drawing,System.Drawing.Design, System.Drawing.Drawing2D, System.Drawing.Imaging, System.Drawing.Printing, and System.Drawing.Text namespaces. These contain classes and typesthat support three broad categories of managing graphics: 2D vector graphics, imaging, andtypography.

Vector graphics include basic line, shape, and curve drawing capabilities defined bypoints. Imaging includes support for displaying graphics that are difficult to render usingvector graphics, like digital photographs, bitmaps, and icons. And, typography supportsdrawing text using a variety of fonts, sizes, and styles. This broad spectrum of supportprovides you with tremendous flexibility for rendering graphics-based images, vectorgraphics, and text.

Understanding the GDI+ Programming ModelThe Internet is a stateless environment. In the Internet era, many things seem to be trendingtoward statelessness. Statelessness means simply that each time code interacts, it must bereminded of what it needs to know. (Sort of like wearing name tags at a meeting whereeveryone is working together but are strangers. A stateless environment means your codeis always a stranger to other code.)

Page 93: Advanced c sharp programming

Statelessness is necessary for the Internet because it makes it very difficult to supporttracking state information for millions of connected users. My assumption is that what isbelieved to work for the potentially huge scale of the Internet could also benefit the demandsof graphics programming.

In practical terms, what statelessness means is that every time you want to display sometext on a canvas, you have to remind GDI+ what font and brush to use. Like a dodderingold grandfather, GDI+ just can’t seem to remember. (We’ll come back to fonts and brusheslater.) This means you have to remember for GDI+. That is to say, each time you draw sometext, you will have to tell GDI+ what font and brush to draw the text with. Not only willGDI+ forget what font and brush you just used, but it won’t keep track of the last locationyou wrote the text at, and it will even forget the screen size. That’s right. GDI+ will notupdate information about the size of the canvas if that size changes between the last time yourequested a Graphics object (think DC) and the next time you use that Graphics object.

Statelessness means the programmer has to explicitly indicate information like the font,brush, pen, color, and location each time the code interacts with a device context. You mustnot cache the DC in .NET either. Code such as the following is a no-no.

public class CacheGraphics : System.Windows.Forms.Form

{

Graphics graphics;

public CacheGraphics()

{

graphics = new Graphics();

}

}

The preceding code defines a class CacheGraphics that inherits from Form. The constructorcreates a new Graphics object and stores it in the graphics field.

While the Graphics object is the name of the class that contains the DC, doing both of thethings shown in the listing—creating a Graphics object directly and caching that Graphicsobject—are mistakes. Since GDI+ is stateless, changes to the DC will not be propagated toyour cached Graphics object. For example, if you resize the form, then your Graphics objectrepresents a region that does not accurately reflect your DC. Additionally, you must invokethe factory method CreateGraphics to get an instance of a Graphics object.

TIPYou can compel consumers of your code to use a factory method by making the constructor of a classprotected or private and invoking that method through a public static method in the same class.

The factory method CreateGraphics is required because there is a lot of work that goeson behind the scenes to instantiate a Graphics object. Using a factory method ensures thatall of that required initialization occurs properly every time. This technique also allows theproducers of the Graphics class to perform some behind-the-scenes optimization. (We wouldknow what that is if we had access to the Common Language Runtime source code.)

6 6 A d v a n c e d C # P r o g r a m m i n g

Page 94: Advanced c sharp programming

As you will see in this chapter, you should not cache a Graphics object; request it usingCreateGraphics each time you need it. You must use the factory method CreateGraphics(or get the Graphics object argument from an event handler) each time you need it, andyou will have to pass in state information, like fonts and brushes, each time you performa graphics operation.

Examining the PlayControlThe PlayControl shown in Figure 3-1 shows the custom graphical user interface. From thefigure you may be able to discern that the control is in a form with slightly rounded edgesand no title bar. More pronounced are the elliptical buttons. It may be difficult to detect inblack and white, but in addition to the buttons being round or oval, they also use a gradientbrush to enhance the 3D effect.

You might also notice that some controls display basic images, like the triangular playbutton, and other controls display images, like the open button displaying the folder.

Other effects include the background appearance, the VK (Video Kiosk trademark logo inbase relief), and the custom slider at the top of the PlayControl. I will describe the code thatcreates these effects, as well as provide the source code to allow you to experiment further orsolve any problem you might have.

Implementing the PlayControlThe PlayControl is composed of buttons. Like standard buttons, these buttons respond to aClick event. In that regard, the control is very simple: click a button, a Click event handler isinvoked, and some code is run.

There are two issues that make the player control reusable in a general sense rather than asingle-use form for one application. The buttons are implemented as separate controls, andthe PlayControl doesn’t do anything. Each Button Click event simply invokes an equivalentoperation on another class that implements the IPlayer interface. (We haven’t discussed theIPlayer interface yet; we’ll get to it next.) For example, if you click Play, then the Playmethod of the player associated with the PlayControl is invoked—as long as it implementsthe IPlayer interface. Hence, classes allow us to reuse individual controls, and interfacesallow us to reuse the whole component. Perhaps the equivalent of a radio, VCR, or cassetteplayer could use this generic component. The IPlayer interface is shown in Listing 3-1.

C h a p t e r 3 : V i d e o K i o s k 6 7

Figure 3-1 A custom graphical user interface implemented using GDI+

Page 95: Advanced c sharp programming

Listing 3-1 The IPlayer interface allows us to separate the graphical user interface of thePlayControl from any particular implementation, creating a truly thin client.

using System;

namespace VideoKiosk

{

public interface IPlayer

{

void Close();

void Play();

void Pause();

void Stop();

void FastForward();

void FastReverse();

void VolumeUp();

void VolumeDown();

bool Open();

double Duration();

bool Mute();

void Mute(bool state);

double Elapsed();

}

}

From the listing we can determine that the IPlayer interface is quite generic and possiblysuited for many modern devices that we might choose to emulate on a computer.

In the Video Kiosk, the main form, Form1.cs, implements the IPlayer interface. Thismeans that the contract between IPlayer and Form1 is that Form1 must implement all of themethods defined by the interface. Subsequently, based on our implementation, Form1 canbe associated as a player with the PlayControl. This can be accomplished by passing to theform a reference to an instance of the PlayControl in the PlayControl’s constructor. Here isthe call to create and associate the PlayControl with Form1 in Form1’s Load event.

private PlayControl control = null;

private void Form1_Load(object sender, System.EventArgs e)

{

control = new PlayControl(this);

control.Show();

}

And here is the constructor for the PlayControl. The PlayControl instances keep track of theplayer it is associated with.

private IPlayer player;

public PlayControl()

6 8 A d v a n c e d C # P r o g r a m m i n g

Page 96: Advanced c sharp programming

C h a p t e r 3 : V i d e o K i o s k 6 9

{

InitializeComponent();

}

public PlayControl(IPlayer player) : this()

{

this.player = player;

}

Since Form1 owns the PlayControl, we know that when Form1 is closed, the PlayControlwill be, too. The PlayControl constructor demonstrates how to invoke the default constructor.The default constructor is the PlayControl method that takes no arguments. Invoking the defaultconstructor is critical because the default constructor contains the call to InitializeComponent.Without the InitializeComponent statement, none of the controls will be created and the formwon’t be displayed or work correctly.

NOTEIf you are having difficulty following the code, you can run the sample VideoKiosk.sln to step through thecode. From the two fragments, Form1_Load is invoked first. The statement containing new PlayControl(this)invokes the PlayControl constructor that takes an IPlayer interface. The default constructor is invoked due tothe presence of the : this() clause in the constructor. In the default constructor InitializeComponent is called,and when the default constructor returns, this.player = player is executed. (It is important to understandhow constructor invocation works.)

It is the : this() clause that invokes the default constructor. After the default constructorreturns, the player arguments are cached into the player field. Our PlayControl now has aplayer to control.

NOTEA sane person might reasonably argue “Why not use the default controls provided with the ActiveX MediaPlayer?” or “Why not implement the play control right in the form that contains the Media Player?” Thesimple answer is “Then what would we talk about?” The better answer is that we could do that. However,for a little more work, we have a new component, the PlayControl, that is a separate component that canbe used for something else later (and we also have something to talk about).

Now when the user clicks the play button (we all know which one that is), the code in thePlayControl is very simple.

player.Play();

This statement runs code in Form1. In our example program, the code in Form1 contains thefollowing.

void IPlayer.Play()

{

axMediaPlayer1.Play();

}

Page 97: Advanced c sharp programming

7 0 A d v a n c e d C # P r o g r a m m i n g

Of course, if we used something besides the ActiveX Media Player, the code in IPlayer.Playwould be a little different. (I introduced interfaces in Chapter 1. You can review that materialif you need a reminder about implementing interfaces.)

In a real application, we need more than the simple statement invoking the Play method.An application would include the event invocation and some sanity checking. The VideoKioskimplements Play, as shown in Listing 3-2.

Listing 3-2 Implementing the PlayControl.Play method.

private void Play()

{

if(player==null) return;

try

{

player.Play();

PlayerState = PlayerState.playing;

}

catch

{

PlayError();

PlayerState = PlayerState.stopped;

}

}

private void playButton1_Click(

object sender, System.EventArgs e)

{

Play();

}

As a general rule, it is bad form to write code directly into an event handler. Instead weuse a well-named method, Play, that in conjunction with the PlayControl context indicatesexactly what is going on. The Play method uses a sentinel that checks to make sure theinternal reference to the player is not null. (Some people like positive if-conditional checksand block statements. I prefer the sentinel. No admittance if the conditional fails.)

In an exception-handling block, the player’s Play method is invoked. Using an interfaceensures that there is a Play method, and using the exception-handling block ensures thatour program won’t explode if something goes wrong on the player. (Think of your infraredremote control. You can push the button on the remote control even if the television isunplugged and nothing bad happens. Same concept here.) If we get past the call to player.Play,then we change the state of the player. (This is used to update the enabled buttons.)

Page 98: Advanced c sharp programming

C h a p t e r 3 : V i d e o K i o s k 7 1

NOTECould you imagine if hardware devices ran like some software? Press the On button on your remote control,and, if you haven’t paid your cable bill, the remote control electrocutes you. Thinking about reasonable failstates is how we code exception handlers. No BSDs! (That is, No Blue Screen of Death!)

If an error does occur, such as having no media file, then the catch block takes over. ThePlayError method is called, and the PlayControl state is returned to stop.

You can look up PlayError. PlayError uses the MessageBox class you learned about inChapter 1 to display a message indicating that the media file could not be played. Using aseparate PlayError method affords the opportunity to add something more helpful at a laterdate if we need to, while stubbing out a solution now. Perhaps we could write code thatdiagnoses the problem more closely and provides a reasonable way to solve the problem.

TIPNotice that I did not handle actually changing button states in the Play method. This would be doing toomuch. Actually changing the PlayerState property provides us with a convenient place to do that. Becausethe PlayerState setter is a method, we can run some code in the setter. For example, we can update thebutton states to reflect the new value of the PlayerState.

Creating the Graphical User Interface for the PlayControlCreating technically advanced graphical user interfaces is challenging. Creating graphicaluser interfaces that are attractive and professional is both technically challenging and requiresartistic skill at a level that artists are more likely than computer scientists to possess. Of course,it is possible that good programmers are also talented artists, and that is the combinationneeded to create interfaces like the Headspace skin of the Windows Media Player shown inFigure 3-2.

There are a few ways to create advanced interfaces, including using digital photography,claymation, vector graphics, and drawing. A great strategy for implementing artisticallyappealing interfaces is to hire artists to do the artistic part. Hire a professional artist to draw,paint, or photograph a graphical user interface, and then charge your programmers withturning the image into an application.

To create the PlayControl, I borrowed the background of the Intervideo WinDVD controlthat shipped with my Dell laptop. The buttons, tracker, and the elapsed time text were addedto the basic background image.

Adding the Background Image to the PlayControlForm classes have a BackgroundImage property. If you need something other than the basicgray background for your Windows applications, then you can create a custom image or usean existing graphic and assign that image to the BackgroundImage property of the form.Doing this will eliminate the need to create advanced appearances with controls only.

Page 99: Advanced c sharp programming

Everything is colored pixels in Windows applications. You are encouraged to usebackground images to speed up development. It is much easier (once you have an image) tocreate advanced custom effects with images than it is to create those effects using controls;additionally, your applications will be lighter and faster with fewer controls. For any visualeffect that does not respond to user feedback, consider using a background image. Figure 3-3shows the PlayControl without the benefit of the background image.

Clearly we could use Windows Forms controls to create various regions for the tracker,buttons, and progress time indicator, but Windows Forms controls are rectilinear. The resultwould not be especially appealing. Simply by adding the background image, we get a visuallyappealing backdrop for the PlayControl (see Figure 3-4).

Now that we have a nice backdrop for our application, we can layer the controls thatprovide dynamic feedback and accept input from the user to complete the PlayControl.

Adding Custom Controls to the PlayControlRectangular buttons would look out of place on our ergonomic PlayControl. To createbuttons that look at home in our PlayControl, we will have to delve into our GDI+ toolbox.

7 2 A d v a n c e d C # P r o g r a m m i n g

Figure 3-2 The Headspace skin for the Windows Media Player demonstrates exceptional artistry.

Figure 3-3 The PlayControl without the background image

Page 100: Advanced c sharp programming

The round shapes in the PlayControl suggest that softer-edged, elliptical buttons wouldlook more at home. Unfortunately, these controls are not part of the standard toolbox; hence,we will have to create custom buttons.

An excellent way to quickly create new controls is to add a custom control to your project.The custom control provides a class that has all of the methods and properties you will needto create a wide variety of controls. Listing 3-3 demonstrates a custom control–based roundbutton that provides the basis for all of the buttons shown on the PlayControl in Figure 3-1.

Listing 3-3 The source code that is the basis for the round buttons on the PlayControl

1: public class RoundButton :

2: System.Windows.Forms.Control

3: {

4: private bool hasOutline = false;

5:

6: protected bool down = false;

7:

8: protected virtual Color GetColor()

9: {

10: Color[] colors = {Color.Silver, Color.Gray};

11: return colors[Convert.ToInt32(down)];

12: }

13:

14: protected virtual Brush GetBrush(bool buttonState)

15: {

16: return new LinearGradientBrush(

17: new Point(2,2), new Point(Width -1 , Height - 1),

18: Color.White, GetColor());

19: }

20:

21: private int GetPenWidth()

22: {

23: // use button state to adjust pen width

24: return 1 + Convert.ToInt32(down);

25: }

C h a p t e r 3 : V i d e o K i o s k 7 3

Figure 3-4 The background image provides a visually appealing backdrop for our applicationwithout code or customized controls.

Page 101: Advanced c sharp programming

7 4 A d v a n c e d C # P r o g r a m m i n g

26:

27: private Pen GetPen()

28: {

29: return new Pen(Brushes.Black, GetPenWidth());

30: }

31:

32: public bool HasOutline

33: {

34: get

35: {

36: return hasOutline;

37: }

38: set

39: {

40: hasOutline = value;

41: Invalidate();

42: }

43: }

44:

45: private void DrawButtonOutline(Graphics graphics)

46: {

47: graphics.DrawEllipse(GetPen(), 1, 1,

48: Bounds.Width - 2, Bounds.Height - 2);

49: }

50:

51: private void DrawButton(Graphics graphics)

52: {

53: graphics.FillEllipse(GetBrush(down), 0, 0,

54: Width, Height);

55: if(hasOutline)DrawButtonOutline(graphics);

56: }

57:

58: protected override void OnPaint(

59: System.Windows.Forms.PaintEventArgs e)

60: {

61: base.OnPaint(e);

62: DrawButton(e.Graphics);

63: DrawGraphic(e.Graphics);

64: }

65:

66: protected override void OnResize(System.EventArgs e)

67: {

68: GraphicsPath path = new GraphicsPath();

69: path.AddEllipse(0, 0, Bounds.Width, Bounds.Height);

Page 102: Advanced c sharp programming

70: Region = new Region(path);

71: Invalidate();

72: base.OnResize(e);

73: }

74:

75: protected override void OnMouseDown(

76: System.Windows.Forms.MouseEventArgs e)

77: {

78: base.OnMouseDown(e);

79: down = true;

80: Invalidate();

81: }

82:

83: protected override void OnMouseUp(

84: System.Windows.Forms.MouseEventArgs e)

85: {

86: base.OnMouseUp(e);

87: down = false;

88: Invalidate();

89: }

90:

91: protected virtual void DrawDownGraphic(

92: Graphics graphics)

93: {

94: Matrix m = new Matrix();

95: m.Scale(1.03F,1.03F);

96: graphics.Transform = m;

97:

98: }

99:

100: protected virtual void DrawGraphic(

101: Graphics graphics)

102: {

103: if(down) DrawDownGraphic(graphics);

104: }

105:

106: protected virtual Brush GraphicBrush()

107: {

108: return Enabled ? Brushes.Black: Brushes.Silver;

109: }

110: }

The code demonstrates several aspects of GDI+. We will decompose the code extracted fromthe RoundButton.cs module in separate sections, describing the techniques demonstrated.

C h a p t e r 3 : V i d e o K i o s k 7 5

Page 103: Advanced c sharp programming

Constructors, Destructors, and Dispose MethodBecause a control is a class, you may encounter things in a control that you are likely tofind in any class. There are three common methods that you are likely to find in any class,including a control—these are constructors, destructors, and a Dispose method.

The RoundButton doesn’t implement a constructor, a destructor, or a Dispose method. Inthis particular instance we do not need them. In this particular instance we do not performany of the actions that require their presence. Other classes in the Controls.dll—part of theVideoKiosk.sln—demonstrate examples of constructors and the Dispose method.

Control Constructors The purpose of constructors is to initialize objects defined in yourclasses. This holds true for Control classes. If your controls do not contain members thatneed to be created with the new operator, or your classes do not need to perform any specialinitialization, then you do not need a constructor.

Constructors are usually public, define no return type, and have the same name as theclass that contains them. In our RoundButton class, we don’t use a constructor because wedo not need to perform any special initialization.

Constructors can be overloaded by defining additional methods with the same name asthe class, but you do not have to specifically indicate that a constructor is overloaded byusing a modifier. Two methods—including constructors and destructors—with the same nameas the class will automatically be overloaded, and calls to that constructor will be resolved,based on unique argument types.

A good strategy demonstrated by the Form class is to call an intializer method from theconstructor, rather than cluttering the constructor. Form1 calls the InitializeComponentmethod. The reason for this is that the Form has controls painted on it that must be initialized.

Control Dispose Methods C# is a .NET language. I state the obvious as a way to segue intoa subject that may not be obvious. Languages like Object Pascal or C++ use deterministicconstruction and destruction. This means that the programmer is responsible for both creatingand destroying objects.

.NET employs non-deterministic destruction. This means that the programmer is notresponsible for destroying objects. The garbage collector will clean up objects for you. Asa result, you will write code that invokes the new operator, but there is no equivalent freeoperator. Inconveniently, sometimes you will be writing code that uses resources that needdeterministic cleanup. This is where the Dispose method comes in.

NOTEYou can invoke the garbage collector explicitly by calling GC.Collect, but this is not a recommended practice.

The result of all this is that you are likely to see more classes that implement a Disposemethod rather than a destructor. Because Dispose is a public method, a consumer caninvoke it explicitly, but you cannot rely on when a destructor is called in .NET. If you needdeterministic cleanup, then implement a Dispose method.

7 6 A d v a n c e d C # P r o g r a m m i n g

Page 104: Advanced c sharp programming

Where Is the Destructor? Where is the destructor in the control? That is a reasonablequestion. As mentioned in the preceding section, C# uses non-deterministic cleanup, so youwill encounter the destructor less often than in languages that use deterministic cleanup.However, you should know what a constructor looks like and the rules that are applied toconstructors.

~RoundButton()

{

}

There are other rules that apply to destructors, too. Classes can have only one destructor,and that destructor cannot be defined to take any arguments. Destructors cannot beoverloaded or inherited, and destructors cannot be called. Destructors are calledautomatically by the garbage collector. Users have no control over when destructors areinvoked.

Using Arrays for Conditional LogicI learned the technique I’m about to describe from Danny Thorpe, another talented developerwho helped build Borland’s Delphi. In Object Pascal, using an array instead of conditionallogic resulted in significantly more optimized code. If you compare conditional logic toarray-indexing in C#, you might be surprised that using conditional logic results in shorter ILcode than indexing an array. The following fragment demonstrates two variations that returna color based on the value of the down field.

protected virtual Color GetColor()

{

Color[] colors = {Color.Silver, Color.Gray};

return colors[Convert.ToInt32(down)];

}

protected virtual Color GetColor()

{

if( down )

return Color.Gray;

else

return Color.Silver;

}

The first example uses an array and results in a concise code listing. The second exampleuses if-conditional logic and is a bit easier to understand. As I mentioned, the secondexample produces shorter IL code. The reason for this is that C# does not allow typedarrays; as a result, we must convert the Boolean down to an integer to index the array.

For very simple evaluations we can use the ternary (?:) operator. The next exampledemonstrates using the ternary operator to evaluate the down state.

protected virtual Color GetColor()

{

C h a p t e r 3 : V i d e o K i o s k 7 7

Page 105: Advanced c sharp programming

return down ? Color.Gray : Color.Silver;

}

The ternary operator is understood as follows. Immediately after the return keyword, avalue that results in a Boolean is evaluated. If the evaluation—in the example down—istrue, then the expression after the question mark symbol (?) is returned. If the evaluationis false, then the expression after the colon (:) is evaluated and returned. In the example,if down is true, then the color Gray is returned.

The third implementation produces the least amount of IL code. Unfortunately, it becomesdifficult to perform multi-choice evaluations using the ternary operator, and if you mustevaluate more than two possible choices you might want to use a case statement. However,the Boolean evaluation demonstrated by the GetColor implementation actually used in theRoundButton produces concise code and is a favorite of mine. Finally, because the array-indexing is contained within a well-named function, GetColor, it is okay to use code that isa bit more esoteric than you might normally prefer.

Regardless of the technique you use you should be aware of the general implications:

� Indexing an array of choices produces concise, esoteric code.

� Using if-conditional code is pretty straightforward and easily understood, butbecomes difficult to manage if you have too many choices or nest if conditionals.

� Case statements are helpful for organizing multiple choices.

� The ternary operator is excellent for simple Boolean evaluations.

Choose whatever technique suits your needs, but try to avoid complicated if-conditional logicor convoluted case statements.

Color Structure The Color structure contains a long list of predefined colors. You can useone of the predefined colors when you need a color, or you can specify a color by expressingthe color as a name. The GetColor method demonstrates using existing colors.

You can also specify custom colors by expressing alpha blend, red, green, and blue valuesfrom 0 to 255. The alpha component of the color represents transparency; a value of 127represents a color that will be approximately 50 percent transparent. The statementColor.FromArgb(255, 0, 0, 255) creates a color that is a nice tone of blue.

Convert Class Functions The Convert class used in GetColor contains static methods that willconvert values from one type to a specific type. Convert.ToInt32 will convert the argumentvalue to a 32-bit integer. Convert.ToInt32(down) converts the Boolean field down to aninteger representation.

TIPThe Convert class demonstrates an example of a class with only methods. Because Convert does not need tomaintain state, all of the methods can be static, which means you will not have to create an instance of theConvert class to use it.

7 8 A d v a n c e d C # P r o g r a m m i n g

Page 106: Advanced c sharp programming

C h a p t e r 3 : V i d e o K i o s k 7 9

Methods in the Convert class are overloaded and defined to accept a wide variety of datatypes. If the Convert method is unable to convert the value you passed to the type requested,an InvalidCastException will be thrown.

Using Graphics ObjectsInstances of the Graphics class represent the canvas in GDI+. You will need to request aGraphics object each time you want to perform custom drawing. There are a couple of waysto get a Graphics object. You can invoke the CreateGraphics method on controls that havea canvas, like forms, and you can get a Graphics object as a property of the PaintEventArgsargument passed to the Paint event handler.

Regardless of how you obtained a Graphics object, once you have a Graphics object,you can invoke any methods defined by the Graphics class. Because GDI+ is stateless, youshould not cache the Graphics object, nor should you use the same Graphics object betweencalls to a method.

Listing 3-3, lines 58 to 64, demonstrates how to use the Graphics object passed to thePaint event handler in the PaintEventArgs parameter. Listing 3-4 demonstrates how torequest a Graphics object each time a paint operation is performed.

Implementing a Paint Event Handler The Paint event handler receives two arguments. The firstargument is an object, and the second argument is an instance of the PaintEventArgs class.

When you need to perform custom painting, you can implement a Paint event handler.For the purposes of our discussion, there are two custom effects that are applied directly tothe PlayControl. The Paint event handler for the PlayControl follows:

private void PlayControl_Paint(

object sender, System.Windows.Forms.PaintEventArgs e)

{

OutlineControl(e.Graphics);

DrawTrademark(e.Graphics);

}

The Refactored methods describe the task each performs. The first statement outlines thePlayControl to provide a nice clean edge. This is necessary because we are not using thestandard form shape. The second statement draws the “VK” trademark in base relief.Listing 3-4 contains the code to support OutlineControl and DrawTrademark.

Listing 3-4 Code to support the Paint event handler for custom drawing on the PlayControl

1: private Point[] GetPoints(int shift)

2: {

3: return new Point[]

4: {

5: new Point(3 + shift, 0 + shift),

6: new Point(Bounds.Width - 3 + shift, 0 + shift),

Page 107: Advanced c sharp programming

7: new Point(Bounds.Width + shift, 3),

8: new Point(Bounds.Width + shift, Bounds.Height - 3 + shift),

9: new Point(Bounds.Width - 3 + shift, Bounds.Height + shift),

10: new Point(3 + shift, Bounds.Height + shift),

11: new Point(0 + shift, Bounds.Height - 3 + shift),

12: new Point(0 + shift, 3 + shift),

13: new Point(3 + shift, 0 + shift)

14: };

15: }

16:

17: private void OutlineControl(Graphics graphics)

18: {

19: graphics.SmoothingMode = SmoothingMode.AntiAlias;

20: graphics.DrawPolygon(new Pen(Brushes.Black, 2),

21: GetPoints(-1));

22: graphics.DrawPolygon(new Pen(Brushes.White, 2),

23: GetPoints(1));

24: }

25:

26: private void DrawShadowText(Graphics graphics,

27: string s, Font font, Brush foreBrush, Brush backBrush, int x, int y)

28: {

29: graphics.DrawString(s, font, backBrush, new Point(x, y));

30: graphics.DrawString(s, font, foreBrush, new Point(x-1, y-1));

31: }

32:

33: private void DrawTrademark(Graphics graphics)

34: {

35: Font font = new Font("Haettenschweiler", 24,

36: FontStyle.Bold);

37: DrawShadowText(graphics, "VK", font,

38: Brushes.DarkSlateBlue, Brushes.White, 5, 5);

39: }

OutlineControl on lines 17 to 24 uses the Graphics object passed to the PlayControl’sPaint event handler and sets the SmoothingMode to AntiAlias. Anti-aliasing will help createa smoother line by overlapping some of the pixels in the polygon to blend pixels, resultingin a smoother-looking appearance. Drawing the polygon with the same points—returned byGetPoints—used to define the shape of the form results in a clean, outlined edge for the form.

DrawTrademark employs a second method that I named DrawShadowText.DrawShadowText draws the text twice using two brushes. By offsetting the text slightlyone way or another, you can create different shadow effects. By offsetting the text by –1and drawing the text with a brighter color, an inset effect (base relief) is created. By varyingthe offsettiung values on line 30, you can create various text effects.

8 0 A d v a n c e d C # P r o g r a m m i n g

Page 108: Advanced c sharp programming

Overloading the OnPaint Method If you create custom effects on a form, then you will needto re-create those effects each time the form is repainted. However, if you create customcontrols, and those controls have custom effects, then you will want to overload the OnPaintmethod of the custom control. It is by overloading the OnPaint method that you ensure thatcustomizations to a control’s appearance are re-created each time the control is painted.This is the strategy employed in the RoundButton class, shown on lines 58 through 64 ofListing 3-3.

Referring to Listing 3-3 now, the overloaded OnPaint method is not an event handler. Themethod is internal to the class that is performing custom painting, so the sender argument isnot needed. The OnPaint method does get a PaintEventArgs object containing the Graphicsobject used to perform the custom drawing. If you want the default drawing performed, thenyou should call the inherited OnPaint method, as is shown on line 92; by calling the inheritedOnPaint method in the base class, you will ensure that the event handler is raised, too.

After the inherited drawing is performed, you can add the code that performs your customdrawing. In Listing 3-3, DrawButton and DrawGraphic are called. Together, these twoadditional methods create the round button. We will look at some of the GDI+ methods thatsupport creating the round buttons in a moment.

Invalidating a Control When you want a control to be repainted, you call the Invalidatemethod. Invalidate will force the control to be painted by calling the OnPaint method, which,in turn, will raise the Paint event. Invalidating a control is demonstrated in the HasOutlineproperty. When HasOutline is changed, the hasOutline field is changed and the control isinvalidated, on line 72, to repaint the control with an outline.

Drawing and Filling Shapes The Graphics class defines several methods for drawing andfilling shapes. The DrawEllipse and FillEllipse methods are used to create the custom buttons.For example, FillEllipse draws and fills the basic button, and DrawEllipse draws the outlineof the button when HasOutline is True.

Drawing methods use a Pen object to draw the line representing the shape. The shapesare expressed as a region constrained by a bounding rectangle. FillEllipse works almostidentically to DrawEllipse; however, instead of a Pen, you need to pass a Brush to FillEllipsebecause the fill method paints the interior of a region.

There are two flavors of fill and draw methods. One flavor takes integer argumentsdefining the rectangular region, and the second flavor accepts floating-point numbersrepresenting the rectangular region. The benefit of having a floating-point version of shape-drawing methods is that you can perform arithmetic operations that return floating-pointnumbers without having to truncate those numbers to integers.

The specific drawing or filling method invoked depends on the type of the arguments.Polymorphism helps here. There are several overloaded versions for each of the drawing andfilling methods, and the compiler determines, based on the argument types, which specificmethod to call.

In addition to defining the rectangular region by expressing four points, you can expressthe rectangular regions as a pair of Point structures or a Rectangle structure. If you use pointsor rectangles, then you will need to use Point or Rectangle if the initial values are integers,

C h a p t e r 3 : V i d e o K i o s k 8 1

Page 109: Advanced c sharp programming

8 2 A d v a n c e d C # P r o g r a m m i n g

and PointF and RectangleF if the initial values are floating-point values. Several examplesdemonstrating using the Point and Rectangle structures can be found in the RoundButton.csmodule.

Applying Transforms to Graphics ObjectsGDI+ uses world, page, and device coordinates. When you make a call to a Graphics method,the positions you express are in world coordinate space. GDI+ transforms the world coordinatearguments through two transformations, from world coordinates to page coordinates and thendevice coordinates.

GDI+ takes care of these transformations behind the scenes. The benefit to us asprogrammers is that we can change the origin, change the PageUnit from the default pixelto some other unit of measure, and perform operations like scaling images.

These capabilities were employed to create the visual effect of the button changing positionwhen RoundButton objects are clicked by the user. If the button doesn’t change appearance,however slight, when the user clicks the button, then the illusion is lost. The followingexcerpt from the RoundButton class applies a scaled Matrix transform to the Graphics objectbefore drawing the button graphic, resulting in the illusion that the button has moved slightlywhen the user has clicked the button.

protected virtual void DrawDownGraphic(Graphics graphics)

{

Matrix m = new Matrix();

m.Scale(1.03F,1.03F);

graphics.Transform = m;

}

protected virtual void DrawGraphic(Graphics graphics)

{

if(down) DrawDownGraphic(graphics);

}

protected override void DrawGraphic(Graphics graphics)

{

base.DrawGraphic(graphics);

graphics.FillPolygon(GraphicBrush(), GetPoints());

}

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)

{

base.OnPaint(e);

DrawButton(e.Graphics);

DrawGraphic(e.Graphics);

}

protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)

Page 110: Advanced c sharp programming

{

base.OnMouseDown(e);

down = true;

Invalidate();

}

When the user clicks an instance of the RoundButton control, the OnMouseDown methodis fired. From the listing we can determine that the base class OnMouseDown method iscalled. This will allow users to intercept that event; that is, we aren’t disrupting the defaultbehavior. Then, the down field is set to True and the control is invalidated. Calling theInvalidate method forces a repaint to occur. At this point, the overloaded OnPaint method iscalled. Again, we perform the base.OnPaint behavior followed by the custom behavior.

The custom Paint behavior draws the button, and then draws the graphic. If down isTrue—which in our scenario, it is—the DrawDownGraphic method is invoked. (This is wherethe transform comes into play.) DrawDownGraphic creates a Matrix object. Matrix.Scale iscalled to scale the Matrix object, and that Matrix object is applied to the graphics.Transformproperty. The end result is a graphic that looks as if it has moved slightly when the buttonis depressed.

When you understand the mechanics, you will find them to be quite straightforward. Theartistry is finding the right combination of brushes, colors, and shadings to make the illusionas realistic as possible. You will have to judge for yourself how realistic the button appearsto be by running the VideoKiosk.sln sample application. The better the artistry, the higherquality the application.

Using the GraphicsPath Object to Create Shaped FormsThe GraphicsPath class is defined in the System.Drawing.Drawing2D namespace.GraphicsPath objects are used to represent a series of connected lines and curves. You haveheard of shaped Windows Forms. Well, GraphicsPath objects are how you create shapedWindows Forms.

GraphicsPath objects allow you to create shaped forms by adding lines and curves to theGraphicsPath object, and then using the object to define the clipping region for a form. ThePlayControl has rounded edges, demonstrating a subtly shaped form. The following excerptfrom the PlayControl demonstrates code that subtly shapes the PlayControl by roundingthe corners. (A second example demonstrating the GraphicsPath object is defined in theOnResize method on lines 66 to 73 of Listing 3-3.)

private void ShapeForm()

{

GraphicsPath path = new GraphicsPath();

path.AddPolygon(GetPoints(0));

Region = new Region(path);

}

A GraphicsPath object is created. A polygon defined by GetPoints is added to theGraphicsPath, and that GraphicsPath object is used to define the clipping region of the

C h a p t e r 3 : V i d e o K i o s k 8 3

Page 111: Advanced c sharp programming

PlayControl form. Region = new Region(path) demonstrates the code that modifies theform’s clipping region.

The code fragment demonstrates a bare-bones example of creating a shaped WindowsForm. The example is beneficial to the PlayControl because it is appropriate, but as astand-alone example it is not very compelling. The next section demonstrates a more-advanced shaped form.

Defining Clipping Regions System.Windows.Forms controls have a Region property. TheRegion property is a class that represents the clipping region of a control. Regions are scalablebecause they are expressed in word coordinates. When you define a clipping region, you arenot just drawing a shape—the clipped region is the form.

Listing 3-5 is taken from the ShapedForm.sln. The code redefines the form’s clippingregion to appear as shown in Figure 3-5. The form is clipped to include the form’s frame.You can click on the top part of the clipped form shown in Figure 3-5 and drag the formaround, and the form will respond to input from the user just as if it were the normallyshaped rectangular form.

Listing 3-5 A shaped form based on a string added to a GraphicsPath object

private void Form1_Load(object sender, System.EventArgs e)

{

BackColor = Color.Red;

GraphicsPath path = new GraphicsPath();

path.AddString("C#", Font.FontFamily, 1, 75,

new Point(0, 0), new StringFormat());

Region = new Region(path);

}

private void Form1_Click(object sender, System.EventArgs e)

{

MessageBox.Show("Clicked!");

}

TIPRemember to add a using statement to include the System.Drawing.Drawing2D namespace.

The code changes the background color in the Form Load event. A GraphicsPath object iscreated, and a string is added to that path, using the form’s Font and some basic position andsize information. A new clipping Region is constructed and assigned to the form’s Regionproperty. The result is shown in Figure 3-1. If you look closely you can see the Form.Text

8 4 A d v a n c e d C # P r o g r a m m i n g

Page 112: Advanced c sharp programming

property, and when you run the application you will be able to discern the form header andclient regions. (The header is blue and the client region is red.) Click on the client region andthe Form1_Click method will be invoked.

Linear Gradient BrushesBrushes are used to fill the interiors of shapes. There are several kinds of brushes, includingthe HatchBrush, LinearGradientBrush, PathGradientBrush, SolidBrush, and TextureBrush.These brush types produce a specific kind of result when you use them to fill graphic shapes.When you need a brush, you can create an instance of one of these brush types or use theBrushes class, which contains predefined solid brushes.

If this book were produced in color, what you would be able to discern from Figure 3-1is that the custom round buttons are using a LinearGradientBrush to enhance the 3D effect.A LinearGradientBrush is a brush whose color transitions from a starting color to an endingcolor over the space painted by the brush. You have probably seen this technique used mostoften in splash screens or on older-style install programs.

The LinearGradientBrush has several overloaded constructors. The version used on lines45 to 50 of Listing 3-3 takes two points and two colors. The points describe the starting andending points of the gradient, and the colors describe the beginning and color of the gradient.In the example, by using a White starting color and a darker ending color, we get theappearance of light reflecting on the button, which enhances the 3D appearance.

You can specify a wide variety of properties that refine the gradient effect. The Blendproperty is a class that describes what percentages of the starting and ending colors to useat positions along the gradient. The GammaCorrection property is a Boolean that indicateswhether gamma correction is applied to the gradient. The InterpolationColors property allowsyou to specify a ColorBlend that supports displaying multi-color linear gradients. TheLinearColors property is an array of colors containing the starting and ending colors for thegradient. The Transform property defines a Matrix that allows you to translate, scale, rotate,or skew gradients. Finally, the Rectangle property defines the starting and ending points ofthe gradient brush.

The LinearGradientBrush is used to paint the interior of RoundButtons. The colors used toproduce the gradients are based on the value of the down field. Up buttons are light and downbuttons are darker.

Listing 3-6 demonstrates the LinearGradientBrush RotateTransform and SetTriangularShapemethods. You will have to run the code to see the neat visual effect. There is no way toreproduce it in black and white.

C h a p t e r 3 : V i d e o K i o s k 8 5

Figure 3-5 A shaped form created by a string, “C#”, added to a GraphicsPath object

Page 113: Advanced c sharp programming

Listing 3-6 The GradientBrush.sln demo program demonstrates some advanced features of theLinearGradientBrush class.

1: using System;

2: using System.Drawing;

3: using System.Drawing.Drawing2D;

4: using System.Collections;

5: using System.ComponentModel;

6: using System.Windows.Forms;

7: using System.Data;

8:

9: namespace GradientBrushDemo

10: {

11: public class Form1 : System.Windows.Forms.Form

12: {

13:

14: [ Chopped out code that was generated by the forms designer]

15: private System.Windows.Forms.Timer timer1;

16:

17: private float angle = 0;

18:

19: private LinearGradientBrush GetBrush()

20: {

21: return new LinearGradientBrush(

22: new Rectangle( 20, 20, 200, 100),

23: Color.Orange,

24: Color.Yellow,

25: 0.0F,

26: true);

27: }

28:

29: private void Rotate( Graphics graphics,

30: LinearGradientBrush brush )

31: {

32: brush.RotateTransform(angle);

33: brush.SetBlendTriangularShape(.5F);

34: graphics.FillEllipse(brush, brush.Rectangle);

35: }

36:

37: private void Rotate(Graphics graphics)

38: {

39: angle += 5 % 360;

40: Rotate(graphics, GetBrush());

41: }

42:

8 6 A d v a n c e d C # P r o g r a m m i n g

Page 114: Advanced c sharp programming

43: private void timer1_Tick(object sender, System.EventArgs e)

44: {

45: Rotate(CreateGraphics());

46: }

47:

48: private void Form1_Paint(object sender,

49: System.Windows.Forms.PaintEventArgs e)

50: {

51: Rotate(e.Graphics);

52: }

53: }

54: }

The example uses a Timer control to paint an ellipse at regular intervals. The gradientbrush uses Yellow and Orange. Each time the Rotate method is called, a floating-point valueis incremented by 5. Modulo arithmetic is used to keep the angle between 0 and 360. Theangle represents degrees. The RotateTransform method rotates the gradient brush on line 32.SetBlendTriangularShape uses the ending color as a center focal point, and FillEllipse drawsa filled elliptical shape using the gradient brush.

PensWhen you draw filled-in areas, you need a Brush. When you draw lines, you will need a Pen.You can get a basic Pen from the Pens object, or you can create an instance of a Pen fromscratch and specify the PenAlignment, Width, Color, and DashStyle.

The following example creates a Pen object initialized with a LinearGradientBrush anddraws an ellipse using the Pen.

LinearGradientBrush brush =

new LinearGradientBrush(new Rectangle(0, 0, 2, 2),

Color.White, Color.Green, LinearGradientMode.ForwardDiagonal);

Pen p = new Pen(brush, 10F);

e.Graphics.DrawEllipse( p, 5, 5, 100, 200);

(The code is contained in the ShapedForm.sln example for convenience.)

Implementing the Tracker ControlThe RoundButton was derived from the Control class. When you are creating customcontrols, an easier technique is to find a control that is close to the new control that you wantto make, and inherit from that control. In the case of the RoundButton, we might improve thecontrol, or simplify its construction, by inheriting from the ButtonBase class.

Another strategy to keep in mind is that, if you want to use a control in your ownapplication, then creating the control in your application is reasonable. If you want toredistribute your controls, then you will want to create a Control Class Library, which will

C h a p t e r 3 : V i d e o K i o s k 8 7

Page 115: Advanced c sharp programming

package your controls into a tidy, redistributable package. (Refer to Chapter 9 for more oncreating custom controls.)

Sometimes you will find that there is no good control to inherit from. This means youwill have to derive your control higher up the control food chain and inherit from theSystem.Windows.Forms.Control class.

NOTEFor our purposes here, we will continue to use the Control as the base class for our controls in thischapter—you can read Chapters 8 and 9 for more information on creating controls in general. In general,you will need to know how to create Control Class libraries, how to implement and test control classes andcustom controls, and how to place those controls in the Toolbox. You will also learn how to associate acustom icon with your control in the Toolbox in later chapters.

Defining the Tracker ControlFor our purposes, it is reasonable to implement the Tracker as a custom control. The Trackeris shown as the little slider at the top of the PlayControl in Figure 3-1. The Tracker behavesjust like the ProgressBar control you are probably already familiar with.

Like the ProgressBar, the Tracker uses a graphic to indicate relative progress. The Trackerallows the user to express a minimum, maximum, and position value. If position is closer tothe minimum value, then the graphic progress indicator is closer to the left side of the control;if the position is closer to the maximum value, then the progress indicator approaches theright side of the control.

Implementing the Tracker control will allow you to explore GDI+ in greater detail. Thecomplete source listing for the Tracker is defined in Tracker.cs in the Controls.dll projectas part of the VideoKiosk.sln. Minimum defines the relative left side of the Tracker, andMaximum defines the relative right side. The progress indicator is painted along a horizontalgroove relative to the value of the Position property. For an experienced programmer, figuringout where the progress indicator is painted is a simple matter of multiplying the percentage ofthe position by the difference between the minimum and maximum values.

Suppose the range of values is between 0 and 1,000. To avoid invalidating the control andrepainting a possible 1,000 times, an arbitrary number of subdivisions are defined to paint theprogress indicator at regular intervals when the change is significant.

Using the SetStyle MethodThe PlayControl simulates an occasion where we invested considerable effort in creating theinterface. The background image represents this investment. It would be a shame to coverthe background image up with controls. By allowing the background image to peek throughcontrols, we are able to sustain the visually appealing background.

Unfortunately, controls do not support a transparent background by default. Hence, wewould either have to make controls like the Tracker look like our background image or letthe background image show through. The second choice is likely to yield the most seamlessresult and is actually cheaper to implement.

8 8 A d v a n c e d C # P r o g r a m m i n g

Page 116: Advanced c sharp programming

C h a p t e r 3 : V i d e o K i o s k 8 9

To allow the background image to show through, a control requires that you set theControlStyles.SupportsTransparentBackColor attribute. This is accomplished by callingthe Control.SetStyle method in the control’s constructor.

public Tracker()

{

this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);

}

When you paint the Tracker control onto the PlayControl, you can set the BackColor toTransparent, which will allow the background image on the form to show through. Theeffective result is that the Tracker control blends into the PlayControl.

Using the ControlPaint ClassWith GDI, if you wanted to draw beveled controls that had a 3D appearance, you could usethe API procedure DrawEdge. GDI+ implements a ControlPaint class that has several staticmethods that support drawing 3D bordered shapes and shapes that look like standardcontrols, such as a Button.

The ControlPaint class is defined in the Systems.Windows.Forms namespace. ControlPainthas static methods for drawing buttons, checkboxes, combo buttons, grids, menu glyphs,radio buttons, and size grips, among other things. Instead of trying to figure out how to drawwhat looks like a groove for the progress indicator, I used the ControlPaint class to create thevisual appearance of the Tracker.

Drawing a 3D Border The Tracker class has a method DrawBar that draws the groove thatthe progress indicator appears to track along. DrawBar takes a graphics object argumentand calls the DrawBorder3D method.

protected virtual void DrawBar(Graphics graphics)

{

ControlPaint.DrawBorder3D(graphics, GetX(), GetY(),

GetWidth(), GetHeight(), Border3DStyle.Etched, Border3DSide.All);

}

DrawBorder3D is a static method, so we do not need to create a ControlPaint object toinvoke this method. As with many drawing methods, we express the region as the boundariesof a rectangle. GetX(), GetY(), GetWidth(), and GetHeight() were implemented by me toreturn calculated offsets for the groove based on the size of the Tracker. The final twoarguments are enumerated values chosen from those available based on the desired result.I wanted the progress indicator to look as if it were tracking along the groove, and the Etchedborder style and All sides of the border created this effect.

Drawing a Button Control There are several ways to create the tracker button. You couldload a graphic image, then scale the image based on the size of the Tracker. For convenience,

Page 117: Advanced c sharp programming

9 0 A d v a n c e d C # P r o g r a m m i n g

the shape of a button seemed suitable. The Tracker.DrawTracker method uses theControlPaint.DrawButton method to create the progress indicator.

protected virtual void DrawTracker(Graphics graphics)

{

ControlPaint.DrawButton(graphics,

TrackPosition(), (Height-8)/2, 5, 10, ButtonState.Normal);

}

I implemented the TrackPosition method to determine where to position the progressindicator along the horizontal axis. The vertical offset is determined by the (Height-8)/2calculation, which will work reasonably well unless the Tracker is too small to be functional.The ButtonState.Normal enumerated value draws the button in its normal up-state appearance.

Calling the Graphics.DrawImage MethodThere are several buttons implemented for the PlayButton. The biggest difference betweenall of the buttons is the image that is displayed on the button. Before we distribute thecontrol, we should take advantage of this knowledge.

If the big difference is the graphic drawn, then we should be able to extract the capabilityof drawing the graphic and consolidate all of the buttons to one button. The RoundButton.csmodule contains several button classes. While I implemented a control named ImageButton,I did not consolidate all of the button classes. If you were to create the image for theother buttons, then you could fairly easily consolidate the button classes and get rid ofthe other buttons.

The code listing that follows is derived from the DarkButton control, which in turn isderived from the RoundButton control. DarkButton demonstrates how to overload somemethods that change the gradient brush. Again, this is code that can be generalized by addingthe two brush colors used to create the gradient as properties of the RoundButton class.Listing 3-7 takes the next leap, which externalizes the graphic aspect of the button.

Listing 3-7 This code implements an ImageButton that is derived from the RoundButton inListing 3-3.

1: public class ImageButton : DarkButton

2: {

3: // TODO: Make sure that the image can be deleted!

4: private Image graphic = null;

5:

6: public ImageButton()

7: {

8:

9: }

10:

Page 118: Advanced c sharp programming

C h a p t e r 3 : V i d e o K i o s k 9 1

11: protected override Color GetColor()

12: {

13: Color[] colors = {Color.MidnightBlue, Color.Black};

14: return colors[Convert.ToInt32(down)];

15: }

16:

17: private int GetX()

18: {

19: return (Width - graphic.Size.Width) / 2;

20: }

21:

22: private int GetY()

23: {

24: return (Height - graphic.Size.Height) / 2;

25: }

26:

27: private ImageAttributes GetImageAttribute()

28: {

29: ImageAttributes attribute = new ImageAttributes();

30: attribute.SetColorKey(Color.White, Color.White,

31: ColorAdjustType.Default);

32: return attribute;

33: }

34:

35: private Rectangle GetRectangle()

36: {

37: return new Rectangle(GetX() + 1, GetY() + 1,

38: graphic.Size.Width, graphic.Size.Height);

39: }

40:

41: protected override void DrawGraphic(Graphics graphics)

42: {

43: base.DrawGraphic(graphics);

44: if( graphic == null ) return;

45:

46: graphics.DrawImage(graphic,

47: GetRectangle(), 0, 0,

48: graphic.Size.Width, graphic.Size.Height,

49: GraphicsUnit.Pixel,

50: GetImageAttribute());

51: }

52:

Page 119: Advanced c sharp programming

53: public Image Graphic

54: {

55: get

56: {

57: return graphic;

58: }

59: set

60: {

61: graphic = value;

62: Invalidate();

63: }

64: }

65: }

The key to painting the image on the button is to allow the surrounding part of the imageto be masked out of the display. For example, if you have a 16×16-pixel image but the imagedoesn’t take up all of the pixels, then there will be part of the image that you don’t want todisplay. To define the pixels that you don’t want to display—that you want to screen out—you will need to create an ImageAttributes object that defines the color of the pixels that youwant to screen.

Shown on line 46, DrawImage has many overloaded versions to accommodate a varietyof arguments designed to precisely manage the incorporation of a graphics image. Theversion used takes an Image argument, a Rectangle that specifies the bounding region forthe image, the four coordinates of the portion of the image to draw—allowing you to subdivideimages—the GraphicsUnit that the coordinates describe, and the ImageAttributes objectdescribed earlier.

TIPYou can write code that reads a specific pixel. For example, you might read the top/left-most pixel and usethe color of that pixel as the color to screen out.

The ImageButton uses an ImageAttributes object with color keys of White. The result isthat White is screened from the displayed image. If you want to conceal part of the image,then in this case make the part of the image you want to conceal White. Figure 3-6 showsthe result of trying to display the image without screening the extra pixels using theImageAttributes object.

9 2 A d v a n c e d C # P r o g r a m m i n g

Figure 3-6 This is what happens (zoomed) if you forget to screen out the extra pixels.

Page 120: Advanced c sharp programming

Secondary TopicsWe have covered a lot of ground in this chapter. The total listing for the VideoKiosk.sln,including the Controls.dll class library, contains several hundred lines of code. As we havediscussed, some of the code can be consolidated. With a little effort, you could reduce thenumber of buttons to a single round image button. If I were to do this for you, then youmight miss all of the various code fragments and techniques that demonstrate the powerof GDI+, such as shape drawing.

There are several other facets of .NET that were employed to complete theVideoKiosk.sln sample program. While these topics are secondary to programming withGDI+, you will find them helpful. Let’s take a couple more pages to examine some of theseaspects of .NET.

Implementing the Elapsed Time ClockThe elapsed time clock is updated on a Timer tick. Using the Timer control is a convenientway to perform lightweight background processing. Clearly, using the Timer is notmultithreading, but for such a simple task as updating a clock, the Timer is reasonableand sufficient.

The Timer control works by raising an event when an interval expressed in millisecondselapses. When the Timer.Interval elapses, the SetPosition method uses the Player.Elapsedvalue to update the position of the Tracker. A side effect of updating the progress indicatorof the Tracker is the updating of the display label containing the elapsed time displayed as adigital clock.

The position value is expressed as seconds; thus, the position value can be used to createthe time value for the clock display. This is done by creating an instance of the TimeSpanstructure, initialized with the elapsed seconds. The code to create the clock is shown next.

private TimeSpan GetSpan(int position)

{

return new TimeSpan(0,0,0,position, 0);

}

private string GetMask()

{

return "{0}:{1,2:0#}:{2,2:0#}";

}

private string GetFormatted(TimeSpan span)

{

return string.Format(GetMask(), span.Hours,

span.Minutes, span.Seconds);

}

private void ChangePosition()

{

C h a p t e r 3 : V i d e o K i o s k 9 3

Page 121: Advanced c sharp programming

labelElapsed.Text = GetFormatted(GetSpan(Position));

}

The TimeSpan structure knows how to convert the elapsed seconds represented by theposition to hours, minutes, and seconds. Every 60 seconds will be converted to anotherminute, and 60 minutes to an hour, automatically. The GetMask method returns a formattingstring used to control the appearance of the TimeSpan, and that formatted string is used toupdate the digital clock.

Using TimeSpan to Evaluate Time DifferencesThe TimeSpan structure is used to measure time periods as short as 100 nanoseconds, whichare represented as one tick in a TimeSpan. TimeSpan values can be represented as positiveand negatives spans of time measured in terms of days, hours, minutes, seconds, and fractionsof seconds. The string format d.hh:mm:ss.ff would display the days, hours, minutes, seconds,and fractions of a second represented by a single TimeSpan. The largest unit of timerepresented by the TimeSpan is the day.

If the default string value of the TimeSpan were acceptable, then we could use theTimeSpan.ToString() method, which would return the TimeSpan in hh:mm:ss formatby default.

If you perform an arithmetic operation on DateTime values, for instance subtracting onetime from another, the operation will return a TimeSpan object.

Formatting StringsFor our purposes we elected not to use the default TimeSpan.ToString method. This isan arbitrary decision that allows us to take a closer look at the string.Format methodand formatting masks. (In a production system, it would be cheaper to use theTimeSpan.ToString method.)

GetMask returns a formatting mask. The mask is "{0}:{1,2:0#}:{2,2:0#}". Charactersin the brackets {} represent replaceable format characters, and everything else represents aliteral value. Inspecting {1,2:0#}, we understand this replaceable parameter to be replaceableparameter 1, which is zero-filled and can be up to two numeric characters in length.

In general, the first value in brackets represents the zero-based argument to be formatted.The second argument—if present, it is delimited by a comma—represents the width of theformatted value. If you add a negative sign, then the value is left-justified, and a plus signwill right-justify the value. By default, the width will pad the formatted value with spaces.The optional formatting string follows the colon. Our 0# formatting string means that theformatted output will be padded with a 0 and a number. If the number takes both spaces,then the 0 will not be displayed.

Using the ToolTip ControlWindows Forms provides a ToolTip component. When you add the ToolTip componentto your application, it is added to the Component Tray, rather than directly on the form.Components in the Component Tray are nonvisible components; that is, they do not showup at run-time.

9 4 A d v a n c e d C # P r o g r a m m i n g

Page 122: Advanced c sharp programming

When you add the ToolTip to the Component Tray for a specific Form, the controls onthat form will have the ToolTip property added to their list of properties. If you provide textfor the ToolTip property, then the tip will be displayed when a user hovers the mouse overthe control.

NOTEComponents are added to the Component Tray by dragging and dropping them onto the form. However,nonvisible components are dropped over a form but actually displayed in the Component Tray. This strategywas probably adopted to help eliminate form clutter.

The ToolTip control’s relationship to other controls is not immediately intuitive. If youexamine Windows Forms controls initially, you might suspect that .NET does not supportToolTips. All you need to do is add the ToolTip component to a form by dropping thecomponent into the Component Tray.

Adding Controls to the ToolboxGenerally, you create a user control to add several controls to a common container. Whenyou add a control to your project, that control is automatically added to the Windows Formstab of the Toolbox.

If you want to add controls in a Control Class Library to the Toolbox, then you will needto create a Control Class Library, select Customize Toolbox from the Toolbox context menu,and use the Customize Toolbox dialog to add the Control Class Library to the list of thosethat you want to display in the Toolbox. Chapters 8 and 9 will elaborate more on this subject.

Catching and Handling Specific ExceptionsChapter 2 introduced the subject of exception handling by demonstrating the resourceprotection block, implemented with the try finally construct. The VideoKiosk demonstratesthe try catch construct and how to catch specific instances of exceptions.

To catch any exception in a method, use the try catch construct, placing the code to try inthe brackets immediately after the try keyword and the error-handling code in the bracketsimmediately after the catch keyword. The following fragment demonstrates the basic syntaxof the try catch exception-handling block.

try

{

// code to try

}

catch

{

// code to run if an error occurs

}

C h a p t e r 3 : V i d e o K i o s k 9 5

Page 123: Advanced c sharp programming

The example demonstrates a catch-all exception handler. If any exception occurs in the tryblock in the preceding code, then the code in the catch block runs. Not every procedureshould have an exception-handling block. If you have an idea of how to reconcile the error,then you will want to implement an exception handler. If the code might cause the programto crash, then you might want to implement an exception handler to shut down in an organizedmanner and record the error. But blanket, do-nothing exception handling should be avoided.The latter is the default behavior of .NET applications.

If you want to handle a specific exception and ignore all others, then you can indicate theclass of the exceptions you want to handle by implementing one or more catch blocks. Thefollowing SetMaximum method demonstrates how to catch the OverflowException thatmight occur if the duration is greater than a 32-bit integer.

private void SetMaximum(double duration)

{

try

{

tracker1.Minimum = 0;

tracker1.Maximum = Convert.ToInt32(duration);

}

catch(OverflowException e)

{

MessageBox.Show(e.Message, "Media Error!",

MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

}

The Convert.ToInt32 method would raise an exception if duration is greater than themaximum value for a 32-bit integer. If this were to happen, then the catch block would catchthe OverflowException in the object e, which in turn can be used to aid in resolving the error.The example simply displays the error to the user with the idea that they might elect to pickanother file.

If an exception other than the OverflowException occurs, then the catch block implementedin SetMaximum will not be notified of the exception.

Adding TODO Items to the Task ListVisual Studio .NET supports comment tokens. Comment tokens are special keywords,including TODO, and those that you define. When these keywords appear immediately afterthe whack whack (//) comment token, those comments are added as tasks to the Task List.

You can view comment tokens in the Task List by selecting View | Show Tasks | Allor View Show Tasks | Comment in Visual Studio. If there is something you want to bereminded to do, add a TODO comment in your code at the point where you want to performthe task. Include text that reminds you what the problem is. To add custom comment tokens,define those custom comments in the Options Dialog on the Environment | Task List page(see Figure 3-7).

9 6 A d v a n c e d C # P r o g r a m m i n g

Page 124: Advanced c sharp programming

Listing 3-7 demonstrates a TODO item on line 3 in the ImageButton class. The TODOcomment reminds me that I have to make sure that I can delete an Image property. If youhave an Image property, then the Image editor, which is automatically associated with anImage property, will allow you to select an Image using an Open dialog. Unfortunately,you cannot automatically delete the Image by setting the Image back to (None). You haveto add a DefaultValueAttribute to the Image property to support deleting the image. TheDefaultValueAttribute provides an initial value and a substitute value if you remove aprovided value in the Properties window. Listing 3-8 demonstrates a revision to theImageButton class that allows me to delete the Image property.

Listing 3-8 Revising the ImageButton class and resolving the TODO reminder from Listing 3-7

1: public class ImageButton : DarkButton

2: {

3: private Image graphic = null;

4:

5: public ImageButton()

6: {

7:

8: }

C h a p t e r 3 : V i d e o K i o s k 9 7

Figure 3-7 Defining custom comment tokens in the Task List page of the Options dialog

Page 125: Advanced c sharp programming

9:

10: protected override Color GetColor()

11: {

12: Color[] colors = {Color.MidnightBlue, Color.Black};

13: return colors[Convert.ToInt32(down)];

14: }

15:

16: private int GetX()

17: {

18: return (Width - graphic.Size.Width) / 2;

19: }

20:

21: private int GetY()

22: {

23: return (Height - graphic.Size.Height) / 2;

24: }

25:

26: private ImageAttributes GetImageAttribute()

27: {

28: ImageAttributes attribute = new ImageAttributes();

29: attribute.SetColorKey(Color.White, Color.White,

30: ColorAdjustType.Default);

31: return attribute;

32: }

33:

34: private Rectangle GetRectangle()

35: {

36: return new Rectangle(GetX() + 1, GetY() + 1,

37: graphic.Size.Width, graphic.Size.Height);

38: }

39:

40: protected override void DrawGraphic(Graphics graphics)

41: {

42: base.DrawGraphic(graphics);

43: if( graphic == null ) return;

44:

45: graphics.DrawImage(graphic,

46: GetRectangle(), 0, 0,

47: graphic.Size.Width, graphic.Size.Height,

48: GraphicsUnit.Pixel,

49: GetImageAttribute());

50: }

51:

52: [DefaultValue(null)]

53: public Image Graphic

9 8 A d v a n c e d C # P r o g r a m m i n g

Page 126: Advanced c sharp programming

54: {

55: get

56: {

57: return graphic;

58: }

59: set

60: {

61: graphic = value;

62: Invalidate();

63: }

64: }

65:

66: }

The DefaultValueAttribute on line 52 in Listing 3-8 resolves the TODO problem. Whenyou fix a TODO problem, remove the TODO comment and the reminder is removed fromthe Task List.

A good strategy is to define Comment Tokens that will help you organize and trackoutstanding issues. You can simply use the TODO comment token or define custom tokensthat have special meaning for you or your team.

Using the Process ClassChapter 2 introduced the Process class. The Process.Start static method is associated with theClick event handler of the leftmost ImageButton on the PlayControl. Click this button andwrite the following code

Process.Start("http://www.softconcepts.com");

COM InteropThere is a concept in .NET referred to as “COM Interop” (for interoperability). COM usestype libraries to describe interfaces, and .NET eliminates the type library by introducingReflection and by embedding metadata into .NET assemblies. Chapter 2 explains Reflection,assemblies, and metadata.

A perfect example of this is the ActiveX MediaPlayer used in the VideoKiosk. TheActiveX MediaPlayer is a COM-based control. When you want to use COM (also referredto as ActiveX) controls in .NET, you can import those controls by selecting CustomizeToolbox from the Toolbox context menu. Visual Studio .NET will import the type libraryfor that COM control, allowing you to use COM-based controls in .NET applications.

Unfortunately, when you use COM controls in .NET assemblies, you are reverting tothe “DLL Hell” zone. You will have to create an installation program that distributes andregisters those COM controls, just as you would if you were developing and deploying anapplication developed before .NET.

C h a p t e r 3 : V i d e o K i o s k 9 9

Page 127: Advanced c sharp programming

SummaryGDI+ is an evolutionary stage of GDI. All of the great graphics programming you could dowith GDI before .NET is much easier to do now that we have .NET. It is easier to do becausewe now have great methods and capabilities that are organized into classes that make thesecapabilities easier to find and employ.

Remember that GDI+ is stateless. You will have to pass information like colors, brushes,and fonts every time you invoke a GDI+ method. Remember also that you should not cacheGraphics objects. Request Graphics objects with the factory method CreateGraphics or usethe Graphics object passed to Paint event handlers.

Finally, GDI+ supports vector graphics, imaging, and typography. Vector graphicssupports line and curve drawing. Imaging supports managing complex images like bitmap,GIF, or JPEG files, and typography supports advanced typesetting and fonts. Like GDI,GDI+ is a big fish—just better organized and more approachable.

1 0 0 A d v a n c e d C # P r o g r a m m i n g

Page 128: Advanced c sharp programming

CHAPTER

4Terrarium

101

IN THIS CHAPTER:

Demonstrated Topics

Downloading, Installing, and ConfiguringTerrarium

Playing Terrarium

Reviewing the Terrarium Framework

Creating Plants and Critters

Introducing Plants and Critters to the Terrarium

Secondary Topics

Serializing Objects

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 129: Advanced c sharp programming

1 0 2 A d v a n c e d C # P r o g r a m m i n g

Children are motivated by fun. Many adults in our field are motivated by fun too.Perhaps some mature, older programmers have lost touch with their inner PeterPan, but a recent experience with a development team in Oregon illustrated that

creative adults are motivated by fun too.While working on a project in Oregon, several contract developers and I had the unenviable

position of helping mainframe programmers learn C#. These programmers had no prior Windowsprogramming experience, only nominal PC skills, and no prior C or C++ experience. Thequestion was how do we go about making learning a new language, paradigm, and complexsubject matter fun. We found the answer in Terrarium.

In 2001, I was visiting Microsoft, and Tom Kaiser mentioned this peer-to-peer gamedeveloped with .NET. He was very excited about the technology and the Terrarium game. Thenext year, I fortunately recalled that discussion when facing the training dilemma. Searchingthe Web I discovered to my delight that Terrarium was available on the www.gotdotnet.comwebsite. I downloaded Terrarium from the site, and a few minutes later had a Terrarium upand running with some critters and plants. I had created a small ecosystem in a few minutes.Fogies and management might scoff at the notion, but creative people are motivated by fun.

What is Terrarium?Terrarium is a game developed in .NET. Terrarium is a virtual ecosystem with plants,

herbivores, and carnivores. The plants grow and reproduce. The herbivores eat plants, grow,and reproduce, and the carnivores eat the herbivores and grow and reproduce. Herbivoreshunt plants and consume them, and carnivores hunt herbivores and consume them. All of thishappens visually on your PC. There are rules that describe the patterns of growth, consumption,and reproduction. The trick is that you and other programmers get to define these rules writing.NET code. The real trick is to either create a balance in the ecosystem or take over, consumingeverything in sight.

Terrarium is fun. Your code interacts with other programmer’s code, and this interactionprovides a powerful way to learn how to program with .NET.

Demonstrated TopicsAside from having fun, it is important that we learn. (This is true unless you are independentlywealthy and have no boss looking over your shoulder.) Creating plants and animals can teachyou a lot about programming, and especially a lot about programming in C#.

Terrarium uses the peer-to-peer model. Instead of a client-server model—also referred toas a spoke-and-hub model—Terrarium demonstrates an application that is composed of peersintercommunicating to work in a concerted way. In the Terrarium, the clients (PCs) interactto create an ecosystem. Your PC and other PCs (peers) are all part of a bigger ecosystem. Plantsand animals are moving between your PC and other PCs in your slice of the ecosystem. To beprecise, code that you write gets transported to other peers that make up your ecosystem, andthat code runs safely on those other peers.

Page 130: Advanced c sharp programming

Plants and animals are teleported, in the vernacular of the Terrarium game. A Terrariumserver stores a peer IP address list. When the teleporter—represented by a blue ball—encounters an organism, a peer is selected at random from your peer list, and the organism issent to that peer’s IP address. Transporting the organism occurs on the PC and is accomplishedby serializing the organism’s state and sending a copy of the organism assembly to the peer.The sent assembly is examined for security violations and breaches of game etiquette—forexample, Reflection and File IO are not allowed. If the organism is safe and doesn’t havecode that would effectively allow cheating then an instance of the organism is created and thestate is deserialized.

What can be learned by participating in a Terrarium ecosystem? That is the subject ofthis chapter. By reading this chapter and completing the examples herein, you will havean opportunity (to have fun and) to learn about

� Smart client updates for Windows Forms applications

� A graphics-based application using DirectX and COM Interop

� Regular expressions

� Reflection

� XML Web Services

� Peer-to-peer networked applications

� Code access security

� Inheritance

� Applying attributes to define behavior

� Implementing event handlers

Downloading, Installing, and Configuring TerrariumThe first thing you will need to do is download the Terrarium client application (shown inFigure 4-1) from www.gotdotnet.com. You can also download a Terrarium server from thesame site if you want to host your own Terrarium. (You don’t have to download the Terrariumserver; the client application will do.) Terrarium is free after you agree to an online customeragreement.

TIPEveryone who wants to participate in your ecosystem will need to download a copy of the Terrarium client.

After you download the Terrarium client (and, optionally, the server), you will need toperform some basic configuration steps to implement how you want to run the Terrarium.There are a couple of ways that you can run Terrarium; each of those and the configurationsettings you will need to make are discussed next.

C h a p t e r 4 : T e r r a r i u m 1 0 3

Page 131: Advanced c sharp programming

Configuring the Terrarium ClientYou can configure the Terrarium client application to interact with other peers in the greatwide world. This option registers your PC with a Terrarium server, so other peers know whereto find you. The Terrarium server will place your PC in a slice of the global ecosystem; yourPC may interact with a couple of dozen or as many as a couple of hundred other PCs.

You can also create your own Terrarium for testing and debugging purposes, create aserverless peer-to-peer ecosystem (this is a good option if your network administrator won’tlet your PC interact outside of the firewall), or install the Terrarium server for a more robustinternal ecosystem. For our purposes, you will need at least one PC with one instance of theTerrarium client running its own ecosystem, a .ter file.

If you elect to run the Terrarium server behind your company’s firewall, then you willneed to install the Terrarium server on a machine with MS SQL Server. (I have found thatMSDE—the desktop SQL Server version—will run the Terrarium server reliably, althoughthis is not recommended by Microsoft.)

1 0 4 A d v a n c e d C # P r o g r a m m i n g

Figure 4-1 A Terrarium ecosystem running on my laptop with the plants and animals definedin this chapter

Page 132: Advanced c sharp programming

Let’s examine the steps for configuring the Terrarium client for screensaver mode,Terrarium mode with or without peers, and ecosystem mode. We will follow this up witha quick review, describing how to install and configure the Terrarium server.

Running the Terrarium ClientTerrarium makes an interesting screensaver. You can configure Terrarium to run as your screen-saver, in addition to several other configuration options. Let’s review these quickly, so we canget to the programming.

Configuring Screen Saver ModeWhen you install the Terrarium client, you can indicate that you want Terrarium to run asyour screensaver. If you elect not to run Terrarium as your screensaver, you can alwaysselect the .NET Terrarium from the Screen Saver tab of the Display applet at any time ifyou change your mind.

If your co-workers have never seen Terrarium, expect to attract some attention whenplants and bugs are being actively pursued and consumed on your screen.

Configuring Terrarium ModeTerrarium mode can be used for debugging and testing your bugs before putting them into anecosystem. You will need to create a Terrarium to run in Terrarium mode. Follow these stepsto create a Terrarium:

1. To run in Terrarium mode, click the Show Terrarium Mode Controls button, shown inFigure 4-2. (The button icon is represented by the fish tank icon in the lower-leftcorner, and the text is visible as a hint when you hover the mouse.)

2. Click Create a New Terrarium and provide a file name in the New Terrarium dialog.Terrarium will restart in Terrarium mode after you click Save.

3. If you want other players to be able to participate in this Terrarium, provide a name forthe Peer Channel (also shown in Figure 4-2).

You are ready to introduce plants and animals into the Terrarium. At the top right of theTerrarium client are status indicators, the number of Animals in your client instance, and thenumber of peers in your slice of the ecosystem. Green status indicators mean that everythingis all right, and red status indicators mean that a particular aspect of the Terrarium is not

C h a p t e r 4 : T e r r a r i u m 1 0 5

Figure 4-2 Terrarium mode controls

Page 133: Advanced c sharp programming

operating correctly. You can hover the mouse cursor over the status indicator for informationor click the Help link at the top center of the client for more information (see Figure 4-3).

Configuring Ecosystem ModeEcosystem mode is where you are playing with others in the larger ecosystem. (You caninstall your own instance of the Terrarium server from www.gotodotnet.com.) To play inecosystem mode, you will need to modify some game settings. Right-click the Terrariumclient to configure your Terrarium client. You will need to make a few changes to participatein the ecosystem:

1. In the Terrarium Game Settings dialog, click the Registration button and enter ane-mail address. This information is used to send you status information.

2. Click the Community button and select your Country and State (you can leave thisinformation blank).

3. You can click the Performance button and adjust the CPU usage slider to experimentwith performance.

4. Finally, click the Server button and enter the URL to the Terrarium server you areparticipating in. For example, the default path is http://www.terrariumgame.net/terrarium/. If you install the Terrarium server on a computer at your location, then youwill need to enter that URL. For example, running the server on my laptop, I changedthe Server URL to http://lap800/terrarium/—that is, the location in which I installedthe Terrarium server.

5. Click Accept Changes to update the Game Settings. (You will need to stop and restartthe client for Server URL game settings to take effect.)

You will need to visit the www.gotdotnet.com website to check for available server locations.

Configuring the Terrarium ServerYou do not need to install and configure a Terrarium server to run the examples defined inthis chapter. For that reason, I won’t elaborate on configuring the Terrarium server. I will tellyou that one has been running behind a firewall at the location I am writing this, and therehas been no difficulty.

Follow the instructions for configuring the Terrarium server, including installing SQLServer. We have been successfully running the Terrarium server using the MicrosoftSQL Server Desktop Engine, known as MSDE, with no difficulty.

1 0 6 A d v a n c e d C # P r o g r a m m i n g

Figure 4-3 Status indicators, animals, and peers

Page 134: Advanced c sharp programming

Playing TerrariumTo play Terrarium, you will need to participate in an ecosystem or run in Terrarium modeand add plants, herbivores, and carnivores. Aside from the various creatures making for morelively interactions, the ecosystem must have some balance. No plants, and the herbivores willstarve to death. No carnivores, and the herbivores will consume all of the available plant lifeand die off. Too many carnivores, and the herbivores are quickly devoured, and then thecarnivores starve, too.

Running in Terrarium mode successfully encourages you to find a balance. In general,plenty of plants, and your herbivores thrive. The ecosystem seems to seek a balance of aboutten herbivores for every carnivore. There is a catch: introduce too many varieties of carnivores,and your carnivores will consume both carnivores and herbivores. Creating either a harmoniousecosystem or killer bugs is fun; it depends on your perspective. (Some people on our teamlike to create the best offensive herbivores and “win” by creating the largest population.)

Environment Reporting Every Six MinutesTerrarium gathers statistics approximately every six minutes when you are runningTerrarium. You can view this information to determine which critters are dominating and therelative health of the plants and animals in your Terrarium. Clicking Show Trace Windowshows a Trace Window and a Population Statistics window. To view the trace informationabout the critters in your ecosystem and the relative populations of the organisms in yourTerrarium, select a specific instance of a critter and open the Trace Window (a Deer trace isshown in Figure 4-4).

If you are running in ecosystem mode and running your own server, then you can obtainbroad server statistics directly from the server via a Web Service. These statistics are accessibleby navigating your browser to the Web application installed with the Terrarium server. Theserver statistics are self-explanatory.

Understanding XML Web Services in TerrariumTerrarium is a peer-to-peer application. The server is used for gathering information likeecosystem statistics. XML Web Services are employed to allow participants to access thosestatistics and find out about the welfare of their creations.

Programming an XML Web ServiceLet’s take a moment to look at XML Web Services.

Computer software needs a straightforward and reliable way to exchange data. Microsoftoffered Distributed COM (DCOM) based on a proprietary implementation. For a variety ofreasons—and partially based on its proprietary nature—DCOM does not satisfy the real needof customers. XML Web Services is a new way to think of an existing problem: how do weget software implemented on different computers and languages to interoperate?

Microsoft believes the answer is XML Web Services. XML, or eXtensible MarkupLanguage, is a standardized markup language, like WML and HTML. XML is simple textcomposed of tags that have inferred meanings and support user-defined extensions. Because

C h a p t e r 4 : T e r r a r i u m 1 0 7

Page 135: Advanced c sharp programming

XML is text, it can easily be passed over a TCP/IP network, such as the Internet. Examiningthe whole picture, we have a user-definable, text-based language that can describe things andpass easily over the Internet. Data can be represented in XML, and, more important, objectscan be represented as XML text. The result is that complex objects can be transported overthe Internet as simple XML text. XML Web Services is the name of the part of the .NETframework that implements these capabilities.

To implement a Web Service, select the ASP.NET Web Service template from the NewProjects dialog. The template will create a DLL-targeted solution that contains a class thatinherits from System.Web.Services.WebService. A Web Service contains code like any otherassembly. The difference is that methods marked with the WebMethodAttribute can be invokeddirectly by consumers. (A Web Service may talk to other services, but let’s set that aside fora moment.)

Web Services is a technology based on XML that supports transferring information acrossthe Internet—any TCP/IP connection—using open standards rather than proprietary standards.The Web Service provider and the consumer are not implemented by the same entity. Codethat is implemented by the same producer and that exists within the same infrastructure isaccessible to that producer. Web Services address problems like operating system interoperabilityand providing access to data to external consumers.

Suppose you work for a tools vendor, and your company has elected to provide a libraryof regular expressions that perform complex string searching capabilities. You could exposethese regular expressions as XML Web Services and allow consumers to use your regularexpressions for a fee. Regular expressions are implemented as a terse language that can performsome very powerful string searching and replacing operations. For example, ^[0-9a-zA-Z]+$

1 0 8 A d v a n c e d C # P r o g r a m m i n g

Figure 4-4 Output from the WriteTrace command showing the internal operation of the Deer trace(code available online)

Page 136: Advanced c sharp programming

can be used to determine whether a string contains at least one or more alpha or numericcharacters. We could expose our regular expressions as a named XML Web Service as part of atool library. (Regular expressions are implemented in the System.Text.RegularExpressionsnamespace in the .NET framework.) Listing 4-1 implements a Web Service with thealphanumeric string test WebMethod.

Listing 4-1 A Web Service that represents a tool vendor publishing regular expressions

1: using System;

2: using System.Collections;

3: using System.ComponentModel;

4: using System.Data;

5: using System.Diagnostics;

6: using System.Web;

7: using System.Web.Services;

8: using System.Text.RegularExpressions;

9:

10: namespace Expressions

11: {

12: /// <summary>

13: /// Summary description for Expressions.

14: /// </summary>

15: public class Expressions : System.Web.Services.WebService

16: {

17: public Service1()

18: {

19: //CODEGEN: This call is required by the

20: //ASP.NET Web Services Designer

21: InitializeComponent();

22: }

23:

24: #region Component Designer generated code

25:

26: //Required by the Web Services Designer

27: private IContainer components = null;

28:

29: /// <summary>

30: /// Required method for Designer support - do not modify

31: /// the contents of this method with the code editor.

32: /// </summary>

33: private void InitializeComponent()

34: {

35: }

36:

C h a p t e r 4 : T e r r a r i u m 1 0 9

Page 137: Advanced c sharp programming

37: /// <summary>

38: /// Clean up any resources being used.

39: /// </summary>

40: protected override void Dispose( bool disposing )

41: {

42: if(disposing && components != null)

43: {

44: components.Dispose();

45: }

46: base.Dispose(disposing);

47: }

48:

49: #endregion

50:

51: [WebMethod()]

52: public bool IsAlphaNumeric(string input)

53: {

54: return Regex.IsMatch(input, "^[0-9a-zA-Z]+$");

55: }

56:

57: }

58: }

The only code that was generated by the ASP.NET Web Service template is the WebMethoddefined on lines 51 through 55. Notice that the method looks like any other method, except forthe WebMethod attribute on line 51. The .NET Framework takes care of a lot of the workfor us. The solution Expressions.sln contains the Web Service shown in Listing 4-1, and theTestExpressions.sln contains a Windows Forms application that invokes the Web Service.

To implement the Web Service represented by the code in Listing 4-1, follow thesenumbered steps.

1. Create a new ASP.NET Web Service project. Name the project Expressions.

2. Add the method from lines 51 through 55 to the WebService class you created in step 1.

3. Add a using System.Text.RegularExpressions; statement to the top of the .cs modulecontaining the code from step 2.

4. Compile and run the Web Service application by selecting Debug, Start. (This step willopen Internet Explorer and the .asmx page (see Figure 4-5) created by the template.You can use this page to test the Web Service.)

You can write a test application by creating a second project. A Windows Forms applicationwill suffice. Add a Web Reference to the test application in the Solution Explorer. You cancreate an instance of the object defined in the Web Service. For example, suppose you namedthe Web Service “Expressions” and installed it on your PC. After adding a Web reference to

1 1 0 A d v a n c e d C # P r o g r a m m i n g

Page 138: Advanced c sharp programming

C h a p t e r 4 : T e r r a r i u m 1 1 1

the Web Service in a second project, you can create an instance of the service, asdemonstrated next.

localhost.Expressions expressions = new localhost.Expressions();

expressions.IsAlphaNumeric("test123");

The first statement creates an instance of the Web Service. After we have an instance of theWeb Service object, we can use that object as if it were defined within the same project.

The example in this section is pretty basic. Read Chapter 12 for more examples of WebServices.

Critter TimingWhen the Terrarium is running, every organism in the Terrarium is provided with a smallslice of CPU time, about 2 to 5 milliseconds. Each organism has that modest interval inwhich to perform actions like resting, searching for food, defending, and procreating.

When you create animals, you will need to keep the amount of time you have available inmind as you implement your strategy. Unfortunately, we can learn the mechanics of programmingorganisms, but good strategies are highly subjective and ecosystem dependent. It will be upto you to experiment with strategy.

Figure 4-5 An .asmx page that you can use to test Web Services

Page 139: Advanced c sharp programming

TeleporterWhile you are playing, a blue ball floats around the ecosystem. The blue ball is the teleporter(shown in Figure 4-6), which transports plants and animals around the ecosystem. Theteleporter is responsible for cross-pollinating peers with organisms from other peers in thesystem. The end result is that you never know what is going to show up in your terrarium.(When playing in ecosystem mode, some of the carnivores can be devastating.)

The teleporter makes Terrarium interesting. Check out the www.gotodotnet.com websitefor advanced strategies, which include tracking the location of the teleporter. The teleportercan be used as an escape route, a convergence point, or a means of spreading your organismsto the greatest number of peers.

Smart Client UpdatesAnother sample application that ships with Terrarium is the Application Updater(AppUpdater.dll). The Application Updater is installed in the \Program Files\Terrarium\bin directory.

When you start Terrarium and at regular intervals while it is running, the Terrarium clientcontacts the master server application and requests updates for the application. The client contactsan XML Web Service on the master server. A comparison between the version of your clientand the latest version is made. If the server contains updated versions of the client, then theupdates are loaded into a new directory. The next time Terrarium is started the new versionis run. At least one old version is kept around in case there is a problem.

.NET supports a revised concept of thin client programming. The Application Updaterdemonstrates this model. The concept is simple: an application seamlessly and automaticallygrabs updated components across a network—a small application starter could be downloadedor deployed, and that application’s whole raison d’être is to download the rest of the applicationand constantly grab updated assemblies across a network. That network could be the Internet.

1 1 2 A d v a n c e d C # P r o g r a m m i n g

Figure 4-6 The blue ball is the teleporter, which is responsible for moving organisms betweenpeers in the ecosystem.

Page 140: Advanced c sharp programming

Dynamically Updating AssembliesThe Assembly class has a method named LoadFrom. Assembly.LoadFrom accepts an assemblyname. The argument assembly can be on the local file system or at the other end of the worldon some Web server. When you have an Assembly, you can compare manifest information,like version numbers, conditionally updating the assembly or not. The benefit is that this canoccur without the user’s intercession.

Let’s return to our regular expressions subscription service. Suppose instead of dynamicallyinvoking a Web method that tests a finite set of regular expressions, we elect to allow ourcustomers to dynamically load the regular expressions across the Internet. We can accomplishthis by loading an assembly across the Web, reading all of the expressions from the assembly,and continuing to process. The user is pleasantly surprised when the available expressionsget better and better. Listing 4-2 implements the assembly that provides regular expressions tosubscribers.

Listing 4-2 A Class Library assembly that can be loaded dynamically across the Web

using System;

using System.Collections;

namespace Expressions

{

public class Expressions

{

public Hashtable GetExpressions()

{

Hashtable hashtable = new Hashtable();

hashtable.Add("Numeric", @"^\d+$");

hashtable.Add("Social Security Number",

@"^(\d\d\d)-(\d\d)-(\d\d\d\d)$" );

return hashtable;

}

}

}

TIPThe “at” (@) symbol prevents the backslashes from being treated like escape characters.

Following our original concept, we don’t want to provide the expressions directly to theuser. If they had the expressions, which are the same as the source code, why would theyneed our service? The example program fills a hash table with keys and values. We can show

C h a p t e r 4 : T e r r a r i u m 1 1 3

Page 141: Advanced c sharp programming

the consumer the keys and apply the values. All the user selects is “Social Security Number,”and internally we apply the regular expressions @“^(\d\d\d)-(\d\d)-(\d\d\d\d)$”. (The regularexpression is understood to mean match strings that have three digits, a hyphen, two moredigits, a hyphen, and four digits. The expressions restrict extra characters at the beginningand end of the string with ^ and $, respectively.)

The consumer application supports complex string matching. To retrieve updated regularexpressions, our consumer application loads the assembly containing the expressions into ourconsumer application. Listing 4-3 demonstrates how to request, load, and get the informationout of the assembly.

Listing 4-3 Loading an assembly from a Web server

private void UpdateExpressions()

{

try

{

Assembly assembly =

Assembly.LoadFrom("http://localhost/regex/expressions.dll");

Type type = assembly.GetType("Expressions.Expressions");

Object instance = Activator.CreateInstance(type);

Hashtable newtable = (Hashtable) type.InvokeMember("GetExpressions",

BindingFlags.Instance | BindingFlags.InvokeMethod |

BindingFlags.Public, null, instance, new object[]{});

foreach(object key in newtable.Keys)

{

comboBox1.Items.Add(key.ToString());

}

hashtable = newtable;

}

catch(Exception e)

{

MessageBox.Show(e.Message, "Update Error",

MessageBoxButtons.OK, MessageBoxIcon.Error);

}

}

The code loads the assembly using the http:// moniker. The namespace and class arepassed—“Expressions.Expressions”—as a string to load the type from the assembly. Aninstance of the object is created using an Activator class; and the method GetExpressions,which returns a Hashtable, is invoked. We read all of the keys representing our expressionsinto a combo box. If all of the preceding code succeeds, then a field named hashtable isassigned to the local Hashtable, newtable. The user sees our well-named expressions butnever the expressions, representing our intellectual property and the reason we get paid.

1 1 4 A d v a n c e d C # P r o g r a m m i n g

Page 142: Advanced c sharp programming

A complete working sample is demonstrated in Client.sln. You will need to compile bothprojects—client.csproj and expressions.csproj—to test the example. The expressions.dllassembly will need to be copied to a Web server. Your PC will work as long as you arerunning Internet Information Services. Our example demonstrates using the PC as a testplatform, but it works just as well using any other Web server.

Peer-to-Peer ComputingEach Terrarium client is an equal peer in the Terrarium game. When you are running inecosystem mode, the server associates several logically close peers to your peer list. (Thisis the role of the server in Terrarium: tracking peers.) All of the peer groups have someoverlap, so every peer in Terrarium is ultimately part of one big ecosystem.

When the blue ball teleporter encounters an organism, a random peer from your peerlist is selected. Terrarium uses System.IO to stream the state of the organism to a file. Theassembly containing the organism selected by the teleporter and the selected organism’sstream state information are sent to the selected peer using a NetworkStream class in theSystem.Net.Sockets namespace.

The receiving peer checks the organism assembly for references to assemblies that mightrepresent cheating in the context of Terrarium, like Reflection. If the organism assemblypasses the evaluation, the organism is loaded into the receiving Terrarium client, andSystem.Runtime.Serialization is used to read the organism’s state. As a result, a duplicateorganism is re-created on the receiver and removed from the sender. (Teleportation—perhapsthis is how we will get Star Trek transporters to work. Scan your brain. Create a clone at thenew location. E-mail the brain scan of the transportee, load the brain scan into the clone, andkill the original.)

Support for Multiple Programming LanguagesTerrarium is an excellent place for programmers to experiment with multi-languageprogramming support. (We won’t go into multi-language programming here.) It is,however, practical to anticipate that in the near future programming teams will becomprised of programmers with skills in more than one .NET programming language.

Code Access SecurityRunning in ecosystem Terrarium means sending and receiving assemblies across a networkconstantly, with compiled code moving to and fro at random. This might make managers,network administrators, and even developers a bit squeamish.

One of the compelling facets of Terrarium is that it demonstrates that compiled code canmove safely between computers if the security model is employed. Refer to the later section“Introducing Code Access Security” for a brief example of code access security and toChapter 16 for a more comprehensive discussion on security.

C h a p t e r 4 : T e r r a r i u m 1 1 5

Page 143: Advanced c sharp programming

Reviewing the Terrarium FrameworkAny reasonable application is likely to be described by its architecture. Terrarium is apeer-to-peer application that you interact with by creating plant and animal organisms. Youcan’t create just any organism. You create organisms by inheriting from existing types—Plant or Animal—defined in the OrganismBase.dll assembly. How well your organism faresis a product of how well you program your strategy within the context of the Terrariumarchitecture.

The Terrarium architecture is too big to cover here in detail. (Someone will probably writea book on Terrarium soon.) You can find a cross-referenced architectural overview on thewww.gotodotnet.com website. Although you will be able to create new organisms withouthaving explored that information, the organisms will be disadvantaged. The top-level classesin Terrarium are Action, AnimalState, DefendAction, EngineSettings, MoveToAction,OrganismState, PlantState, Vector, Animal, AttackAction, EatAction, MovementVector,Organism, Plant, and ReproduceAction. From the top-level types, you might infer thatinterplay in Terrarium can be quite complex. You would be correct.

There are also several events, event arguments, attributes, exceptions, and interfaces inTerrarium. All of these members in the Terrarium framework are used to describe the behaviorof your organism. The basic idea is that you create an organism by inheriting from a Plant orAnimal. You apply attributes to describe the characteristics of your organism, and youimplement event handlers to codify the organism’s contextual responses. To be successful,plants have to grow and seed. More important, animals have to hunt, feed, breed, andpossibly attack, defend, and evade.

I will provide basic examples of a plant, an herbivore, and a carnivore. However, if youare going to compete in the larger ecosystem, then be forewarned that there are some seriousbugs out there.

Creating Plants and CrittersThe graphics in Terrarium are fun, but they are not nearly as good as the conceptual andcompetitive aspect of coding plants and animals. Plants grow and reproduce by spreadingseeds. Your plants will thrive if there is more seeding and growing than there is being eaten.Unfortunately, where plants are concerned, once you release a plant you are at the mercy ofthe Herbivores directly and the Carnivores indirectly. In general, plants are easy to implementand do reasonably well. Conversely, plants aren’t very fun to watch.

Animals are different. Animals move and actively participate in the ecosystem. How wellan animal does is more dependent on the clever strategy implemented by the programmer.(Don’t feel bad if your animals get chomped. Being a good programmer and a good strategistare not the same thing. Good code might actually be counterproductive in Terrarium becauseobject-oriented code may run slower than hack code.) In this next section we will take a lookat some basic plants and animals.

1 1 6 A d v a n c e d C # P r o g r a m m i n g

Page 144: Advanced c sharp programming

C h a p t e r 4 : T e r r a r i u m 1 1 7

Creating a PlantPlants are boring but an important part of the ecosystem. Plants provide us with a convenientand easy way to introduce an organism into the ecosystem. The basic steps for creating anorganism are as follows:

1. Create a Class Library project.

2. Include a reference to organismbase.dll, which is installed with the Terrarium client.

3. Include a reference to system.drawing.dll, the GDI+ assembly that ships with the .NETframework.

4. Define a class that inherits from the Plant class (or Animal to create an animal).

5. Define some required attributes.

6. Code your organism (see Listing 4-4).

Listing 4-4 An implementation for a Plant

1: using System;

2: using System.Collections;

3: using System.Drawing;

4: using System.Reflection;

5: using System.IO;

6:

7: [assembly: OrganismClass("AloeVera")]

8: [assembly: AuthorInformation("Paul Kimmel", "[email protected]")]

9:

10: [MaximumEnergyPoints(10)]

11: [MatureSize(26)]

12: [SeedSpreadDistance(50)]

13:

14: public class AloeVera : Plant

15: {

16: public override void SerializePlant(MemoryStream m)

17: {}

18:

19: public override void DeserializePlant(MemoryStream m)

20: {}

21:

22: }

The assembly attribute OrganismClass names the class we will be introducing into theecosystem. You will need to define a class with the same name as the one used to initialize

Page 145: Advanced c sharp programming

1 1 8 A d v a n c e d C # P r o g r a m m i n g

the OrganismClass attribute. (Lines 14 through 22 implement the AloeVera plant.) Theassembly-level attribute AuthorInformation is required; this information is used to indicateownership and provide contact information in case something is awry in the ecosystem.

The MaximumEnergyPoints, MatureSize, and SeedSpreadDistance are values that worklike the plant’s DNA, describing the plant. You can experiment with these attribute values tocreate plants that thrive, grow, and germinate.

Lines 16 and 19 implement empty serialization and deserialization methods. Thesemethods are abstract methods that you must implement; they have more meaning for animals.You can store knowledge about an organism’s ecosystem when that organism is teleported.You may want to implement these methods for animals. (Refer to the later section “SerializingObjects” for more information on implementing serialization in Terrarium.)

Creating CrittersAnimals are substantially more challenging to implement than plants. There are some aspectsof programming animals that are the same whether you are programming an herbivore or acarnivore. We’ll examine the common aspects of implementing animals before we implementan herbivore and a carnivore.

To implement an animal, whether a plant eater or meat eater, you will need to performthese steps:

1. Create a Class Library project.

2. Add references to organismbase.dll and system.drawing.dll.

3. Create a class that inherits from Animal and has the same name as the one you willprovide to the OrganismClass assembly-level attribute.

4. Implement a suite of attributes that approximately describe the conceptual DNA ofyour organism. These attributes are MatureSize, Carnivore, AnimalSkin, MarkingColor,MaximumEnergyPoints, EatingSpeedPoints, AttackDamagePoints, DefendDamagePoints,MaximumSpeedPoints, CamouflagePoints, and EyesightPoints.

5. Provide code to describe the behavior of your organism. (This is the tricky part.)

Rules in Common for AnimalsYou have 100 points that you can distribute among the MaximumEnergyPoints,EatingSpeedPoints, AttackDamagePoints, DefendDamagePoints, MaximumSpeedPoints,CamouflagePoints, and EyesightPoints. For herbivores you will likely want to emphasizethings like MaximumSpeedPoints and EatingSpeedPoints. You might also want to emphasizeDefendDamagePoints—think rhinoceros and cape buffalo. CamouflagePoints, hiding, mightbe a good attribute for small fast animals, and DefendDamagePoints might be a good attributefor large, slow animals. If you are creating a carnivore, then you might want to have hugeAttackDamagePoints, EyesightPoints, and MaximumSpeedPoints but to de-emphasize thingslike camouflage. I’m not a zoologist, but I can think of reasonable combinations that makesense for various attributes. Lions are reasonably successful because they have some speed,

Page 146: Advanced c sharp programming

use camouflage, and inflict maximum damage. On the other hand, hyenas hunt in packs andscavenge. (I think of the carnivores and herbivores as a particular kind of animal and emphasizethe characteristics those animals have.)

If you test your critter in Terrarium mode—as opposed to ecosystem mode—you mightget a warning based on attribute settings. Arbitrarily, it seems, DefendDamagePoints shouldbe in increments of 4 and EyesightPoints should be in increments of 10 (see Figure 4-7).

Animals have their attribute characteristics, which enhance or detract from the success ofa species. More important is the code you write for these animals. A clever strategist mightwrite code to create collaborative hunting behavior or aggressive predator-avoidance responses.

Certain things will rush your creature’s demise. If your creature throws an unhandledexception, then that instance of your critter will be destroyed and removed from the ecosystem.Terrarium promotes writing code that is proactive about exception handling. If your crittertakes too long to do its thing, then it will be destroyed too. You have about 2 milliseconds perturn to squeeze in all of the activity your creature needs to initiate or complete. Complicatedcode might cause your creature to die simply because the code is constipated.

You can use WriteTrace to send messages to the trace window in Terrarium mode. UseWriteTrace to debug your critters before introducing them into the ecosystem.

After you have created your organism, you can add your critters to a local Terrarium. Thiswill provide you with the greatest opportunity to debug…well, your bug. You can view tracemessages to see how your bug is actually behaving and view statistics about the success ofyour critter. Once you have introduced your organism into an ecosystem, you may notreintroduce the same critter. You can add more copies of the existing critter, but not revisedcopies. If you change the same bug and want to reintroduce that bug, then you will need tocreate a new local Terrarium.

When your bug is ready for prime time, introduce it into a larger ecosystem. As a sidenote, there are some Terrarium contests at the time of this writing, and you can win coolthings like an Xbox if your bug is the most successful. To see some really successful critters,you are encouraged to participate in a larger ecosystem and obtain a copy of the leadingbugs. You can use the .NET IL disassembler (ILDASM.EXE) to view the elements of thesecritters. Some of the leaders have been programmed to exhibit very complex behaviors.

Creating a HerbivoreI will start by demonstrating an herbivore. Herbivores are easier to program and tend tobe about ten times more successful than carnivores, although even herbivore behavior canbecome quite complex.

C h a p t e r 4 : T e r r a r i u m 1 1 9

Figure 4-7 A hint you will receive providing guidance on the application of attribute points

Page 147: Advanced c sharp programming

I picked a cow. It seems that the reason cows survive at all is because we keep them inpens and bring food to them. Based on my understanding of cow behavior, I programmed thecow to look around. If the cow sees any food, then the cow moves toward the food. If thecow bumps into anything, it tries to eat it. The cow has no concept of predators and has onlysimple food-searching behavior; the cow probably will not do very well in an ecosystem (seeListing 4-5).

Listing 4-5 The Terrarium equivalent of a cow

1: using System;

2: using System.Drawing;

3: using System.Collections;

4: using System.Reflection;

5: using System.IO;

6:

7: [assembly: AuthorInformation("Paul Kimmel",

8: "[email protected]")]

9: [assembly: OrganismClass("Cow")]

10:

11: [MatureSize(30)]

12: [Carnivore(false)]

13: [AnimalSkin(AnimalSkinFamilyEnum.Beetle)]

14: [MarkingColor(KnownColor.Blue)]

15: [MaximumEnergyPoints(10)]

16: [EatingSpeedPoints(10)]

17: [AttackDamagePoints(0)]

18: [DefendDamagePoints(10)]

19: [MaximumSpeedPoints(30)]

20: [CamouflagePoints(15)]

21: [EyesightPoints(25)]

22:

23: public class Cow : Animal

24: {

25: PlantState target = null;

26:

27: protected override void Initialize()

28: {

29: Idle += new IdleEventHandler(IdleEvent);

30: MoveCompleted +=

31: new MoveCompletedEventHandler(MoveCompletedEvent);

32: }

33:

34: private void IdleEvent(object sender, IdleEventArgs e)

35: {

36: WriteTrace("Idle");

37: if(CanReproduce)

38: BeginReproduction(null);

39:

1 2 0 A d v a n c e d C # P r o g r a m m i n g

Page 148: Advanced c sharp programming

40: if( IsMoving || IsReproducing || IsEating )

41: return;

42:

43: SearchForFood();

44: }

45:

46: private void SearchForFood()

47: {

48: ArrayList organisms = Scan();

49: foreach(OrganismState organism in organisms)

50: {

51: if( organism is PlantState )

52: {

53: MoveTo( (PlantState)organism );

54: return;

55: }

56: }

57: }

58:

59: private int PossibleSpeed()

60: {

61: int possibleSpeed = Species.MaximumSpeed;

62: while( possibleSpeed > 2 &&

63: State.EnergyRequiredToMove(possibleSpeed,

64: possibleSpeed) > (State.StoredEnergy / 2))

65: {

66: possibleSpeed /= 2;

67: }

68:

69: if( possibleSpeed < 2) possibleSpeed = 2;

70: return possibleSpeed;

71: }

72:

73: private void MoveCompletedEvent(object sender,

74: MoveCompletedEventArgs e)

75: {

76:

77: try

78: {

79: if(WithinEatingRange(target)

80: && (State.EnergyState < EnergyState.Full))

81: BeginEating(target);

82: }

83: catch

84: {}

85: }

86:

87:

88: private void MoveTo(PlantState plant)

89: {

C h a p t e r 4 : T e r r a r i u m 1 2 1

Page 149: Advanced c sharp programming

90: target = plant;

91: BeginMoving(new MovementVector(plant.Position, PossibleSpeed()));

92: }

93:

94: public override void SerializeAnimal(MemoryStream m)

95: {}

96:

97: public override void DeserializeAnimal(MemoryStream m)

98: {}

99: }

Cows are reasonably large, as indicated by their MatureSize attribute of 30 (the largestanimals can have a mature size of 48). The larger the size to maturity, the longer it will bebefore the animal can reproduce. However, large animals can be intimidating; hence, sizemight be a good quality for a super predator.

Setting the Carnivore attribute to False indicates that the animal is a herbivore (line 12).You have to pick from one of the existing skins. You may be describing the equivalentof a cow, but all critters in Terrarium visually appear to be one of the bugs defined in theAnimalSkinFamilyEnum value. Use the AnimalSkin attribute to describe the appearanceof your organism. The MarkingColor attribute will be used to describe colorful bugs. (TheMarkingColor attribute was not implemented in earlier versions of Terrarium.)

The cow attributes defined on lines 15 through 21 are pretty weak. The Cow has lowenergy points, eats very slowly, has almost no DefendDamagePoints, is slow, and has pooreyesight. (Does it sound like an animal that can only thrive in captivity?)

The Cow is defined on lines 23 through 96. Critter behaviors are implemented as eventhandlers. These handlers are established in the overridden Initialize method (see lines 27through 32). The Cow implements an IdleEventHandler and a MoveCompletedEventHandler.(There are several more behaviors that can be described, including attacking and defendingbehaviors.) Event handlers are associated with events using the overloaded += operator, asdemonstrated on lines 29 through 31.

When our Cow gets an idle slice of CPU time—a turn—it checks to see if it can reproduce(lines 37 through 38). Methods that have a Begin prefix are asynchronous. This means thatwhen line 38 is invoked it returns immediately. Line 40 checks to see if we are reproducingor moving—perhaps searching for food or eating. If the Cow is doing any of these things, ascodified on lines 40 and 41, then we return immediately. Finally, if none of the conditions online 40 exists, then we begin searching for food.

Lines 46 through 57 implement the food-searching behavior. The Scan method on line48 returns all of the organisms that the Cow can see, as defined by the EyesightPointsfield-of-vision attribute. Each organism is evaluated. When a plant is discovered, the Cowmoves toward the plant, represented by the PlantState check on line 51.

We move at a rate that consumes up to half of our available energy. The MoveTo methodis defined on lines 88 to 92. BeginMoving is an asynchronous method that puts our bug onthe move. MovementVector takes a Point structure argument and a speed. We calculate thespeed in the PossibleSpeed method.

1 2 2 A d v a n c e d C # P r o g r a m m i n g

Page 150: Advanced c sharp programming

PossibleSpeed is a popular algorithm that I borrowed from some code samples on thewww.gotdotnet website. By trial and error, I discovered that running a bug at top speed is agood way to commit suicide. PossibleSpeed (on lines 59 through 71) evaluates the critter’smaximum speed, defined by the MaximumSpeedPoints. The PossibleSpeed value is used todetermine a speed and distance the critter can travel based on the energy stored and the energyrequired to move a specific distance at a specific speed. Running at maximum speed willlikely cause your critter to race at a startling rate and then seemingly die of a heart attack.

The MoveCompletedEvent might be called for a variety of reasons. Recall that BeginMovingis asynchronous. Our bug may stop moving due to its arrival at the food source or runninginto something else. Our bug—lines 73 through 85—simply assumes that we have arrivedat our food destination. If we are WithinEatingRange and hungry—represented by theState.EnergyState value on line 80—then we begin eating. A successful bug might detectthe presence of a predator or an obstacle and do something else, like run.

Finally, we must implement overridden SerializeAnimal and DeserializeAnimal methods.In the case of the Cow, these are empty methods. (See “Serializing Objects” later in this chapterfor more information.)

Iterating a Collection of Objects Lines 49 through 56 of Listing 4-5 demonstrates the foreachconstruct. In Chapter 2, I demonstrated the IEnumerator interface. You can request an enumeratorfrom a class, like ArrayList, that implements IEnumerable to iterate through a collection ofobjects. An easier means of iterating objects is the foreach construct.

Foreach supports declaring a variable and iterating through a collection containing objectsdefined by the collection, as demonstrated in Listing 4-5. This is easier than using an enumerator,although it is dependent on the IEnumerable interface. You can create strongly typedcollections that can be iterated with a foreach by implementing IEnumerable. There is aneasy way to implement strongly typed collections, as you’ll see next.

Inheriting from CollectionBase and ReadOnlyCollectionBase A strongly typed collection is acollection of a specific kind of type, for example, a collection of organisms or contacts. The mostdirect means of creating a strongly typed collection is to inherit from ReadOnlyCollectionBase orCollectionBase. ReadOnlyCollectionBase will facilitate creating a readable collection ofstrongly typed objects, and CollectionBase will facilitate creating a readable and writablecollection of strongly typed objects. Listing 4-6 demonstrates how to create a strongly typedcollection of customer objects.

Listing 4-6 This code demonstrates a strongly typed collection of Customer objects,created by inheriting from ReadOnlyCollectionBase.

1: public class Customers : ReadOnlyCollectionBase

2: {

3: public Customers()

4: {

5: ReadCustomers();

6: }

C h a p t e r 4 : T e r r a r i u m 1 2 3

Page 151: Advanced c sharp programming

7:

8: public Customer this[int index]

9: {

10: get

11: {

12: return (Customer)InnerList[index];

13: }

14: }

15:

16:

17: public Customer this[string name]

18: {

19: get

20: {

21: return (Customer)this[Find(name)];

22: }

23: }

24:

25: private int Find(string name)

26: {

27: for( int i = 0; i < Count - 1; i++)

28: {

29: if( this[i].Name.Equals(name) ) return i;

30: }

31: return -1;

32: }

33:

34: private void ReadCustomers()

35: {

36: InnerList.Add(

37: new Customer("Robert Golieb", "(617) 555-1212"));

38: InnerList.Add(

39: new Customer("Mark Davis", "(503) 555-1212"));

40: }

41: }

42:

43: public class Customer

44: {

45: private string name;

46: private string phone;

47:

48: public Customer(string name, string phone)

49: {

50: this.name = name;

51: this.phone = phone;

1 2 4 A d v a n c e d C # P r o g r a m m i n g

Page 152: Advanced c sharp programming

52: }

53:

54: public string Name

55: {

56: get

57: {

58: return name;

59: }

60: set

61: {

62: name = value;

63: }

64: }

65:

66: public string Phone

67: {

68: get

69: {

70: return phone;

71: }

72:

73: set

74: {

75: phone = value;

76: }

77: }

78: }

The ReadOnlyCollectionBase class implements ICollection and IEnumerable. ICollectionis designed to provide a Count, copying elements to an array, and synchronized access tothe underlying elements. IEnumerable has one method, GetEnumerator. IEnumerable is theinterface that foreach requires to iterate over the elements in a collection or array.

The code in Listing 4-6 implements a Customer class and a collection of Customer objects,named Customers. By inheriting from ReadOnlyCollectionBase, we get an underlyingstructure to store elements in and code that implements ICollection and IEnumerable. Byimplementing the indexer, this, we can treat instances of Customers just like a typed array.For example, customers[0], where customers is an instance of the Customers class, wouldreturn the Customer object at the 0th index position. An indexer is implemented on lines 8through 14. We can also implement an indexer that takes a string (see lines 17 through 23).The string indexer and the Find method collaborate to accept a string and find the objectcontaining a Name field that contains the index value. For example, Customers[“Robert Golieb”]would return the object containing the Name initialized to Robert Golieb.

Only producers can initialize the elements of a ReadOnlyCollectionBase. TheReadCustomers method on lines 34 to 40 demonstrates a means of initializing the collection.

C h a p t e r 4 : T e r r a r i u m 1 2 5

Page 153: Advanced c sharp programming

You could just as easily initialize the collection by reading from an XML file or a database.Inherit from the CollectionBase class for a readable and writable, strongly typed collection.

Improving the CowWe can improve the Cow herbivore by adding some intelligent behavior. We can add eventhandlers to respond to attacks and exhibit defensive behavior, and I have demonstrated howto find food already. The Deer.sln provides an improvement over the Cow. The deer keepstrack of food using a ReadOnlyCollectionBase and looks for the closest food to eat. You cansee the code for the deer by opening the Deer.sln, available on the www.osborne.comwebsite. Listing 4-7 demonstrates a collection used to track plants for the deer.

Listing 4-7 Implementing a food-tracking collection for an herbivore

public class Plants : ReadOnlyCollectionBase

{

private Animal animal;

public PlantState ClosestPlant()

{

PlantState closest = null;

foreach(PlantState plant in this)

{

if((closest == null) ||

( animal.DistanceTo(plant) < animal.DistanceTo(closest)))

closest = plant;

}

InnerList.Remove(closest);

return closest;

}

public Plants(Animal animal)

{

this.animal = animal;

ArrayList organisms = animal.Scan();

foreach(OrganismState organism in organisms)

{

if( organism is PlantState )

InnerList.Add(organism);

}

}

public PlantState this[int index]

1 2 6 A d v a n c e d C # P r o g r a m m i n g

Page 154: Advanced c sharp programming

C h a p t e r 4 : T e r r a r i u m 1 2 7

{

get

{

return (PlantState)InnerList[index];

}

}

}

NOTEYou can create savage herbivores by setting the AttackDamagePoints attribute and invoking theBeginAttacking method on plant eaters. Perhaps you can kill all of the plant-eating competitorsor destroy weaker carnivores.

When the Deer is hungry, it requests a collection of plants within range. The Plantscollection removes animals from the scan. The Plants.ClosestPlant method returns the plantthat is closest to an individual Deer’s position and removes that plant from the collection.The closest plant is returned, and the Deer can eat with the least amount of expended energy.The Cow as provided in Listing 4-5 dies from lack of initiative. The Deer (solution availablefor download), using the plant tracking strategy, is much more productive than the Cow anddestroyed so many plants that it eventually starved itself out.

Creating a CarnivoreCarnivores are a bit trickier to create. Their food source moves and might even fightback. Carnivores can be eaten. Observations of some carnivores in a few ecosystemsindicate that there seems to be a ratio of 1 carnivore for every 10 herbivores. This isprobably codified behavior in the Terrarium game itself. Without carnivores, it may bedifficult to achieve balance in a Terrarium.

The code for a slightly modified carnivore required ten pages. Instead of padding thisbook with a huge code listing, I am not reproducing the code here, but I do encourage youto download the sample Lion.sln code and examine the complete listing. I will cover somebasic points from Lion.sln that will help you build a carnivore.

Setting Carnivore Attributes Carnivores and herbivores use the same attributes. You maywant to balance them differently for carnivores. To create a meat eater, you will have toinitialize the Carnivore attribute with True, or your critter won’t be able to eat animals.(There are no omnivores.) You can increase attributes like AttackDamagePoints to inflictgreat injury on your prey or like MaximumSpeedPoints to create the equivalent of a Lionor Cheetah.

Some attributes arbitrarily have to be even multiples of some value. The EatingSpeedPointsattribute needs to be multiples of 4. (Hungry carnivores like Lions can eat at their leisure,unless there are hyenas roaming about.) Testing your organism in Terrarium mode willprovide you with hints about wasted DNA points.

Page 155: Advanced c sharp programming

Programming Hunting Behavior The Scan method will return an ArrayList of organism thatyour critter can see. Scan returns plants and animals. You can filter the list to only animals oronly plants, as demonstrated in Listing 4-6. For example, a carnivore might care only aboutanimals; you might also program advanced behaviors, such as heading toward plants hopinghungry herbivores might show up.

Introducing Plants and Critters to the TerrariumWhen you have completed your plant or animal, you can introduce it into the ecosystem. Ifyour code has defects, like unhandled exceptions, then you will not be able to modify it andreintroduce the same organism. For this reason, it is preferable to test the organism inTerrarium mode before submitting it to a larger ecosystem.

An overly aggressive exception handling approach will prevent your organisms frombeing annihilated by the Terrarium application. The following fragment demonstrates anAttackCompletedEvent handler for the Lion.

private void AttackCompletedEvent(object sender, AttackCompletedEventArgs e)

{

try

{

if(e.Killed)

{

if(CanEat)

{

BeginEating(e.AttackAction.TargetAnimal);

}

}

}

catch(Exception x)

{

WriteTrace(x.Message);

}

}

When the AttackCompletedEvent is raised, we can check the AttackCompletedEventArgs,which will indicate whether the attackee is dead. If it is, then we can check whether we can eat—State.EnergyState < EnergyState.Full—and, if so, call the asynchronous BeginEatingmethod to begin eating the prey.

When a new assembly is introduced to each client, the assembly is examined for code thatmay be deemed as cheating in the context of the game. If your organism passes muster, thenten copies of the organism are added to the Terrarium when you first introduce the organism.

If your organism becomes extinct, then you can add the original critter from the extinctspecies list. You cannot modify the species and reintroduce modified code. As a result, a speciesthat has become extinct is likely to die again for the same reasons.

1 2 8 A d v a n c e d C # P r o g r a m m i n g

Page 156: Advanced c sharp programming

Introducing Code Access SecurityTerrarium typifies code that is arriving from a variety of destinations. In ecosystem mode,code may be coming from anywhere in the world—recall that there are no isolated slices ofthe ecosystem. Terrarium is a good way for Microsoft to introduce the new Code AccessSecurity model

Any code that uses the Common Language Runtime must interact with the security model.The fundamental premise is that code using the CLR must produce verifiably type-safe code,and you cannot presume that code will be granted any permissions. For example, if you produce aWeb Service for general consumption, you should not presume that your code will be grantedspecific permissions, and you must handle errors resulting from denied permissions. (Refer toChapter 16 for more information on Code Access Security and Imperative and Declarativesecurity.)

Secondary TopicsTerrarium supports the notion of serializing object state. Serializing object state simplymeans writing the state information of an object to another form—for example, a binary file.Writing the state of an object to a file promotes moving that state information across a network.The symmetric deserializing method knows exactly the order and type of data to read fromthe serialized file.

Terrarium provides you with a suitable opportunity to experiment with serialization. Whenthe Teleporter bumps into an instance of your organism, a peer is selected from your peer list.The organism is removed from your Terrarium, and a copy of the DLL is sent to the peerselected from the list. State information is sent too. If you provide an implementation forthe SerializePlant or SerializeAnimal, and DeserializePlant or DeserializeAnimal, as the casemay be, then that additional state information is also sent. The remaining section in thischapter demonstrates serialization by implementing Serialize and Deserialize methods forthe Lion carnivore.

Serializing ObjectsEarlier in the chapter, I mentioned how Terrarium uses the System.IO namespace. TheSerialize and Deserialize methods in organisms use the MemoryStream class. A stream issimply a contiguous collection of bytes. Simply representing data as contiguous bytes is howyour computer sees the data, and a contiguous block of bytes can be easily ported in a varietyof ways, including across the Internet. A MemoryStream is simply a stream that is stored inmemory—as opposed to a FileStream, which is a file represented as a stream.

A conceptual level of programming is the class. By representing data as various streamswith a common interface, it becomes possible for data in various forms to be treated consistentlyand polymorphically. Implement a method that takes a stream, and the specific operationsyou perform are the same, whether you are dealing with a MemoryStream or a FileStream.There are minor differences between stream types. For example, you don’t express a file

C h a p t e r 4 : T e r r a r i u m 1 2 9

Page 157: Advanced c sharp programming

name with a MemoryStream, but FileStreams represent files as contiguous blocks of bytes,and it is intuitive that you actually need a file for this purpose.

You can track additional state information for organisms in Terrarium by implementingthe Serialize and Deserialize methods. You are required only to stub out these methods, butthe code in Listing 4-8 demonstrates how to serialize state information as an organism isbeing teleported and deserialize state information after the organism is teleported.

Serializing Organisms Using MemoryStream ObjectsMethods marked with the abstract modifier must be overridden and implemented in childclasses, even if that implementation is simply an empty method call. This is what we havedone with the implementations of the organisms thus far. We didn’t implement the serializationmethods because there wasn’t a practical reason to do so. However, you could contrive areason. Perhaps your critter tracks the number of successful kills and the kind of species thatfrequently attacks it. You might elect to stream the most offensive critter and the easiest preyand choose to avoid the former and seek out the latter to help make your organisms moresuccessful. This is an issue of strategy. Listing 4-8 demonstrates serialization and deserializationby streaming those activities that the Lion was participating in prior to being teleported.

Listing 4-8 Serializing and deserializing state information using a MemoryStream

public override void SerializeAnimal(MemoryStream m)

{

try

{

byte[] buffer = new Byte[]{

Convert.ToByte(IsDefending), Convert.ToByte(IsAttacking),

Convert.ToByte(IsEating), Convert.ToByte(IsMoving)};

m.Write(buffer, 0, buffer.Length);

WriteTrace("SerializeAnimal");

}

catch(Exception e)

{

WriteTrace(e.Message);

}

}

public override void DeserializeAnimal(MemoryStream m)

{

try

{

byte[] buffer = new Byte[4];

m.Read(buffer, 0, buffer.Length);

WriteTrace("WasDefending: " + Convert.ToBoolean(buffer[0]));

1 3 0 A d v a n c e d C # P r o g r a m m i n g

Page 158: Advanced c sharp programming

WriteTrace("WasAttacking: " + Convert.ToBoolean(buffer[1]));

WriteTrace("WasEating: " + Convert.ToBoolean(buffer[2]));

WriteTrace("WasMoving: " + Convert.ToBoolean(buffer[3]));

WriteTrace("DeserializeAnimal");

}

catch(Exception e)

{

WriteTrace(e.Message);

}

}

TIPIndicate the type of the Exception you want to catch in the catch predicate of an exception handler to obtainan instance of the exception object, as demonstrated in Listing 4-8.

The SerializeAnimal method writes the IsDefending, IsAttacking, IsEating, and IsMovingproperties of the animal containing the code in Listing 4-8. To demonstrate that we canretrieve the state information, the DeserializeAnimal method reads the exact same data inthe same quantity as was written in the SerializeAnimal method.

Writing to a FileStreamA FileStream represents the kind of code you cannot add to your organism. When an organismis loaded into Terrarium, one of the things Terrarium looks for is code that tries to performfile IO. File IO is not permitted in Terrarium because this might allow malicious code to betransported between Peers. This prohibition is one example of the security model in action in.NET. If you try the code in Listing 4-9, you will receive an error and your organism will fail,but the code will demonstrate how to obtain a temporary file name and to write informationto a file.

Listing 4-9 This code demonstrates file IO using the FileStream class.

string filename = Path.GetTempFileName();

FileStream file = new FileStream(filename, FileMode.CreateNew);

m.WriteTo(file);

file.Close();

The first statement in Listing 4-9 demonstrates how to request a temporary file name in.NET. (You might recall that GetTempFileName used to be a Windows API method. NowGetTempFileName is a static method in the Path class defined in the System.IO namespace.)The second statement demonstrates how to create a new FileStream and file, passing the .tmpfile name created in the first statement. The m.WriteTo statement demonstrates how to write

C h a p t e r 4 : T e r r a r i u m 1 3 1

Page 159: Advanced c sharp programming

information from one stream to another. The object m is representative of a MemoryStreampassed to SerializeAnimal. Any instance of a MemoryStream will do. Finally, we close theFileStream, returning the file handle to the operating system.

SummaryTerrarium provides an excellent opportunity to learn a great detail about C# programming.Terrarium is also a bit addictive if you have a bit of imagination. An addiction to learning isa good thing. Hopefully, you will have an opportunity to install the Terrarium client—perhapseven the server—and create several organisms to add to an ecosystem. In the process ofexperimenting with Terrarium, you will get a crash course in inheritance, implementing eventhandlers and using attributes, exception handlers, regular expressions, Reflection, implementingstrongly typed collections, and how streams and serialization work in .NET. These topicsare all demonstrated at length in this chapter and the code that is available from thewww.osborne.com and www.softconcepts.com websites.

This chapter also introduced Code Access Security. Security is demonstrated at length inChapter 16.

1 3 2 A d v a n c e d C # P r o g r a m m i n g

Page 160: Advanced c sharp programming

CHAPTER

5Building DatabaseApplications with

ADO.NET

133

IN THIS CHAPTER:

Demonstrated Topics

A Quick Review of ADO.NET Namespaces

Connecting to DataSources

Understanding the Role of the Adapter

Working with the DataSet

Using the DataTable

Using the DataView

Using the DataReader for Read-Only Data

Displaying Information in the DataGrid

Using the Command Object

Generating SQL with the CommandBuilder

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 161: Advanced c sharp programming

The current version of ADO incorporates revisions designed to accommodate theworld we live in today. In the 21st century, information is big business. Speedyaccess to mountains of data by thousands or even millions of simultaneous users

is the reality. ADO.NET was revised to mirror this reality.Previous versions of ADO were based largely on a connected model. Each user held

a connection to a data source while interacting with the data. The result were bottleneckscaused by the large number of possible physical connections. Assuming clients are PCsconnected to the Internet and the servers are web servers, then database problems can beexacerbated by distance and bandwidth.

Generally, the number of transactions per interval of time can be used to determine totalthroughput. At one completed transaction per minute, an application could support 1,440 trans-actions per day. One transaction per second and an application could support 86,400 transactionsper day. Clearly, these numbers are not in the millions. ADO.NET was revised to address theproblem of limited physical connections to a data source, to increase reliability, and to workin the world as it exists today—a connected world.

The current ADO.NET is based on a disconnected model and centers around the DataSetand XML. In short, the ADO.NET model follows the pattern of connecting to the datasource, performing a short transaction, and disconnecting from the data source. There aresome new capabilities and classes that were introduced with ADO.NET. This chapter willdemonstrate how to use ADO.NET to write database applications.

Demonstrated TopicsChapter 2 introduced the subject of Reflection by demonstrating how to explore the CLR.A tools provider could use such a utility to completely document the CLR and provide areference application that associated specific aspects of the CLR with example code. For thisconcept to work, we would need to write the information we discovered by Reflection to adatabase and then add code examples to that database.

Based on a discussion I had with a Microsoft program manager, Microsoft has an internalapplication that fills the role of a resource tool for developers internally working with .NET.Fortunately, Microsoft has released Rotor, which is the shared source code for the commonlanguage infrastructure (CLI), a significant part of the base classes for .NET. Rotor is availablefor download from Microsoft.

We can create our own reference application as a reasonable means of demonstratingADO.NET. The demonstrated topics in this chapter are set against the backdrop of a CLRreference application and will show you how to

� Use connections

� Use adapters

� Program with the new DataSet class

� Fill and interact with the DataTable

� Use the DataView class

1 3 4 A d v a n c e d C # P r o g r a m m i n g

Page 162: Advanced c sharp programming

� Speed up database access with the DataReader

� Execute SQL commands using the Command object

� Automatically generate SQL statements with the CommandBuilder

� Create data bound graphical user interfaces with the DataGrid

The Secondary Topics section will borrow from the demonstrated topics and describe howto use the DataSet as a return type for an XML Web Service, binding data to controls onWeb Pages and inheriting the TraceListener to facilitate debugging and testing.

A Quick Review of ADO.NET NamespacesADO.NET is comprised of assemblies, namespaces, and classes that are part of the bigger.NET Framework. The main namespace for ADO.NET is the System.Data namespace.System.Data contains classes like the DataSet and DataTable. Within the System.Datanamespace is System.Data.Common, System.Data.OleDb, System.Data.SqlClient, andSystem.Data.SQLTypes. Additionally, System.XML is fundamental to ADO.NET and to.NET in general.

The System.Data.Common namespace contains classes that are shared by ADO.NETproviders. For example, both the System.Data.OleDb and System.Data.SQLClientnamespaces contain adapters that inherit from a common adapter in the System.Data.Commonnamespace.

System.Data.OleDb and System.Data.SQLClient are namespaces that contain symmetriccapabilities. The SQLClient namespace contains classes for working with MS SQL Server 7.0or higher databases, and the OleDb namespace contains classes for working with all otherOleDb-compatible databases, including MS Access.

System.Data.SQLTypes contains classes that represent native SQL data types.Last but not least is System.XML. XML is used to describe data. For example, if you

specify the DataSet as a return type for a Web Service, then the DataSet will be serializedas XML to facilitate transporting the DataSet. XML is used to define data schemas (XSDschemas).

As we proceed with the examples in this chapter, I will indicate where specific classescome from, and you can use this short section as a resource to explore additional informationabout ADO.NET.

Let’s approach ADO.NET systematically, beginning with the first thing we must do:create a connection to a data source.

Connecting to DataSourcesProgrammers experienced with prior versions of ADO know that ADO supporteda disconnected database model. However, ADO prior to .NET was fundamentally aconnected model and was not based on XML. The disconnected ADO.NET will not hold

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 3 5

Page 163: Advanced c sharp programming

connections—we say it is disconnected—and uses XML, which makes it easy to move dataacross networks because XML is just hypertext.

However, you will need to create and use a connection to get at the data. You can createan SqlConnection for MS SQL Server 7.0 data sources or higher or an OleDbConnection forany other data source that supports OLE DB.

To optimize connection usage, you will want to take advantage of connection pooling.Connection pooling is a collection, or pool, of connections that applications can share.An OleDbConnection uses connection pooling automatically. An SqlConnection managesconnection pooling implicitly. An SqlConnection that uses the same connection string canbe pooled. You must close connections to take advantage of connection pooling.

Connecting to an OLE DB Data StoreADO.NET creates an environment where the conditions of working with various datasources are similar from the perspective of the code you write. To learn how to programusing ADO.NET, you can use any data source that is available. One such data source thatsupports OLE DB is the Microsoft Access Jet Engine. (MS Access ships with MicrosoftOffice Professional.) We’ll use Access as our OLE DB example.

To connect to an OLE DB provider you will need to provide a specific connection string.There are a couple of good strategies that you can employ to obtain a working connectionstring. If you are adding the connection in the presentation layer, then you can drag anOleDbConnection right out of Server Explorer onto the Windows Form or Web Form(see Figure 5-1). Another good strategy for creating a second string has to do with creatinga Microsoft Data Link file.

If you create a text file with a .UDL extension (for example, in Windows Explorer) anddouble-click that file, then you can use the Data Link Properties applet (see Figure 5-2) toconfigure a connection. The Data Link Properties applet provides you with a visual interfaceto create the connection. Complete the information on each tab and the .UDL file will containa valid connection string. To create a connection to a Microsoft Access 2000 or 2002 database,follow these steps:

1. Create a blank text file with a .UDL extension. Double-click the file to open the filewith the Data Link Properties applet.

2. On the Provider tab, select the Microsoft Jet 4.0 OLE DB Provider. Click the Nextbutton, shown in Figure 5-2.

3. On the Connection tab, use the browse button—a button with an ellipses caption—tobrowse to your database file. Access databases have an .MDB extension. (You can useWindows Explorer to search for an Access database.) You can ignore item 2, leavingthe default user name “Admin” in the user name field unless you know this isn’t validfor the database you selected.

4. You can use the Test Connection button to determine if you have a valid connection.

5. If the Test succeeds, then you can click OK to save the configuration changes.

1 3 6 A d v a n c e d C # P r o g r a m m i n g

Page 164: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 3 7

Figure 5-1 Drag a connection from the Data Connections in Server Explorer to automaticallyadd an OleDbConnection component to your project.

Figure 5-2 Use the Data Link Properties applet to quickly and accurately configure aconnection string.

Page 165: Advanced c sharp programming

The Advanced tab of the Data Link Properties applet allows you to specify accesspermissions. The default is shared, read-write access to the database through this connection.The All tab contains name and value pairs that allow you to modify every connection value.Generally, the defaults will do, unless you have a specific reason for modifying initializationvalues, such as Jet OLEDB: Encrypt Database.

After you close the Data Link Properties applet, you open the .UDL file with Notepad andcopy the connection string created by the applet. Here is an example of the connection stringvalue created by the UDL applet for the Reference.mdb sample database available with thesource code for this book.

Provider=Microsoft.Jet.OLEDB.4.0;DataSource=C:\Temp\Reference.mdb;

Persist Security Info=False

Wrap the preceding statement in quotes and you can use it to initialize an OleDbConnectionobject without having to remember the provider name, Microsoft.Jet.OLEDB.4.0. Listing 5-1demonstrates how to write code that will open the database described by our exampleconnection string.

Listing 5-1 The following demonstrates how to open an OleDbConnection and ensure it isclosed using a resource protection block.

public static void TestConnection()

{

OleDbConnection connection = new OleDbConnection();

connection.ConnectionString =

@"Provider=Microsoft.Jet.OLEDB.4.0;" +

@"Data Source=C:\Temp\Reference.mdb;" +

@"Persist Security Info=False";

connection.Open();

try

{

Console.WriteLine(connection.State.ToString());

Console.ReadLine();

}

finally

{

connection.Close();

}

}

TIPStrings in C# can be @-quoted or quoted. A quoted string will treat a single backslash as an escapecharacter and a double backslash as a backslash. An @-quoted string will prevent escape charactersfrom being processed.

1 3 8 A d v a n c e d C # P r o g r a m m i n g

Page 166: Advanced c sharp programming

Add a using statement to refer to the System.Data.OleDb namespace to use the OleDbproviders in Listing 5-1. You can create the OleDbConnection object passing the connectionstring to the constructor or assigning the connection string to the ConnectionString propertyafter the object is created. Invoke the Open method to open the connection. The code in thetry part of the try finally handler—also referred to as a resource protection block—representswork. In Listing 5-1, we are simply writing the state of the connection object to the console.The finally block is always invoked, which is what we want. We always want to close theconnection, and the finally block will ensure that the connection is closed even in the eventof an exception.

Connecting to an MS SQL Server Data StoreFrom the perspective of the code needed to connect an SQL database, the code is almostidentical to Listing 5-1. The biggest difference resides in the connection string. Theconnection string will need to contain information that is relevant to an MS SQL Serverdatabase. You can use the same two techniques described in the previous section—draga connection from Server Explorer or create a .UDL file and use the Data Link Propertiesapplet—to define a connection string to an SQL Server database.

We can use almost the identical code to that found in Listing 5-1 to open a connection toan SQL Server database. (The Microsoft Desktop Engine [MSDE] is a good SQL Sever databaseto use in a development environment, because you can perform initial software writing ona disconnected PC. This is especially useful when you are programming at the beach or in ahammock.) When connecting to an SQL Server database, you will need to include theSystem.Data.SqlClient namespace and create an instance of the SqlConnection object.

SqlConnection connection = new SqlConnection();

connection.ConnectionString =

@"data source=LAP800\VSDOTNET;initial catalog=master;" +

@"integrated security=SSPI;persist security info=False;" +

@"workstation id=LAP800;packet size=4096";

There are huge benefits to be reaped when using a well-architected framework. Let’s takea moment to look at one such benefit that is offered to us in ADO.NET.

Using ADO.NET Interfaces to Declare TypesA reasonable thing you may want to do is to make it easy to switch between data providers.There are several scenarios where this is likely, and I will describe one next.

Working on a project in Portland, Oregon, we were building an enterprise solution in C#to IBM’s Universal Database. Residing in Michigan, I found it nice to occasionally go homeand telecommute. (It helps me get caught up on yard work.) I want to work from the homeoffice—for the multi-processor workstations with flat screens, a great library, a huge officechair, and, best of all, family—but I don’t want to install every database a customer may beusing. Instead, I will write my applications to allow me to quickly and simply switch to analternate data provider. Combine ADO.NET with CASE tools for databases and you caneasily replicate a production database in a non-production environment. Of course, if your

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 3 9

Page 167: Advanced c sharp programming

employer doesn’t buy my arguments, then you could present the alternate database scenarioas a flexibility issue.

The basic idea is that you define methods that return ADO.NET interfaces (instead ofspecific provider classes) and then construct a specific provider class. You can combineCASE tools to replicate a production database with ADO.NET interfaces rather than classesand a Boolean switch managed with an external XML file, and you have a versatile application-development environment and database heterogeneity.

Switching Between Databases Using an IDbConnectionListing 5-2 demonstrates how we can declare variables as interfaces realized by theADO.NET providers and create an instance of a specific provider based on dynamiccriterion. Listing 5-2 uses a conditional compiler directive to switch between databases.This solution requires a recompile. In a moment, I will demonstrate how to achieve thesame result without recompiling.

Listing 5-2 Use the IDbConnection interface to declare your types and you can assign anyADO.NET connection that realizes the interface.

1: public class MultiConnection

2: {

3: private static string GetConnectionString()

4: {

5: #if DEBUG

6: return @"Provider=Microsoft.Jet.OLEDB.4.0;" +

7: @"Data Source=C:\Temp\Refernce.mdb;" +

8: @"Persist Security Info=False";

9: #else

10: return @"data source=LAP800\VSDOTNET;initial catalog=master;" +

11: @"integrated security=SSPI;persist security info=False;" +

12: @"workstation id=LAP800;packet size=4096";

13: #endif

14: }

15: private static IDbConnection GetConnection()

16: {

17: #if DEBUG

18: return new OleDbConnection(GetConnectionString());

19: #else

20: return new SqlConnection(GetConnectionString());

21: #endif

22: }

23:

24: public static void Test()

25: {

26: IDbConnection connection = GetConnection();

1 4 0 A d v a n c e d C # P r o g r a m m i n g

Page 168: Advanced c sharp programming

27: connection.Open();

28: try

29: {

30: Console.WriteLine(connection.State.ToString());

31: Console.ReadLine();

32: }

33: finally

34: {

35: connection.Close();

36: }

37: }

38: }

The only real change made to the code is to declare the connection as an IDbConnectioninstead of an SqlConnection or an OleDbConnection. The balance of the code is consistentwith the previous example in Listing 5-1. You can toggle the conditional compiler statementson lines 5 and 17 of Listing 5-2 to switch between the SQL and OLE databases.

A common framework makes it possible to write code once, and switch between somethingas significant as a database without writing multiple versions of the code for each database.A great framework puts this kind of flexibility at your fingertips. The .NET framework isa superlative framework. We can use a BooleanSwitch to externalize switching databaseswithout recompiling.

Switching Between Data Providers with a BooleanSwitchA second scenario where it may be beneficial to allow a user to switch between databases iswhen you are writing a consumer product, and you don’t want to force your customer to usea specific database provider. Suppose you write a consumer application and want to allowthe customer to choose between databases. By the time the customer has the application, itis too late to recompile. You can use a BooleanSwitch internally and an XML file externallyto support switching between data providers (as well as supporting any kind of externalizeddynamic switching).

Listing 5-3 is a revision of Listing 5-2. The revision demonstrates the addition of aBooleanSwitch, and Listing 5-4 provides the listing for the XML file that defines the switch.

Listing 5-3 This code (a revision of Listing 5-2) demonstrates using a BooleanSwitch.

1: public class SwitchedMultiConnection

2: {

3: private static BooleanSwitch Switch =

4: new BooleanSwitch("Provider",

5: "Supports switching between data providers");

6:

7: private static string GetConnectionString()

8: {

9: if( Switch.Enabled )

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 4 1

Page 169: Advanced c sharp programming

10: {

11: return @"Provider=Microsoft.Jet.OLEDB.4.0;" +

12: @"Data Source=C:\Books\Osborne\Instant C#" +

13: @"\Source\Chapter 5\Database\Reference.mdb;" +

14: @"Persist Security Info=False";

15: }

16: else

17: {

18: return @"data source=LAP800\VSDOTNET;initial catalog=master;" +

19: @"integrated security=SSPI;persist security info=False;" +

20: @"workstation id=LAP800;packet size=4096";

21: }

22: }

23:

24: private static IDbConnection GetConnection()

25: {

26: if (Switch.Enabled)

27: {

28: return new OleDbConnection(GetConnectionString());

29: }

30: else

31: {

32: return new SqlConnection(GetConnectionString());

33: }

34: }

35:

36: public static void Test()

37: {

39: IDbConnection connection = GetConnection();

40: connection.Open();

41: try

42: {

43: Console.WriteLine(connection.State.ToString());

44: Console.ReadLine();

45: }

46: finally

47: {

48: connection.Close();

49: }

48: }

49: }

Lines 3 through 5 of Listing 5-3 declare and initialize a BooleanSwitch as a static member.Making the switch static means that we will only instantiate one BooleanSwitch and share it

1 4 2 A d v a n c e d C # P r o g r a m m i n g

Page 170: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 4 3

between all instances of the class using the switch. Lines 9 and 26 use the BooleanSwitch inplace of the conditional compiler directive to determine whether we should use the OleDb orSqlClient connection.

There are a few differences between a BooleanSwitch and conditional compiler directives.The conditional compiler directive must be changed and recompiled. The code that failsevaluation is excluded from the compiled assembly. Code in both parts of the BooleanSwitchcode block is written to the compiled assembly, and to switch between different versions ofthe code we change the external definition of the switch in an XML file without recompiling.The .config file containing the switch definition is provided in Listing 5-4.

Listing 5-4 This code defines the .config file containing the BooleanSwitch.

<configuration>

<system.diagnostics>

<switches>

<add name="Provider" value="1" />

</switches>

</system.diagnostics>

</configuration>

Name the configuration file the same name as the assembly that will use it, addinga .config extension. For example, if your assembly is named myapp.exe, then yourconfiguration file will be myapp.exe.config. Create the configuration file in the samedirectory as the one containing the assembly. A complete description of the contents ofa BooleanSwitch defined in a configuration file is provided in Chapter 7, in the sectionentitled “Using Switches.”

Combining provider interfaces with BooleanSwitch objects provides us with a convenientway to switch between providers without recompiling our code. The next piece of theADO.NET puzzle is the adapter.

Understanding the Role of the AdapterADO.NET separates data access from data manipulation. Adapters are part of the data accesslayer of ADO.NET. Adapters are used to move data between a connection and classes thatstore cached data for manipulation, including the DataSet and DataTable. To read data froma connection to a data source, you can use the Fill method. To read the schema only, you caninvoke the FillSchema method, and Update will accept the modified revisions to data in aDataTable or DataSet and update the data source with the revisions.

There is an OleDbDataAdapter for OLE DB providers, an SqlDataAdapter for MS SQLServer providers, and an IDbAdapter interface that allows you to write provider-independentcode, as we did with IDbConnection in the preceding section.

Adapters are initialized with SQL and connection objects. The adapter provides the bridgefrom the connection to the data manipulation objects. In this section, I will demonstrate theFill, FillSchema, and Update methods of an adapter.

Page 171: Advanced c sharp programming

Initializing an AdapterAdapters are initialized with an SQL SELECT command and a connection object of the sameprovider type. Use an OleDbDataAdapter with an OleDbConnection and a SqlDataAdapterwith a SqlConnection. (I won’t repeat this information. Assume that, if one or the other of theOleDb provider or SqlClient provider has a specific class, there is a symmetric class for theother provider type.)

To initialize an adapter, create an instance of the adapter with an SQL SELECT commandand a like connection. Here is an example of an adapter object being created with anOleDbConnection referring to the Reference.mdb MS Access database available with this book.

OleDbConnection connection = new OleDbConnection();

connection.ConnectionString =

@"Provider=Microsoft.Jet.OLEDB.4.0;" +

@"Data Source=C:\Temp\Reference.mdb;" +

@"Persist Security Info=False";

OleDbDataAdapter adapter = new OleDbDataAdapter(

"SELECT * FROM METHOD", connection);

The last statement demonstrates one of the four possible ways to initialize an adapter object.The other three overloaded constructors for the adapter are variations of the one shown; youcan use the Visual Studio help documentation for examples of the other variations.

Connection Pooling StrategyIf you recall, we spoke about connection pooling earlier. Connection pooling is implicitlybased on the connection string. To take advantage of the more optimal use of connections viaconnection pooling, you can use a simple technique for ensuring that your code isn’t litteredwith literal connection strings. Listing 5-5 provides an example of a factored class that supportsmaintaining only one instance of a connection string.

Listing 5-5 This code demonstrates how to use a class to ensure that you have only oneinstance of a connection string.

1: public class FactoredConnection

2: {

3: private static BooleanSwitch booleanSwitch =

4: new BooleanSwitch("PKimmel",

5: "Used to switch between connection strings");

6:

7: public static string GetOleDbConnectionString()

8: {

9: if(booleanSwitch.Enabled)

10: {

11: return @"Provider=Microsoft.Jet.OLEDB.4.0;" +

1 4 4 A d v a n c e d C # P r o g r a m m i n g

Page 172: Advanced c sharp programming

12: @"Data Source=C:\Books\Osborne\Instant C#" +

13: @"\Source\Chapter 5\Database\Reference.mdb;" +

14: @"Persist Security Info=False";

15: }

16: else

17: {

18: return @"Provider=Microsoft.Jet.OLEDB.4.0;" +

19: @"Data Source=C:\Temp\Reference.mdb;" +

20: @"Persist Security Info=False";

21: }

22: }

23:

24: public static string GetSqlConnectionString()

25: {

26: if(booleanSwitch.Enabled)

27: {

28: return @"data source=LAP800\VSDOTNET;initial catalog=master;" +

29: @"integrated security=SSPI;persist security info=False;" +

30: @"workstation id=LAP800;packet size=4096";

31: }

32: else

33: {

34: throw new Exception(

35: "Make sure you have a MS SQL Server instance available.");

36: }

37: }

39: public static OleDbConnection GetOleDbConnection()

40: {

41: return new OleDbConnection(GetOleDbConnectionString());

42: }

43:

44: public static SqlConnection GetSqlConnection()

45: {

46: return new SqlConnection(GetSqlConnectionString());

47: }

48: }

Again, in Listing 5-5 we use a BooleanSwitch—on lines 9 and 26—to support easyswitching between versions of the connection string. This is consistent with an approach wemight take for a connected versus a disconnected development environment. Notice that theelse condition for GetSqlConnectionString throws an exception. You will need to ensure thatyou have access to MS SQL Server or MSDE installed on your desktop and have an instanceaccessible to your application.

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 4 5

Page 173: Advanced c sharp programming

1 4 6 A d v a n c e d C # P r o g r a m m i n g

Using the approach demonstrated in Listing 5-5, we can be sure that we are facilitatingconnection pooling by using the same connection string. This approach does not prohibityou from creating a new instance of a connection without the FactoredConnection class.

General Programming StrategyThe preceding section demonstrated a FactoredConnection class that facilitates connectionpooling by creating a separate class containing the connection string. There is anotherstrategic reason to write code like that demonstrated. I refer to it as Kimmel’s Theory ofConvergent Code.

NOTEKimmel’s Theory of Convergent Code: Code that converges on a single instance of an algorithm or classis good because it promotes reuse, consistency, and a high degree of re-orchestrated dynamic behavior.

The basic idea behind convergent versus divergent code is that the smallest piece of codethat can be reused without replicating the code is the method. Anything below a method mustbe duplicated to be reused. That is, lines of code must be copied and pasted to be reused.Copied and pasted code is divergent code for the simple reason that there is more than oneinstance of an algorithm. The negative result of divergent code is that for each instance of thedivergent code, a programmer must replicate, individually test, and separately maintain theindividuated lines of code. The result is that divergent code tends to decay over time accordingto the number of times the code is copied and pasted. The result is that divergent code tendstoward semantically similar operations diverging in behavior, yielding the perception ofunreliable performance.

To summarize: relative to our FactoredConnection class, we have only one instance ofthe connection string to maintain. If the connection changes, we propagate the change in oneplace. If we want the behavior of the FactoredConnection to change then, we again only needto change the code in place. Consider the case where we want to read the connection stringfrom an external resource like the registry. Again, we only need change the code in one place.

Convergent code in the form of methods yields the following good rule of thumb: prefersingular, short, well-named, highly reusable methods to lines of code, which represent plural,monolithic, non-reusable methods. William Opdikes’ doctoral dissertation from 1990 was theimpetus for the subject of refactoring, which supports my theory of convergence. You canread about refactoring in Martin Fowler’s superlative Refactoring: Improving the Design ofExisting Code (Addison-Wesley).

Invoking the Adapter Fill MethodThe Fill method is used to move data between a connection and a DataSet. To fill a DataSet,you will need to create a connection and an adapter and invoke the adapter’s Fill method.A DataSet object is passed as an argument to the Fill method. You do not need to specificallycreate an instance of a connection to use an adapter. You may pass a connection stringinstead of a connection object to the adapter, and the adapter will internally create an instance

Page 174: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 4 7

of a connection and then open and close the connection automatically. Here are a couple ofexamples that demonstrate using the Fill command.

public static void TestFill()

{

OleDbConnection connection = FactoredConnection.GetOleDbConnection();

OleDbDataAdapter adapter = new OleDbDataAdapter(

"SELECT * FROM METHOD", connection);

DataSet dataSet = new DataSet();

connection.Open();

try

{

adapter.Fill( dataSet );

}

finally

{

connection.Close();

}

WalkDataSet( dataSet );

}

The preceding example verbosely creates an OlDbConnection, OleDbDataAdapter,and a DataSet, opens the connection and then fills the DataSet. WalkDataSet representsa method that performs useful work. (You can find the code for WalkDataSet in theADOSampleCode.sln available online at www.osborne.com.)

The second example, which follows, demonstrates a concise version that passes aconnection string to the OleDbDataAdapter constructor, and the adapter will be responsiblefor opening and closing the connection.

private static void TestFill2()

{

OleDbDataAdapter adapter = new OleDbDataAdapter(

"SELECT * FROM METHOD",

FactoredConnection.GetOleDbConnectionString());

DataSet dataSet = new DataSet();

adapter.Fill( dataSet );

WalkDataSet( dataSet );

}

(The FactoredConnection class was introduced in the earlier section “Connection PoolingStrategy.”) The preceding example demonstrates a concise way to fill a DataSet. Moreimportant, notice that in both instances we are operating on the DataSet after the connectionis closed. (The first example closes the DataSet explicitly, and the second example closes theDataSet after the Fill operation.) This demonstrates the connectionless mode of operation inADO.NET. The data has been cached in the DataSet and is available completely independentof the connection and adapter.

Page 175: Advanced c sharp programming

1 4 8 A d v a n c e d C # P r o g r a m m i n g

Invoking the Adapter FillSchema MethodIf you are performing a large number of insert statements and aren’t interested in existingrows of data, then you can request the schema only. To read schema—a description of atable—into a DataSet only, employ the FillSchema method.

As is true with the Fill method, you can actually fill a single DataTable or add a table to aDataSet. The DataSet is a collection of tables and optional relationships between those tables.(Refer to the later sections “Working with the DataSet” and “Using the DataTable” for moreinformation.) FillSchema takes a DataSet or DataTable argument and a SchemaType enumeratedvalue, either Mapped or Source. Here is an example of a method that reads the description ofa table only into a DataSet.

public static void TestFillSchema()

{

OleDbDataAdapter adapter = new OleDbDataAdapter(

"SELECT * FROM METHOD",

FactoredConnection.GetOleDbConnectionString());

DataSet dataSet = new DataSet();

adapter.FillSchema(dataSet, SchemaType.Source);

}

The DataSet in the preceding fragment will contain a single table with columns only; thecolumns represent the schema information. The enumerated value SchemaType.Source—of two possible values, the other being SchemaType.Mapped—instructs the adapter to usethe schema described by the source table. The alternative is to use the schema transformedby column mappings. (Refer to the section “Working with the DataSet” later in the chapterfor more information on column mappings.)

Updating Changes to DataAn adapter plays the role of bridge between a connection and a DataSet. Just as we used anadapter to read data from a data source via a connection into a DataSet, we use the adapter toupdate changes made to the data. Updating includes SQL UPDATE, SQL INSERT, and SQLDELETE operations.

The means by which we instruct the adapter to update data is to provide SQL commandsfor the operations we want to perform. For example, if we have added new rows, then weneed to provide an INSERT command to the adapter before we call the Update method. Thebasic steps for updating data are to open a connection, create an adapter providing an SQLSELECT statement and the connection as initial values, fill a DataSet with data, modify thedata, create SQL commands that describe how to perform updates, and invoke the adapterUpdate method. One version of these steps is demonstrated in Listing 5-6.

Page 176: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 4 9

Listing 5-6 This code demonstrates how to update a DataSet using an adapter.

1: public static void TestUpdate()

2: {

3: OleDbConnection connection =

4: FactoredConnection.GetOleDbConnection();

5:

6: OleDbDataAdapter adapter = new OleDbDataAdapter(

7: "SELECT * FROM METHOD", connection);

8:

9: DataSet dataSet = new DataSet();

10: connection.Open();

11: adapter.Fill(dataSet);

12: connection.Close();

13:

14: DataRow row = dataSet.Tables[0].NewRow();

15: row["Name"] = "TestUpdate";

16: dataSet.Tables[0].Rows.Add(row);

17:

18: OleDbCommandBuilder commandBuilder =

19: new OleDbCommandBuilder(adapter);

20:

21: connection.Open();

22: adapter.Update(dataSet);

23: connection.Close();

24: }

Lines 3 and 4 of Listing 5-6 create a connection object. Lines 6 and 7 use the connectionobject to create the adapter. Line 9 creates an instance of a DataSet. Line 10 opens theconnection, line 11 fills the DataSet, and line 12 closes the connection. We don’t need anopen connection to manage the DataSet, and it is better if we close the connection immediately.In a real application, it is unlikely that the code actually adding data will be this simple.

Lines 14 through 16 of Listing 5-6 demonstrate how to add a new row to the tables in aDataSet. Line 14 adds the row. Line 15 updates one field in the row, and line 16 adds the rowto the table’s collection of Rows using the Rows.Add command.

Line 18 of Listing 5-6 uses the adapter to construct an OleDbCommandBuilder. Thecommand builder automatically uses the schema information for a table to generate INSERT,DELETE, and UPDATE SQL commands. Refer to the section “Generating SQL with theCommandBuilder” later in this chapter for more information on the OleDbCommandBuilder.

Lines 21 through 22 of Listing 5-6 opens the connection, updates the database, and closesthe connection. The DataSet knows that we inserted a new row; hence, the adapter knows touse the adapter’s InsertCommand property to write the row of data we added on lines 14through 16.

Page 177: Advanced c sharp programming

1 5 0 A d v a n c e d C # P r o g r a m m i n g

Working with the DataSetThe DataSet is an evolution of the ADO RecordSet. The DataSet is a connectionless repositoryfor ADO.NET, and you can use DataSet objects without ever connecting to a data source. Wehave only briefly introduced the DataColumn, DataRow, and DataTable, but these objects canexist and you can programmatically interact with them without ever connecting to a data source.

In general, the DataSet model is consistent with how we think about a relationaldatabase. The DataSet contains a DataRelationCollection and a DataTableCollection.The DataTableCollection contains a DataRowCollection, DataColumnCollection,ChildRelations, ParentRelations, and ExtendedProperties. The DataRowCollectioncontains instances of DataRow objects, and the DataColumnCollection containsDataColumn objects. There is also a view of data represented by the DataView class.

You can use the DataSet and contained objects in the traditional way by connecting todata providers as demonstrated earlier in this chapter, or you can use the DataSet as anin-memory repository to store data in an organized way. This next section demonstrateshow to employ aspects of the DataSet.

Adding DataTable Objects to a DataSetWhen you create a DataSet object and fill it from a DataAdapter based on a single SQLSELECT statement, what really happens is that a DataTable is created and is added tothe DataSet’s DataTableCollection. The premise is that for ADO.NET to support aconnectionless model for database management, it must support storing and managingdata in relations that are likely to exist.

The most common relationship is the simple master-detail relationship. One table playsthe role of the master table and the other table plays the role of detail table. In a typicalmaster-detail relationship, you will need a master table, a detail table, and a DataRelation.The AssemblyViewer.sln for this chapter available for download demonstrates one suchrelationship between a table containing CLR TypeInfo objects and ConstructorInfo objects.Listing 5-7 demonstrates how to fill DataTable objects and add those objects to a DataSet.

Listing 5-7 This code demonstrates how to add DataTable objects to a DataSet.

1: OleDbConnection connection = new OleDbConnection(connectionString);

2: OleDbDataAdapter adapter = new OleDbDataAdapter(

3: "SELECT * FROM TYPE", connection);

4: DataSet dataSet = new DataSet();

5:

6: DataTable typeTable = new DataTable("TYPE");

7: adapter.Fill( dataTable );

8: dataSet.Tables.Add(typeTable);

9:

10: DataTable constructorTable = new DataTable("CONSTRUCTOR");

Page 178: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 5 1

11: adapter = new OleDbDataAdapter(

12: "SELECT * FROM CONSTRUCTOR", connection);

13: adapter.Fill( constructorTable );

14:

15: dataSet.Tables.Add(constructorTable);

Line 1 of Listing 5-7 creates a connection object. Because we don’t explicitly open theconnection, the adapter will take care of opening and closing the connection for us. (Youhave seen several examples of a connection string to the Reference.mdb database. Thevariable connectionString was used to represent the actual connection string.) The secondstatement creates an OleDbDataAdapter to bridge between the connection and a DataTable.

Line 4 of Listing 5-7 creates a DataSet. When we want to represent a collection of tables,we use the DataSet, although it is acceptable and possible to use a single DataTable if weonly need one table. Line 6 creates a new DataTable object, and line 7 demonstrates howeasy it is to fill a DataTable directly without using a DataSet. Line 8 adds the DataTable toour DataSet object. Lines 10 through 15 repeat the process for the CONSTRUCTOR table.

When the last statement in line 15 of Listing 5-7 finishes executing, the DataSet containstwo tables. There is no relationship between the tables yet. We need to define the relationshipas a separate object and add it to the DataSet.

Creating Master-Detail RelationshipsIn the preceding section, we added two tables to a DataSet. To logically join those tables, weneed to create a DataRelation object and add that to the table. Listing 5-8 demonstrates thecode that, when combined with the code in Listing 5-7, defines a master-detail relationshipwithin the DataSet.

Listing 5-8 Add a DataRelation to a DataSet.

DataColumn parent =

dataSet.Tables["TYPE"].Columns["UnderlyingSystemType"];

DataColumn child =

dataSet.Tables["CONSTRUCTOR"].Columns["DeclaringType"];

DataRelation relation = new DataRelation(

"TypesAndConstructors", parent, child);

dataSet.Relations.Add(relation);

NOTEGenerally, I prefer to name my related columns identically to facilitate identifying relationshipsbetween tables in the same database. However, the Reference.mdb database represents the typesdefined in the CLR, and the columns are created from the names of actual properties in the CLR.For example, UnderlyingSystemType is a property in the TypeInfo type defined in the CLR.

Page 179: Advanced c sharp programming

1 5 2 A d v a n c e d C # P r o g r a m m i n g

Just as with JOIN statements in SQL, a DataRelation relies on columns. In Listing 5-8 weinitialize two DataColumn objects by selecting a column from each DataTable that representsa logical relationship between two disparate tables. In the Common Language Runtime, aTypeInfo object has an UnderlyingSystemType property that is logically related to theConstructorInfo’s DeclaringType property. We use the TYPE table’s UnderlyingSystemType column and the CONSTRUCTOR table’s DeclaringType column to initialize a newDataRelation, providing a name and the two logically related columns as arguments tothe DataRelation constructor.

When the last statement—dataSet.Relations.Add(relation)—runs in Listing 5-8, you havea logical master-detail relationship in the DataSet. This relationship is depicted Figure 5-3.

Figure 5-3 was created from the Relationships dialog in MS Access 2002 but is anaccurate visualization of the DataSet after the code in Listing 5-7 and Listing 5-8 runs.Each table shown represents a DataTable, and the line between the tables represents theDataRelation object.

You can bind the DataSet to a Windows Forms or Web Forms DataGrid, and the DataGridwill accurately manage the master-detail aspects by presenting the data in a hierarchicalrelationship. You can explore the AssemblyViewer.sln for an example. The code to bind theDataSet to a DataGrid requires a single statement:

dataGrid1.DataSource = dataSet;

Figure 5-3 The Relationships dialog in MS Access is an accurate depiction of the two DataTablesrelated by a single DataRelation.

Page 180: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 5 3

Creating Data Column MappingsAnother common thing that you might want to do is clean up unsightly column names.I have encountered a large number of databases with obscure column names, like theNAME_SEQ_TS. It is often very difficult to figure out the role of such columns after theoriginal DBA has left the project. Unfortunately, you may not always be in a position tore-design a database.

NOTEDocumenting and managing databases are good reasons for using CASE tools. ERwin, DataArchitect,and Select are all good CASE tools that make it easy to design, document, and maintain databases.

One such instance where we elected not to re-design the database was where thecustomer’s motivation for re-implementing the system was not dissatisfaction with theexisting implementation. The customer simply no longer wanted to be held hostage bya hardware vendor that charged exorbitant rent. Our mission statement was to provide anew system with behavior that duplicated the behavior of the existing system. There weresupposed to be no new features. The existing system was implemented in Natural andAdabas, and we were porting it to C#, ASP.NET, and UDB. In essence, we were portinga non-object–oriented system implemented as a terminal application to a thin client Webapplication. Our charter did not really permit re-engineering the database, so we did thenext best thing. We used column mappings to more clearly name older legacy columnsthat had obscure names. Listing 5-9 demonstrates how we can use DataTableMapping andDataColumnMapping objects in concert to support providing alternate names for columns.

Listing 5-9 Using DataTableMapping and DataColumnMapping objects

OleDbConnection connection = new OleDbConnection(connectionString);

OleDbDataAdapter adapter = new OleDbDataAdapter(

"SELECT * FROM PARAMETER_TABLE", connection);

DataSet dataSet = new DataSet();

DataTableMapping tableMapping =

adapter.TableMappings.Add("Parameter", "PARAMETER_TABLE");

tableMapping.ColumnMappings.Add("ParameterType", "Parameter Type");

adapter.Fill(dataSet, "Parameter");

dataGrid1.DataSource = dataSet.Tables[0];

Listing 5-9 creates a table mapping named Parameter that maps to the Reference.mdbPARAMETER_TABLE table. The DataColumnMapping is implicitly created when weinvoke the DataTableMapping.ColumnMappings.Add method. The source table columnParameterType is mapped to the reader-friendly “Parameter Type” column. Invoking the

Page 181: Advanced c sharp programming

1 5 4 A d v a n c e d C # P r o g r a m m i n g

Fill method on adapter and using the DataTableMapping name will fill the data table withthe columns from the source table, PARAMETER_TABLE, using the mapped column names.

Of course, you can accomplish this much with SQL statements that describe columnaliases—usually with an As clause in the SELECT statement. The DataTableMapping andDataColumnMappings can be used for updates too. Listing 5-10 demonstrates how to usethe mapped table and column to perform an update using the mapped values.

Listing 5-10 A revised version of Listing 5-9 demonstrates how to perform an update againstthe mapped table and column.

OleDbConnection connection = new OleDbConnection(connectionString);

OleDbDataAdapter adapter = new OleDbDataAdapter(

"SELECT * FROM PARAMETER_TABLE", connection);

DataSet dataSet = new DataSet();

DataTableMapping tableMapping =

adapter.TableMappings.Add("Parameter", "PARAMETER_TABLE");

tableMapping.ColumnMappings.Add("ParameterType", "Parameter Type");

adapter.Fill(dataSet, "Parameter");

// Demonstrates updating using the mapped objects

DataRow dataRow = dataSet.Tables[0].NewRow();

dataRow["Parameter Type"] = "Test";

dataSet.Tables[0].Rows.Add(dataRow);

adapter.InsertCommand = (new

OleDbCommandBuilder(adapter)).GetInsertCommand();

adapter.Update(dataSet, "Parameter");

dataGrid1.DataSource = dataSet.Tables[0];

TIPIt is perfectly acceptable to create an inline object to avoid littering your code with temporary variables,as demonstrated by the OleDbCommandBuilder created inline in Listing 5-10. Use an inline object if youdon’t need a reference to that object later in the same code.

The comment beginning with the word “Demonstrates” is where the new code has beeninserted to revise Listing 5-9. This code is very similar to performing an update withoutmappings. We request a new DataRow. We use the indexer for the DataRow to modify asingle field by referring to the mapped column name. We add the modified row to the Rowscollection and wrap it up by creating an OleDbCommandBuilder to write our SQL for us andinvoking the OleDbDataAdapter.Update command. Remember to use the mapped table namefor the update just as we did with the Fill operation.

Page 182: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 5 5

Using the DataTableThe DataTable maps to a table in a database. You can also use a DataTable as an in-memorydata store for data that will never see the inside of a database. As my good and smart friendEric Cotter says, “Think of a DataTable as just a convenient way to store data.”

Although you are not required to limit the use of a DataTable to database applications, theDataTable was primarily defined for this purpose. DataTable objects are primarily composedof DataRowCollection and DataColumnCollection objects. Logically, a DataTable lookslike a single spreadsheet where the intersection of a column and row represents a field.Operations you are likely to want to perform on a DataTable include filling a table froma data source and updating that data source after you modify the data in the table. Wehave already looked at several examples demonstrating this use of the DataTable, and theAssemblyViewer.sln for this chapter has several more. Another task you may want toperform is to create and define a DataTable programmatically, including creating keyedand incremented columns.

The AssemblyViewer.sln defines several types that inherit from the DataTable. Each ofthese types is capable of creating an in-memory DataTable or a table in an Access databasethat represents a type object. For example, the PropertyTable defined in Database.cs iscapable of reading all of the properties from a PropertyInfo object, defining a DataTable,and adding all of the PropertyInfo details to the DataTable. (For discussions on Reflection,see Chapter 2 and Chapter 11.)

The basic idea is that a type is passed to the PropertyTable’s CreateNew method.Reflection is used to get all of the PropertyInfo—the property descriptions—objects for thattype. A table is dynamically created using the property names and types of the PropertyInfoobject, then each of the PropertyInfo objects representing the properties of the type passedto CreateNew is iterated, and the values of each of the PropertyInfo objects is added to thedynamic table. (I’d like to add the complete listing here for the AssemblyViewer.sln, butthe Refactored Database module alone is 728 lines of code. You will have to download theexample solution and step through the application to explore all of the code.) To facilitateyour understanding, the following bulleted list describes the Reflection algorithm used withall of the members of a type.

� Determine the elements of the type you want to explore. In this instance, we willexplore the Properties of a type only.

� Properties are described by the System.Reflection.PropertyInfo class.

� Iterate all of the properties of a PropertyInfo class, adding a column to a DataTable foreach property defined by the PropertyInfo class. Every property will itself be describedby the same properties.

� Iterate all of the properties of the type you want to explore, writing the value associatedwith each property’s PropertyInfo.

Page 183: Advanced c sharp programming

A property is described by the PropertyInfo object. A PropertyInfo object has severalproperties that describe a property. (A bit of a tongue twister.) A property is described byMemberType, PropertyType, Attributes, IsSpecialName, CanRead, CanWrite, Name,DeclaringType, and ReflectedType. Every property in a type is initialized with a value foreach of these elements of its type record, if you will. To create a table that describes a classrelative to its properties, we can store the value for each property of the PropertyInfo object.When adding this data to a table it is helpful to add a keyed column and columns for each ofthe PropertyInfo properties. This is what the AssemblyViewer.sln does. We’ll examine eachpart of this process in the sections that follow by walking through the code that creates theProperty table in the Reference.mdb database.

Creating a DataTable ObjectCreating the DataTable is easy. Ensure that you have the System.Data.dll assembly referenced,which it is by default in new projects. Add a using statement that refers to the System.Datanamespace to the module that you will be declaring a DataTable in. Construct a new instanceof a DataTable providing a name for the DataTable.

DataTable table = new DataTable("PROPERTY");

Recall that I said that you can use a DataTable independent of providers and DataSets.The preceding code is all you need to create a new DataTable.

Creating a Primary Key, Auto-Incremented ColumnFor our purposes, we can use a simple auto-increment column as the primary key for ourproperty table. We add a DataColumn to the table created in the preceding section and providesome specific information for that data column. The updated code follows in Listing 5-11.

Listing 5-11 This code demonstrates how to create an auto-incremented primary key columnin a DataTable.

DataTable table = new DataTable("PROPERTY");

DataColumn column = table.Columns.Add();

column.ColumnName = columnName;

column.AutoIncrement = true;

column.AutoIncrementSeed = 1;

column.AutoIncrementStep = 1;

column.Unique = true;

table.PrimaryKey = new DataColumn[]{column};

After we create the DataTable, we create a new column by invoking theDataTable.Columns.Add method. Provide a ColumnName as demonstrated, indicate that thiscolumn is auto- incremented by setting the DataColumn.AutoIncrement property to True. Youcan optionally set the DataColumn.AutoIncrementSeed and DataColumn.AutoIncrementStep

1 5 6 A d v a n c e d C # P r o g r a m m i n g

Page 184: Advanced c sharp programming

properties or use the default value 1 for each. The AutoIncrementSeed value is the startingnumber for the automatically incremented column, and the AutoIncrementStep value definesthe value added to each increment. Indicate that the column must be unique, as demonstratedin the preceding fragment.

Finally, a DataTable’s primary key can be composed of multiple columns. For this reasonwe must initialize the DataTable.PrimaryKey property with an array of DataColumn objects.In our example—on the last line of code in Listing 5-11—the array of columns that make upthe key is composed of our single column.

Walking the Properties of the PropertyInfo ClassAdding your basic vanilla column is much easier than adding keyed and incrementedcolumns. All we need to do is create a DataColumn object by requesting it from theDataTable and provide a name and data type for the column. Following this logic, wecan create a DataTable that maps to a PropertyInfo record with the code in Listing 5-12.

Listing 5-12 This code demonstrates how to create a table that mirrors the PropertyInfo class.

DataTable table = new DataTable("PROPERTY");

DataColumn column = table.Columns.Add();

column.ColumnName = columnName;

column.AutoIncrement = true;

column.AutoIncrementSeed = 1;

column.AutoIncrementStep = 1;

column.Unique = true;

table.PrimaryKey = new DataColumn[]{column};

PropertyInfo[] infos = typeof(PropertyInfo).GetProperties(

BindingFlags.Public | BindingFlags.NonPublic |

BindingFlags.Instance | BindingFlags.Static);

foreach(PropertyInfo info in infos)

{

column = table.Columns.Add();

column.ColumnName = info.Name;

column.DataType = typeof(string);

column.ReadOnly = true;

}

The new code retrieves the array of PropertyInfo objects for a PropertyInfo class itself.The BindingFlags indicate that we should get both public and non-public as well as instanceand static members of the type. The foreach statement works under the covers by requestingan Enumerator from a type. The implication is that the type must implement the IEnumerableinterface. System.Array types—anything declared with the [ ] array operator—implicitly

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 5 7

Page 185: Advanced c sharp programming

1 5 8 A d v a n c e d C # P r o g r a m m i n g

implement the IEnumerable interface. For each PropertyInfo, a DataColumn is requestedfrom the DataTable; the ColumnName, DataType, and ReadOnly properties are set. (TheReadOnly property is set because we can’t really change the implementation of a type bymodifying our database; hence, it makes no sense to allow users to modify PropertyInfovalues.)

When we are all finished, we have a table whose schema mirrors the properties of aPropertyInfo class. A similar approach can be used to add rows of data to the DataTable.Refer to Listings 5-6 and 5-10 for examples of adding rows of data to a DataTable. You canalso refer to the AssemblyViewer.sln for several examples of adding data to a DataTable.

Using the DataViewA DataView is by default a read-only view of data in a DataTable. You can enable modifyinga DataView by changing the AllowNew, AllowEdit, and AllowDelete properties of theDataView to True. A DataView is initialized with a DataTable and can be used to sort, filter,and modify data.

We can create a DataView that shows read-only properties by filtering the CanWrite fieldof our Property table. Listing 5-13 demonstrates how to create a DataView and apply a rowfilter using syntax roughly equivalent to a predicate in a Where clause.

Listing 5-13 This code demonstrates how to create a DataView and apply a row filter.

OleDbConnection connection = new OleDbConnection(connectionString);

OleDbDataAdapter adapter =

new OleDbDataAdapter("SELECT * FROM PROPERTY", connection);

DataTable table = new DataTable();

adapter.Fill(table);

DataView dataView = new DataView(table);

dataView.RowFilter = "CanWrite = 'False'";

dataGrid1.DataSource = dataView;

The big difference between Listing 5-13 and earlier listings is the creation of theDataView. As mentioned, we initialize a DataView with a DataTable. Optionally, we canfilter and sort the DataView without requerying the data source. The example demonstrates aDataView.RowFilter that filters rows in the PROPERTY table by the string value of ‘False’for CanWrite.

Page 186: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 5 9

The last step demonstrates how to bind the DataView to a Windows Forms DataGrid.Refer to the AssemblyViewer.sln for a complete code listing. Check out the section “Bindinga DataSet to a DataGrid” later in this chapter for more on using ADO.NET with controls.

Using the DataReader for Read-Only DataA DataReader can be used to obtain a forward-only and read-only view of data without theoverhead of a DataSet. A DataReader requires a connection and a command and is initializedby invoking a command object’s ExecuteReader method. Listing 5-14 provides a brief exampleof using an OleDbDataReader.

Listing 5-14 Using an OleDbDataReader to read all of the rows returned by an SQL command

OleDbConnection connection = new OleDbConnection(connectionString);

OleDbCommand command =

new OleDbCommand("SELECT * FROM FIELD", connection);

connection.Open();

OleDbDataReader reader = command.ExecuteReader();

while( reader.Read())

{

Debug.WriteLine(reader.GetValue(0));

}

reader.Close();

connection.Close();

We can’t bind the DataReader to the grid because a DataReader does not implement theIList or IListSource interfaces. That is the technical reason. The logical reason is that a gridcan be scrolled forward and backward, and a DataReader is forward-only.

You must maintain an open connection while a DataReader is active, as demonstratedby the code. Ensure that you call Close on the reader when you have finished with it, becausethe connection cannot be reused until the reader has been closed. The benefit of using aDataReader is that it performs very fast read operations.

Displaying Information in the DataGridObject-oriented frameworks tend to be very flexible because there is a certain amount ofcommonality throughout the framework. One example is the Windows Forms DataGridcontrol. The DataGrid control has a DataSource property. You can assign a variety of objects

Page 187: Advanced c sharp programming

to a DataSource because the DataSource is implemented to work with any object thatimplements the IList or IListSource interfaces. (IListSource has two members, one of whichreturns an object that implements IList.) As a result, there is a tremendous flexibility when itcomes to binding to the DataSource property.

In general, the DataSource property can be assigned to a DataTable, DataSet, DataView,DataViewManager, and any object that implements IList or IListSource. The net effect of thegeneric implementation of the DataSource is that you can obviously bind to objects definedin ADO.NET, but you can also bind to a list of any kind of object.

Recall that in Listing 5-12 we invoked the GetProperties method. GetProperties returned anarray of PropertyInfo objects, represented in code as PropertyInfo[ ]. An array’s underlying typeis System.Array. Interestingly enough, System.Array implements the IList interface. Theimplication, then, is that we should be able to bind the DataGrid.DataSource property directlyto the PropertyInfo[ ] returned by GetProperties. In fact, if you write the following code, youwill get almost an identical result to the one you would get if you assigned the DataTable inListing 5-12 to a DataGrid’s DataSource property.

dataGrid1.DataSource = this.GetType().GetProperties();

The preceding single statement produces the view shown in the DataGrid in Figure 5-4.Try binding to the arrays returned by invoking GetMembers, GetFields, and GetMethods.

1 6 0 A d v a n c e d C # P r o g r a m m i n g

Figure 5-4 A DataGrid that has been bound directly to an array of PropertyInfo objects

Page 188: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 6 1

Using the Command ObjectCommand objects are used to encapsulate SQL commands in an object. Use the SqlCommandfor MS SQL Server providers and the OleDbCommand for OLE DB providers. Commandobjects store the SQL text that represents SELECT, UPDATE, INSERT, and DELETEstatements, as well as provide you with an object for other SQL commands for particularproviders. Examples of other commands include CREATE TABLE, ALTER TABLE, andDROP TABLE.

To create an OleDbCommand object, we can construct an instance of the object bypassing the SQL text and an instance of an OleDbConnection object. The OleDbCommandobject can be used as an instruction to an OleDbDataAdapter or directly to execute SQLcommands that don’t return a result set. Listing 15-4 provides an example of creatingan OleDbCommand used to request a DataReader. You can look at that example andOleDbCommand objects created in the AssemblyViewer.sln for more examples.

One kind of command that we haven’t looked at yet are commands used to create entitiesin a database. We can use an OleDbCommand and an SQL CREATE TABLE statement toadd a new table to a database. The code in Listing 5-15 demonstrates how to create a tablein an Access database. (You will need to vary the SQL slightly to match the syntax of theprovider that you are using; the provider’s user’s guide is the best resource for specificSQL syntax.)

Listing 5-15 This code demonstrates how to create a table in an OLE DB provider.

const string connectionString =

@"Provider=Microsoft.Jet.OLEDB.4.0;Password=;" +

@"User ID=;Data Source=C:Temp\Reference.mdb;";

OleDbConnection connection = new OleDbConnection();

const string SQL =

@"CREATE TABLE Constructor ([ID] COUNTER (1,1), " +

@"[MemberType] string,[MethodHandle] string, " +

@"[Attributes] string,[IsPublic] string, " +

@"[IsPrivate] string,[IsFamily] string, " +

@"[IsAssembly] string,[IsFamilyAndAssembly] string, " +

@"[IsFamilyOrAssembly] string,[IsStatic] string, " +

@[IsFinal] string,[IsVirtual] string,[IsHideBySig] string, " +

@"[IsAbstract] string,[IsSpecialName] string, " +

@"[IsConstructor] string,[CallingConvention] string, " +

@"[Name] string,[DeclaringType] string, " +

@"CONSTRAINT [Index1] PRIMARY KEY ([ID]))";

OleDbCommand command = new OleDbCommand(SQL, connection);

command.ExecuteNonQuery();

Page 189: Advanced c sharp programming

1 6 2 A d v a n c e d C # P r o g r a m m i n g

Most of the code in Listing 5-15 is the SQL text. (We’ll see a better way to manage SQLstatements in a moment.) The first statement is a literal connection string for the Jet OLE DBprovider, including the Provider, Password, User ID, and Data Source clauses. The secondstatement creates an instance of an OleDbConnection. The third statement is a literalCREATE TABLE SQL statement that is appropriate for a Jet OLE DB provider. (Jet is thename of MS Access’ database engine.) The OleDbCommand object is created by initializingan instance with the SQL text and the connection object. Finally, we send the query to thedatabase engine with the command object.

The CREATE TABLE statement is specific to the database we are sending the SQL textto. The basic syntax is CREATE TABLE (tablename fielddefinitions [fielddefinition1, ...fielddefinition(n)] constraints), where the table name is a valid name, and the field definitionsindicate the field name and data type and any constraints such as a primary key.

The CREATE TABLE statement demonstrates how to create an auto-increment column.The ID column described as [ID] COUNTER (1,1) defines an auto-increment column thatuses a seed value of 1 and an increment value of 1. The CONSTRAINT [Index1] PRIMARYKEY ([ID]) defines an index named Index1 as the primary key based on the ID column.

Generating SQL with the CommandBuilderWriting SQL statements can be a little tedious. There is an easier way to generate SQL withADO.NET. Construct an instance of an OleDbCommandBuilder and initialize the commandbuilder with an adapter that was initialized with a SELECT statement. The schema returnedby the SELECT statement can be used to generate SQL INSERT, DELETE, and UPDATEstatements.

Lines 18 and 19 of Listing 5-6 constructs an OleDbCommandBuilder with anOleDbDataAdapter instance. The command text used to initialize the OleDbDataAdapterwas “SELECT * FROM METHOD”. From the schema that can be read from the METHODtable, verbose instances of SQL commands can be generated. When you invoke theOleDbDataAdapter.Update command, the SQL created by the OleDbCommandBuilder isnecessary to update the data source based on changes made to the tables in the DataSet.

Secondary TopicsYou know how to create connections and use adapters to bridge data read via a connectioninto a DataTable or DataSet. These objects are seldom used in isolation. In your averageapplication, you will be using ADO.NET objects in conjunction with visual controls,applications, and perhaps Web Services.

This part of the chapter introduces contextual ways in which you will use ADO.NET,including returning a DataSet from a Web Service. You can use the examples in this partof the chapter as a brief introduction to these subjects and explore the rest of the book foradditional examples. Explore the sample applications that ship with Visual Studio .NET andthe www.gotdotnet.com and www.codeguru.com websites as additional resources for morecode examples.

Page 190: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 6 3

Binding a DataSet to a DataGridYou can bind data directly to a System.Web.UI.WebControls.DataGrid. The requirementis that the data must implement the IEnumerable interface. Data can be bound to aSystem.Windows.Forms.DataGrid control if the data object implements the IList orIListSource interfaces. The DataSet class implements IEnumerable, IList, and IListSource,which means that you can quickly create a user interface to bind a DataSet to DataGrids inWindows Forms or Web Forms.

These two fragments demonstrate how to bind a DataSet to a Windows Forms and WebForms DataGrid.

DataGrid.DataSource = DataSet;

The preceding statement is all you need to bind a DataSet to a Windows Forms DataGrid.

DataGrid.DataSource = DataSet;

DataGrid.DataBind();

The preceding are the basic two statements necessary to bind a DataSet to a Web FormsDataGrid.

The DataGrid represents a specific instance of a DataGrid, and the DataSet represents aspecific instance of a DataSet object. Additionally, you can bind a DataTable or any othercollection that implements one of the necessary interfaces. For example, System.Array—which is the underlying type when you declare an array of any type—implements the IListinterface. Hence, as demonstrated earlier in “Displaying Information in the DataGrid,” anyarray of types can be displayed in a DataGrid. The public properties of the type of the objectin the array will be used to create columns in the DataGrid.

Returning a DataSet from a Web ServiceXML is an integral part of the .NET Framework. ADO.NET employs XML to manage data.The DataSet was defined with the SerializableAttribute. It is the SerializableAttribute andXML that support returning a DataSet from a Web Service. Listing 5-16 contains a monolithicWeb Method (defined in the Reference.sln available for download from www.osborne.com)that dynamically explores the methods of the type passed to the Web Method.

Listing 5-16 This code demonstrates a Web Method that returns a DataSet.

[WebMethod, ReflectionPermission(SecurityAction.Demand)]

public DataSet GetMethods(string type)

{

DataTable table = new DataTable("METHOD");

MethodInfo[] methods = Type.GetType(type).GetMethods();

Page 191: Advanced c sharp programming

1 6 4 A d v a n c e d C # P r o g r a m m i n g

DataColumn column;

foreach(PropertyInfo propertyInfo

in typeof(MethodInfo).GetProperties())

{

column = table.Columns.Add();

column.ColumnName = propertyInfo.Name;

column.DataType = typeof(string);

}

DataRow dataRow;

foreach(MethodInfo methodInfo in methods)

{

dataRow = table.NewRow();

foreach(PropertyInfo propertyInfo

in methodInfo.GetType().GetProperties())

{

dataRow[propertyInfo.Name] =

propertyInfo.GetValue(methodInfo, null);

}

table.Rows.Add(dataRow);

}

DataSet dataSet = new DataSet();

dataSet.Tables.Add(table);

return dataSet;

}

Pass in the full namespace of a type, and the GetMethods Web Method will create a tablebased on the properties of the MethodInfo type. The properties become the columns of theDataTable. Each method’s properties become the rows for the DataTable. Add the DataTableto a DataSet, and we can return the dynamically reflected type’s methods.

It is necessary to Demand ReflectionPermission if we are going to use Reflection from aWeb Service. Any code that is downloaded from a network is not guaranteed to be grantedReflection permission.

Implementing a TraceListenerThe AssemblyViewer.sln defines a nested class that inherits from TraceListener. Byinheriting from TraceListener, we can register an instance of our custom TraceListener

Page 192: Advanced c sharp programming

C h a p t e r 5 : B u i l d i n g D a t a b a s e A p p l i c a t i o n s w i t h A D O . N E T 1 6 5

listener with the System.Diagnostics.Trace class’s Listeners collection. The result is that ourcustom listener can catch Trace messages and display them as part of our presentation layer.(This is consistent with the Trace window provided with Terrarium. Refer to Chapter 4 forinformation on Terrarium.)

Listing 15-17 demonstrates a private nested TraceListener class added to the main formof the AssemblyViewer.sln, and we can use the main form as a TraceListener window todisplay Trace information while we are testing our application.

Listing 15-17 Implementation of a custom TraceListener

private class Listener : TraceListener

{

private StatusBar statusBar;

public Listener(StatusBar statusBar) : base()

{

this.statusBar = statusBar;

}

public override void Write(string text)

{

statusBar.Text = text;

Application.DoEvents();

}

public override void WriteLine(string text)

{

statusBar.Text = text;

Application.DoEvents();

}

}

To implement a custom TraceListener, you need to override the Write and WriteLinemethods. When we add an instance of our TraceListener, Listener, to the System.Diagnostics.Trace.Listeners collection, messages written with the Trace object are sent to our listenertoo. Based on the implementation of our listener, we store a reference to a StatusBar anddisplay trace information on the StatusBar control. In effect, the owning form’s StatusBarcontrol becomes a visual Trace Window.

Rather than inventing new classes, we can find new ways to use existing classes. The.NET Framework is well organized but extensive. Before you write a significant amountof new code, leverage as much of the existing framework as you can.

Page 193: Advanced c sharp programming

SummaryConsistent with the general approach of this book, this chapter demonstrated several topicsthat you are likely to find in a single application. A large number of applications are databaseapplications. ADO.NET provides advancements in database programming that are intendedto promote scalability. You can use an older version of ADO with COM Interop—but onceyou get used to ADO.NET you will not want to go back in time.

This chapter provided an AssemblyViewer.sln that builds on your knowledge ofReflection to demonstrate how to create dynamic databases. Reflected properties werea convenient mechanism for accomplishing this.

After completing this chapter, you should have a good understanding of ADO.NET andan improved understanding of Reflection; you should also know how to bind data to theDataGrid control for Windows Forms and Web Forms and how to implement a customTraceListener. This chapter also further introduced Web Services. You will find these skillsbeneficial in simple Windows applications, as well as in complex enterprise solutions.

Page 194: Advanced c sharp programming

PART

IITools and Components

OBJECTIVES� Learn about the Visual Studio .NET extensibility

model, including the Common Object Model� Create Visual Studio .NET project templates� Learn how to write bullet-proof applications with the

System.Diagnostics namespace� Use PerformanceCounters to track your application’s

behavior� Create user and custom controls, including surface

constituent controls� Learn to use the ThreadPool for optimal control

behavior� Create a TypeConverter, a TypeDescriptor, and a

TypeEditor, and implement an extender provider andIConvertible

� Add metadata to your application using attributesand create custom attributes

� Learn how to add controls to the Toolbox, includinghow to associate a custom image with your control

� Explore the .NET framework using Reflection

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 195: Advanced c sharp programming

This page intentionally left blank.

Page 196: Advanced c sharp programming

CHAPTER

6Customizing Visual

Studio .NET

169

IN THIS CHAPTER:

Demonstrated Topics

Creating a Custom Wizard

Creating a Project Template for VisualStudio .NET Wizards

Extending Visual Studio .NET with Wizards

Secondary Topics

Returning to Jscript .NET

Using the regasm Utility

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 197: Advanced c sharp programming

1 7 0 A d v a n c e d C # P r o g r a m m i n g

Microsoft has made a considerable effort in providing an automation andextensibility model for Visual Studio .NET. Automating and extending VisualStudio .NET to support common tasks is an optimal way to facilitate rapid

application development. Tedious, highly repetitive, or monotonous tasks can be automatedby creating wizards, add-ins, macros, or project templates to speed up development.

Macros are based on the Visual Basic language and are supported by a large extensibilitymodel that lets you control every facet of Visual Studio .NET. Instead of trying to provide areference manual for the extensibility interface, which needs its own book, I will demonstratehow to create wizards, create a custom project template, and write macros. As a result,you will have a practical tool for creating your own wizards—extensions to Visual Studio.NET—and a good introduction to the extensibility model for .NET.

Demonstrated TopicsIf you find yourself performing the same tasks repeatedly, then you are ready for this chapter.An obvious task you and I perform regularly is to create a new project. Whether we areimplementing a Web Service, ASP.NET, Windows, or Console application, we use theFile | New Project applet to create a project based on an existing project template. One of thethings you can do with Visual Studio .NET is create your own custom project templates. Thesteps for creating a project template are a bit involved, but in this chapter I will provide youwith examples that demonstrate how to

� Create a custom wizard by implementing the IDTWizard interface

� Create a custom project template

� Register and test wizards

� Write macros in the Macros IDE

The secondary topics in the second part of this chapter demonstrate supporting topics. I willintroduce Jscript.NET and how it is employed by the wizard engine that ships with VisualStudio .NET and how to use the regasm utility, which is necessary for registering assembliesin the global assembly cache.

Creating a Custom WizardMicrosoft made wizards ubiquitous. Almost everyone that has used a computer has used awizard. The term wizard as it is generally used refers to a linear series of dialogs that requirefeedback to create a desired result. Wizards are essentially linear subprograms. Prior tothe early 1990s many programs were linear. When the Windows operating system andprogramming languages for Windows became commonplace developers began writingapplications that behaved in a spatial way. Interestingly, we have returned to linear sub-

Page 198: Advanced c sharp programming

programs—wizards—because it is easier to limit user interaction to a series of linearsteps in some instances. This is especially true when performing outcome-based, new, ordifficult tasks.

For professional programmers, a wizard refers to writing an application or subprogramthat supports a linear series of steps toward completion of a specific goal. Specifically, weaccomplish this by implementing the IDTWizard interface. For a user, the mechanics ofoperating a wizard are simple, and for the most part involve clicking Next. For developers,creating a wizard involves some specific mechanics and requires a moderate amount of effortto synthesize the choices to simple inputs in order to get the wizard user to the Next buttonas quickly as possible. Fortunately, the Microsoft developers have made the mechanics ofimplementing a wizard simple, but, as always, the creative part is left to the individualdevelopment effort. Listing 6-1 provides a vanilla Hello World–style wizard as an introduction.Refer to the section “Extending Visual Studio .NET with Wizards,” later in this chapter, foran example of a practical wizard.

Listing 6-1 Inheriting the IDTWizard interface to create a wizard for Visual Studio .NET

using System;

using EnvDTE;

using System.Windows.Forms;

namespace HelloWizard

{

public class HelloWizard : IDTWizard

{

public void Execute(object Application,

int hwndOwner, ref object[] ContextParams,

ref object[] CustomParams, ref wizardResult retval)

{

MessageBox.Show("Hello Wizard");

retval = EnvDTE.wizardResult.wizardResultSuccess;

}

}

}

Superficially, a wizard is a class that implements the IDTWizard interface defined inEnvDTE. There is only one wizard method, the Execute method. Execute starts the wizard.What your code does from that point depends on the task you are walking the user through.Our wizard displays a message box and returns the enumerated value that indicates success.There are some more steps that we have to perform to test the wizard, which we’ll discuss next.

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 7 1

Page 199: Advanced c sharp programming

Creating the Wizard ProjectWizards are created as class library projects. Use the New Project dialog (shown in Figure 6-1)and select the Class Library project template. Provide a name for the wizard and click OK.There are no special steps you need to take to create the wizard project.

Implementing the IDTWizard InterfaceTo implement the IDTWizard interface, you will need to add a reference to the envdte.dllassembly in the project references section. The EnvDTE represents the design timeenvironment, that is, Visual Studio .NET’s extensibility model. Add a using statement torefer to the EnvDTE namespace and indicate that your class realizes the IDTWizardinterface. (Implementing interfaces and inheritance use the same notation, as demonstratedin the class header of Listing 6-1.)

TIPType using and then press CTRL -SPACEBAR to get a drop-down list of available namespaces.

IDTWizard has a single method that you must implement. The wizard will be initializedwith the handle of an owner, an array of context, an array of custom parameters, and a returnvalue. The return value uses the pass-by-reference parameter modifier.

1 7 2 A d v a n c e d C # P r o g r a m m i n g

Figure 6-1 The New Project dialog with the Class Library template selected

Page 200: Advanced c sharp programming

Passing Arguments by ReferenceIf you want to return a single value from a method, then define and implement a method thatis referred to as a function. Functions are methods that have a return type other than void.You can also use the ref modifier for parameters to return multiple values. Arguments definedwith the ref modifier reflect changes made to those arguments by the caller. The ref modifieris equivalent to passing a pointer in C++, a ByRef argument in Visual Basic .NET, or a varargument in Delphi. See Listing 6-1 for the proper placement of the ref modifier.

Testing the WizardAn easy way to test your wizards is to create a Macro that invokes the wizard. The Singletonobject DTE contains a method, LaunchWizard, that will load and run a wizard. Refer to“Writing a Macro” and “Testing the Wizard with a Macro” later in this chapter, for anexplanation of macros and wizard launch files. Listing 6-2 contains the macro code that testsHelloWizard, and Listing 6-3 contains text that you can put in a wizard launch file. (Theselistings are provided for reference. The rest of the chapter will describe the relationships andsteps you need to complete to use these files.)

Listing 6-2 A Visual Basic macro to test HelloWizard

Imports System

Imports EnvDTE

Imports System.Diagnostics

Public Module Module1

Public Sub Test()

Const Path As String = _

"C:\Temp\HelloWizard.vsz"

Debug.WriteLine("This is running!")

Dim ContextParams(1) As Object

ContextParams(0) = Nothing

DTE.LaunchWizard(Path, ContextParams)

End Sub

End Module

The macro can be created by opening the Macro IDE. Select Tools | Macros | Macros IDEto open the Macros IDE (shown in Figure 6-2, with the code shown in Listing 6-2). From

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 7 3

Page 201: Advanced c sharp programming

Listing 6-2 it should be evident that you need to create a wizard launch file. Wizard launchfiles have a .vsz extension and are named identically to the class library containing thewizard that the launch file is associated with. Listing 6-3 contains the wizard launch file forHelloWizard.dll.

Listing 6-3 An example wizard launch file for the HelloWizard.dll class library

VSWIZARD 7.0

Wizard=HelloWizard.HelloWizard

Param=<Nothing>

The text shown in Listing 6-3 should be placed in a file named HelloWizard.vsz that isplaced in the same directory as the one containing the HelloWorld.dll that has been registeredwith regasm.exe. Regasm is a utility that ships with Visual Studio .NET. Refer to “RegisteringWizards” later in this chapter for more information.

1 7 4 A d v a n c e d C # P r o g r a m m i n g

Figure 6-2 The Macros IDE can be used to automate repetitive tasks without creating add-ins orwizards.

Page 202: Advanced c sharp programming

Creating a Project Template forVisual Studio .NET WizardsThere are several project templates that ship with Visual Studio .NET that allow you to createprojects like the Class Library project, Windows and Web applications, Web Services, controls,and add-ins. There is no specific project template for creating a wizard. As an introduction toproject templates, we will walk through creating a project template that will show up in theNew Project dialog.

There is a wizard—VsWizard.dll, in C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\VsWizard.dll—that provides a basic engine for project templates. Thisgeneric engine uses specific sample files, scripts, and other input files to create a new projectfrom a project template. Accordingly, we do not have to write a custom engine to create aproject template, but we do have to create some specific files that the engine uses as inputsand some sample files used as templates to create a new wizard project. Our goal, stated assimply as possible, is to create a new project template for Visual Studio .NET wizards. Hereare the steps we will need to complete to create a wizard project template, which I will referto as a Wizard Library henceforth:

� Copy an existing template that is close to the new template we want to create. We willuse the existing Class Library template because a wizard project is a class library or DLL.

� Add new template files to our template project based on the kind of template we arecreating. To create a wizard, we need a .cs file containing a class that implementsIDTWizard. We can use Listing 6-1 without the MessageBox as a template.

� Modify the templates.inf file that contains the list of files contained in our template project.

� Modify the common.js (Jscript .NET) source code that contains generic methods invokedby the vsWizard engine. We need to add code to create a specific class library project thatcontains a reference to the envdte.dll assembly.

� Modify the default.js script file that contains methods that are generic to our templateproject.

� Add an icon file to our project template.

� Modify the wizard launch file to make sure the vsWizard engine wizard is associatedwith our template.

� Modify the VSDir file to add our project template to the New Project dialog.

These steps do not have to be completed in any specific order, but we will approach them asdefined. As you can see, this is an involved process (which I would like to see become mucheasier). However, this is an extensible solution and a generic one that supports any projecttemplate. Each of the remaining subsections will, in turn, describe each part of the overallprocess as listed, with explanatory details.

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 7 5

Page 203: Advanced c sharp programming

Copying an Existing Project TemplateProject templates are represented by the icons you see in the New Project dialog (see Figure 6-2).They are much easier to use than creating a project from scratch using a text editor, althoughyou could create a project from scratch with any text editor.

CAUTIONDifferent versions of Visual Studio will provide different project templates. Check your help documentationfor the features available with your version of Visual Studio .NET. Professional developers should have theEnterprise Architect version.

The easiest way to create a new project template is to copy an existing one that is close tothe new one you want to create. The closest one available is the Class Library template. Bydefault, these files are installed in C:\Program Files\Microsoft Visual Studio .NET\VC#\VC#Wizards. The Class Library template is represented by CSharpDLLWiz. To create thenew derivative project template, follow these numbered steps:

1. Copy the C:\Program Files\Microsoft Visual Studio .NET\VC#\VC#Wizards\CSharpDLLWiz folder and paste the copy into the same directory. (If you did notuse the default installation directories then you will need to make the necessaryadjustments.)

2. The copy will be named “Copy of CSharpDLLWiz” by default. Using WindowsExplorer rename this file to CSharpDLLWizWiz to stay relatively close to theconvention exhibited.

These wizard folders contain a Scripts and Templates subdirectory, each containinga language identifier. In the version I have, the language identifier is 1033. (I believe thelanguage identifier is an identifier that represents English; non-English users will havea different identifier.)

The Scripts\1033 folder contains the Jscript default.js file containing script specific toour project template. Refer to “Modifying the Default.js Script” later in this section. TheTemplates\1033 folder contains actual files containing code that will be copied by the Wizardengine to create our new project. Refer to the next section, “Adding Files for the WizardLibrary Template,” for more information on this part of the process.

If you have a new folder that looks like the following example, then we are readyto continue.

C:\Program Files\Microsoft Visual Studio .NET\VC#\VC#Wizards

CSharpDLLWizWiz\Scripts\1033

default.js

CSharpDLLWizWiz\Templates\1033

assemblyinfo.cs

file1.cs

Templates.inf

1 7 6 A d v a n c e d C # P r o g r a m m i n g

Page 204: Advanced c sharp programming

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 7 7

Remember, these files were all copied from the CSharpDLLWiz, which represents the ClassLibrary project template.

Adding Files for the Wizard Library TemplateThe Templates\1033 folder needs to be updated to contain files that we want in our WizardLibrary template. We don’t need to change the file names in use; hence, we don’t need tochange the contents of the Templates.inf . However, we do need to change the file1.cs file tocontain code that describes a wizard template. Listing 6-4 provides the code for the modifiedfile1.cs file.

TIPMake sure you use the same file names. These are used by the Jscript functions, and your project templatewill not work if you change the file names without updating the script to conform to the new file names.

Listing 6-4 Modify the template source file, file1.cs, to describe a class that contains animplementation of IDTWizard

using System;

using EnvDTE;

namespace [!output SAFE_NAMESPACE_NAME]

{

/// <summary>

/// Summary description for [!output SAFE_CLASS_NAME].

/// </summary>

public class [!output SAFE_CLASS_NAME] : IDTWizard

{

public [!output SAFE_CLASS_NAME]()

{

//

// TODO: Add constructor logic here

//

}

public void Execute(object Application, int hwndOwner,

ref object[] ContextParams,

ref object[] CustomParams, ref wizardResult retval)

{

//

// TODO: Add your custom code here

//

Page 205: Advanced c sharp programming

1 7 8 A d v a n c e d C # P r o g r a m m i n g

retval = EnvDTE.wizardResult.wizardResultSuccess;

}

}

}

The code looks almost identical to our example in Listing 6-1, except for the addition ofreplaceable tokens represented by SAFE_CLASS_NAME. Again, don’t change these unlessyou are prepared to change the script files that refer to these values. I won’t demonstrate thisprocess, but you can probably figure it out by closely examining the supporting Jscript code.

Modifying the default.js ScriptThe default.js script file is defined in Scripts\1033. We only have to make a small change,but it is an important change. As you know, wizard projects need a reference to the envdte.dllassembly. We will need to load a reference to this assembly in new Wizard Library projects,so we will have to modify our script accordingly to ensure that this happens. This is a changethat must be coordinated with changes in the common.js script file to work properly—and isone of the reasons I think project templates could use some work. (Refer to the next section,“Modifying the common.js Script,” for information on coordinating changes.) Listing 6-5provides the modified default.js script file.

Listing 6-5 Modify the copied default.js file, making the small change indicated in bold face font

1: // (c) 2001 Microsoft Corporation

2:

3: function OnFinish(selProj, selObj)

4: {

5: var oldSuppressUIValue = true;

6: try

7: {

8: oldSuppressUIValue = dte.SuppressUI;

9: var strProjectPath = wizard.FindSymbol("PROJECT_PATH");

10: var strProjectName = wizard.FindSymbol("PROJECT_NAME");

11: var strSafeProjectName = CreateSafeName(strProjectName);

12: wizard.AddSymbol("SAFE_PROJECT_NAME", strSafeProjectName);

13:

14: var bEmptyProject = 0; //wizard.FindSymbol("EMPTY_PROJECT");

15:

16: var proj = CreateCSharpProject(strProjectName,

17: strProjectPath, "defaultDll.csproj");

18:

Page 206: Advanced c sharp programming

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 7 9

19: var InfFile = CreateInfFile();

20: if (!bEmptyProject)

21: {

22: AddReferencesForWizard(proj);

23: AddFilesToCSharpProject(proj,

24: strProjectName, strProjectPath, InfFile, false);

25: }

26: proj.Save();

28: }

29: catch(e)

30: {

31: if( e.description.length > 0 )

32: SetErrorInfo(e);

33: return e.number;

34: }

35: finally

36: {

37: dte.SuppressUI = oldSuppressUIValue;

38: if( InfFile )

39: InfFile.Delete();

40: }

41: }

42:

43: function GetCSharpTargetName(strName, strProjectName)

44: {

45: var strTarget = strName;

46:

47: switch (strName)

48: {

49: case "readme.txt":

50: strTarget = "ReadMe.txt";

51: break;

52: case "File1.cs":

53: strTarget = "Class1.cs";

54: break;

55: case "assemblyinfo.cs":

56: strTarget = "AssemblyInfo.cs";

57: break;

58: }

59: return strTarget;

60: }

61:

Page 207: Advanced c sharp programming

1 8 0 A d v a n c e d C # P r o g r a m m i n g

62: function DoOpenFile(strName)

63: {

64: var bOpen = false;

65:

66: switch (strName)

67: {

68: case "Class1.cs":

69: bOpen = true;

70: break;

71: }

72: return bOpen;

73: }

74:

75: function SetFileProperties(oFileItem, strFileName)

76: {

77: if(strFileName == "File1.cs" || strFileName == "assemblyinfo.cs")

78: {

79: oFileItem.Properties("SubType").Value = "Code";

80: }

81: }

The wizard engine was designed to use external Jscript functions, which is certainly adiscretionary choice of the innovator. If you know Jscript, then the code is self-evident.There are four functions: OnFinish, GetCSharpTargetName, DoOpenFile, and SetFileProperties.These methods are invoked specifically when the Wizard Library project template is requested.

SetFileProperties sets a property to indicate that the two .cs files contain code.DoOpenFile is used to open the class module inside Visual Studio .NET, which is the fileyou will be interested in modifying, as it contains the implementation of the custom wizard.GetCSharpTargetName performs simple file name substitution. The only relevant change inour template is that file1.cs is created as class1.cs. OnFinish performs the substitutions forreplaceable parameters in file1.cs and puts the project together, including adding references.The only change we needed to make was to substitute a call to AddReferencesForWizard online 22. The original script file contained a call to AddReferencesForClass(proj),which isdefined in common.js. We need to implement AddReferencesForWizard; we’ll complete thisstep in the next section.

Modifying the common.js ScriptProject Templates are separated into various pieces based on the shared wizard engine, apre-determined folder structure, some pre-existing template files, and two Jscript files thatinterplay to create a new project template. I found this convolution of folders and scripts a bitunapproachable and not at all intuitive. As a programmer, I would prefer a unified approach

Page 208: Advanced c sharp programming

that involved more programming and less interaction with external directory structures; thisis one of the reasons I am describing it for you. I thought you might need a bit of help.

In the C:\Program Files\Microsoft Visual Studio .NET\VC#\VC#Wizards\1033 folder,there is a common.js Jscript file that contains script common to all project templates. Wewill need to modify this file to ensure that our project has the correct references. Listing 6-6provides that the revised common.js file contains AddReferencesForWizard. (I didn’t providethe entire listing because it is very long. Just add the code in Listing 6-6 to common.js.)

TIPYou are modifying files that are installed by Visual Studio .NET. It is a good idea to back up these filesbefore proceeding.

Listing 6-6 The revised common.js file containing AddReferenceFor Wizard

/**************************************************************

Description:

oProj: Project object

********************************************************/

function AddReferencesForWizard(oProj)

{

var refmanager = GetCSharpReferenceManager(oProj);

var bExpanded = IsReferencesNodeExpanded(oProj)

refmanager.Add("System");

refmanager.Add("System.Data");

refmanager.Add("System.XML");

refmanager.Add("envdte");

if(!bExpanded)

CollapseReferencesNode(oProj);

}

The key is old-fashioned reuse: copy and paste. Copy and paste AddReferencesForClassand add the single statement that will include a reference to envdte, as shown in boldface.Our Wizard Library is very close to the Class Library template, which provides us with anopportunity for copy-and-paste reuse. Of course, if you are making an as-yet unheard oftemplate, then you might have guessed that you have your work cut out for you. The finaltwo steps are to create our wizard launch file and a VSDir file.

Creating the Wizard Launch FileWe have already discussed the wizard launch file in the earlier section “Creating a CustomWizard.” In this instance, the wizard already exists, so we will be instructing the New Projectdialog which wizard to use. The wizard launch files for project templates are contained in the

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 8 1

Page 209: Advanced c sharp programming

1 8 2 A d v a n c e d C # P r o g r a m m i n g

C:\program Files\Microsoft Visual Studio .NET\VC#\CSharpProjects folder when you usethe default installation.

The Class Library project template wizard launching file is contained in CSharpDLL.vsz. Inkeeping with the exhibited extension, we will name our wizard launch file CSharpDLLWiz.vsz.You can start by copying the CShaprDLL.vsz file and pasting it to the same directory. Renamethe file CSharpDLLWiz.vsz and modify it to point to our template directory, as shown inListing 6-7.

Listing 6-7 The wizard launch file for our Wizard Library project template

VSWIZARD 7.0

Wizard=VsWizard.VsWizardEngine

Param="WIZARD_NAME = CSharpDLLWizWiz"

Param="WIZARD_UI = FALSE"

Param="PROJECT_TYPE = CSPROJ"

The only change made was to change the first parameter to refer to our Wizard Libraryfolder. The first parameter is a relative path, and the other parameters provide conditionalvalues for the wizard engine.

We are almost finished. The final step is to create the VSDir and test the project templateby creating a new wizard project.

Creating the VSDir FileVSDir files are used to provide information to the Add Item and New Project dialogs todescribe how items are displayed. From visual observation, you can get a reasonable idea ofthe contents of these files. In general, you need to indicate an icon and a name and specify theassociation between the applet in the Add Item and New Project dialogs and which operation isinvoked. The VSDir file is provided in Listing 6-8. An explanation of the cryptic lookingentries is provided after the listing.

Listing 6-8 The VSDir file for our project template wizard

CSharpDLLWiz.vsz|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|Wizard Library|

20|#2323|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|4547| |WizardLibrary

The entry is made as a single line of text in the VSDir file that contains existing projecttemplates items. By default, this file is C:\Program Files\Microsoft Visual Studio .NET\VC#\CShaprProjects\CSharpEx.vsdir. This is the same folder as the one that will contain yourwizard launch file.

The entry is broken into fields by the pipe ( | ) symbol. Each pipe delimits a field. Table 6-1describes the name and entry for each field in the VSDir file.

Page 210: Advanced c sharp programming

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 8 3

The examples shown in Table 6-1 were used to describe our Wizard Library projecttemplate as it is shown in Figure 6-3.

To test the Wizard Library project template, select File | New | Project and pick theWizard Library template, as shown in Figure 6-3. When you click OK, you should have anew Class Library project containing an assemblyinfo.cs file and a file named Class1.csthat provides a basic implementation for an IDTWizard. Make sure that a reference to theenvdte.dll assembly has been added too. Now all you will need to focus on is the code thatperforms the automated task.

Relative Path Name CSharpDLLWiz.vsz Specifies the wizard launch file using arelative path. Store the .vsz file in thesame directory as the .vsdir file.

{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}

CLSID package A GUID representing a resource DLL.This particular GUID refers to somethingnamed the C Sharp Editor Factory.

Wizard Library Localized name This is the localized name of the templateand the name that appears in the dialog(see Figure 6-3).

20 Sort priority Determines the order in which elements arelisted. A sort priority of 1 is the highest.

#2323 Description A string or resource identifier containing alocalized text description of the template.(The description appears in the NewProject dialog—again, refer to Figure 6-3.)

{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}

DLLPath or CLSID package Contains a GUID that refers to an EXEor DLL resource that contains icons forthe template.

4547 Icon resource ID Combined with the preceding entry,indicates which icon to use from theresource file. (We’ll use the same oneas the Class Library.)

<blank> Flags Bitwise flags, not used by our template.You can refer to the Visual Studio .NEThelp for VSDir files for individual flagvalues.

WizardLibrary Suggested base name Specifies the default name for the template.Our entry represents a project; therefore,the root name for Wizard Library projectswill be WizardLibrary. The first newproject will be WizardLibrary1, and so on.

Table 6-1 Fields Used to Describe Entries in the Add Item or New Project Dialog

Page 211: Advanced c sharp programming

1 8 4 A d v a n c e d C # P r o g r a m m i n g

You might wonder where the project template comes from. I haven’t mentioned it.Consistent with the entire process, there are several files that are shared. One of these isthe DefaultDLL.csproj file. This file is shared by DLL project templates and is locatedin the C:\Program Files\Microsoft Visual Studio .NET\VC#\VC#Wizards folder, namedDefaultDLL.csproj.

Creating project templates is more of a copy-and-paste and file management process thana programming process. However, if you want to automate repetitive tasks or simplify difficulttasks, then create a project template.

Extending Visual Studio .NET with WizardsThe benefits of wizards are that you can provide an easy way to do something that may bedifficult for beginner-to-intermediate developers and you can automate certain tasks. By creatinga wizard, you can make advanced aspects of .NET programming available to everyone onyour team.

In this section I will demonstrate a practical wizard that automatically creates a stronglytyped collection. A strongly typed collection inherits from either of the abstract classesReadOnlyCollectionBase or CollectionBase. These classes implement IList, IListSource,and IEnumerable. The result is that you can create a collection that behaves like an array, canbe bound to controls such as a DataGrid, and can serialize the collection into an ADO.NETDataSet.

Figure 6-3 The Wizard Library project template as described by our VSDir entry in Listing 6-8

Page 212: Advanced c sharp programming

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 8 5

The TypedCollectionWizard automates creating a strongly typed collection based on theSystem.Collections.CollectionBase class; creating a new module, a class that inherits fromCollectionBase; and adding an indexer and an Add method to support serialization for WebServices. I will walk you through creating the TypedCollectionWizard, which demonstratesVisual Studio .NET extensibility. You will learn how to write macros and how to programagainst the Common Environment Object Model.

Writing a MacroThe steps for creating, registering, and testing wizards are the same as those described in“Creating a Custom Wizard.” The skills that will be helpful include writing and runningmacros, general programming in C#, creating Windows Forms user interfaces, registeringassemblies with regasm.exe, and writing a wizard launch file. For no particular reason, we’llstart with writing macros.

Part of the automation and extensibility model is the support for macros. The Macros IDEis a separate project-based development environment. Macros are written using Visual Basic.NET. You can record interaction with Visual Studio .NET or write macros from scratch.You start the Macros IDE from the Tools | Macros | Macros IDE menu item in Visual Studio.NET. You already know how to use Visual Studio .NET, and the Macros IDE is very similar.

Open the Macros IDE. From the View menu select Project Explorer. The Macros IDE isproject-centric, and macro projects are stored in files with a .vsmacro extension. However,you can import and export modules containing Visual Basic .NET code. To create a macro,select an existing or create a new module—source code file—and open it. Add a simplesubroutine. (Subroutine is the VB vernacular for a method.) Place the cursor in the texteditor on your subroutine and select Debug | Start from the Macros IDE menu. Listing 6-9demonstrates a HelloWorld macro.

Listing 6-9 A HelloWorld macro written in the Macros IDE in the Visual Basic .NET language

Sub HelloWorld()

MsgBox("Hello World!")

End Sub

As you might have guessed, this macro displays a message box and is equivalent to thestatic C# method invocation MessageBox.Show(“Hello World!”). All of the macros fromthis chapter are available for download in a file named Module1.vb.

Such macros are useful for introduction only. A more useful macro might be one that addsa new class to an existing project. This is similar in nature to what we will be doing with theTypedCollectionWizard.

Page 213: Advanced c sharp programming

The macro in Listing 6-10 adds a new class to the active project in Visual Studio .NET.The argument “Local Project Items\Class” is literally a path in the Add New Item dialog ifyou string the command—AddNewItem—to the folder Local Project Items and the itemClass. This might be obvious from Figure 6-4.

Listing 6-10 A macro that adds a new class to an existing project

Sub TestNewFile()

DTE.ItemOperations.AddNewItem( _

"Local Project Items\Class", "")

End Sub

The second argument to AddNewItem is the name of the file to add. If you leave this optionblank or pass an empty string, then a unique and appropriately named file will be created. If youwrite code equivalent to that in Listing 6-10 in C#, you will need to pass an empty string. Great.The big question is what is the DTE? I will take a moment to answer that next.

A Brief Introduction to the Common Environment Object ModelI think of the DTE as an object representing the design-time environment. In the help literature,the design-time environment is referred to as the Common Environment Object Model, theautomation model, or the extensibility model. A single, good name would be helpful. We’lljust use DTE when referring to Visual Studio .NET extensibility.

1 8 6 A d v a n c e d C # P r o g r a m m i n g

Figure 6-4 The AddNewItem method requires the logical path to the item you want to add.

Page 214: Advanced c sharp programming

The DTE is the root object that represents an object model that exposes the capabilitiesof Visual Studio .NET. (Visual SourceSafe has something similar, and so do many otherMicrosoft applications.) The DTE provides object-oriented accessibility to Visual Studio.NET using interfaces. This is commonly referred to as automation. You can get an idea ofhow extensive the Common Environment Object Model—the DTE—is by looking at thevisual reference accessible by following the help link ms-help://MS.VSCC/MS.MSDNVS/vsintro7/html/vxgrfautomationobjectmodelchart.htm in Visual Studio .Net. It is extensive.

Unfortunately, the DTE is so big that we would have a hard time scratching the surfacewithout writing another whole book. (If enough letters get sent to my publisher, then sucha book might get published.) Instead, we’ll use a target approach to solve our particularproblem and at the same time provide you with an introduction. The sections that followdemonstrate how to use the DTE from macros and C# code, which will provide you with areasonable start. A good conceptual vision of the DTE is to think of interfaces that act as afaçade that allows you to interact with Visual Studio .NET, including menus, documents,projects, macros, and the rest.

Writing Code with MacrosEarlier in the chapter, I demonstrated how to use project templates to effectively generatecode. The wizard engine essentially uses existing code as a template and fills in the part thatchanges, like class names. However, you can use the DTE to dynamically generate new codeon-the-fly. Writing code on-the-fly in a macro was a good way to test my approach to creatinga C# code generator for the TypedCollectionWizard. Listing 6-11 demonstrates the macro Iused to test the DTE’s ability to generate code.

Listing 6-11 Generating code using a macro

1: Public Sub TestWriteCode()

2: Try

3: Dim item As ProjectItem = _

4: DTE.ItemOperations.AddNewItem( _

5: "Local Project Items\Code File")

6:

7: Dim defaultNamespace = _

8: item.ContainingProject.Properties. _

9: Item("DefaultNamespace").Value

10:

11: Dim code As CodeNamespace = _

12: item.FileCodeModel.AddNamespace(defaultNamespace)

13:

14: Dim editPoint As EditPoint = _

15: code.StartPoint.CreateEditPoint

16:

17: editPoint.LineDown(2)

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 8 7

Page 215: Advanced c sharp programming

1 8 8 A d v a n c e d C # P r o g r a m m i n g

18:

19: editPoint.Insert(DateTime.Now.ToString() + vbCrLf)

20:

21: Catch e As Exception

22: MsgBox(e.Message, MsgBoxStyle.OKOnly, "Error")

23: End Try

24: End Sub

In summary, the code in Listing 6-11 creates a new C# code file, adds a namespace to it,and inserts some text into the namespace. Listing 6-12 provides an example of the outputfrom running the macro in Listing 6-11.

Lines 3 through 5 of Listing 6-11 create a new C# source code file that contains no codeand obtains a reference to the ProjectItem representing the file. (This is similar to the macroin Listing 6-10.) This source code file will be added to whatever project has the focus inVisual Studio .NET, but won’t create a project. Lines 7 through 10 determine what thedefault namespace is for the project containing our new file, and lines 11 and 12 create aformatted namespace element (see Listing 6-12). Notice that we retain a reference to theCodeNamespace object. Just as if we were writing code manually, everything is contextdependent. The objects returned represent the context.

Lines 14 and 15 of Listing 6-11 create an edit point at the start of the namespace. An editpoint can be used to insert code manually, or we can use other Add methods (explore theFileCodeModel for available methods). Line 17 moves the edit point down two lines, andline 19 inserts some text representing our code. The result is shown in Listing 6-12.

Listing 6-12 Code generated by the macro in Listing 6-11

namespace TestWizardOuput

{

5/9/2002 5:12:23 PM

}

Clearly, if we can create a file, add a namespace and other elements, and write text directly,then it is possible to write code in this manner. The TypedCollectionWizard uses a partialtemplate and the DTE to generate a complete strongly typed collection class.

Creating the Code GeneratorA code generator can be created by factoring out elements of code that never change and usingreplaceable string parameters for elements that will change. This is the approach used by thewizard engine that creates code from templates, and it is the approach used to generate the codefor the TypedCollectionWizard. Listing 6-13 contains the class that represents the template.

Page 216: Advanced c sharp programming

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 8 9

Listing 6-13 The Wizard class represents the template code for the wizard-created typed collection.

1: using System;

2:

3: namespace TypedCollectionWizard

4: {

5: /// <summary>

6: /// Summary description for Wizard.

7: /// </summary>

8: public class Wizard

9: {

10: /// <summary>

11: /// Parameter 0 is the class name; parameter 1 is the type name;

12: /// parameter 2 is the optional setter; parameter 3 is the comment.

13: /// </summary>

14: private const string template =

15: " // Code generator by [email protected]\r\n" +

16: " {3}\r\n" +

17: " public class {0} : System.Collections.CollectionBase\r\n" +

18: " {{\r\n" +

19: " public {1} this[int index]\r\n" +

20: " {{\r\n" +

21: " get{{ return ({1})InnerList[index]; }}\r\n" +

22: " {2}" +

23: " }}\r\n\r\n" +

24: " public int Add({1} value)\r\n" +

25: " {{\r\n" +

26: " return InnerList.Add(value);\r\n" +

27: " }}\r\n" +

28: " }}\r\n";

29:

30: private const string setter =

31: "set{ InnerList[index] = value; }\r\n";

32:

33: private string name;

34: private string typeName;

35: private bool hasGetter = true;

36: private bool hasSetter = true;

37: private string comment;

38:

Page 217: Advanced c sharp programming

1 9 0 A d v a n c e d C # P r o g r a m m i n g

39: public Wizard(string name, string typeName, bool hasGetter,

40: bool hasSetter, string comment)

41: {

42: this.name = name;

43: this.typeName = typeName;

44: this.hasGetter = hasGetter;

45: this.hasSetter = hasSetter;

46: this.comment = comment;

47: }

48:

49: private string GetSetter()

50: {

51: return hasSetter ? setter : "\r\n";

52: }

53:

54: private string GetCode()

55: {

56: return

57: string.Format(template, name, typeName,

58: GetSetter(), comment );

59: }

60:

61: public string GetTemplate

62: { get{ return template; } }

63:

64: public string Code

65: { get{ return GetCode(); }}

66:

67: }

68: }

The code for the wizard is easy to follow. I will summarize. Lines 14 through 28 ofListing 6-13 represent the template code using parameterized values for parts that willvary from class to class. The balance of the code accepts the parameters as arguments andsubstitutes those values for the parameters on request—that is, when GetCode is called.

More important is strategy. By using a class for the wizard, I can test the generator withoutcompleting the wizard, making sure that the generated code is accurate. TestWizardCode.csproj,available for download, does exactly that. Assuming the name of the typed collection isCustomers and it represents a collection of Customer objects, the wizard generates the classshown in Listing 6-14.

Page 218: Advanced c sharp programming

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 9 1

Listing 6-14 Wizard-generated code

public class Customers : System.Collections.CollectionBase

{

public Customer this[int index]

{

get{ return (Customer)InnerList[index]; }

set{ InnerList[index] = value; }

}

public int Add(Customer value)

{

return InnerList.Add(value);

}

}

Listing 6-14 is, in fact, syntactically accurate code. Assuming you have implemented aclass that describes Customer, then this code will compile and work correctly. The inheritedcode from the collection class allows us to bind typed collections to controls, enumerate theelements using the foreach construct, serialize Customers to a DataSet, and return Customersfrom Web Services. A pretty powerful combination and a common design pattern that thereader should become accustomed to.

The next step is to create a user interface that will allow the consumer to enter the valuesrepresenting the collection and collected object type.

Implementing the Wizard User InterfaceI shamelessly copied the Add Indexer Wizard from Visual Studio .NET. (In reality, I wantedthe TypedCollectionWizard to look as if it belonged with similar wizards.) The graphicaluser interface is shown in Figure 6-5.

The code for the user interface is available for download in a file named FormWizard.cs.I will tell you about a couple of strategies I employ to make working with dialog forms easier.

User Interface StrategiesIn general, I use property methods to access the value of controls. As a result, I refer to thedata rather than the control; if I change the control, then I only have to change the referenceto the control in one place. For example, here is the code representing the typed collectionname entered in the TextBox.

private string ClassName

{ get{ return textBoxClassName.Text; }}

Page 219: Advanced c sharp programming

1 9 2 A d v a n c e d C # P r o g r a m m i n g

If I changed the underlying control from a TextBox to something else, then I only haveto change GUI code that refers to the value of the TextBox in this one location.

Another strategy I employ is to validate field input at the user interface and in the classtoo. This is based on the concept G.I.G.O., or garbage in, garbage out, one of the firstacronyms I learned in computer science. I employed regular expressions to validate input.A class name must be a contiguous string of alphanumeric characters, beginning with analphabetic character. The following code confirms that this is the case:

private void DoValidateField(Control control, string value,

string expression, string errorText)

{

if( !Regex.IsMatch(value, expression))

{

control.Focus();

throw new Exception(errorText);

}

}

private void ValidateClassName()

{

DoValidateField(textBoxClassName,

ClassName, @"^\w$|^\w[A-Za-z0-9]+$",

"Valid class name is required");

}

Figure 6-5 The TypedCollectionWizard user interface

Page 220: Advanced c sharp programming

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 9 3

TIPRefactoring is a methodical approach to improving the design of existing code, named by William Opdikeand popularized when XP (Extreme Programming) was introduced.

The method ValidateClassName invokes a refactored method that accepts a control andthree string arguments. The third argument is a regular expression. If the Text content of thecontrol does not match the regular expression, then the control is given the focus and anexception is thrown. (Refer to Chapter 4 for more on regular expressions.)

Dialog Form StrategiesThe FormWizard is employed as a dialog form. A strategy I use for dialog forms is to createa static method named Execute. The static method accepts the inputs and returns the outputsfor the form. The form consumer does not need to worry about creating and destroyingthe form, only the inputs and outputs. The following short listing demonstrates how theFormWizard provides this service to a consumer.

public static bool Execute(ref string generatedCode)

{

using(Form1 form = new Form1())

{

if( form.ShowDialog() == DialogResult.OK)

{

generatedCode = form.Code;

return true;

}

else

return false;

}

}

The consumer ultimately will be the TypedCollectionWizard. The class that implementsIDTWizard simply needs to invoke this static method, evaluate the Boolean return value,and work with the generated code.

If you are writing a Windows Forms–based application, you can use the same approachas shown in the preceding fragment. The alternative is that consumers of your dialog formshave to construct the forms, evaluate the DialogResult, and concern themselves with specificproperties of the form. Essentially, you will have to duplicate the preceding code everywhere

Page 221: Advanced c sharp programming

you interact with the form, which will make it at least twice as long as the code needs to be.Here is how you show the form based on the static Execute method.

string generatedCode = string.Empty;

If(FormWizard.Execute(generatedCode))

// do something

The preceding is a very simple interaction between the form and the form consumer.

Implementing the WizardBecause we have implemented the generator as a class and the form with a simple Executemethod interaction, implementing the wizard is a snap.

If you will recall from the earlier section “Creating a Custom Wizard,” all we mustdo is implement the IDTWizard.Execute method. Additionally, our wizard must take thecode returned by the form and write it to a code file. Listing 6-15 demonstrates a meansof implementing the wizard.

Listing 6-15 Implementing the ITDWizard interface and completing the wizard

1: using System;

2: using System.IO;

3: using EnvDTE;

4: using System.Windows.Forms;

5:

6: namespace TypedCollectionWizard

7: {

8: /// <summary>

9: /// Summary description for Class1.

10: /// </summary>

11: public class CollectionWizard : IDTWizard

12: {

13: // Implements IDTWizard.Execute

14: public void Execute(object Application,

15: int hwndOwner, ref object[] ContextParams,

16: ref object[] CustomParams, ref wizardResult retval)

17: {

18:

19: string generatedCode = string.Empty;

20: if( FormWizard.Execute(ref generatedCode))

21: {

22: WriteCode((DTE)Application, generatedCode);

23: retval = EnvDTE.wizardResult.wizardResultSuccess;

24: }

1 9 4 A d v a n c e d C # P r o g r a m m i n g

Page 222: Advanced c sharp programming

25: else

26: {

27: retval = EnvDTE.wizardResult.wizardResultCancel;

28: }

29: }

30:

31: private void WriteCode(DTE dte, string generatedCode)

32: {

33: try

34: {

35: DoWriteCode(dte, generatedCode);

36: }

37: catch(Exception e)

38: {

39: MessageBox.Show(e.Message, "Error Writing Code",

40: MessageBoxButtons.OK, MessageBoxIcon.Error);

41: }

42: }

43:

44:

45: private void DoWriteCode(DTE dte, string generatedCode)

46: {

47: ProjectItem item = dte.ItemOperations.AddNewItem(

48: @"Local Project Items\Code File", "" );

49:

50: string defaultNamespace =

51: (string)item.ContainingProject.Properties.Item(

52: "DefaultNamespace").Value;

53:

54: CodeNamespace code =

55: item.FileCodeModel.AddNamespace(defaultNamespace, 0);

56:

57: EditPoint editPoint = code.StartPoint.CreateEditPoint();

58: editPoint.LineDown(2);

59: editPoint.Insert(generatedCode);

60: }

61: }

62: }

By now most of this code should look familiar. Lines 14 through 29 of Listing 6-15satisfy the IDTWizard contract. Instead of displaying “Hello Wizard,” we display the formand write the code if Execute returns True. WriteCode manages the error handling using a trycatch exception handler, and DoWriteCode generates the code file.

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 9 5

Page 223: Advanced c sharp programming

What may not be obvious is that the DTE object is passed as the first parameter to theExecute method; we cast the Application object parameter to a DTE object on line 22 ofListing 6-15 and use that DTE instance to create the source code file. DoWriteCode on lines 45through 60 is almost identical to the macro code in Listing 6-11 and the explanation thatfollows. Instead of writing the date and time, we write the generated code returned by theWizard class on line 59.

The automation model in Visual Studio .NET is powerful and vast. It is one of the aspectsof Visual Studio .NET that make .NET a compelling choice for software developers andsoftware development. Next, we need to test the wizard. We’ll start by creating a wizardlaunch file.

Completing the Sample Wizard Launch FileThe wizard launch file has a .vsz extension and is read when the wizard is loaded and run.The launch file for the TypedCollectionWizard is straightforward. We only have to provide aname for the wizard and indicate that there are no parameters expected. The following text isadded to a file named TypedCollectionWizard.vsz and copied to the same folder containingthe TypedCollectionWizard.dll.

VSWIZARD 7.0

Wizard=TypedCollectionWizard.CollectionWizard

Param=<Nothing>

The wizard engine does not require that you have the .vsz file in the same folder as the wizardDLL. However placing the wizard and the launch file in the same directory works easily.

The first statement is required and must be added as is. The second statement describesthe assembly and the class in that assembly that contains the IDTWizard implementation.Subsequent statements are used to describe parameters that are passed to the wizard. Ourwizard takes no parameters, which is what the Param statement in the launch file indicates.The next listing is the C# indexer wizard launch file, which works with the wizard engine.This file shows two parameter arguments.

VSWIZARD 7.0

Wizard=VsWizard.VsWizardEngine

Param="WIZARD_NAME = CSharpIndexerWiz"

Param="PROJECT_TYPE = CSPROJ"

You can pass whatever parameters your wizard needs. The style demonstrated describes valuesfor the names WIZARD_NAME and PROJECT_TYPE.

Registering WizardsAssemblies that are not added to the global assembly cache—the GAC, pronounced gak—but need registry information can be registered with the regasm utility that ships with .NET.To register the TypedCollectionWizard, enter the following command:

1 9 6 A d v a n c e d C # P r o g r a m m i n g

Page 224: Advanced c sharp programming

c:\winnt\Microsoft.NET\v1.0.3705\regasm

c:\temp\TypedCollectionWizard.dll /codebase

The path described is the default path for the .NET framework, specifically for version1.0.3705. The directory c:\temp assumes that you are registering the wizard from the c:\tempdirectory. If you are using another directory, then substitute that directory in place of c:\temp.The /codebase switch stores the complete path using the file:// moniker in the registry. Thewizard is not a COM component; it is the Visual Studio .NET application that is using theregistry information to load the assembly. (Refer to “Using the regasm Utility” at the end ofthis chapter for more on regasm.)

I mentioned the global assembly cache in the beginning of this section. The GAC is amachine-wide cache for assemblies that are shared by many applications. Wizards are usedonly by Visual Studio .NET; hence, we don’t need to add wizards to the GAC. The GAC isliterally a folder-like repository that uses assembly metadata to ascertain differences betweenassemblies, thereby helping to avoid conflicts that commonly occurred with COM. You canlearn more about the GAC from the Visual Studio .NET help documentation. .NET shipswith GACutil.exe, a utility for adding assemblies to the GAC.

Testing the Wizard with a MacroHaving created a test application for the wizard user interface and wizard class, createdthe wizard, and created the launch file, we can use the macro from Listing 6-2 to test thewizard. After the const Path variable is replaced with the path information for theTypedCollectionWizard.vsz wizard launch file, the wizard can be run using a macro.

Visual Studio .NET supports adding a menu item or some other metaphor to VisualStudio .NET. You can create an Add-In that implements the IDTExtensibility2 interfaceand combine the IDTWizard interface to create a menu item in Visual Studio .NET. Referto the “Creating an Add-In” help topic in Visual Studio .NET for more on Add-Ins andIDTExtensibility2. The IDTCommandTarget interface will let you run the wizard fromthe command window as an alternative option.

Running the Wizard from the Command WindowNow that we have a macro that launches our wizard, we can run the TypedCollectionWizardfrom the command window. Select View | Other Windows | Command Window to open thecommand window. If you created the test macro—borrowing the code from Listing 6-2—then you should see a list of macros as soon as you type M in the command window. I createdmy test macro in the MyMacros project, in a module named Module1, and named the subroutineTestCollectionWizard. Referring to the Macros object, the whole command becomes

Macros.MyMacros.Module1.TestCollectionWizard

Intellisense will provide a list of available commands, as shown in Figure 6-6, as I begin typingthe word Macros.

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 9 7

Page 225: Advanced c sharp programming

TIPYou can use the command window to access the automation model in Visual Studio .NET and initiatecommands directly. For example, type File.NewFile in the command window to open the New File dialog.

Secondary TopicsAs you might have determined by now, there are several languages and utilities that professionalprogrammers will have to become familiar with to make the most of .NET. This chapter introducedgacutil.exe, regasm.exe, Jscript.NET, macros, and Visual Basic .NET. We can’t learn themall here. I learned how to use them over a dozen or so years of reading and experimenting.However, I can introduce subjects and point you in the right direction to help you pursuefurther interests (and sell more books for my publisher). To wrap up this chapter, I want tobriefly return to Jscript .NET and the regasm.exe utility.

Returning to Jscript .NETI briefly mentioned Jscript in the beginning of this chapter. Jscript plays a secondary rolein the implementation of template projects. There is an external compiler that can be usedto compile Jscript .NET code. However, Jscript is a complete language like C# and VisualBasic .NET. There is a tremendous amount of information on Jscript in Visual Studio .NET,and you can use the command-line compiler jsc.exe to compile Jscript code. Refer to thewww.osborne.com and www.amazon.com websites for books on Jscript if you are interested.

Places you are likely to encounter script (besides when creating template projects) areclient-side code for Web pages and ACT. ACT, or Microsoft Application Center Test, isan application that ships with the enterprise version of Visual Studio .NET. ACT is used toautomate testing for Web client applications. You can write custom script or modify recordedscript in ACT to test your Web pages.

Follow the numbered steps to write and compile a Jscript .NET application. (Jscript .NETapplications must be compiled from a command prompt.)

1 9 8 A d v a n c e d C # P r o g r a m m i n g

Figure 6-6 Intellisense shows the list of available commands in the Command Window.

Page 226: Advanced c sharp programming

1. Open Notepad.

2. Type print(“Hello World!”).

3. Save the text as Hello.js, keeping track of where you save the Hello.js file, and closeNotepad.

4. Open the command prompt (for old-timers, a DOS window).

5. At the command prompt, type c:\winnt\Microsoft.NET\Framework\V1.0.3705\jsc Hello.js, including the path to Hello.js, if necessary

The jsc.exe compiler will convert the Hello.js source into an executable assembly Hello.exe.Enter Hello to run the Hello.exe application. The output should simply be the text “Hello World!”

Using the regasm UtilityWizards are DLL assemblies that are integrated into Visual Studio .NET using COM. Themost reliable way to register a wizard is to use the regasm.exe utility that ships with the .NETframework. The two forms of regasm.exe that seem to work the best are

regasm assemblyname /codebase

and

regasm assemblyname /unregister

The regasm part of the statement refers to the utility. The italicized assemblyname is the DLLassembly containing the wizard, and the /codebase switch adds the code base to the registry.Technically, the /codebase switch equates to the complete path information.

The second statement unregisters the wizard assembly. Before you modify and recompilea wizard assembly, you will need to run the second statement, passing the name of yourwizard in place of the assemblyname value.

The wizard itself is not using COM. Visual Studio .NET extensibility relies on COM.Registering the wizard assembly allows Visual Studio .NET to load it. When you registeran assembly with /codebase, you will get a warning indicating that you should add a strongname to the assembly. This will help avoid version problems. You can use the strong nameutility—sn.exe—that ships with .NET to add a strong name to your assembly. This is a goodidea if you are going to share the wizard.

To view the entries that will be written to the registry, run regasm with the /regfile switchas follows:

regasm assemblyname /regfile:regfile

Replace assemblyname with the name of your assembly and the second instance of regfilewith the name of a file with a .reg extension. If you generate a registry file, then the entriesare not written to the registry. If you experiment with and without the /codebase switch, youwill see that the file:// moniker and path information are used when you specify the /codebaseswitch. Without the /codebase switch, Visual Studio cannot find the wizard.

C h a p t e r 6 : C u s t o m i z i n g V i s u a l S t u d i o . N E T 1 9 9

Page 227: Advanced c sharp programming

SummarySo many great technologies and so little time. Just ten short years ago I recall writing myown interrupt handler for the hard error interrupt 0x24. This system error occurs when, forexample, a user takes a floppy disk out of the drive while the disk is being read from orwritten to. Exception handling has supplanted writing interrupt handlers as a way to catchserious errors.

Technology is growing so rapidly that professional programmers will ultimately have tospecialize. Like plumbers, electricians, roofers, carpenters, architects, general contractors,sheet rock hangers, roofers (and whatever other jobs there are for home builders), programmerswill have to specialize too. In this one chapter we touched on three programming languages—Visual Basic .NET, Jscript, and C#—and a couple of utility programs—GACutil.exe andregasm.exe. Just as with other industries that have been around a while, our industry willlead to specialization. We are still dominated by a programmer-centric world, but specialtieshave been identified, and the complexity of software tools and the demands of customerswill ultimately require our industry to train specialists. I specialize in object-orientedarchitecture using UML and OOP languages.

Interestingly enough, the need for specialists was well documented in Adam Smith’s “TheWealth of Nations.” Smith simply describes how specializing roles for pin makers—littlepieces of wire with a point, used by tailors—allowed pin manufacturers to produce thousandsof pins per day instead of hundreds. Smith’s description is probably a major impetus behindthe modern assembly line. Software tool complexity will ultimately force specialization andperhaps a form of assembly-line software development. Specialization is long overdue, but,fortunately, assembly-line software development is still a long way off.

2 0 0 A d v a n c e d C # P r o g r a m m i n g

Page 228: Advanced c sharp programming

CHAPTER

7Event Logger

201

IN THIS CHAPTER:

Demonstrated Topics

Exploring System.Diagnostics Namespace

Using the EventLog Component

Tracing as a Debugging Strategy

Dumping the Stack

Managing Debug Code Automatically

Implementing the Logger

Secondary Topics

Creating Dialog Boxes

FileVersion Information

Using the Debug Class

Measuring Performance

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 229: Advanced c sharp programming

Planning how you will test and debug your code is almost as important as how andwhat you write. A good UML model and design without an equally good testing-and-debugging strategy is like Batman without Robin. “Dynamic Solo” does not

have the same ring as “Dynamic Duo.”When you are building applications you will need unit testing, white box and black box

testing, a pinch of automated testing, and a good quality-assurance process. The first step isto adopt some basic strategies that will ensure your code will be great before it leaves yourworkstation. When a programmer tests his own code, this is referred to as unit testing. Byincorporating test code into your production code, you can reduce the amount of time youactually spend testing because your code will automatically tell you when something is wrong.Including the ability to turn your test code off and on with a compiler switch is the hallmarkof professionalism. Making it easy to incorporate self-diagnosing or tracking capabilities foryou and others is how programming legends are born.

One of the first, best books I read on debugging and testing strategies for the C programminglanguage is Dave Thielen’s No Bugs! (Addison-Wesley, 1992). Thielen’s strategies wereinstrumental to Microsoft in shipping MS-DOS 5.0. (DOS 5.0 is ancient history now.) Manyof the techniques introduced by Thielen and others, like Gerald Weinberg, are still prevalent.Just a couple of years ago programmers had to contrive several good techniques because suitableones weren’t readily available. We no longer have to build debugging and testing code fromscratch. The Common Language Runtime introduces a battalion of proofing capabilities inthe Systems.Diagnostics namespace. When moderately employed, you can wipe out bugs.If you diligently and consistently employ some basic strategies, the bugs don’t have muchof a chance.

This chapter demonstrates several of the capabilities directly supported by the .NETFramework that will help you minimize the number of bugs that make it off your workstation.

Demonstrated TopicsThere are many facets to the .NET Framework. .NET represents a phenomenal commitmentby Microsoft to make a coherent and powerful framework for all of their development tools.If you explore any part of .NET you will quickly find that you could explore it for days. TheSystem.Diagnostics namespace is one of these areas, and I encourage you to spend a lot oftime in it.

This chapter begins by showing you some basic capabilities that you may be familiar within concept, but that may be new to you in implementation. We will progress to capabilitiesthat are brand new in .NET.

From the examples in this chapter you will be able to build better applications by mastering

� Using the EventLog component

� Logging to a remote computer

� Employing tracing as a debugging strategy

� Dumping and viewing the stack frame

2 0 2 A d v a n c e d C # P r o g r a m m i n g

Page 230: Advanced c sharp programming

� Managing debug code automatically using the BooleanSwitch andConditionalAttribute

� Eliciting and responding to user input using custom dialogs

� Accessing FileVersion information

� Writing debug information to the Output window and asserting program invariants

� And, using the PerformanceCounter to keep track of your applications’ use of resources

Exploring System.Diagnostics NamespaceYou can easily find all of the classes, namespaces, and members of the System.Diagnosticsnamespace, so I won’t list them here. (We would need another 500 pages if we started goingdown that road.) What I will tell you is that the Diagnostics namespace contains many moreclasses than the Debug class.

The System.Diagnostics namespace contains a complement of classes that allow you toattack risks to application efficacy on a broad scale. Like the Debug class, the Diagnosticsnamespace contains classes for tracing code behind the scenes and writing to the event log;debug attributes; switches that read from external configuration files and allow you to turncode on and off without rebuilding your application; and an army of performance counters.(The number and variety of performance counters alone could fill a book. Look at theperformance counters in the Server Explorer for evidence.)

I will use specific capabilities of the Diagnostics namespace that allow you to provide asafety net for both Windows and Web applications. I will demonstrate how the Debug classwrites information about what your program is doing behind the scenes, and how it ensuresthat basic assumptions about the state of a program are inviolate. We will use the BooleanSwitchclass to read external configuration information that will turn our debug code off and on withoutrebuilding our application. We will use the ConditionalAttribute to let the compiler turncode off and on at compile time by automatically stripping out capabilities before emittingIntermediate Language (IL). We will review the PerfMon.sln that demonstrates how to usePerformanceCounters to track any application’s use of resources. And, we will use theEventLog to provide a convenient place to store information about our application in acustom event log while building, as well as after deploying, applications.

Using the EventLog ComponentThe Components tab of the Toolbox contains several useful components. A particularly usefulone is the EventLog component. The EventLog component can be dragged from the Toolboxinto the Component Tray. By modifying the properties of the EventLog component you canquickly specify the name of the log you want to write to, the Source that log entries will beentered under, and the computer containing the log you will write to. The default log namesare Application, System, and Security. Generally you will write log entries to a custom log orthe Application log. If you enter a unique name in the EventLog.Log property, you can createa custom log file. (You can find the physical log files on your computer by searching for files

C h a p t e r 7 : E v e n t L o g g e r 2 0 3

Page 231: Advanced c sharp programming

with a .evt file extension. Generally, log files are found in the c:\winnt\system32\config directory.)Use the custom log if you will be writing a lot of entries to the log during testing. When youdeploy your application, write to the Application log listed under a Source that identifiesyour application.

NOTELogs and Sources are listed in the registry under the key HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Eventlog. You can modify keys directly, but if EventLog keys are incorrectly modified then someapplications, likely important ones, may stop working. If you are going to modify the registry, it’s a goodidea to export a complete copy of the registry before you modify it.

The Source is a unique name that identifies the source of the log entries. When youspecify the Source name, it must be unique across all logs. This means that if you specifya Source named “Demo” in the Application log, you may not define a Demo Source in anyother log. If you want to use a Source in a new log, you will need to delete the Source froman existing log.

Writing to the EventLogWhen you are building and testing your application, create a custom log and write as muchinformation as possible to ensure that you are able to diagnose problems that might ariselater. When you deploy your application, you will want to be more selective, logging onlycritical information. Remember, event logs are stored in files on your hard drive and bothdisks and files fill up.

TIPThe EventLog class has several static WriteEntry methods. It is possible to write text to a log by passing theSource and message to one of the static EventLog.WriteEntry methods without an instance of an EventLogcomponent.

Whether you are writing to a custom log during development or to the application logafter deployment, the process of writing to the log is identical. Set the Log, Source, andMachineName and invoke one of the overloaded WriteEntry methods. At a minimum, youcan simply pass a string message containing the text you want to log. Supposing we have anEventLog component named eventLog1, we could write an entry to the event log with thefollowing statement:

eventLog1.WriteEntry("test");

Assuming the component eventLog1.Source is set to the Application source, the precedingstatement would create an event log entry that is very similar to the one shown in Figure 7-1.(Results will vary slightly according to the operating system, date and time, computer name,and other factors particular to the computer you are writing event log entries to.)

2 0 4 A d v a n c e d C # P r o g r a m m i n g

Page 232: Advanced c sharp programming

Handling EntryWritten EventsThe EventLog component defines one event, EntryWritten. The EntryWritten event handlerallows your program to respond to events written by your application or other applications aslong as the event log entry is written on the local host. Events written to a remote computer willnot be received by the EntryWritten event. This feature of the EventLog component supportswriting a utility application that monitors the behavior of other applications.

The EntryWritten event is a delegate that receives two arguments: an object and aSystem.Diagnostics.EntryWrittenEventArgs. The EntryWrittenEventArgs argument containsan Entry property. The EntryWrittenEventArgs.Entry property is an EventLogEntry objectthat contains the information that describes the event. The EventLogEntry class containsinformation about the time an event was generated and written, the event message, the eventsource, and the machine name. You can use this information to monitor the health of anapplication.

C h a p t e r 7 : E v e n t L o g g e r 2 0 5

Figure 7-1 An EventLog EntryWritten, specifying only the message “test”.

Page 233: Advanced c sharp programming

Responding to Event EntriesTo respond to EntryWritten events, set the EventLog.EnableRaisingEvents property to Trueand implement an EntryWritten event handler. Either or both of these tasks can be doneprogrammatically or by using the Properties window.

The DebugDemo.sln example application provided on the McGraw-Hill/Osborne web site,www.osborne.com, demonstrates an EntryWritten event handler. If you run a second instanceof the DebugDemo.exe application you will see that both instances of the application areresponding to event log entries even though you may be interacting with a single instance.The following event handler demonstrates an example of writing the information being sentto the event log to a RichTextBox control.

private void eventLog1_EntryWritten(object sender,

System.Diagnostics.EntryWrittenEventArgs e)

{

richTextBox1.AppendText(e.Entry.Message + "\n");

}

The string Message content and a new-line character are appended to the RichTextBoxtext value.

Frequency of EntryWritten EventsThe EntryWritten event receives events that occurred at least five seconds ago. This meansthat you will receive only one EntryWritten event per five-second interval. If you invokeWriteEntry more frequently than every five seconds you may not be notified of recent,written events.

It is my experience that you may receive several entries written at very close intervals, butnot all of them. For example, suppose you write a for loop that writes 100 entries in succession.You are likely to get only a dozen or so the first time the loop executes and get the rest of theevents written in the first loop the next time the log is written to.

TIPTo run the EventViewer on a Windows XP machine, open the Control Panel, click to open the AdministrativeTools applet, and double-click the Event Viewer shortcut. You can also run an instance of the MicrosoftManagement Console (mmc.exe) and snap in the Event Viewer.

Of course you can use the EntryWritten handler for early notification of a problem withyour system and query the event log for detailed information. You can use the EventViewerto find out more about the behavior of applications or use the EventLog component to writea custom querying tool.

Logging Events to a Remote MachineThe ability to log events to a remote machine can be an excellent way to look over theshoulder of a mission-critical application.

2 0 6 A d v a n c e d C # P r o g r a m m i n g

Page 234: Advanced c sharp programming

Suppose you are in a skunk works project—like a recent project I was working on in Oregon.You develop an application and hand it off to a testing group after you diligently unit-test yourefforts. The testing group deploys the application in the test lab and begins running theapplication using automated tests, as well as letting users occasionally tinker with the betaproduct. By implementing the application using the EventLog component and remote machinelogging, you can look in on your application from time to time without going to the test lab.If something does go wrong, you will appear clairvoyant when the testers call. More important,you will have an accurate picture of what was going on because this picture will have beenillustrated by the entries written to the log on your machine.

By default, when you call EventLog.WriteEntry the entry is written to the local machine.The local machine is the default value, represented by a period (.) in the EventLog.MachineNameproperty. To write to a specific machine, modify the EventLog.MachineName property to thename of the logging device; for example, FORD-4PBWU would instruct the event logcontrol to write entries to the machine named FORD-4PBWU.

Tracing as a Debugging StrategyFor as long as I can remember, programmers have been using functions like printf (in C) towrite debug information to a console to track what is going on internally in an application.This strategy is still an excellent technique to use to keep track of the internal operation of yourapplication (kind of like cutting a portal in a cow’s stomach to peek in on its digestive process).

With the wide adoption of GUI interfaces and Web applications it is often no longerconvenient to write output to a console. The general strategy of tracing the internal digestiveprocess of your application is still valid. Fortunately, the tools that support tracing the internaloperation of your application have improved to keep pace with the kinds of applications weare building.

To this end, .NET has implemented a Trace class in the System.Diagnostics namespace.The Trace class writes output to a TraceListener, the Output window in Visual Studio .NETby default. You may also create an instance of other classes that inherit the abstract TraceListenerclass and instruct the Trace class to send traced messages to those listeners. (This is an excellentexample of object-oriented techniques: the capability to Trace is separated from the recipientof the TraceListener.)

TIPWrite Trace information when you write your production code. This is when you will know the most about theexhibited behavior you are interested in.

One of the convenient places you can send Trace information is the EventLog. TheEventLogTraceListener is subclassed from TraceListener and facilitates sending Traceinformation to an event log. Instead of contriving a mechanism for writing trace details, youcan combine event logs with the tracing capability to follow your applications execution pathdown to the minutest detail.

C h a p t e r 7 : E v e n t L o g g e r 2 0 7

Page 235: Advanced c sharp programming

Tracing to the Event LogTracing is the equivalent of writing a print statement that tells you something about yourapplication’s innards while the program is running. You can use a custom event log if youwant to keep a record of trace information.

To use a custom event log to capture a record of trace information you will need anEventLog object, an EventLogTraceListener initialized with the EventLog object, andstatements invoking the Trace.WriteLine method, for example.

Creating an EventLogTraceListenerThe EventLogTraceListener interacts with an EventLog object to send trace information toan event log. To create an instance of an EventLogTraceListener you can write a statementlike the following:

System.Diagnostics.EventLogTraceListener listener = new

System.Diagnostics.EventLogTraceListener(eventLog1);

The preceding example assumes the existence of an EventLog component named eventLog1,and that, at a minimum, the EventLog.Source property has been set. Assuming you includethe Systems.Diagnostics namespace in a using statement, you could shorten the precedingcode to simply include the class name, as demonstrated next.

Using System.Diagnostics;

...

EventLogTraceListener listener = new EventLogTraceListener(eventLog1);

Registering Trace ListenersThe EventLogTraceListener is defined in the Systems.Diagnostics namespace. If you want totrace to an EventLog, the following code example demonstrates how to set up this relationship.(You can find the example code in the DebugDemo.sln sample application provided withthis book.)

private void MenuForm_Load(object sender, System.EventArgs e)

{

eventLog1.Source = "Trace";

Trace.Listeners.Add( new EventLogTraceListener(eventLog1));

Trace.WriteLine("Test!");

}

The preceding code assumes there is an EventLog component named eventLog1. Thecomponent only has the default information defined when you drag it from the Componentstab of the Toolbox—that is, there is no Source or Log name, and logging will be done to thelocal machine. You must establish the name of a Source. If the Source does not exist, thenthe component will create it for you. The Trace class has a static property called Listeners.Listeners is a static read-only property whose type is defined as a TraceListenerCollection.To make eventLog1 a listener for the Trace class we can pass a new instance of an

2 0 8 A d v a n c e d C # P r o g r a m m i n g

Page 236: Advanced c sharp programming

EventLogTraceListener initialized with eventLog1 as demonstrated in the preceding codelisting. The last statement demonstrates a Trace statement that writes the text “Test!” to theOutput window and the application event log.

Dumping the StackTwo more classes defined in the System.Diagnostics namespace are the StackFrame andStackTrace classes. These classes are used for a variety of purposes that include diagnosingyour application. For example, an instance of the StackTrace class is defined as part of anException.

Using the StackTrace as a Diagnostic ToolYou may want to Trace and write unexpected exceptions to a custom event log. For furtherdiagnosis, it might be helpful to figure out the order of processing that actually occurredin the event that something goes wrong. The Exception.StackTrace object can help youaccomplish this.

To refresh your memory: When your program is running, the address of each method invokedis pushed onto the stack memory. When the method returns, the information is pulled fromthe stack memory. In this manner, the stack winds and unwinds perhaps millions of timeswhen your application is running. At any point there are some number of frames on the stackthat indicate the order of method invocation. You can use this information to diagnose yourapplication.

The DebugDemo.sln contains a menu item Tools | Stack Trace that intentionally winds upthe stack by forcing an infinite recursion—although we know it’s there, so we catch the recursionbefore it overflows the stack frame—and catching the recursion by showing the stack trace(see Listing 7-1).

Listing 7-1 This code simulates an infinite loop scenario and dumps the stack trace onan exception.

private int i = 0;

private void StackWinder()

{

i += 1;

if( i > 50 ) throw(new Exception("StackWinder"));

StackWinder(); // intentionally wind up the stack!

}

private void menuItem16_Click(object sender, System.EventArgs e)

{

try

C h a p t e r 7 : E v e n t L o g g e r 2 0 9

Page 237: Advanced c sharp programming

{

StackWinder();

}

catch(Exception x)

{

richTextBox1.Clear();

richTextBox1.AppendText(x.StackTrace.ToString());

}

}

When the Tools | Stack Trace menu item is clicked in DebugDemo.sln, the event handlerin Listing 7-1 is invoked. The handler calls StackWinder, which intentionally winds up thestack by invoking itself 50 times. After the fiftieth call to StackWinder an exception is thrown,as shown in Figure 7-2. The catch block in the menu item click handler catches the exceptioninto the object “x” and writes the stack trace to the RichTextBox. (This final step simulatestracing the stack trace.)

2 1 0 A d v a n c e d C # P r o g r a m m i n g

Figure 7-2 The StackWinder StackTrace after the exception is thrown

Page 238: Advanced c sharp programming

Using the StackFrame as a Diagnostic ToolThe following code fragment demonstrates how to create an instance of a StackFrame objectfor the current file and displays the file, line, and column information. (This output is consistentwith each line of information found in a StackTrace.)

StackFrame f = new StackFrame(true);

richTextBox1.AppendText(f.ToString () + "\n");

You can view the text contents of a StackFrame object to see the exact order of your code’sexecution.

Managing Debug Code AutomaticallyTracing and diagnostic capabilities would be a pain in the neck if you had to write thediagnostic code, delete it before you deployed your application, and then rewrite it when yourapplication goes into maintenance mode. For years, most programmers have been familiarwith conditional preprocessor directives.

For example, wrap a conditional directive around code. If the directive evaluates to false,the code is removed by the compiler. C# implements preprocessor directives; thus the followingdirective would only allow the StackFrame example from the preceding section if thepreprocessor conditional constant were defined.

#if DEBUG

StackFrame f = new StackFrame(true);

richTextBox1.AppendText(f.ToString () + "\n");

#endif

If the constant DEBUG is defined, the preprocessor includes the code. If not, then the code isexcluded. Visual Studio .NET provides you with a hint. If the code will be included then thecode will be in color. If the compiler directive evaluates to False, then the code is grayed out.(The fact that this coloration occurs while you are coding suggests that the preprocessor isrunning while you are coding. Of course, the truth is that the VS.NET IDE is formatting codeand providing Intellisense information while you are coding.)

The Visual Studio .NET IDE provides improvements over conditional compiler directives.The drawback with compiler directives is that you have to put them everywhere you want toconditionally chop out code, and you will have to recompile each time you change the valueof the conditional constant. .NET supports an improved form of conditional compilation andswitches, maintained in an external XML file. Using the switch class in an XML file meansthat you are able to turn debugging code off or on without recompiling your source code.We’ll explore conditional compilation and XML switches in this section.

C h a p t e r 7 : E v e n t L o g g e r 2 1 1

Page 239: Advanced c sharp programming

Conditional CompilationConditional compilation is managed by compiler directives. A compiler directive is a pragmafollowed by a value that is evaluated as a Boolean. For example, #if is the preprocessorconditional if statement. If token after the #if pragma evaluates to true, then the compilerincludes the code between the #if and the #endif. If the conditional compiler variable evaluatesto false, then the code is not included in the output assembly. The following code demonstrateslittered conditional code.

#if DEBUG

EventLog.WriteEntry("conditionally written debug information");

#endif

// ... Some more code

#if DEBUG

EventLog.WriteEntry("some more conditionally written debug information");

#endif

TIPConditional compiler directives are case sensitive in C#. For example, DEBUG and debug represent twoseparate constants.

By wrapping the EventLog.WriteEntry statement inside a method, you can reduce thenumber of occurrences of preprocessor directives to one for each conditionally compiledblock of code. Here is the revision.

Private void DebugWrite(string message)

{

#if DEBUG

EventLog.WriteEntry(message);

#endif

}

DebugWrite("conditionally written debug information");

// ... Some more code

DebugWrite("some more conditionally written debug information");

Now you see that the conditional code exists in just one place: the wrapper function. Althoughthis is a better solution, there is still a problem. Can you infer the deficiency?

Even when you deploy your application and undefine the DEBUG compiler constant, allof those calls to DebugWrite still occur. DebugWrite is now an empty procedure, but it stillexists in the application. Microsoft has invented a better way of managing conditional codeusing attributes.

The ConditionalAttribute class will evaluate a constant. If the constant exists, the procedureremains. If the constant is undefined, the procedure is stripped from the IL and every invocationof the procedure is automatically stripped from the IL.

The ConditionalAttribute applies to methods. You precede each method with theConditionalAttribute that should conditionally be emitted to IL and construct the attribute

2 1 2 A d v a n c e d C # P r o g r a m m i n g

Page 240: Advanced c sharp programming

with a case-sensitive string to evaluate. If the string is defined then all invocationsof the conditional method are emitted to IL. Undefine the string used to initialize theConditionalAttribute and the invocations and the conditional methods are automaticallyremoved from the assembly. Listing 7-2 contains an example that demonstrates theConditionalAttribute. Figures 7-3 and 7-4 show the emitted IL before and after the conditionaldirective has been undefined.

Listing 7-2 Using the ConditionalAttribute

using System;

namespace ConditionalDemo

{

class Class1

{

[STAThread]

static void Main(string[] args)

{

HelloWorld();

}

[System.Diagnostics.Conditional("DEBUG")]

static void HelloWorld()

{

Console.WriteLine("Hello World");

Console.ReadLine();

}

}

}

You can clearly see the HelloWorld method and its invocation in the Main method in the ILdisassembly. When you deploy an application, you change the Configuration Manager to reflectthat you are releasing your code. This is accomplished by changing the Active SolutionConfiguration from Debug to Release in the Configuration Manager. One effect of changingthe Active Solution Configuration is that the compiler conditional DEBUG is not defined in theRelease configuration. Figure 7-4 shows the emitted IL when DEBUG is no longer defined.

TIPCLS-compliant languages are allowed to ignore the ConditionalAttribute.

What’s important here is that you can write all of the testing code you want withoutworrying that your assembly will suffer from bloat or method fatigue. Calls to conditionalmethods are removed by the compiler.

C h a p t e r 7 : E v e n t L o g g e r 2 1 3

Page 241: Advanced c sharp programming

2 1 4 A d v a n c e d C # P r o g r a m m i n g

Figure 7-3 The IL as shown in ildasm.exe (an IL disassembler utility), showing the IL in theConditionalDemo.exe

Figure 7-4 The IL as shown in ildasme.exe for ConditionalDemo.exe when DEBUG is not defined

Page 242: Advanced c sharp programming

C h a p t e r 7 : E v e n t L o g g e r 2 1 5

Using SwitchesThe ConditionalAttribute is an improvement over plain preprocessor directives, but you stillhave to recompile your application to include or exclude conditional methods. There isa third choice, the BooleanSwitch.

A BooleanSwitch ExampleThe DebugDemo.exe sample application demonstrates using a BooleanSwitch to determinewhether an EventLogTraceListener is created or not. Here is the code.

static BooleanSwitch booleanSwitch =

new BooleanSwitch("MySwitch", "Test Switch");

private void MenuForm_Load(object sender, System.EventArgs e)

{

if( booleanSwitch.Enabled)

{

eventLog1.Source = "Trace";

Trace.Listeners.Add( new EventLogTraceListener(eventLog1));

Trace.WriteLine("Test!");

}

}

The first statement defines a static BooleanSwitch object, initializing it with the switch MySwitch.The BooleanSwitch in the example will look for a switch named MySwitch defined in the.config file associated with this assembly.

The Load event handler evaluates the BooleanSwitch. If the XML .config file definesa non-zero value for the switch, then BooleanSwitch.Enabled is True and the code in theexample will run. The name of the switch is not case-sensitive. Any non-zero value forthe switch will be treated as a True value.

Defining the BooleanSwitch in the .config FileYou can define a .config file for every assembly. The configuration file is named the same as theassembly with a .config tacked onto the end of the file name. For example, our DebugDemo.exeapplication will evaluate a DebugDemo.exe.config file in the same folder as the assembly.Listing 7-3 shows the .config file that describes the BooleanSwitch MySwitch.

Listing 7-3 Describing a BooleanSwitch in an XML .config file

<configuration>

<system.diagnostics>

<switches>

Page 243: Advanced c sharp programming

2 1 6 A d v a n c e d C # P r o g r a m m i n g

<add name="MySwitch" value="0" />

</switches>

</system.diagnostics>

</configuration>

Similar to HTML, XML uses tag pairs. The opening tag is defined inside of a greater-thanand less-than (<>) pair of tokens, and the closing tags include a forward whack </>. The<configuration></configuration> tags define the content of the configuration file. Configurationfiles can contain only one pair of configuration tags.

Implementing the LoggerOf course you can create instances of EventLog objects or use the static methods any timeyou need event logging. However, speed and robustness are achieved by building collectivelyeasier and more useful chunks of code. For instance, we can wrap up the EventLog class andmake a utility that is even easier to use than creating instances and defining sources. We willcreate the TestLog class library that has an easy-to-use interface and automatically createsand manages event sources. We can plug this class library into any application for which wewant to create a custom log during testing.

NOTEThere is another motivation for wrapping up classes and components that are part of a specific framework.By putting a wrapper, or façade, around an existing component you can make it easier to use or exchangethe code that implements the façade without adversely affecting clients. Façade is the name of a patternthat supports the concept of simplifying interfaces to classes.

The complete listing for the Logger class defined in the TestLog class library is providedin Listing 7-4. The code adds a façade to an EventLog object. Because the Logger class doesnot completely conceal the underlying EventLog object, it might be difficult to exchange theunderlying implementation—that is, the EventLog. The EventLog is easier to use, though.

Listing 7-4 The Logger class shown uses a façade to make using an EventLog easier.

1: using System;

2: using System.Diagnostics;

3:

4: namespace TestLog

5: {

6: public class Logger

7: {

8: #region Private Members

9: private static EventLog log = null;

Page 244: Advanced c sharp programming

C h a p t e r 7 : E v e n t L o g g e r 2 1 7

10: private static EventLogTraceListener listener = null;

11: private static string source = "";

12: private static string logName = "";

13: private static string machineName = "";

14:

15: private Logger(string source, string logName, string machineName)

16: {

17: Logger.source = source;

18: Logger.logName = logName;

19: Logger.machineName = machineName;

20: Initialize();

21: }

22:

23: private void Initialize()

24: {

25: Check();

26: CheckListener();

27: log = new EventLog(logName, machineName, source);

28: listener = new EventLogTraceListener(log);

29: Trace.Listeners.Add(listener);

30: }

31:

32: private void Check()

33: {

34: if(SourceExists() && !LogNamesEqual())

35: {

36: string name =

37: EventLog.LogNameFromSourceName(source, machineName);

38: CheckLog(name);

39: EventLog.DeleteEventSource(source, machineName);

40: EventLog.Delete(name, machineName);

41: }

42: }

43: private void CheckLog(string log)

44: {

45: const string Mask = "Do not delete default log {0}";

46: if( log.ToUpper() == "APPLICATION"

47: || log.ToUpper() == "SYSTEM"

48: || log.ToUpper() == "SECURITY" )

49: throw(new Exception(string.Format(Mask, source)));

50: }

51:

52: private void CheckListener()

53: {

Page 245: Advanced c sharp programming

2 1 8 A d v a n c e d C # P r o g r a m m i n g

54: Trace.Listeners.Remove(listener);

55: }

56:

57: private bool SourceExists()

58: {

59: return EventLog.SourceExists(source, machineName);

60: }

61:

62: private bool LogNamesEqual()

63: {

64: return EventLog.LogNameFromSourceName(

65: source, machineName).ToUpper() == logName.ToUpper();

66: }

67:

68: private static EventLog CreateDefault()

69: {

70: CreateNew("Logger", "Test", ".");

71: return log;

72: }

73: #endregion

74:

75: public static void CreateNew(string source,

76: string logName, string machineName)

77: {

78: Logger drop = new Logger(source, logName, machineName);

79: }

80:

81: public static EventLog Log

82: {

83: get

84: {

85: return log != null ? log : CreateDefault();

86: }

87: }

88:

89: [Conditional("DEBUG")]

90: public static void WriteEntry(string message)

91: {

92: Log.WriteEntry(message);

93: }

94:

95: [Conditional("DEBUG")]

96: public static void WriterEntry(string message,

97: EventLogEntryType type)

Page 246: Advanced c sharp programming

98: {

99: Log.WriteEntry(message, type);

100: }

101:

102: public static EventLogEntryCollection Entries

103: {

104: get

105: {

106: return Log.Entries;

107: }

108: }

109:

110: }

111: }

The Logger class makes the EventLog easier to use based on pretty simple reasoning. Whenyou are building your application you are encouraged to write as much information about yourapplication as necessary to help you eradicate bugs. When you ship your application, you willwant to write critical information to the Application log. Otherwise, you might fill up theevent log, which creates problems of its own.

A good strategy is to create a custom log during testing and write anything and everythingthat will help you hunt down bugs. The TestLog.Logger class was implemented with buildingand testing in mind. When I am building an application I simply want to log my trace informationbut not worry about creating unique source names or specifying a custom log. By writing theLogger I can automate creating and managing a custom log and ensure that the Source is uniqueacross all logs. Each time I use the Logger, it will take care of this process for me.

The following sections define the steps necessary to managing a custom event log, andexplain the things that are managed automatically by the Logger.

Creating an Event SourceWhen you create an instance of an EventLog, you need to specify the event source. TheSource must be unique across all logs on a single machine. If you want to manually create anevent source, you can invoke the static method CreateEventSource passing the source and logname or the source, log, and machine name. Here is an example demonstrating the methodinvocation.

EventLog.CreateEventSource("MyApp", "Application");

The preceding example creates the source MyApp in the Application log.If you look at the Logger, you will see that we do not explicitly create the event source.

(We do explicitly delete an event source, though.) The reason we don’t create the eventsource is because the WriteEntry method will create a source that doesn’t automatically exist.If the source already exists in another log then an exception will be raised. We do get rid of aconflicting source if one exists.

C h a p t e r 7 : E v e n t L o g g e r 2 1 9

Page 247: Advanced c sharp programming

Determining if a Source ExistsSourceExists is a static method defined in the EventLog. Pass the name of a source to themethod and you get a Boolean response indicating if the source exists. This method checksall event logs on the machine. EventLog.SourceExists is demonstrated on line 50 of Listing 7-4.

CAUTIONDon’t delete the default Application, System, or Security logs. Deleting these logs may cause your computerto stop running. Another benefit of wrapping the EventLog into the Logger class is that I can codify the checkto ensure that one of the default logs is not accidentally deleted. The CheckLog method plays this role.

If the source exists we use the EventLog.LogNameFromSourceName method to determinewhich log contains the source name we want to use and then delete the source from that log.The Check method on lines 32 through 42 in Listing 7-4 implements the source nameconflict resolution code. If the source exists and the log name I want to use is not identical tothe log name the source exists in, then the source and log are deleted.

Deleting an Event SourceYou can delete an event source when you have finished with it or remove a conflictingsource by calling the static method EventLog.DeleteEventSource. Pass the Source andMachineName, and the source will be removed from the Log containing this source. Line 39of Listing 7-4 demonstrates how to delete an event source.

Deleting a Custom LogWhen you have finished with a custom event log, you may want to delete it. Don’t delete anyof the default logs, including the Application, System, or Security logs. You may have a verydifficult time recreating these log entries in the registry, and, as a result of their deletion, yourcomputer may stop working correctly.

NOTEIf recollection serves, I invoked delete on the Application log to see if the EventLog would let me do it.EventLog.Delete removed the Application log. Fortunately, I had exported this branch of the registry andwas able to recover it. It is not something I would recommend doing unless you are willing to scrap amachine configuration and start over. But Microsoft may have implemented safeguards into the EventLogsince then.

The Logger deletes the log containing our source on line 40 of Listing 7-4 as long as thelog isn’t one of the big three.

2 2 0 A d v a n c e d C # P r o g r a m m i n g

Page 248: Advanced c sharp programming

Adding a Trace ListenerThe Logger class automatically establishes my custom event log as a Trace listener, too. Thisoccurs on lines 28 and 29 of Listing 7-4.

The net result of the Logger class is that by simply invoking one of the two staticLogger.WriteEntry methods, a custom log and trace listener are established. If you carefullylook at Listing 7-4 again, you will also see that the number of methods necessary for a consumerto use the Logger class is constrained to a couple.

The Logger class makes it easy to create and use a custom log while building an applicationand writing Trace information to it.

Secondary TopicsExplore the DebugDemo.sln example program and you will find several other features of theDiagnostics namespace demonstrated. The DebugDemo.sln demonstrates how to

� Implement custom dialog boxes

� Implement private constructors and when to do it

� Pass arguments by reference

� Use the FileVersion class (instead of Reflection) to create an AboutBox dialog

� Use the Debug class (along with a review of the Assert method)

Finally, we will look at PerformanceCounter objects. PerformanceCounters facilitatemeasuring your system’s performance and use of resources. This can be an invaluable toolto have in your arsenal.

Creating Dialog BoxesDialog boxes are forms that are displayed modally to prompt users for information. Visual Basic.NET has the InputBox method, which is nothing more than a function wrapped around aform. The benefit of emulating this function-around-a-form behavior is to make it easy forconsumers to perform basic operations.

The DebugDemo.sln implements an InputBox class that displays a single input TextBox.This form (shown in Figure 7-5) is used to query information from the user. The InputBox acceptsa title, caption, and default value that describe the content shown. (The InputBox is modeledafter the VB.NET InputBox function.)

If the user clicks OK, the form returns DialogResult.OK. If the user clicks Cancel, theform returns DialogResult.Cancel.

C h a p t e r 7 : E v e n t L o g g e r 2 2 1

Page 249: Advanced c sharp programming

2 2 2 A d v a n c e d C # P r o g r a m m i n g

Dialogs Return a DialogResultDialogs return one of the DialogResult enumerated values. When you are creating custom dialogboxes you should enforce this convention. The Form.ShowDialog function returns a DialogResultvalue. All you have to do is ensure that the ShowDialog method is invoked and use a controlthat returns one of the DialogResult values.

The System.Windows.Forms.Button control has a DialogResult property. To create an OKbutton, type OK in a button’s Text property and set the DialogResult value to OK. When theuser clicks the OK button it will close the form and set the return value of the ShowDialogmethod to DialogResult.OK. The same procedure will work for a Cancel button.

Private Constructors for DialogsTo make the dialog easier to use, you want to ensure that consumers invoke the dialog in aconstrained way through the InputBox.Show static method. In this instance, Show is a factorymethod. To make it the only way to construct the form, apply the access modifier privateto the form’s constructor. As a result, only member methods can construct instances of theform. The following code fragment demonstrates this technique.

private InputBox()

{

InitializeComponent();

}

private InputBox( string title, string caption,

string defaultValue) : this()

{

Text = title;

label1.Text = caption;

textBox1.Text = defaultValue;

}

public static DialogResult Show( string title, string caption,

Figure 7-5 A custom dialog modeled after the VB.NET InputBox function

Page 250: Advanced c sharp programming

C h a p t e r 7 : E v e n t L o g g e r 2 2 3

ref string defaultValue )

{

using(InputBox form = new InputBox(title, caption, defaultValue))

{

DialogResult result = form.ShowDialog();

if( result == DialogResult.OK )

defaultValue = form.textBox1.Text;

return result;

}

}

TIPThe using statement demonstrated in the listing will ensure that the form’s Dispose method will be called.Objects constructed in a using statement will be Disposed automatically at the end of the using block.

From the listing we can determine that there are two constructors, both having the samename as the class. The default constructor calls InitializeComponents. Without this constructorthe controls on our form don’t get created. The second constructor, also private, accepts ourtitle, caption, and defaultValue arguments. The : this() clause instructs the compiler to invokethe default constructor, too.

Because both constructors are private, the only way we can create an instance of our formis through the public factory method, Show. This is exactly what we want. Because we wantto test the DialogResult value for our Show method, we will need an alternative way to getthe input value back from the method. We can accomplish this feat by passing the defaultValueby reference.

Passing Arguments by Reference in C#If you need to get more than one return value from a method in C#, you can define argumentsas reference arguments. The result is that changes to the reference argument will be reflectedback to the caller. (Passing arguments by reference is similar to passing a pointer in C++,ByRef in VB, and var in Pascal.)

TIPTwo methods whose argument lists differ only by the ref keyword will be overloaded. Note that you cannotpass a property by ref.

The Show method shown in the listing in the previous section defines the defaultValue asan argument that will be changed by adding the ref keyword preceding the argument type(string) in the Show method. You also must specify the ref keyword when invoking themethod. From the DebugDemo.sln MenuForm we can seehow to invoke the InputBox.Show method.

if( InputBox.Show(title, caption, ref log) == DialogResult.OK)

Page 251: Advanced c sharp programming

Note the use of the ref keyword preceding the variable log. Methods can be overloaded bythe presence or absence of the ref keyword.

Changes to arguments passed without the ref or out keyword area will not be reflected tothe caller. Use of the ref keyword and changes will be reflected to the caller, and the ref argumentmust be assigned some value. Use of the out keyword and changes will be reflected to thecaller, but the out argument is not guaranteed to be initialized. If you pass an Array by ref orby out, you are indicating that the array itself will be changed, rather than the values in thearray. If you only want to change the values in an array, you do not need to use eitherthe ref or out keywords.

FileVersion InformationThe About form in Chapter 2 demonstrates how to get file version information from theAssembly class by using Reflection. Retrieving version information using Reflection gave usa convenient opportunity to introduce the Assembly class and Reflection. Fortunately, thereis a much easier way to retrieve version information.

The System.Diagnostics namespace contains a FileVersion class that has properties thatrepresent all of the values we might use in an About form. The FormAbout defined in theDebugDemo.sln demonstrates how easy it is to retrieve application information. (Of courseyou will still need to specify this information in the assembly attributes in the AssemblyInfo.csmodule contained in your project.)

FileVersionInfo info =

FileVersionInfo.GetVersionInfo(Application.ExecutablePath);

label1.Text = info.ProductName;

label2.Text = "Version: " + info.ProductVersion;

label3.Text = info.FileDescription;

label4.Text = info.LegalCopyright;

label5.Text = info.Comments;

The FileVersionInfo.GetVersionInfo static method retrieves a FileVersionInfo objectfrom the file name passed to the method. As demonstrated in the preceding fragment, ifyou want to get the file information for the executing application, you can pass the Application.ExecutablePath property. When you have the FileVersionInfo object, it is simply a matterof querying the properties for the information you want.

One drawback to the FileVersionInfo class is having to match the assembly attributes tothe FileVersionInfo properties. The FileVersionInfo names are not identical to the assemblyattribute names. Table 7-1 demonstrates the pairings that you may find useful for creating anAbout form.

The AssemblyCopyright attribute embeds the © symbol into the attribute text. You canembed special symbols into strings by using literal values. The literal value for the copyrightsymbol is \xA9, the hexadecimal value A9.

AssemblyVersion information is generated automatically if you use an asterisk for theminor, build, and revision values. For example, [assembly: AssemblyVersion("1.0.0.*")]

2 2 4 A d v a n c e d C # P r o g r a m m i n g

Page 252: Advanced c sharp programming

will generate a version number 1.0.0 with the last value, the revision number, being generatedby the compiler.

Using the Debug ClassThe Debug class is defined in the System.Diagnostics namespace. Debug contains all staticmethods that facilitate finding and resolving defects in your application. The WriteLine methodis used to write text to the Output window about the internal operation of applications, andthe Assert method is used to enforce application invariants.

Writing information that shows the internal state of your application combined withasserting that predetermined assumptions remain true is invaluable to building error-freeapplications.

Writing Debug Information to the Output WindowDebug.WriteLine is used to write text to the Output window by default. You can combine Debug.Indent and Debug.Unindent to write formatted, hierarchical information to the Output window.Debug.WriteLineIf works similarly to WriteLine, but only writes information if a Booleanevaluation is True. The following demonstrates the WriteLine and WriteLineIf methods:

Debug.WriteLine("source deleted");

Debug.WriteLinfIf( EventLog.SourceExists("MySource"), "Deleting MySource");

If you call the Debug.Indent method, then debug information will be written with a tabpreceding the text for each time you invoke Indent. Unindent will reduce the indentationby one tab each time it is invoked.

Assert EverythingThe Debug.Assert class is used to enforce assumptions about the state of your program whileyou are building it. Assert enforces assumptions by suspending your application and alerting youwhenever an assumption is broken. It does this simply by evaluating a Boolean expression.For example, if you write a conditional test to determine if an event log name is not“APPLICATION” then you can use the same test for the assertion. The if conditional

C h a p t e r 7 : E v e n t L o g g e r 2 2 5

Assembly Attribute FileVersionInfo PropertyAssemblyProduct FileVersionInfo.ProductName

AssemblyVersion FileVersioninfo.ProductVersion

AssemblyTitle FileVersioninfo.FileDescription

AssemblyCopyright FileVersionInfo.LegalCopyright

Table 7-1 Correspondences Between Assembly Attributes and FileVersionInfo Properties

Page 253: Advanced c sharp programming

check is to make sure your code behaves correctly, and the assertion is combined to tell you,the developer, while you are unit testing if the test ever fails.

Debug.Assert( log.ToUpper() != "APPLICATION");

The technique is to combine assertions with your conditional logic. The conditional logicis for deployment and the assertion that matches is for debugging. For example, if you as theprogrammer want to be notified whenever the Logger attempts to delete one of the defaultlogs, then you could write an assertion that makes sure that you are notified. Modifying theLogger.CheckLog method we could add an assertion that tells us if the log is the Application log.

private void CheckLog(string log)

{

const string Mask = "Do not delete default log {0}";

Debug.Assert(log.ToUpper() != "APPLICATION");

if( log.ToUpper() == "APPLICATION"

|| log.ToUpper() == "SYSTEM"

|| log.ToUpper() == "SECURITY" )

throw(new Exception(string.Format(Mask, source)));

}

2 2 6 A d v a n c e d C # P r o g r a m m i n g

A Brief History of Debugging ToolsThe Assert capability is one of the oldest and best programmer tricks around. Capabilitieslike asserting are built into the operating system. The operating system BIOS loads functionsthat provide basic system capabilities. These capabilities—such as printing to the screen,writing to the video buffer, or managing mouse behavior—are referred to as interrupts.

One of the lowest numbered interrupts—presumably one of the first—is the interrupt3, the debug or “soft-ice” interrupt. Interrupt 3 suspends an application’s execution,putting the application in debug mode. The earliest programmers—in the case ofMS-DOS Tim Paterson, Bill Gates, and Paul Allen—needed debugging tools.

You can still use assembly language and the debug interrupt with tools that supportassembly language programming. The following script is an assembly language versionof the Hello World! application. The program can be written using the debug.exe programat the command line and following each instruction below at a command window.

debug

a100

jmp 110

db "Hello, World$"

mov dx,102

mov ah,9

int 3

Page 254: Advanced c sharp programming

If the log names the Application log, then execution will slow way down, allowing you toassess this potentially critical situation. The dialog in Figure 7-6 will be displayed, providingyou with an opportunity to Abort, Retry, or Ignore the assertion failure. In this particularinstance, you can step through the method and make sure that the exception will be thrown.

If you click Abort, execution will terminate. If you click Retry, the application will breakat the next line after the assertion, and you can slowly step through the code or evaluate whythings went awry. The Ignore button will instruct the application to continue processing andignore the assertion.

Enabling and Disabling the Debug ClassIf you had to add and remove the Debug code each time the application was modified anddeployed, using the Debug class would require a lot of extra work. However, if you write yourdebug code when you are writing the production code, you will have the greatest awarenessabout assumptions you are making about the state of your application at a given moment.

When the Configuration Manager is configured for a Debug release the DEBUGconditional compiler constant will be defined and the Debug code will run. When you changethe configuration to Release, the DEBUG constant will be undefined and the Debug code willbecome noop code—that is, the Debug code will automatically be disabled.

Leave your Debug code in place even when you ship your application and use the conditionalcompiler constant DEBUG to toggle the Debug code off and on.

C h a p t e r 7 : E v e n t L o g g e r 2 2 7

int 21

int 20

<return>

rcx

1a

w

g

The lines between the instruction a100 and the <return> constitute the assemblycode. The instruction a100 means assemble at offset 100 (hexadecimal). The instructionjmp 110 is like a goto statement, db is string pointer, mov dx,102 points the data registerdx at the string "Hello, World!$", and $ is the string terminator. Mov ah,9 moves function9 into the ah register, and interrupt 21 (function 9) runs the print string function loadedby the operating system. If you run this program with the g(go) instruction inside thedebugger, then the program will stop at the instruction int 3, the debug interrupt and dumpthe state of the CPU up to that point.

The hello.com program is included with this book’s source code on the McGraw-Hill/Osborne web site, www.osborne.com. The point of this discourse is that debuggingtools are among the oldest and most long-lived—and for good reason.

Page 255: Advanced c sharp programming

2 2 8 A d v a n c e d C # P r o g r a m m i n g

Measuring PerformanceThe PerformanceCounter class is defined in the System.Diagnostics namespace.Performance counters are associated with performance objects by category. PerformanceCountersallow you to track resource usage, including things like memory, physical disks, CPUs, andthreads. (If you look in the Server Explorer—not available in the standard edition of VisualStudio .NET—you will see that there are hundreds of performance objects that can be measured.)

You can use PerformanceCounters by dragging a counter from the Server Explorer ontoyour application. Just as the Server Explorer can list multiple servers you can selectPerformanceCounters from other servers.

The following steps will demonstrate one example of using a LogicalDiskPerformanceCounter to track and display the available disk space on the C: drive.

1. In Visual Studio .NET select the View | Server Explorer menu option.

2. Select the server name representing your PC and expand the server tree node.

3. Expand the PerformanceCounters node until and find the LogicalDisk tree node.(The performance counters are listed by category in alphabetical order.)

4. Expand the LogicalDisk node and the subordinate FreeSpace node (see Figure 7-7).

Figure 7-6 The Abort, Retry, Ignore dialog is displayed when an assertion fails.

Page 256: Advanced c sharp programming

5. Drag the C: drive performance object onto the Component Tray for the form that youwant to contain the reference to the counter.

C h a p t e r 7 : E v e n t L o g g e r 2 2 9

Figure 7-7 The Server Explorer expanded to the C: drive performance object

Page 257: Advanced c sharp programming

2 3 0 A d v a n c e d C # P r o g r a m m i n g

Dragging the performance object from the Server Explorer configures the performanceobject correctly. Of course, you can create an instance of the PerformanceCounterprogrammatically and initialize the properties to indicate the performance object youwant to track. You will need to manually create a PerformanceCounter if you are creating anassembly that does not have a form. To configure this PerformanceCounter manually setthe CategoryName to “LogicalDisk”, the CounterName to “% Free Space”, the InstanceNameto “C:”, and the MachineName to your computer’s name.

When the PerformanceCounter is configured we can query the counter for present measuredvalues. The DebugDemo.sln demonstrates the percent free space counter by querying theavailable space on the Application.Idle event. (Application.Idle is an event that fires whenyour application isn’t actively doing anything.) The Form_Load event is used to attach anOnIdle event handler to the Application.Idle event. The code listing to read the free spacevalue and display it in a StatusBar follows.

private float GetFreeSpace(){return (float)performanceCounter1.RawValue / 1024;

}

private string GetFreeSpaceString(){const string Mask = "Free Space C: {0:f} GB";return string.Format(Mask, GetFreeSpace());

}

private void OnIdle(object sender, System.EventArgs e){statusBar1.Text = GetFreeSpaceString();

}

The PerformanceCounter value is read from the RawValue property. GetFreeSpace convertsthe value to a decimal representing a multiple of kilobytes. GetFreeSpaceString formats thevalue, embedding the decimal in a string, and appends the correct scalar. The RawValuefor the LogicalDisk category is thousands, and the multiple is millions. By dividing the rawvalue by 1,024, the multiple becomes 1,024×1,000,000, or a gigabyte.

For an advanced example demonstrating the capabilities of PerformanceCounters, checkout the PerfMon.sln that ships with the .NET samples.

SummaryThis chapter demonstrates how to use classes in the System.Diagnostics to write debuginformation, employ assertions, and trace and write information about the vitality of yourapplication to the event log. If you have programmed with VB6 or the Windows APIextensively in the past then you may be familiar with some of these capabilities.

A new feature in .NET is enhanced support for these capabilities as well as the ability toview a stack trace or peek in on information in the stack frame at any particular time. Combineall of these capabilities with FileVersion information and the hundreds of PerformanceCounters,and you can obtain a detailed picture of your application and its use of resources.

Page 258: Advanced c sharp programming

CHAPTER

8Creating UserControls

231

IN THIS CHAPTER:

Demonstrated Topics

Understanding UserControls

Creating a ButtonCluster Control

Creating a PickList Control

Implementing an AboutBox Control

Defining a Data Bound UserControl

Custom Painting in UserControls

Transparent UserControl Background

Extending UserControls Through Inheritance

Secondary Topics

Loading ListBoxes

Dynamically Positioning and Sizing Controls

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 259: Advanced c sharp programming

Extending the .NET framework by creating new classes and custom controlsis one of the most effective and enjoyable activities that programmers canparticipate in.

The UserControl class is a control that contains a drawing surface, like a form, that allowsyou to design composite controls in the Forms Designer for Windows Forms and ASP.NET.Rather than using code to design a control based on a graphical user interface, you can usethe same RAD approach that has existed in Visual Basic for years and visually designreusable controls.

When designing UserControls, you perform the same activities that you would performwhen designing a form. Drag and drop controls onto the form, generate the event handlers,modify properties, and implement new methods. The only noteworthy difference betweendesigning forms and UserControls is that forms are generally designed for end users andUserControls are designed for both end users and other developers—that is, your UserControlswill appear on forms and in the Toolbox. The last bit of information suggests that if we wantto build reusable UserControls then we need to make them general enough so they are not toodependent on a specific application.

This chapter will demonstrate how to use the Forms Designer to design UserControls forWindows Forms applications and how to make those controls reusable.

Demonstrated TopicsThere are several aspects that you need to know about to build usable UserControls. Asmentioned, UserControls are very similar to forms in design and use. This means that youruser controls will contain constituent controls, and those constituent controls will have propertiesand events that you may want to expose to UserControl consumers. Additionally, you maywant to perform custom painting and binding data to UserControls.

From examples in this chapter, you will be able to do the following:

� Implement UserControls

� Surface constituent control properties and events

� Perform custom painting in user controls

� Bind data to UserControls using ADO.NET and objects that implementthe IList interface

Understanding UserControlsThere are three basic strategies for implementing custom controls in C#. You can inheritfrom the Control class to have the ultimate flexibility but the most amount of work. You canimplement from a control that is closer to the new one that you want to make, resulting in lessflexibility and less work. For instance, if you want to make a label control that has specialeffects, you could inherit from System.Windows.Forms.Label. Finally, if you want to make

2 3 2 C # D e v e l o p e r ’ s G u i d e

Page 260: Advanced c sharp programming

a control that is composed of several visual elements, then the best way to do it is to create aUserControl.

This chapter will demonstrate how to create UserControls, and Chapter 9 will demonstratehow to build custom controls from scratch.

What Is a UserControl?In the .NET Framework, UserControls are defined in the System.Windows.Formsand System.Web.UI namespaces. For our purposes, we are referring to theSystem.Windows.Forms.UserControl. UserControls inherit from ContainerControl,which in turn inherits from ScrollableControl, which itself inherits from the ControlClass. If you look at the .NET Framework, you will note that this is exactly the sameancestry as a Windows Form.

When you are designing a UserControl you will perform the same activities that youperform when designing a Form. Like Forms, UserControls contain other controls referredto as constituent controls. A UserControl has a graphical user interface, as do Forms, andusually contains many constituent controls to make up part of a whole solution. For example,you might implement a UserControl with basic contact information and incorporate that controlinto a contact management form. (Refer to the section “Defining a Data Bound UserControl”later in this chapter.)

GUI Is KingI mean this in the best possible way: the more I see Windows application GUIs implementedby programmers, the more I am convinced graphics artists should render them first and thenthe programmers should figure out the technological challenges. (Alan Cooper refers to theproblems arising from left-brained programmers designing software for a right-brained worldin The Inmates are Running the Asylum. We programmers are the inmates he is referring to.)

There are several strategies that you can employ when designing the visual aspect ofUserControls:

� You can get great background graphics and make your controls semi-transparent.

� You can get a right-brained person or graphic artist to design some basic visuals foryou, and then you can create a standard set of UserControls that you can use over andover again.

� You can layer controls to create the effect of both depth and richness, mirroring someof the better-looking software that exists. (There are some good-looking applicationsthat you can borrow from to get you started.)

Unfortunately, I have not been very successful at getting customers to hire artists to designinterfaces and have had to rely on the last method more than I would have liked. (I amhopeful that, by writing here that GUIs could be rendered nicely by real artists, someonewill adopt that approach.)

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 3 3

Page 261: Advanced c sharp programming

Assuming you must rely on trichromacy—three colors—and symmetry to createyour graphical user interfaces, then at least you can depend on UserControls to facilitateconsistency. Trichromatic, symmetric, and consistent isn’t art, but it won’t be garish either.Now you need a place to begin building your UserControls.

Creating a Control LibraryThe Windows Control Library template in the New Project dialog is a good way to beginbuilding UserControls. The Windows Control Library will yield an assembly with a DLLextension, and the project will be created with a single UserControl. (You can start with aClass Library and add UserControls manually, but there is no reason not to take reasonableshortcuts if they are available.)

When you create a Windows Control Library, the output type will be Class Library(read DLL), and the template will add the System, System.Data, System.Drawing,System.Windows.Forms, and System.XML namespace references. The template will alsoadd AssemblyInfo.cs and UserControl1.cs modules. The AssemblyInfo file is provided foradding metadata, and the UserControl module contains a class, which inherits from UserControl,that you can use to begin implementing your first UserControl.

You can add as many UserControls to a control library as you would like. You are notlimited to a single class per module, but you do want to limit each module to a singleUserControl because a .resx file will be associated with the UserControl by file name.

A recommended approach to building UserControls at this point is to build the controlwith a general solution in mind and to use a generic application to test the UserControl. Whenthe control has the general properties, methods, events, and constituent controls that you havedetermined are needed, you can incorporate the new UserControl into your application. Wewill use this approach to build UserControls. The first control is a stack of four buttons thatare automatically resized on the basis of the size of the UserControl. The second controlincorporates the buttons to make a UserControl with two listboxes, with the buttons servingas a visual metaphor for moving elements between the two listboxes. (After we build thesetwo controls we will explore some additional kinds of controls.)

Creating a ButtonCluster ControlThe ButtonCluster uses four standard Button controls on a UserControl container. TheButtonCluster control is shown at the center of the form in Figure 8-1. The text on the buttonssuggests that clicking a particular button will move the elements in the direction indicatedby the arrow (see Figure 8-1).

NOTEIt is worth noting that the behavior of these buttons may seem intuitive to those familiar with Windowsapplications, but may not be at all intuitive to non-Windows users. This is a difficulty that every newsoftware application presents.

2 3 4 A d v a n c e d C # P r o g r a m m i n g

Page 262: Advanced c sharp programming

The status quo is that software developers have to know a little about their audience,and if Windows applications deviate from the Windows look and feel too much, then evenexperienced computer users will be lost. The current approach is to have custom-tailoredtraining for users of new applications when needed.

From Figure 8-1 we can determine that the UserControl is composed of four buttons. Tokeep the buttons aligned, we will need to respond to Resize events sent to the UserControl,we will need to modify the Text property for the buttons, and we will need to surface theindividual Button Click events in order to allow users to respond to Click events.

TIPControls on a UserControl (or Form) are referred to as constituent controls. You will encounter the termsurfacing in such uses as “surfacing properties of constituent controls.” Other texts refer to this as interfacepromotion. When you expose the member of an aggregate in the containing control, both “surfacing themember” and “interface promotion” are OK. We will use “surfacing” to be consistent with the .NET helpyou are likely to encounter.

Designing the UserControl Visual InterfaceThe visual appearance of our first user control is composed of four equally-sized buttonslined up in a vertical stack. To make the control as generic as possible, we might want toallow the buttons to be aligned horizontally. We may also want to allow the actual buttons

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 3 5

Figure 8-1 The ButtonCluster user control is located between the two listboxes.

Page 263: Advanced c sharp programming

to be rendered using other kinds of buttons (like the VideoKiosk buttons in Chapter 2), butwe’ll keep the button cluster simple.

To create the button cluster class, create a new Windows Control Library project. Dropfour buttons on the UserControl container. Use the Visual Studio .NET Format | Make SameSize | Both menu to make the buttons the exact same size. (Make the buttons roughly square.)Use the Format | Align | Centers menu item to align the buttons along a center vertical axis.Resize the UserControl to approximately the same width as a single button and the heightto that of all four buttons (an example can be seen in Figure 8-1). Don’t worry about precisevisual sizing, as we will write the code to make the control visually perfect. The next thingwe need to do is write the code to resize each of the buttons proportionately to the size of thecontaining control.

Implementing the UserControl Resize Event HandlerWhen the UserControl is resized, we want our buttons to occupy and fully consume exactlyone-fourth of the available space. Fortunately, the UserControl can be designed like a form.To generate the Resize event handler, we can use the Event view of the Properties window.

private void UserControlButtons_Resize(object sender,

System.EventArgs e)

{

ResizeButtons();

}

Following a good convention, we simply implement the event handler by calling a well-named method. The well-named method mitigates the need for a comment, and the nameindicates precisely what the code will do. As an alternative, we can override the OnResizemethod. If you choose to override OnResize, make sure you call the base class method. Byconvention, .NET uses the On prefix for event methods; hence, the Resize event would havean OnResize method that invokes it. (You can look this information up or simply rely onconvention.)

If you elect to override the OnResize method, then the overridden method might beimplemented as follows:

protected override void OnResize(System.EventArgs e)

{

base.OnResize(e);

ResizeButtons();

}

We can use an overridden OnResize method to implement the constituent resize behavior.We do not need both the event handler and the overridden event handler; although eitherapproach works, usually I prefer to override the method.

It is important to understand why you might have to override the OnResize method, ratherthan implement an event handler, if we were working in something else besides .NET.

Other languages implement procedural types and function pointers—both are really thesame thing. However, your basic function pointer can point to only one function at a time.

2 3 6 A d v a n c e d C # P r o g r a m m i n g

Page 264: Advanced c sharp programming

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 3 7

Thus, if you were to use a function pointer to implement the resize behavior and consumerswere to implement this event, your event handler would be lost. This is because a functionpointer event handler can point to only one function at a time.

Delegates are different. Delegates maintain an internal invocation list. This internalinvocation list allows delegates—events in .NET—to point to multiple event handlers andnotify each of those handlers every time an event is fired. This is referred to as multicasting.Since delegates can multicast, we can implement the Resize behavior using an event handlerand not worry about some other developer coming along and stomping all over our resizeevent-handling behavior. Multicast delegates allow us to use event handlers or overriddenmethods to extend the behavior of UserControls.

Determining Equal Subdivisions for the ButtonsNow that we have an event handler, our UserControl is notified when it is resized. From thispoint, we can implement the code that sizes each button equally.

When we are coding, it is useful to determine when a solution is generic enough to make sensein some other context. Determining the fractional part of a rectangle is generally useful,and experience suggests that I can reuse this behavior. When code may be generally useful, itis a good idea to create a new class and implement the behavior in the new class. Listing 8-1implements a Rectangle class that can calculate a new top and new bottom for a rectanglethat is a fractional subdivision of a bounding rectangle.

Listing 8-1 The Rectangles class contains static methods for determining subdivisions ofbounding rectangles.

using System;

using System.Drawing;

namespace UserControlsExamples

{

public class Rectangles

{

private Rectangles(){}

public static Rectangle GetVerticalRectangle(

Rectangle rectangle, int index, int segments )

{

Rectangle r = rectangle;

r.Size = new Size( r.Width,

NewBottom(rectangle, index, segments));

r.Location = new Point(0,

NewTop(rectangle, index, segments));

return r;

}

Page 265: Advanced c sharp programming

public static int NewTop( Rectangle rectangle,

int index, int segments )

{

return (int)((float)index / segments *

rectangle.Height);

}

public static int NewBottom( Rectangle rectangle,

int index, int segments)

{

return rectangle.Height / segments;

}

}

}

The class is pretty straightforward: calculate a new top based on the number of subdivisionsand the actual division you want, and calculate the bottom by making the height an equalsubdivision. More important is how we are able to determine when we have a new class.Simplistically, we provide a good name for a method and then ask ourselves whether thismethod sounds like a behavior of this particular thing, this class. I named the methodGetVerticleRectangle. Thus, the question is “Does ButtonCluster.GetVerticalRectangle—that is, GetVerticalRectangle—sound like a good method for a button cluster?” The clue isin the name of the method, GetVerticalRectangle. The noun Rectangle provides a reasonableclue as to the kind of thing this method probably should belong to. This is not a perfect science,but experience and experimentation will help.

TIPHow do we determine that the Rectangles class does not need to be created? The answer: there is no stateinformation. That is, there are no fields. If a class only has methods, then those methods can be static, andwe don’t need instances of the class.

Now that we have some general methods for determining rectangular subdivisions, we canresize the buttons in the cluster quite easily.

private void Initialize()

{

buttons = new Button[]{buttonAllRight, buttonRight,

buttonLeft, buttonAllLeft};

}

private void ResizeButtons()

{

if( buttons == null ) return;

for( int i=0; i<buttons.Length; i++)

{

2 3 8 A d v a n c e d C # P r o g r a m m i n g

Page 266: Advanced c sharp programming

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 3 9

buttons[i].Bounds = Rectangles.GetVerticalRectangle(

this.Bounds, i, buttons.Length);

}

}

The ButtonCluster class was implemented to store a reference to each button in an array(see the preceding listing). Storing the buttons in an array field facilitates iterating over eachbutton and invoking the Rectangles.GetVerticalRectangle method on each rectangle. Theargument this.Bounds is the rectangle for the user control. The index i is used as the rectangularsubdivision, and the Length of the array indicates the number of subdivisions. This approachactually makes the code quite extensible, since we are not relying on literal values to determinethe index or number of buttons.

A final word on strategy is in order. You could, of course, use the UserControl’s Controlsarray to access the buttons; however, if you go this route, then you will be more inclined towrite code that checks the Type information of each Control in the Controls array. Eitherapproach works reasonably well. If you use the Controls array, then you might run into difficultyif the UserControl contains controls of a similar type, but you don’t want to perform a uniformoperation on all controls of the same type.

Surfacing Constituent EventsWhen it comes to the visual aspects of our button cluster, all we need to do is modify theText property and we are finished. For a more refined look, use the Button.Image propertyand provide a graphic arrow.

Programmatically, however, we are not quite finished. The purpose of buttons is to respondto Click events. As the event handler is implemented so far, consumers will not be able to getat the Click events for the buttons contained on this control. This presents a slightly stickyproblem. A solution—albeit not the best one—is that we must surface the events of theconstituent controls. Surfacing constituent control events is not as difficult as it may seem.But there is the question of why we have to do it?

I will answer the “how” first and then we will look at the “why.” Understanding whywe have to surface constituent control members, and what the alternative is, will help usanticipate what is likely to come in the very near future. (You are learning what is and what islikely to be.)

Promoting Events in Constituent ControlsWhen you add controls to a UserControl, the new controls are added as fields in the UserControl.The properties of the controls on the UserControl are encapsulated two layers deep. Assumeyou have an instance of a UserControl, userControl1. Further assume that userControl1 containsa Button control. To get at the Text property of the button, a consumer would have to writesome code like userControl1.Button.Text. Counting the number of member-of operators—there are two—we know that the Text property is two layers deep. As a result, the button’sText property is not going to show up in the Properties window.

Assuming that Button is public, a consumer could programmatically modify the propertiesof the Button contained in the UserControl. However, if you want to modify those properties andevents in the Properties window, then you will have to surface the properties and events of

Page 267: Advanced c sharp programming

constituent controls. We want to enable consumers to write code that responds to the individualbutton clicks, so we’ll need to surface the Button Click events.

To surface constituent control events, we need to write event handlers for the controls. Thenwe need to implement new events in the UserControl. When the constituent control events areraised, we pass the raised events up to the new UserControl events. Effectively, we bubble theevents from the contained controls up to the interface level of the UserControl. Listing 8-2demonstrates the necessary steps.

Listing 8-2 Surfacing constituent control events

1: private void Initialize()

2: {

3: buttons = new Button[]{buttonAllRight, buttonRight,

4: buttonLeft, buttonAllLeft};

5:

6: buttonAllRight.Click += new EventHandler(OnAllRightClick);

7: buttonRight.Click += new EventHandler(OnRightClick);

8: buttonAllLeft.Click += new EventHandler(OnAllLeftClick);

9: buttonLeft.Click += new EventHandler(OnLeftClick);

10: }

11:

12: private bool IsValidIndex(int index)

13: {

14: return (index >= buttons.GetLowerBound(0) &&

15: index <= buttons.GetUpperBound(0));

16: }

17:

18: public Button this[int index]

19: {

20: get

21: {

22: Debug.Assert(IsValidIndex(index));

23: return buttons[index];

24: }

25: }

26:

27: public event EventHandler AllRightClick;

28: public event EventHandler RightClick;

29: public event EventHandler AllLeftClick;

30: public event EventHandler LeftClick;

31:

32: private void OnAllRightClick(object sender, System.EventArgs e)

33: {

34: if(AllRightClick != null)

2 4 0 A d v a n c e d C # P r o g r a m m i n g

Page 268: Advanced c sharp programming

35: AllRightClick(sender, e);

36: }

37:

38: private void OnRightClick(object sender, System.EventArgs e)

39: {

40: if(RightClick != null)

41: RightClick(sender, e);

42: }

43:

44: private void OnAllLeftClick(object sender, System.EventArgs e)

45: {

46: if(AllLeftClick != null)

47: AllLeftClick(sender, e);

48: }

49:

50: private void OnLeftClick(object sender, System.EventArgs e)

51: {

52: if(LeftClick != null)

53: LeftClick(sender, e);

54: }

Lines 27 through 30 in Listing 8-2 declare four public events. These events will showup in the Event view of the Properties window as AllRightClick, RightClick, AllLeftClick,and LeftClick. Consumers can add event handlers to these event properties. Following convention,lines 6 through 9 in Listing 8-2 add private event handlers to the Click event for our constituentbutton controls. When the Button Click events are raised, our internal events check to see ifconsumers have associated event handlers with our public events. If the UserControl’s publicevents have handlers assigned to them, then the events are bubbled up to the surface of theUserControl. Figure 8-2 shows the Call Stack view with the events in the order they were called.

TIPThe operator for events is the += (and – =), as demonstrated in Listing 8-2. Delegates do not even supportthe assignment operator =. Assignment implies a single value, as in a single delegate. However all delegatesare multicast delegates. You may add or remove delegates from the invocation list, but you may not assignonly one event handler to an event property.

We could have used the Forms Designer to generate the event handlers for the constituentbutton controls in Listing 8-2, but the result is the same whether we write the event handlersor the Forms Designer generates them. The real question is why we have to surface constituentcontrol properties and events at all.

Viable Alternatives to Member PromotionYou might assume that you could declare a constituent control, like the buttons in theButtonCluster, as public. Once the constituent control is public, consumers could access

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 4 1

Page 269: Advanced c sharp programming

2 4 2 A d v a n c e d C # P r o g r a m m i n g

its properties and events directly. In fact, the Visual Studio .NET IDE will allow you to dothis (see Figure 8-3).

Figure 8-3 shows the AllRightButton as a nested reference property. You can modifyproperty values, but you may not modify events for nested reference type properties; theydon’t even show up in the Properties window. Of course, when you do modify properties ofnested reference types, those properties are not persisted correctly to the resource file. Theresult is that nested properties are not maintained when you close the IDE, nor are they maintainedwhen you compile and run the application. Technically, this is a bug.

NOTEMaking constituent object fields public does not work correctly in the Properties window, either.Only properties and events will show up in the Properties window; public fields will not.

If you know a little bit about the history of C#, you can draw some conclusions. The firstis that what we are trying to do, as shown in Figure 8-2, seems intuitive. If you can modifynested properties programmatically, then you should be able to modify them at design time,too. Second, C# is partly the brainchild of the same guy who built Borland’s Delphi,Anders Hejlsberg. Delphi is built on the Object Pascal language, and Delphi in its recentversions supports design-time nested objects. My intuition tells me that you will not haveto surface constituent control properties and events for long. Look for nested objects to workproperly in the IDE in the near future. For now, public nested object properties do not persiststate correctly at design time.

The second thing you might try to do is surface constituent events directly. Consider thefollowing code:

public EventHandler AllRightClick

{

Figure 8-2 The Call Stack shows that the constituent control responded to the event first and thatthe event is bubbled up to the containing control.

Page 270: Advanced c sharp programming

get

{

return buttonAllRight.Click;

}

}

To surface a constituent event directly, you might assume that you can do something likereturn the event property as demonstrated. This declaration would allow the event propertyto be the right-hand side value for an assignment statement, but assignment (=) is not definedfor events. (The addition-assignment (+=) and the subtraction-assignment (– =) operators aredefined for events, but not plain-old assignment.)

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 4 3

Figure 8-3 A reference type—a button—nested in the Properties window

Page 271: Advanced c sharp programming

Further, you might try to subclass a delegate class and overload the assignment (=) operator.However, delegate classes are declared with the sealed modifier, which means you may notsubclass delegates. You may only declare new delegates based on a method signature.

Creating a PickList ControlWe have the ButtonCluster. We can use it in any application by referencing the DLL assemblythat contains the ButtonCluster. We can also use the ButtonCluster as a control in anotherUserControl. By layering complexity, rather than building a single, monolithic control, weend with more individual controls and controls that are easier to manage.

The next control we will build is a UserControl with two ListBox controls and oneButtonCluster. As the ButtonCluster is a separate UserControl, when we build the PickListcontrol, we are acting as a consumer of the ButtonCluster and only have to focus on the newbehavior. This is clearly simpler than building a monolithic control that defines the Buttonand ListBox behavior in one class. The PickList UserControl is shown in Figure 8-1.

Implement the PickList by creating a new UserControl. Paint two ListBox controls on theUserControl, place a panel in between, and add a ButtonCluster to the panel. To maintainthe relative position of the ListBoxes and the panel containing the ButtonCluster, change theleft ListBox’s Dock property to Left, the right ListBox’s Dock property to Right, and the centerpanel to Fill. When the UserControl is resized, the ListBoxes and ButtonClusters will maintainthe appearance shown in Figure 8-1. The ButtonCluster’s position can be maintained with thefollowing statement:

buttonCluster1.Location = new Point(

panel1.Left + (panel1.Width - buttonCluster1.Width) / 2,

panel1.Top + (panel1.Height - buttonCluster1.Height) / 2);

The horizontal location of the ButtonCluster is offset from the left edge of the panel byhalf the difference of the containing panel and the ButtonCluster. The vertical location of theButtonCluster is offset from the top of the panel by half the difference of the height of the paneland height of the ButtonCluster.

The visual effects are finished. Now we need to implement the behavior that moveselements between the two ListBoxes. We can stub this behavior out by using the Propertieswindow to generate the event handler for each button.

Adding and Removing Elements from ListBoxesThere are four buttons, and we can implement the individual behavior of each button in a handfulof ways. Click the >> (all right) button, and all of the elements are moved from the left list tothe right list. The << (all left) button performs the opposite operation. Click the > (right) button,and only the selected items are moved from the left list to the right, and the < (left) button movesthe selected items from the right list to the left list.

The items in a ListBox are defined as a nested type, ObjectCollection. Listing 8-3demonstrates methods that will handle moving elements between ListBox controls.

2 4 4 A d v a n c e d C # P r o g r a m m i n g

Page 272: Advanced c sharp programming

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 4 5

Listing 8-3 Moving items between ListBox controls

1: private void SourceToTarget(ListBox.ObjectCollection source,

2: ListBox.ObjectCollection target)

3: {

4: target.AddRange(source);

5: source.Clear();

6: }

7:

8: private void MoveAllLeft()

9: {

10: SourceToTarget(listBoxRight.Items, listBoxLeft.Items);

11: }

12:

13: private void MoveAllRight()

14: {

15: SourceToTarget(listBoxLeft.Items, listBoxRight.Items);

16: }

17:

18: private void SourceToTarget(ListBox.SelectedObjectCollection

19: source, ListBox.ObjectCollection target)

20: {

21: IEnumerator e = source.GetEnumerator();

22: while(e.MoveNext())

23: {

24: target.Add(e.Current);

25: }

26: }

27:

28: private void RemoveSelected(ListBox listBox)

29: {

30: for( int i=listBox.Items.Count - 1; i>=0; i--)

31: if(listBox.GetSelected(i))

32: listBox.Items.RemoveAt(i);

33: }

34:

35: private void MoveLeft()

36: {

37: SourceToTarget(listBoxRight.SelectedItems,

38: listBoxLeft.Items);

39: RemoveSelected(listBoxRight);

40: }

41:

Page 273: Advanced c sharp programming

42: private void MoveRight()

43: {

44: SourceToTarget(listBoxLeft.SelectedItems,

45: listBoxRight.Items);

46: RemoveSelected(listBoxLeft);

47: }

MoveAllRight and MoveAllLeft call the SourceToTarget method, which takes twoListBox.ObjectCollection objects. The lists are transposed according to which direction weare moving the items. ListBox.ObjectCollection represents all of the items in the list. Afterwe invoke AddRange on line 4 in Listing 8-3, we clear all of the elements in the source list,line 5. To move only the selected items, we invoke the SourceToTarget method that takes aListBox.SelectedObjectCollection as the first argument and a ListBox.ObjectCollection asthe second argument. We transpose the arguments according to the direction we are movingelements. We copy only selected items by using an enumerator; interestingly enough, AddRangeis not defined for the ListBox.SelectedObjectCollection. After we enumerate all selected itemsand copy the selected items to the target—lines 18 to 26—we invoke RemoveSelected.RemoveSelected uses an integer, because GetSelected and RemoveAt—lines 31 and 32—are defined to take an integer.

It is a bit unfortunate that ListBox uses a special collection to store objects in the list. Thisprobably facilitates memory optimization, but it makes it more difficult to write a general-purpose utility that manages inter-list item movements.

BeginUpdate and EndUpdateSometimes you will need to move a large number of items into a ListBox. If you allow theListBox to update the list view each time you add an element, the ListBox will take longerto load. To load a large number of items, call ListBox.BeginUpdate before you begin loadingthe list, and call ListBox.EndUpdate when you have finished loading the list. BeginUpdatewill prevent the listing from drawing the new items until you call EndUpdate. The followingcode fragment demonstrates the technique:

listBox1.BeginUpdate();

// load the list. For example, add the code on lines 21 to 25 of listing 3

listBox1.EndUpdate();

Implementing an AboutBox ControlThe AboutBox is a form that you will find in most Windows applications. This is a greatcandidate for building once and using many times. This section demonstrates how to implementan AboutBox UserControl. The About properties are read from the assembly metadata usingthe System.Diagnostics.FileVersionInfo class we learned about in Chapter 7. I won’t repeatthat information here (see the section “FileVersion Information” in Chapter 7). In this section,we’ll focus on surfacing constituent properties like the About Image property.

2 4 6 A d v a n c e d C # P r o g r a m m i n g

Page 274: Advanced c sharp programming

Drawing and Coding the AboutBoxYou can copy and paste the controls from the AboutBox referred to in Chapter 7 onto aUserControl. (You will need to download the source code from the www.osborne.com website.) You can also copy the FormAbout_Load code from the FormAbout.cs form in Chapter 7’sDebugDemo to the UserControl Load event. (I won’t repeat that code here.)

The FileVersionInfo dynamically reads the assembly metadata to fill in the values displayedon the AboutBox form; hence, moving the code from the form to the UserControl makes itreusable as long as the controls are the same. Of course, you could elect to leave the AboutBoxcode in a form and import a copy of the form every time you want About information, but theUserControl version gives you added flexibility.

Surfacing Constituent PropertiesSurfacing constituent properties is easier than surfacing constituent events. To surface a propertyof a control contained in your UserControl, declare a public property that has the same typeas the control’s property you want to surface. Return and set the value of the constituentcontrol’s property in the newly defined UserControl property.

For example, we have defined the AboutBoxInfo UserControl. To allow users to changethe PictureBox.Image property, we need to surface the Image property. Assuming that ourPictureBox control were allowed to use the default name, then adding the next propertystatement to our user control would suitably surface the constituent image property.

public Image Image

{

get

{

return pictureBox1.Image;

}

set

{

pictureBox1.Image = value;

}

}

From the outside looking in, the UserControl now has an Image property.I’d like to mention a note on style here. Notice that I used the default name of the PictureBox,

“pictureBox1.” Previously, I mentioned that you should provide good, descriptive names formethods, properties, events, and fields. Learning when to worry about such things is importanttoo. In the case of the UserControl AboutBoxInfo, there is only one PictureBox and it is a privatemember of the UserControl. Since consumers can’t interact with the PictureBox directly andthere is only one, pictureBox1 is as good a name as any. This is a subjective judgment, ofcourse, but software development is as much about knowing what to do as knowing what notto do or worry about.

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 4 7

Page 275: Advanced c sharp programming

2 4 8 A d v a n c e d C # P r o g r a m m i n g

It would be great if we could simply make constituent controls public and manipulate themdirectly. You can make constituent controls public, but they will not work correctly in theProperties window. You can modify the values of public constituent properties at design time inthe Properties window, but those values will not be maintained when you close Visual Studio.NET or when you compile the application. I suspect this feature will be supported in the nextrelease or two, and you will not have to surface constituent properties and events. (As mentioned,I feel comfortable in making this supposition because public constituent controls seem intuitiveand because Anders Hejlsberg is participating in the development of .NET.)

Defining a Data Bound UserControlThe Control class introduces a DataBindings property that is a ControlBindingsCollection.You can bind controls to any class that implements the IBindingList, ITypedList, or IListinterfaces. The obvious classes are the ArrayList and Hashtable and the ADO.NET DataSetDataTable, DataView, and DataViewManager classes. Additionally, you can bind to lists ofstrongly typed objects of the same type. This means that you can bind controls to arraysof user-defined objects.

Chapter 5 focuses on ADO.NET, so we will look at binding arrays of custom types to ourUserControl. The first thing we need to do is fabricate a UserControl that contains constituentcontrols we want to bind to. We will define a UserControl that displays contact information.Next, we’ll need to define a class that is capable of containing the information we want tomanage, and then add some instances of that class to an array. Finally, we’ll bind our customobjects to the UserControl and provide a way to navigate over the elements in the array.

Implementing the UserControlCreate a UserControl named ContactInformation. Add three Labels and three TextBox controlsto ContactInformation. Change the Text property of the Labels to “First Name:”, “Last Name”,and “Phone Number:”. Define three public properties named FirstName, LastName, andPhoneNumber. Define the properties as string types. The completed example is shown inFigure 8-4. Use the figure as a visual guide.

The only code you have to write for the ContactInformation control is code that surfacesthe three constituent TextBox control’s Text properties:

public string FirstName

{

get

{

return textBox1.Text;

}

set

{

textBox1.Text = value;

}

}

Page 276: Advanced c sharp programming

public string LastName

{

get

{

return textBox2.Text;

}

set

{

textBox2.Text = value;

}

}

public string PhoneNumber

{

get

{

return textBox3.Text;

}

set

{

textBox3.Text = value;

}

}

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 4 9

Figure 8-4 A form with the data bound ContactInformation UserControl

Page 277: Advanced c sharp programming

2 5 0 A d v a n c e d C # P r o g r a m m i n g

Consumers will never see the TextBox controls themselves, so you do not have torename the TextBox controls. From a consumer’s perspective, you have a UserControlnamed ContactInformation, and the properties are ContactInformation.FirstName,ContactInformation.LastName, and ContactInformation.PhoneNumber.

Implementing the ContactInformation ClassIt is convenient and easy to define a class that has properties that match the ContactInformationUserControl. It is helpful to define a plurally named class that contains a collection of ourcustom class. (These classes represent what are commonly referred to as business objects.)Listing 8-4 defines the Contact class and the container, Contacts.

Listing 8-4 The Contact and Contacts classes

1: public class Contacts

2: {

3: private ArrayList items;

4:

5: public Contacts()

6: {

7: items = new ArrayList();

8: }

9:

10: public ArrayList Items

11: {

12: get

13: {

14: return items;

15: }

16: }

17: }

18:

19: public class Contact

20: {

21: private string firstName;

22: private string lastName;

23: private string phoneNumber;

24:

25: public Contact( string firstName, string lastName, string phoneNumber)

26: {

27: this.firstName = firstName;

28: this.lastName = lastName;

29: this.phoneNumber = phoneNumber;

Page 278: Advanced c sharp programming

30: }

31:

32: public string FirstName

33: {

34: get

35: {

36: return firstName;

37: }

38: set

39: {

40: firstName = value;

41: }

42: }

43:

44: public string LastName

45: {

46: get

47: {

48: return lastName;

49: }

50: set

51: {

52: lastName = value;

53: }

54: }

55:

56: public string PhoneNumber

57: {

58: get

59: {

60: return phoneNumber;

61: }

62: set

63: {

64: phoneNumber = value;

65: }

66: }

67: }

Listing 8-4 implements a class that has three fields and three matching properties. Theconstructor is defined to initialize these three fields: firstName, lastName, and phoneNumber.The Contacts class on lines 1 through 17 contains a single ArrayList field, items. The Itemsproperty is defined as a read-only property (because we only implemented a getter method).

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 5 1

Page 279: Advanced c sharp programming

We want consumers to be able to change the elements of the ArrayList but not the ArrayListitself. Making the ArrayList property read-only is how we accomplish this.

If we look in Visual Studio .NET help, we can determine that ArrayList implements IListand is a suitable property to bind to our UserControl properties.

Binding and NavigatingWe will need to create some test data to exercise our UserControl. Listing 8-5 creates a coupleof instances of Contact and a Contacts object to store them in. Every control, includingUserControls, has a DataBindings property. We’ll use the ContactInformation DataBindingsproperty to express the bindings between Contact objects and the UserControl. We will wrap theexample up by using the UserControl’s BindingContext property to support navigatingthe Contacts.

Listing 8-5 A form that contains the ContactInformation UserControl and the code that bindsthe homogeneous objects in an ArrayList to that UserControl

1: Contacts c = new Contacts();

2:

3: private void FormDataBound_Load(object sender, System.EventArgs e)

4: {

5: c.Items.Add( new Contact("Paul", "Kimmel", "(517) 555-1212"));

6: c.Items.Add( new Contact("Trevor", "MacDonald", "(517) 555-1212"));

7:

8: contactInformation1.DataBindings.Add(

9: new Binding("FirstName", c.Items, "FirstName"));

10:

11: contactInformation1.DataBindings.Add(

12: new Binding("LastName", c.Items, "LastName"));

13:

14: contactInformation1.DataBindings.Add(

15: new Binding("PhoneNumber", c.Items, "PhoneNumber"));

16: }

17:

18: private void buttonNext_Click(object sender, System.EventArgs e)

19: {

20: ((CurrencyManager)contactInformation1.

21: BindingContext[c.Items]).Position -= 1;

22: }

23:

24: private void buttonPrevious_Click(object sender, System.EventArgs e)

25: {

26: ((CurrencyManager)contactInformation1.

2 5 2 A d v a n c e d C # P r o g r a m m i n g

Page 280: Advanced c sharp programming

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 5 3

27: BindingContext[c.Items]).Position += 1;

28: }

TIPYou can add line breaks at the member-of operator for readability without adding any specialline-break character.

The code in Listing 8-5 can be found in the TestControls.sln example program and iscontained in the FormDataBound.cs Form module. The Form_Load event creates two Contactobjects and a Contacts object. The DataBindings.Add takes a Binding object. The Binding objectis initialized with a property to bind to, a data source to bind from, and a data member in thatsource to bind to. The first argument is the property in our UserControl. The data sourceis represented by the ArrayList Items, and the data member is a named property of oneof the objects in the ArrayList, specifically Contact.FirstName, Contact.LastName, andContact.PhoneNumber.

Because I was the author of the UserControl and the business classes, I named the propertiesin both identically. This was done precisely for ease of use and convenience.

To navigate the elements in the Contacts object, we have to get synchronize the data source,Contacts.Items, with the UserControl, contactInformation1. The BindingContext propertyreturns a specific child class of the BindingManagerBase. If the data source returns a singleproperty, then BindingContext returns a PropertyManager, and our example returns a collection;hence, BindingContext returns a CurrencyManager. CurrencyManager allows us to iterateover the elements by incrementing or decrementing the Position property.

The CurrencyManager handles the beginning and end of collection management automatically.For that reason, there is no special exception handling code when the position propertyis modified.

After binding the control to an array of objects, you do not need to write any special codeto modify the data. Change the data in the TextBox and the bound field is modified; this isexactly the behavior that you get when you bind to ADO objects, too.

Custom Painting in UserControlsWhen you add constituent controls to a custom UserControl, you will have to provide custompainting for the constituent controls in each particular control’s Paint event handler.

Suppose we wanted to make the labels for our ContactInformation UserControl appearembossed. (Embossing is an effect that makes the text appear to have depth.) Using aSystem.Windows.Forms.Label control painted on the UserControl, we would need to implementan event handler to handle the Label’s Paint event, as the following code demonstrates:

this.label1.Paint += new

System.Windows.Forms.PaintEventHandler(this.label1_Paint);

private void label1_Paint(object sender,

System.Windows.Forms.PaintEventArgs e)

Page 281: Advanced c sharp programming

{

// Demonstrates text customization

Label l = (sender as Label);

e.Graphics.DrawString(l.Text, l.Font, Brushes.White, new PointF(2,0));

}

The first statement adds the event handler for label1’s Paint event. You can use theProperties window to generate this statement and the event handler method body. The twolines of code create a temporary variable by typecasting the sender argument to a Label andusing the Graphics object to paint the text. It just happens that text in a label is printed atoffset 2, 2. By offsetting the second drawing of the text and using a brighter color, we cancreate an embossed effect.

There are a couple of problems with this simplistic approach. If the text wraps or thealignment changes, then this code won’t work. We can fix that by writing some more code toexamine the properties of the label, and compensating for wrapped text or alternate text alignment.

Assuming that you have resolved any problems regarding the layout of the text, you shouldavoid deploying an application containing UserControls that have custom painting behavior(or any custom behavior) implemented as an event handler. If you want custom text effectsfor labels, then subclass the label type and use the new class in the UserControl. When you writecode, consider the question of what class is responsible for this behavior. When you determinethe answer, put the behavior in that class. Read Chapter 9 for examples of creating customcontrols, and inheriting and customizing existing controls.

Transparent UserControl BackgroundChapter 2 proposed that you could create a high-quality graphic background and paint yourcontrols over this background. Blending transparent controls into a graphic background willallow you to create rich user interfaces.

To allow your custom UserControls to be transparent, you will need to set the controlstyle to support a transparent background. This is easily accomplished by calling the SetStylemethod in the UserControl’s constructor, as demonstrated here:

SetStyle(ControlStyles.SupportsTransparentBackColor, true);

Because a UserControl does not support the Color.Transparent BackColor value, you can setthe BackColor to be Transparent after you invoke SetStyle or when you drop the UserControlonto a form. The TransparentForm Form in the TestControls example project demonstrates atransparent UserControl with a balloon in a picture box, floating seamlessly over a watery surface.

Extending UserControls Through InheritanceYou can extend existing UserControls through inheritance. From the Project menu, selectAdd Inherited Control. The Add New Items dialog will open, with the Inherited User Controltemplate selected. After you click OK, the Inheritance Picker dialog (see Figure 8-5) will be

2 5 4 A d v a n c e d C # P r o g r a m m i n g

Page 282: Advanced c sharp programming

displayed. Select one of the controls from the current solution or browse for an additionalDLL assembly. Add the new behavior and state information, and then compile and test yourcustom control.

You can implement a class from scratch and express the UserControl inheritance relationship.Visual Studio .NET will generate the resource file automatically, but it is easier to use theInheritance Picker as just described.

Secondary TopicsThere are some additional capabilities that we relied on to build some of the user controls inthis chapter. We’ll cover these in this section. You will find these topics useful for generalprogramming or for creating custom controls. The two major topics we’ll cover are

� Using BeginUpdate and EndUpdate while loading list controls

� Dynamically positioning and sizing controls

Loading ListBoxesThe strategies that you pick for large tasks can have an impact on the performance of yourapplication. If you load thousands of items in ListBoxes, then users may be caught waitingwhile your application updates. If you load those items one at a time, then the result may beeven worse.

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 5 5

Figure 8-5 Pick a component to inherit from the Inheritance Picker.

Page 283: Advanced c sharp programming

There are a couple of strategies you can use when it comes to loading ListBoxes that willmake your application seem more responsive. You can use BeginUpdate and EndUpdate toprevent the ListBox from repainting each time an item is added, or you can use a backgroundthread to load the items. I will demonstrate both techniques here.

Using BeginUpdate and EndUpdateEarlier, I introduced ListBox.BeginUpdate and ListBox.EndUpdate methods. If you write codethat adds a large number of elements to a ListBox, each added item will force the ListBox toupdate, incurring the overhead of repainting the list each time an element is added. For biglists, this can result in very poor performance.

Instead, every time you load items to a ListBox (or ComboBox) one element at a time, youcan precede loading the items with a call to BeginUpdate and follow the load process withan EndUpdate. You can include a try finally block to ensure that the EndUpdate method isinvoked. The code that follows demonstrates loading 100,000 integer objects to a ListBox.

private void LoadListBox()

{

listBox1.BeginUpdate();

try

{

for(int i=0; i<100000; i++)

listBox1.Items.Add(i);

}

finally

{

listBox1.EndUpdate();

}

}

If this code were to run when a form is being loaded, the user would have to wait on theitems to be loaded. Another alternative is to use a separate thread to load the items. If the userwon’t need all of the items before being allowed to interact with the form containing the ListBox,then we can load the ListBox on a background process.

Using the ThreadPool to Load a ListBoxThere are several ways to use threads in .NET. One of the easiest is to use a thread availablein the ThreadPool. The ThreadPool class contains a pool of threads that you can send workto in the form of a delegate. Define the delegate to perform the work of loading the ListBoxand send the delegate to the ThreadPool.QueueUserWorkItem. Additionally, Windows Formsare not directly thread-safe. You must call the control’s Invoke method to interact with aWindows Forms control on the same thread on which it was created. Listing 8-6 is a bit morecomplex, so line numbers were added for reference.

2 5 6 A d v a n c e d C # P r o g r a m m i n g

Page 284: Advanced c sharp programming

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 5 7

Listing 8-6 Using the ThreadPool to perform background tasks

1: private delegate void Add(int i);

2:

3: private void AddItem(int i)

4: {

5: listBox1.Items.Add(i);

6: Application.DoEvents();

7: }

8:

9: private void LoadListBox(object state)

10: {

11: for( int i=0; i<100000; i++)

12: listBox1.Invoke(new Add(AddItem), new object[]{i});

13: }

14:

15: private void Form1_Load(object sender, System.EventArgs e)

16: {

17: System.Threading.ThreadPool.QueueUserWorkItem(

18: new System.Threading.WaitCallback(LoadListBox));

19: }

To load the ListBox using the ThreadPool class, we need to complete several steps. Thefirst step is to define a delegate type that reflects a procedure we will use to interact withthe Form’s thread. Line 1 defines a new delegate, Add, using a method signature that takesan integer and returns void. In Listing 8-6, lines 3 through 7 define a method that willinteract with the ListBox directly. AddItem inserts a single item at a time and calls DoEventsto help flush the message queue of the Form’s thread. Lines 9 through 13 represent themethod that will run on its own thread. LoadListBox takes a single argument, an object.A method that returns void and takes an object has the same signature as a WaitCallbackdelegate. LoadListBox iterates through a loop and invokes the AddItem method. We uselistBox1.Invoke—Invoke is thread-safe—to ensure that items are actually added to the liston the same thread that the ListBox lives on.

Lines 15 through 19 ask the ThreadPool to perform some work. ThreadPool.QueueUserWorkItem places work in the form of a WaitCallback delegate in the ThreadPoolqueue. The ThreadPool invokes the delegate method using an available Thread or spins up anew one. Either way, we only care that the ThreadPool does its job.

Place a breakpoint on line 12 and start the application. You can open the Threads window—by clicking Debug | Windows | Threads in the IDE—and see that the LoadListBox is runningon a different thread than the Form is running on (see Figure 8-6). If you step through theapplication, you can determine that listBox1.Invoke causes the application to jump to thethread that the Form and ListBox reside on to add items. Most important, the form loads and

Page 285: Advanced c sharp programming

displays very quickly, and the list completes its initialization in the background, making yourapplication appear to be very responsive.

Dynamically Positioning and Sizing ControlsControl extents are defined in two ways. Controls have Top, Left, Width, and Height properties,as well as Location and Size properties. You can modify the extent of a control by directlychanging the Top, Left, Width, or Height property, but you may not modify Location.X andLocation.Y and the Size.Width and Size.Height properties. Location and Size are immutable.

If you want to change the Location.X and Location.Y (the Left-Top) corner of a controlat once, or the Size.Width and Size.Height (which, combined with the Left-Top, describe theBottom-Right), then you will have to create a new instance of a Location or Size structure.

The Location property of a control is a Point structure, and the Size property is actually astructure named “Size.” You construct instances of Location by using the new operator andpassing X and Y values. You construct an instance of the Size structure by using the newoperator and expressing a new value for the Width and Height values. The Rectangles classin the UserControlsExamples.sln demonstrates how to construct an instance of Size and Location.

To change the upper-left position of the ListBox, you could write the following code:

listBox1.Location = new Point(5,5);

If you modify Left, Top, Width, or Height values one at a time, then the control will berepainted for each adjustment. By modifying the Top and Left values at the same time, youwill reduce the number of times the control is repainted.

2 5 8 A d v a n c e d C # P r o g r a m m i n g

Figure 8-6 The Threads window shows the threads currently running in your application.

Page 286: Advanced c sharp programming

SummaryImplementing custom UserControls is a great way to build new controls that are composed ofmany existing controls. UserControls are ideal for building what are commonly referred to asbusiness controls. An example of a business control, the ContactInformation UserControl, wasdemonstrated in this chapter.

In this chapter, you learned how to implement custom UserControls, how to surfaceconstituent properties and events, how to perform custom painting, and how to bind businessobjects to custom UserControls. You also saw that any class that implements the IList interfacecan be bound to controls, and you learned to use the BindingContext to navigate arrays ofthose objects.

Combining custom painting, transparency, aggregation, and inheritance will allow you tolayer in effects, resulting in a diverse toolset of UserControls. For added performance, you canroll in an extra worker thread, as we did to load a large number of items in a ListBox. It is thecombination of these techniques that yields flexible and powerful UserControls that can beused many times.

C h a p t e r 8 : C r e a t i n g U s e r C o n t r o l s 2 5 9

Page 287: Advanced c sharp programming

This page intentionally left blank.

Page 288: Advanced c sharp programming

CHAPTER

9Special Effects Text

261

IN THIS CHAPTER:

Demonstrated Topics

Rapid Control Prototyping

Creating a Class Library

Testing Your Component

Adding the Component to the Toolbox

Creating a Merge Module

Secondary Topics

Creating a Type Converter

Implementing a Type Editor

Applying the Custom Editor with theEditorAttribute

Defining a Windows Forms Designer

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 289: Advanced c sharp programming

2 6 2 A d v a n c e d C # P r o g r a m m i n g

There is a tremendous amount of support for building custom controls in C#. Thischapter continues our discussion of control building in C#. In Chapter 8 we exploredthe UserControl. Now we will explore in detail those aspects you will need to build,

test, and register custom controls.We’ll begin our discussion by building a special effects Label control for Windows Forms,

as this will exercise most of the aspects of control building. In addition, we will explore C#’ssupport for serializing components, creating designers, type converters, and type editors. Theseskills will allow you to use the full range of support for building professional controls in C#that will interact well with Windows Forms applications. (If you are interested in buildingcontrols for ASP.NET, then check out Chapter 14.)

Demonstrated TopicsThe primary purpose of this chapter will be to demonstrate most of what you need to knowabout building, testing, and deploying professional custom controls in an assembly. From theexamples in this chapter, you will be able to do the following:

� Prototype components

� Create a library project for your control

� Use attributes from the ComponentModel namespace

� Test your controls

� Add your controls to the Toolbox

� Create a Merge Module project to share your controls

All of these skills will be demonstrated by building an EffectsLabel control that rendersvarious shadow effects. (If what you are looking for is not listed in these topics, make sureyou check the “Secondary Topics” section later in the chapter. There are several advancedcapabilities demonstrated in that part of the chapter; I just didn’t need them to implement theEffectsLabel.)

Rapid Control PrototypingA convenient way to begin building controls is to prototype them in a Windows Formsapplication. Pick a control that is close to the control you want to customize. Drop thatcontrol onto the form and use the events of the form and the control to rapidly prototype yourcustom control. (Rapid Application Design (RAD) applies to controls too. Controls are definedin a DLL assembly, which is an application. RAD techniques are valid for controls too.)

In Chapter 3 we implemented a DrawShadowText method to create an embossed effectfor the PlayControl. It is likely that we might want to use special text effects in the future.Thus, we will prototype a more permanent solution, and when we have the kinks worked out,we’ll capture our solution in a custom control that can be reused without any future codingafter the control is finished.

Page 290: Advanced c sharp programming

The first step is to pick a control that is similar to the control we ultimately want to create.Picking a close ancestor to inherit from means that we will have to write only a little additionalcode. If we pick too generic an ancestor, then we will have more work to do. The Label controlis perfect for displaying text, so we will use the System.Windows.Forms.Label control as thebasis for our custom control. Paint a Label control on a Form in a Windows Application.(Remember, we are prototyping here. Refer to the TestLabel.sln for a completed example ofthe prototype solution.)

Implement a PaintEventHandler for Custom PaintingThe label has most of the solution captured. If we paint the text for the label a second timeand slightly offset the text when we paint the second time, we can create various effects. Wecan perform custom painting in the label’s PaintEventHandler.

TIPTo create a PaintEventHandler for a label, select the label and click the Events button (the small lightning bolticon) in the Properties window to switch to the Events view. Double-click the Paint event to generate the handler.

The label’s Paint handler has a PaintEventArgs argument that is initialized with theGraphics object representing the label’s device context. We can use GDI+—represented bythe Graphics object—to draw the string the second time at an offset to create the effects. (Youalready know how to do this, to a limited extent, because it was demonstrated in Chapter 3.There are additional considerations for a robust solution, which we will discuss in a moment.)

Pass an Instance of the Control to MethodsWhen we are prototyping, pass the instance of the control you will be inheriting from to anymethods we might need to define. Until the new control is actually created, we will need aninstance of the control we are prototyping with. By passing the control around, rather thanrelying on its existence in the containing form, we can copy the code from the prototypeverbatim, and it should work with relatively minor changes.

Thus far we have a Windows application with a label to prototype our solution. You candraw the Text property a second time using Graphics.DrawString to create the initial effect.It won’t take long before you realize that there is more to it than just writing the text. Let’sproceed by examining the considerations involved in creating a robust solution.

Examining the ProblemOur problem is to create a Label control with special text effects. Here is a complete breakdownof the problems we have to tackle to create a robust solution:

� Text in a label can wrap if the length of the text exceeds the width of the control bounds.

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 6 3

Page 291: Advanced c sharp programming

� The ampersand is the mnemonic that represents the hotkey prefix. If we are using themnemonic, then we have to display the mnemonic rather than the literal ampersand.

� The text can be aligned in several different ways. Our text will have to be displayedwith consideration for the TextAlign property.

� We will have to accommodate special display and layout information.

� We will need a second color and an X and Y offset to create the visual effects.

That should do it.As you can see, there is quite a bit to writing the text that appears in a label. The end result

is worth the effort, as long as we have to pay for that effort only once. The sub-sections thatfollow solve each of the problems described in the preceding list.

Drawing the TextTo draw the string, we can use the Graphics object passed to the Label’s PaintEventHandler,named Paint. We must use the Label’s Text and Font, and we’ll need to determine the Brush,Rectangle, and StringFormat object. Here is the Label’s PaintEventHandler and the supportingDraw method as they are implemented in the TestLabel.csproj.

private void Draw(Label label, Graphics g)

{

Rectangle r = label.ClientRectangle;

r.X -= 1;

r.Y -= 1;

g.DrawString(label.Text, label.Font, Brushes.Black, r, GetFormat(label));

}

private void label1_Paint(object sender,

System.Windows.Forms.PaintEventArgs e)

{

Draw((Label)sender, e.Graphics);

}

(Remember to avoid implementing behaviors directly in event handlers. Writing well-namedmethods facilitates reuse and supports self-documenting code.) What we cannot determine fromthe listing is how the Brush, the Rectangle, and the StringFormat objects are created.

The code looks pretty simple so far. When the code looks simple, it means that we are gettingan appropriate amount of detail in our solution. We’ll spend the rest of this section describinghow to implement the unknown elements: Brush, Rectangle, and StringFormat.

Creating the BrushFor the prototype we can use a Brush from the Brushes class. Brushes contain dozens ofproperties that define a suite of brushes and colors. For the prototype we can use one of thesestandard brushes. When we implement the actual control we can let the user specify a color.From the color we can construct a brush as follows:

Brush brush = new SolidBrush(color);

2 6 4 A d v a n c e d C # P r o g r a m m i n g

Page 292: Advanced c sharp programming

The SolidBrush class is defined in the System.Drawing namespace. You will need to add areference to the System.Drawing.dll assembly and a using statement to the module containinga reference to a brush. The using statement follows:

using System.Drawing;

Creating the RectangleTo ensure that the second time the text is written it is written in the same rectangular regionas the original text, we can use the ClientRectangle of the Label itself. To create a specifictext effect, we need to slightly offset the text the second time it is drawn. A shallow shadoweffect can be created by offsetting the text the second time by X–1 and Y–1, where X and Yrepresent the upper-left position of the ClientRectangle.

Other effects can be created changing the text offset values. Implementing a truly flexiblesolution means that we ultimately let the consumer specify the offsetting values. Whileprototyping, it is sufficient to use literal values.

Creating the StringFormat ObjectThe StringFormat object is the most challenging value we have to derive. The GetFormatfunction called in the fragment (in the earlier section “Drawing the Text”) conceals the hoopswe have to jump through to derive the StringFormat object.

From the bulleted list at the beginning of this section, we have to solve the mnemonic—orhotkey—problem, the text alignment problem, and the layout problem. When we derivesolutions to these problems, we can test the prototype. Assuming everything works correctly,we can create the control.

I believe in the caveat divide et impera, or divide and conquer. The implementation of theGetFormat method contains named methods that describe all of the steps we need to completeto create the StringFormat object. This is essential in ensuring that the second time the text isdrawn, it looks precisely like the first time except for the brush color and the offset appearance.

private StringFormat GetFormat( Label label )

{

StringFormat format = new StringFormat();

SetLineAlignment(label.TextAlign, format);

SetAlignment(label.TextAlign, format);

SetFlags(label.RightToLeft, format);

SetMnemonic(label.UseMnemonic, format);

return format;

}

From the code we can determine that we need to create a StringFormat object. To ensurethat the text is aligned properly, we need to set the StringFormat.LineAlignment and theStringFormat.Alignment properties. This is accomplished by converting the Label.TextAlign—a ContentAlignment enumerated value—to a StringAlignment enumerated value. Unfortunately,StringFormat needs StringAlignment values, but the Label uses ContentAlignment properties.The TextALign property needs to be transposed. Next, we need to set the StringFormat.FormatFlagsproperty and determine whether we are using the ampersand (&) as a mnemonic (a hotkey

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 6 5

Page 293: Advanced c sharp programming

indicator). Listing 9-1 contains the four formatting methods described in GetFormat in thepreceding listing.

Listing 9-1 A numbered listing for SetLineAlignment, SetAlignment, SetFlags, and SetMnemonic

1: private StringFormat GetFormat( Label label )

2: {

3: StringFormat format = new StringFormat();

4: SetLineAlignment(label.TextAlign, format);

5: SetAlignment(label, format);

6: SetFlags(label.RightToLeft, format);

7: SetMnemonic(label.UseMnemonic, format);

8: return format;

9: }

10:

11:

12: private void SetLineAlignment(ContentAlignment TextAlign,

13: StringFormat format)

14: {

15: switch(TextAlign)

16: {

17: case ContentAlignment.BottomLeft:

18: format.LineAlignment = StringAlignment.Far;

19: break;

20: case ContentAlignment.MiddleLeft:

21: format.LineAlignment = StringAlignment.Center;

22: break;

23:

24: case ContentAlignment.TopLeft:

25: format.LineAlignment = StringAlignment.Near;

26: break;

27:

28: case ContentAlignment.BottomCenter:

29: format.LineAlignment = StringAlignment.Far;

30: break;

31:

32: case ContentAlignment.MiddleCenter:

33: format.LineAlignment = StringAlignment.Center;

34: break;

35:

36: case ContentAlignment.TopCenter:

37: format.LineAlignment = StringAlignment.Near;

38: break;

39:

2 6 6 A d v a n c e d C # P r o g r a m m i n g

Page 294: Advanced c sharp programming

40: case ContentAlignment.BottomRight:

41: format.LineAlignment = StringAlignment.Far;

42: break;

43:

44: case ContentAlignment.MiddleRight:

45: format.LineAlignment = StringAlignment.Center;

46: break;

47:

48: case ContentAlignment.TopRight:

49: format.LineAlignment = StringAlignment.Near;

50: break;

51: }

52: }

53:

54: private void SetAlignment(Label label, StringFormat format)

55: {

56: switch(label.TextAlign)

57: {

58: case ContentAlignment.BottomLeft:

59: format.Alignment = StringAlignment.Near;

60: break;

61: case ContentAlignment.MiddleLeft:

62: format.Alignment = StringAlignment.Near;

63: break;

64:

65: case ContentAlignment.TopLeft:

66: format.Alignment = StringAlignment.Near;

67: break;

68:

69: case ContentAlignment.BottomCenter:

70: format.Alignment = StringAlignment.Center;

71: break;

72:

73: case ContentAlignment.MiddleCenter:

74: format.Alignment = StringAlignment.Center;

75: break;

76:

77: case ContentAlignment.TopCenter:

78: format.Alignment = StringAlignment.Center;

79: break;

80:

81: case ContentAlignment.BottomRight:

82: format.Alignment = StringAlignment.Far;

83: break;

84:

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 6 7

Page 295: Advanced c sharp programming

85: case ContentAlignment.MiddleRight:

86: format.Alignment = StringAlignment.Far;

87: break;

88:

89: case ContentAlignment.TopRight:

90: format.Alignment = StringAlignment.Far;

91: break;

92: }

93: }

94:

95: private void SetFlags(RightToLeft rightToLeft, StringFormat format)

96: {

97: if(rightToLeft == RightToLeft.Yes)

98: format.FormatFlags |= StringFormatFlags.DirectionRightToLeft;

99: }

100:

101: private void SetMnemonic(bool UseMnemonic, StringFormat format)

102: {

103: if(UseMnemonic )

104: format.HotkeyPrefix = HotkeyPrefix.Show;

105: }

106:

The code in Listing 9-1 contains two methods that are longer than what I normally prefer.SetAlignment and SetLineAlignment employ long switch statements to map ContentAlignmentto StringAlignment values. StringFormat.Alignment relies on the StringAlignment enumeratedvalues rather than ContentAlignment values. We could try to algorithmically convert betweenContentAlignment and StringAlignment by manipulating the underlying bits, but the result isfragile code. For example, if the CLR is modified to change the underlying bits, then ourcode would break. By referring to the enumerated names, the resultant code is ungainly butmore robust.

SetFlags and SetMnemonic are straightforward methods—although SetMnemonic is amouthful—that demonstrate self-describing code. Instead of writing a comment that describeswhat we are doing, we can employ a well-named method that both performs the action anddescribes it. As a general rule, prefer well-named methods over comments.

Creating a Class LibraryHaving prototyped the control, we are ready to create the class library and convert our prototypecode into a custom control. There are several steps that we need to complete to finish the controland get the most mileage out of our code.

To achieve additional benefits from our code, we can abstract generic behavior from behaviorthat is useful only for the control. This part of the process is highly subjective, but it is worth

2 6 8 A d v a n c e d C # P r o g r a m m i n g

Page 296: Advanced c sharp programming

the effort to find code that might be reusable and to separate it from the custom control. Codethat is abstracted will need to be passed an instance of the control, and code that becomes partof the custom control does not need a reference to the control. We can remove any Labelarguments for methods that are added to the control itself.

Abstract Generic BehaviorYou might easily imagine scenarios where printing that wraps and uses mnemonics canbe used. To avoid having to write those switch statements again, it will be worthwhile toplace SetAlignment and SetLineAlignment in a separate utility class. I also chose to placeSetFlags, SetMnemonic, the GetFormat factory method, and DrawText in a class bythemselves. All of this code can be reused in other contexts.

Because I have described only methods and not fields, all of these methods can beimplemented as static methods. When a class does not need to maintain state, you can usestatic methods—such as described for our utility class—and you won’t have to create aninstance of the class to use the methods.

As the methods that were prototyped in the Windows application will be placed in a class,separate from the custom control itself, we will have to pass in an instance of the Labelcontrol. I have provided a partial listing for the TextEffects utility class, showing the changeto SetMnemonic. (The rest of the code can be found in the EffectsLabel.cs module onwww.osborne.com.)

public class TextEffects

{

public static void SetMnemonic(Label label, StringFormat format)

{

if(label.UseMnemonic)

format.HotkeyPrefix = HotkeyPrefix.Show;

}

}

The rest of the methods that we said would be part of the preceding utility class need thesame changes. Pass in any data the method needs to run, and make the method static.

Define a New ControlDefining a Custom Control is straightforward. When you create a class library project, theClass Library template will add a class module. By default there will be a class in the module;rename the class to the name we will use for our control and express the inheritance relationshipin the class header.

You can inherit from the Control class if you want to create a completely new control. Inour example, we are building a custom effects label based on the System.Windows.Forms.Labelcontrol, so we will inherit from the Label control. We can indicate that we are inheriting fromthe Label using the namespaces, or we can add a using statement to the module and shorten

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 6 9

Page 297: Advanced c sharp programming

the class header. Here are both forms. The first example uses the namespace names in theclass header, and the second demonstrates the using statement and the shortened class header.

public class EffectsLabel : System.Windows.Forms.Label

or

using System.Windows.Forms;

public class EffectsLabel : Label

NOTEIf we reference a namespace, then we need to add a reference to the assembly that contains that namespace.Class libraries do not reference System.Windows.Forms.Dll by default; hence we’ll need to add a referenceto it for our custom control.

Either form works—namespaces are used for organization, but become ungainly in codeif you need to write them many times. If you have two identical names, then you can use thenamespaces to clarify the membership of those names.

After we have defined the inheritance relationship, we need to add fields, properties, events,methods, and any other necessary members to our custom control. The complete code isprovided in Listing 9-2. For now we will explore the individual elements.

Invoking Base Class ConstructorIf we provide a constructor for our control, then we need to invoke the inherited base classconstructor. The EffectsLabel has several styles that we will define; thus, we will use theconstructor to set the default label style and invoke the base class constructor.

public EffectsLabel() : base()

{

Style = DrawStyle.Shadowed;

}

Constructors have no return type and have the same name as the class. The base classconstructor is invoked by adding a colon followed by the keyword base in the constructorheader. Then, we can add any additional code to initialize our class. The Style propertywas introduced as part of the custom behavior, so I will demonstrate the code that supportschanging the style next.

Adding Properties and MethodsControls are just classes. They just happen to be classes that inherit some fundamentalcapabilities that allow them to be displayed in Windows Forms or Web Forms and to respondto user inputs. This basic capability is inherited from the Control class. For the most part, allwe do is add members to describe the class’ behavior and appearance with fields, properties,methods, and events.

To support the notion of a Style for the EffectsLabel, we can implement an enumerationthat describes the style with text rather than integers; enumerations are more expressive. And,

2 7 0 A d v a n c e d C # P r o g r a m m i n g

Page 298: Advanced c sharp programming

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 7 1

we need to implement a field to store the Style in and a property so that the Propertieswindow can display the Style at design time. Here is the additional code:

public class EffectsLabel : Label

{

public enum DrawStyle{Custom, Shadowed, Engraved, Embossed};

private DrawStyle style;

private Point offset;

public EffectsLabel() : base()

{

Style = DrawStyle.Shadowed;

}

public DrawStyle Style

{

get

{

return style;

}

set

{

style = value;

DrawStyleChanged();

Invalidate();

}

}

protected virtual void DrawStyleChanged()

{

switch(style)

{

case DrawStyle.Shadowed:

offset = new Point(-1, -1);

break;

case DrawStyle.Embossed:

offset = new Point(-1, -2);

break;

case DrawStyle.Engraved:

offset = new Point(1, 2);

break;

case DrawStyle.Custom:

offset = new Point(0, 0);

break;

}

}

}

Page 299: Advanced c sharp programming

The enumeration DrawStyle will let the user pick from named styles. The underlying fieldthat stores the style is listed after the numerator. The constructor follows the enumerationand the field. The public property Style (note the use of the convention of camel-casingfields and Pascal-casing properties) will let the user change the style at design time. Thegetter simply returns the underlying field value, and the setter performs the steps necessaryto update the control.

The setter changes the field value and calls DrawStyleChanged. DrawStyleChanged usesa case statement to define the offsetting values based on the DrawStyle selected. Finally, thecontrol is invalidated, causing the OnPaint method to be invoked. Use Invalidate instead ofcalling OnPaint directly to repaint the control. Using Invalidate will help avoid flickering bysending a message to the control to paint changed regions of the control, and the messagequeue can manage the number of times the control is actually repainted.

Adding the Finishing TouchThere are several attributes we can use to add fit and finish to our custom control. Thissection provides the complete listing for the EffectsLabel control, and we will describe theattributes incorporated into the code, as well as other details that yield the finished control.

Listing 9-2 The complete listing for the EffectsLabel control

1: /// <summary>

2: /// Custom text effects for Windows Forms

3: /// </summary>

4: [ToolboxBitmap(

5: @"EffectsLabel2.bmp")]

6: //[ToolboxBitmap(typeof(Label))]

7: public class EffectsLabel : Label

8: {

9: public enum DrawStyle{Custom, Shadowed, Engraved, Embossed};

10: private DrawStyle style;

11: private Point offset;

12: private Color overlayColor = Color.Black;

13:

14: public EffectsLabel() : base()

15: {

16: Style = DrawStyle.Shadowed;

17: }

18:

19: [Description("Describes the appearance of the label's text."),

20: Category("Appearance")]

21: public DrawStyle Style

22: {

23: get

24: {

25: return style;

26: }

2 7 2 A d v a n c e d C # P r o g r a m m i n g

Page 300: Advanced c sharp programming

27: set

28: {

29: style = value;

30: DrawStyleChanged();

31: Invalidate();

32: }

33: }

34:

35: [Description(

36: "The ForeColor to create the shadow effect."),

37: Category("Appearance")]

38: public Color OverlayColor

39: {

40: get

41: {

42: return overlayColor;

43: }

44: set

45: {

46: overlayColor = value;

47: Invalidate();

48: }

49: }

50:

51: [Description("Change this value directly to create a custom effect.)"),

52: Category("Appearance")]

53: public Point Offset

54: {

55: get

56: {

57: return offset;

58: }

59: set

60: {

61: Style = DrawStyle.Custom;

62: offset = value;

63: Invalidate();

64: }

65: }

66:

67: protected virtual void DrawStyleChanged()

68: {

69: switch(style)

70: {

71: case DrawStyle.Shadowed:

72: offset = new Point(-1, -1);

73: break;

74: case DrawStyle.Embossed:

75: offset = new Point(-1, -2);

76: break;

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 7 3

Page 301: Advanced c sharp programming

2 7 4 A d v a n c e d C # P r o g r a m m i n g

77: case DrawStyle.Engraved:

78: offset = new Point(1, 2);

79: break;

80: case DrawStyle.Custom:

81: offset = new Point(0, 0);

82: break;

83: }

84: }

85:

86: protected override void OnPaint(PaintEventArgs e)

87: {

88: base.OnPaint(e);

89: Draw(e.Graphics);

90: }

91:

92: private void Draw(Graphics g)

93: {

94: TextEffects.DrawText(this, g, new SolidBrush(overlayColor),

95: offset.X, offset.Y);

96: }

97:

98: }

Auto Documentation FeatureLines 1 through 3 in Listing 9-2 demonstrate the three hash mark (///) comments that can beused to generate documentation for your code. The Tools | Build Comment Web Pages menuoption will incorporate comments tagged with three hash marks into HTML help documentation.Figure 9-1 shows the comment on line 2 of Listing 9-2 incorporated into the help documentationfor the EffectsLabel.

In addition to using the three hash marks for documenting your code, you can use specialXML tags to create other documentation features. Table 9-1 lists the comment tags forgenerating documentation.

Defining the CategoryThe Properties window organizes properties either alphabetically or by category. This is tohelp consumers find properties at design time. The CategoryAttribute is demonstrated severalplaces in Listing 9-2. Line 20 demonstrates the CategoryAttribute. We are indicating that theDrawStyle is categorized as an Appearance property. If you click the Categorized button in theProperties window, then the properties for the selected control will be displayed by category, asshown in Figure 9-2.

If the string used to initialize the CategoryAttribute is unique, then a new category will becreated in the Properties window.

Providing a DescriptionAt the bottom of the Properties window is a description of the selected property. This valuedidn’t get there by accident. The DescriptionAttribute takes a string argument. The text used

Page 302: Advanced c sharp programming

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 7 5

Figure 9-1 The generated documentation, created in part by reading the comments tagged withthree hash marks

Comment Tags Description<c> Indicates that text in a description refers to code.

<code> Use this tag to indicate that multiple lines of comments refer to code.

<example> Combine with the <code> tag to indicate that a comment represents example code.

<exception> Used to specify which exceptions a class can throw.

<include> Use <include> to refer to comments in an external file.

<list> This tag allows you to specify a heading for a table or list. Use <listheader> and<item> to build the elements of the list or table.

<para> Provide structure to comments by separating line text into paragraphs.

<param> Provide reference for a named parameter.

<paramref> Use <paramref> to indicate that a word is a parameter.

<permission> Supports documenting the permission level for a member.

<remarks> Use <remarks> to provide an overview of a type.

<returns> Supports providing a description of the return type of a method.

Table 9-1 Comment XML Document Tags for Generating HTML Documentation for Your Code

Page 303: Advanced c sharp programming

2 7 6 A d v a n c e d C # P r o g r a m m i n g

Comment Tags Description<see> Creates a cross-reference for a document item.

<seealso> Adds an item to a See Also section of the documentation.

<summary> <remarks> is used to provide information about a type, and <summary> is used toprovide information about members of that type.

<value> Supports providing descriptions for properties.

Table 9-1 Comment XML Document Tags for Generating HTML Documentation for Your Code(continued)

Figure 9-2 The Properties window with the EffectsLabel properties ordered by category

Page 304: Advanced c sharp programming

to initialize the DescriptionAttribute will be displayed at the bottom of the Properties windowunless you deselect the Description context menu item.

If you apply multiple attributes, as we did in several places in Listing 9-2, then you canseparate the attributes with commas. Here is an excerpt from Listing 9-2 that demonstratestwo attributes applied to the DrawStyle property.

19: [Description("Describes the appearance of the label's text."),

20: Category("Appearance")]

Adding a Custom Bitmap for Your ControlIf you want your control to have a custom bitmap, then you can apply the ToolboxBitmapAttributeto the class. This attribute can refer to an external bitmap file or use type information torequest an existing icon for an existing control.

[ToolboxBitmap(@"c:\temp\EffectsLabel2.bmp")]

//[ToolboxBitmap(typeof(Label))]

The first statement demonstrates how to associate an external bitmap with our control (seeFigure 9-3), and the second (commented) statement demonstrates how to request the typeinformation for the Label control. If we use the second attribute statement, then ourEffectsLabel will have the same icon as the Label control.

NOTENotice the use of the @ symbol in the ToolboxBitmapAttribute statement. The \ (slash character) is used forescape text sequences. (Recall that we used it to create the © symbol for our About box.) To actually get thebackslash in a string, you will need to use two backslashes \\ or you can use a single one and the @ characterto indicate that the string should be interpreted literally rather than as the processing escapes sequences.

To create a custom 16×16 pixel bitmap for the EffectsLabel, you can use the Project | AddNew Item menu and add a Bitmap to the solution. This step will automatically open the Imageeditor, which will allow you to draw a custom bitmap. The Image editor works like the Paintprogram that is provided with Windows.

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 7 7

Figure 9-3 The custom EffectLabel2.bmp—zoomed—in the Toolbox

Page 305: Advanced c sharp programming

Testing Your ComponentThe next important step in the macro process is to test your custom control. You will want tocontrive some unit tests that exercise all code paths for you custom control. In systems, unittesting needs to be done for every class and method within that class. For a control, a singleclass (or a few classes) represents the entire assembly; you will still need to test every class,but the process is shorter, as there is less to test.

An easy way to test your custom control is to add an application to the solution containingthe control. If the control is a Windows Forms control, then add a Windows application to thesolution. Add a project reference to the control in your Windows application, and programmaticallycreate and invoke methods that exercise the behavior of your custom control. The code listingthat follows creates the text shown in Figure 9-4.

private void TestLabel_Load(object sender, System.EventArgs e)

{

CustomControl.EffectsLabel effectsLabel = new

CustomControl.EffectsLabel();

effectsLabel.Text = "Dynamically Created!";

effectsLabel.AutoSize = true;

effectsLabel.Style = CustomControl.EffectsLabel.DrawStyle.Embossed;

effectsLabel.ForeColor = Color.White;

effectsLabel.OverlayColor = Color.Black;

effectsLabel.Font = new Font("Times new Roman", 24);

Controls.Add(effectsLabel);

}

Code to test a Windows Forms control can be placed in the Load event handler for aForm. The preceding code tests many of the code paths, but you need a good strategy fortesting all code paths. There are some changes you can make to the EffectsLabel class thatwill facilitate this process.

Implementing a Test FunctionThe first change is to make the test method part of the EffectsLabel class. Implement a staticmethod called Test and invoke that method. Because we are testing a Windows Forms control,we will need to pass in the Controls collection we want to add our control to, or we can returnan instance of the custom control and make the addition to the Controls collection externally.

2 7 8 A d v a n c e d C # P r o g r a m m i n g

Figure 9-4 A dynamically created instance of your custom control provides a convenient way tostep through the code and test every code path.

Page 306: Advanced c sharp programming

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 7 9

The following addition to the EffectsLabel class makes our custom control self-contained,including testability.

public static void Test(ControlCollection Controls)

{

EffectsLabel effectsLabel = new EffectsLabel();

effectsLabel.Text = "Dynamically Created!";

effectsLabel.AutoSize = true;

effectsLabel.Style = DrawStyle.Embossed;

effectsLabel.ForeColor = Color.White;

effectsLabel.OverlayColor = Color.Black;

effectsLabel.Font = new Font("Times new Roman", 24);

Controls.Add(effectsLabel);

}

To test our control, we simply need to invoke EffectsLabel.Test, passing theControlCollection of the owning ContainerControl. This will usually be a Form’s Controlsproperty, but you could pass the Controls property of any derivative of the ContainerControl,like a Panel control.

Implementing Trap BehaviorPresident Reagan used to say “trust but verify” when referring to the Russians at the end ofthe Cold War. This sage advice is wise counsel for software development, too. Trust thatyour code works because you’re smart—but verify, too. Verifying that your code works willhelp avoid embarrassing situations.

You can verify that all code paths work to some degree by simply exercising those paths.Exercising a path simply means that you compile and test the code, providing test data thattakes all possible branches within your code. This won’t test your code for all possible states,but it will help you ensure that there are no obvious, embarrassing defects. A good techniquefor ensuring that you have exercised all states is to implement a trap.

A trap is a method that is called that halts processing when a code path has been executed.You can implement a trap simply by calling the Debug.Assert method with the argumentFalse. If you place a trap at each code branch and comment out the traps each time one istriggered, you have a record verifying that all branches have been tested. Remove all of thecomments to reset the traps if you need to run the tests after your component is updated. Hereis a statement that demonstrates the technique. (This statement assumes that you have includeda using statement with the System.Diagnostics namespace in the module containing the trap.)

Debug.Assert(false);

For example, if we wanted to ensure that all of the DrawStyles yield the right visualeffect, we could place a trap for each case in the DrawStyleChanged method. By visuallyinspecting the resultant effect of each DrawStyle and commenting out the trap when the

Page 307: Advanced c sharp programming

appearance is as desired, we can ensure that all styles paint correctly. Here is the revision tothe DrawStyleChanged method as described.

protected virtual void DrawStyleChanged()

{

switch(style)

{

case DrawStyle.Shadowed:

Debug.Assert(false);

offset = new Point(-1, -1);

break;

case DrawStyle.Embossed:

Debug.Assert(false);

offset = new Point(-1, -2);

break;

case DrawStyle.Engraved:

Debug.Assert(false);

offset = new Point(1, 2);

break;

case DrawStyle.Custom:

Debug.Assert(false);

offset = new Point(0, 0);

break;

}

}

When each trap is thrown and you are satisfied with the result, simply comment out the trapin the source code and click the Ignore button in the Assert dialog. Because we are using theDebug.Assert method, the trap code will automatically be turned off when we compile ourcontrol assembly in release mode.

It is easier to test this kind of code before you install the control into the Toolbox. Youwill save time and effort by testing the control programmatically. (Of course you can add thecode to the Toolbox before testing if you want to.) Ultimately, when you are satisfied withthe control you will want to add it to the Toolbox. We will do that in the next section.

Adding the Component to the ToolboxAfter you have created your custom control, you will want to incorporate the control into theToolbox. This step is accomplished by selecting Tools | Customize Toolbox and browsing tothe assembly containing the custom control.

You can add additional tabs to the Toolbox through the Toolbox context menu and dragyour controls to their own tabs.

After you have added your control to the Toolbox, you can drag and drop instances of thecontrol to Forms. When you add a control to your application from the Toolbox, an instance

2 8 0 A d v a n c e d C # P r o g r a m m i n g

Page 308: Advanced c sharp programming

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 8 1

of the control is added to the Form and a reference to the assembly is added to the list ofReferences in the solution.

If you modify your control, then you may need to Reset the Toolbox and re-add themodified assembly to the Toolbox. (If changes to the control do not seem to be reflected inyour application, then remove the reference to the control’s assembly and Reset the Toolbox,adding the updated list of assemblies referenced by the Toolbox.)

Creating a Merge ModuleThus far we have built and tested a custom control and installed it in our Toolbox. Supposeyou want to distribute your control to other developers. To reliably distribute your controlwith your applications, you will need to create a Merge Module Project.

Merge Module Projects are defined as template projects in Visual Studio .NET. Therecommended process is to define a Merge Module Project for every custom control and includeall of the files, resources, registry entries, and setup logic necessary to install your customcontrol. Merge Modules contain version information for a component, ensuring that componentsare not removed when other application assemblies have dependencies on those components. Forthis reason, a new Merge Module should be created for every version of your component after aMerge Module has been added to an Installer file. Merge Modules cannot be installed directly;a Merge Module must be installed as part of a Windows Installer (.msi) file.

Follow the numbered steps provided next to add a Merge Module Project to the solutioncontaining the Custom Control, add the output from the CustomControl project to the MergeModule, and create a Windows Installer.

1. Select File | Add Project | New Project and select the Merge Module Project templatefrom the Add New Project dialog. (The Merge Module Project is a template in theSetup and Deployment Projects folder.)

2. Provide a meaningful name for the Merge Module and click OK. (I usedCustomControls.msm for the EffectsLabel assembly.)

3. Select the Merge Module Project in the Solution Explorer, and click Project | Add |Project Output to open the Add Project Output Group dialog (shown in Figure 9-5).

4. Repeat step 1. This time, select the Setup Project template.

5. Select the Setup Project in the Solution Explorer, and select Project | Add | MergeModule. The Merge Modules explorer dialog will be displayed. Navigate to theCustomControls.msm Merge Module and click OK.

6. When the Setup Project is compiled, the Windows Installer, setup, and .ini files will becopied or created in the Setup Project folder.

The primary output from the Setup Project is the setup.msi file. This file contains theapplication files, registry settings, dependent files like our CustomControl.dll, andinstructions for installing these files.

Page 309: Advanced c sharp programming

2 8 2 A d v a n c e d C # P r o g r a m m i n g

Secondary TopicsThere are several advanced topics that facilitate creating advanced custom controls. We didn’tnecessarily need these capabilities for the EffectsLabel, but you will find them useful forcreating a professional fit and finish. To demonstrate the topics in this section of the chapter,I have included several additional components for showing how to

� Implement the IConvertible interface for run-time type conversion and demonstrateoperator overloading

� Create a Type Converter and apply the convert using the TypeConverterAttribute

� Create a custom Type Editor

� Implement the IDesigner interface

These skills will be used to create a Shape control, a gradient text label, and a control thatrepresents a complex number. The last control was created to demonstrate a customTypeConverter but is not especially useful as a control.

Creating a Type ConverterType converters let data be represented in more than one logical way. For example, it makessense to represent an integer as both a numeric value and a string value. When you want to

Figure 9-5 The Add Project Output Group is used to add the output from our control assembly tothe Merge Module Project.

Page 310: Advanced c sharp programming

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 8 3

perform arithmetic operations you need the integer, and when you want to display the valueyou need a string.

The Common Language Runtime provides two ways to convert types. The IConvertibleinterface requires that you implement methods that convert between simple data types, suchas between the integer and string. The IConvertible interface is useful only at run-time, and itthrows an exception if it doesn’t make sense to convert between two types. (Remember, youmust implement every method defined by an interface.) An example would be trying toconvert the string “Hello World!” to a DateTime value.

The second way to convert between types is to derive a TypeConverter class. TheTypeConverter is a bit more complex to implement but supports converting betweenmore complex types. For example, we can use a TypeConverter to provide a suitablerepresentation for an Image as string. This is the role of the ImageConverter definedin the System.ComponentModel namespace.

In general, the IConvertible interface is faster because you are invoking methods directly,but it is less flexible and powerful and is supported only at run time. The TypeConverter isslower because it relies on Reflection (which is slow because it relies on dynamic bindingand method invocation) but supports design time and run-time conversion and is significantlymore flexible than the IConvertible interface. Implement IConvertible to convert betweenexisting .NET types. Implement a TypeConverter to convert between new types andrepresentative types.

The two subsections that follow demonstrate how to implement the IConvertible interfaceand a TypeConverter.

Implementing IConvertibleClasses that implement IConvertible will let consumers convert between the type representedby the class and the types named by the IConvertible interface. IConvertible does not supportconverting back to the original type, because there is no way to know in advance what typesmight be converted from. If you define a class that implements IConvertible, then you mustimplement GetTypeCode, ToBoolean, ToByte, ToChar, ToDateTime, ToDecimal, ToDouble,ToInt16, ToInt32, ToInt64, ToByte, ToSingle, ToString, ToType, ToUInt16, ToUint32,and ToUInt64.

TIPThe System.Convert class implements static methods that support converting between an argument type andthe type named by the method. Use the Convert class for general type conversions at run time.

To demonstrate the IConvertible interface, we implement a class that represents a complexnumber. A complex number is a number comprised of a real and an imaginary part. Whentwo complex numbers are added, the real parts are added and the imaginary parts are added,yielding the new number.

Implementing a complex number will reasonably require support for converting betweenthe values that represent the complex number and a string. We also have to implement some

Page 311: Advanced c sharp programming

2 8 4 A d v a n c e d C # P r o g r a m m i n g

operators to support arithmetic operations, and we might elect to convert a complex numberto an integer, simply truncating the imaginary part. We can start with a struct that implementsa Complex number. We will use the struct shown in Listing 9-3 to demonstrate how toimplement the IConvertible interface and alternately to demonstrate the TypeConverter.

Listing 9-3 Defining a Complex number struct that implements the IConvertible interface.

1: public struct Complex : IConvertible

2: {

3: private int r;

4: private int i;

5:

6: public Complex( int r, int i )

7: {

8: this.r = r;

9: this.i = i;

10: }

11:

12: public override string ToString()

13: {

14: return i>=0 ? string.Format("{0} + {1}i", r, i) :

15: string.Format("{0} - {1}i", r, i * -1);

16: }

17:

18: private static Complex op_Add(Complex lhs, Complex rhs)

19: {

20: return new Complex(lhs.r + rhs.r, lhs.i + rhs.i);

21: }

22:

23: public static Complex operator+(Complex lhs, Complex rhs)

24: {

25: return op_Add(lhs, rhs);

26: }

27:

28: private static Complex op_Subtract(Complex lhs, Complex rhs)

29: {

30: return new Complex(lhs.r - rhs.r, lhs.i - rhs.i);

31: }

32:

33: public static Complex operator-(Complex lhs, Complex rhs)

34: {

35: return op_Subtract(lhs, rhs);

36: }

37:

Page 312: Advanced c sharp programming

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 8 5

38: TypeCode IConvertible.GetTypeCode()

39: {

40: return TypeCode.Object;

41: }

42:

43: private void ThrowException(string message)

44: {

45: throw new InvalidCastException(message);

46: }

47:

48: bool IConvertible.ToBoolean(IFormatProvider provider)

49: {

50: ThrowException("conversion to Boolean not supported");

51: return false;

52: }

53:

54: byte IConvertible.ToByte(IFormatProvider provider)

55: {

56: ThrowException("conversion to byte not supported");

57: return 0;

58: }

59:

60: char IConvertible.ToChar(IFormatProvider provider)

61: {

62: ThrowException("conversion to char not supported");

63: return ' ';

64: }

65:

66: DateTime IConvertible.ToDateTime(IFormatProvider provider)

67: {

68: ThrowException("conversion to DateTime not supported");

69: return DateTime.Now;

70: }

71:

72: decimal IConvertible.ToDecimal(IFormatProvider provider)

73: {

74: return (decimal)r;

75: }

76:

77: double IConvertible.ToDouble(IFormatProvider provider)

78: {

79: return (double)r;

80: }

81:

82: short IConvertible.ToInt16(IFormatProvider provider)

Page 313: Advanced c sharp programming

2 8 6 A d v a n c e d C # P r o g r a m m i n g

83: {

84: return (short)r;

85: }

86:

87: int IConvertible.ToInt32(IFormatProvider provider)

88: {

89: return (int)r;

90: }

91:

92: long IConvertible.ToInt64(IFormatProvider provider)

93: {

94: return (long)r;

95: }

96:

97: sbyte IConvertible.ToSByte(IFormatProvider provider)

98: {

99: ThrowException("conversion to sbyte not supported");

100: return (sbyte)0;

101: }

102:

103: float IConvertible.ToSingle(IFormatProvider provider)

104: {

105: return (float)r;

106: }

107:

108: string IConvertible.ToString(IFormatProvider provider)

109: {

110: return ToString();

111: }

112:

113: object IConvertible.ToType(Type type, IFormatProvider provider)

114: {

115: return this.GetType();

116: }

117:

118: ushort IConvertible.ToUInt16(IFormatProvider provider)

119: {

120: return (ushort)r;

121: }

122:

123: uint IConvertible.ToUInt32(IFormatProvider provider)

124: {

125: return (uint)r;

Page 314: Advanced c sharp programming

126: }

127:

128: ulong IConvertible.ToUInt64(IFormatProvider provider)

129: {

130: return (ulong)r;

131: }

132:

133: [Conditional("DEBUG")]

134: public static void Test()

135: {

136: IConvertible c = new Complex(1,1);

137: Debug.WriteLine(c.GetTypeCode().ToString());

138: try{Debug.WriteLine(c.ToBoolean(null));}

139: catch(InvalidCastException e){Debug.WriteLine(e.Message);}

140: // test each method in a try catch block

141: }

142: }

The IConvertible interface defines the methods described in the first paragraph of thissection. You have to provide an implementation for every interface member, but an appropriateimplementation might be to throw an exception for a type we don’t want to convert to. Lines97 to 101 of Listing 9-3 demonstrate throwing an exception when a user tries to convert ourComplex struct to a signed byte.

IConvertible methods are called by the System.Convert class. All of the members of Convertare static. For example, to Convert a Complex to an integer, you can write

Complex c = new Complex(1, 3);

Convert.ToInt32(c);

The Convert class will invoke the Complex method that implements IConvertible.ToInt32.As mentioned, the methods in the IConvertible interface are only usable at run time. RAD

tools like Visual Studio .NET have a design time aspect, too. To support converting types atdesign time, we can implement a TypeConverter. The next section uses the Complex struct todemonstrate design time type conversion.

Implementing a TypeConverterThe TypeConverter class is used to support converting between types. A common conversionis one between an object and a string representation of an object. An example we encounteredearlier in the chapter is the ImageConverter that displays text in the absence of an Image foran Image property.

This section demonstrates a custom TypeConverter, how to associate the TypeConverterwith a type using the TypeConverterAttribute, and how to use the TypeDescriptor class toconvert between two types supported by the TypeConverter.

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 8 7

Page 315: Advanced c sharp programming

2 8 8 A d v a n c e d C # P r o g r a m m i n g

Defining the ComplexConverter ClassThe TypeConverter base class introduces several methods that can be overloaded, dependingon the kind of conversion behavior you want to support. Unlike the IConvertible interface,the TypeConverter is supported at design time and run time, and the TypeConverter supportsconverting back and forth between types. For instance, our converter supports converting astring to a Complex and a Complex back to a string.

Listing 9-4 demonstrates a TypeConverter for the Complex struct. After Listing 9-4, I willdescribe the ComplexConverter class and demonstrate how to associate a TypeConverterwith a class using the TypeConverterAttribute.

Listing 9-4 Implementing a TypeConverter for the Complex struct

1: public class ComplexConverter : TypeConverter

2: {

3: public override bool CanConvertFrom(ITypeDescriptorContext context,

4: Type sourceType)

5: {

6: return sourceType == typeof(string) ? true :

7: base.CanConvertFrom(context, sourceType);

8: }

9:

10: public override object ConvertFrom(ITypeDescriptorContext context,

11: CultureInfo culture, object value)

12: {

13: if (value is string)

14: {

15: string[] v = ((string)value).Split(new char[] {'+', '-', 'i'});

16: return ((string)value).IndexOf('-') > 0 ?

17: new Complex(int.Parse(v[0]), int.Parse(v[1]) * -1) :

18: new Complex(int.Parse(v[0]), int.Parse(v[1]));

19: }

20: return base.ConvertFrom(context, culture, value);

21: }

22:

23: public override object ConvertTo(ITypeDescriptorContext context,

24: CultureInfo culture, object value, Type destinationType)

25: {

26: if (destinationType == typeof(string))

27: {

28: return value.ToString();

29: }

Page 316: Advanced c sharp programming

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 8 9

30: return base.ConvertTo(context, culture, value, destinationType);

31: }

32:

33: public override bool CanConvertTo(ITypeDescriptorContext context,

34: Type destinationType)

35: {

36: return destinationType == typeof(string) ? true :

37: base.CanConvertTo(context, destinationType);

38: }

39: }

The ComplexConverter implements conversion between a string and a Complex value. (Recallthat you can convert between more complex types, too, as illustrated by the ImageConverterimplemented in the CLR.) ComplexConverter implements CanConvertFrom, ConvertFrom,ConvertTo, and CanConvertTo. The methods prefixed with Can answer the questions indicatingwhether a conversion for a specific type is supported. The methods without the Can prefixactually perform the conversions.

TIPLine 7 of Listing 9-4 demonstrates how to invoke methods or refer to base class members by using thebase keyword.

CanConvertFrom on lines 3 through 6 of Listing 9-4 performs a dynamic type evaluation.If the sourceType argument on line 4 is a string, then the method returns True; otherwise, thecode passes on responsibility to the parent TypeConverter class.

Lines 10 through 21 of Listing 9-4 implement the overridden ConvertFrom method.Line 15 uses the string.Split method to determine whether the string is correctly formatted.Correctly formatted strings for the Complex type are in the following format: number ±imaginaryi. The piece represented by number is the real number part, and the piece presentedby imaginary is affixed with an i suffix. Lines 17 and 18 demonstrate the int.Parse methodthat is used to clean up the string array returned by Split and to construct a new Complexobject.

Lines 23 through 31 of Listing 9-4 implement the ConvertTo method. We implemented anoverridden Complex.ToString method that we can use (see line 28 to convert a Complex to astring). Finally, the CanConvertTo method implemented on lines 33 through 38 returns Trueif we are trying to convert to a strng representation of the Complex type.

There are additional capabilities that can be overridden in a custom TypeConvert. Referto the Visual Studio .NET help documentation for the TypeConverter class defined in theSystem.ComponentModel namespace.

Using the TypeConverterAttributeApplying a custom TypeConverter is done by associating the converter with the type itconverts using the TypeConverterAttribute. To associate the ComplexConverter with the

Page 317: Advanced c sharp programming

2 9 0 A d v a n c e d C # P r o g r a m m i n g

Complex struct, add an attribute to the struct defined in Listing 9-3. The following codedemonstrates the attribute immediately preceding the struct header in Listing 9-3.

[TypeConverter(typeof(ComplexConverter))]

public struct Complex : IConvertible

You will need to add a using statement that refers to the System.ComponentModel namespace,and the ComplexConverter class will need to be accessible to the Complex struct. Both theComplex struct and the ComplexConverter are implemented in the imaginary.cs module inthe ComplexControl.csproj available for download from the www.osborne.com website.

TIPAll code from this chapter will be available online at http://www.softconcept.com/books/sourceand http://www.osborne.com.

Run-Time Conversion with the TypeDescriptor ClassApplying the TypeConverterAttribute to a class ensures that type conversion will occur atdesign time. For example, deleting an image will set an image property back to the text“(None)” because the ImageConverter is part of the CLR.

After we associated the ComplexConverter with the Complex struct, design-time supportwill exist for Complex properties too. If you want to perform run-time type conversion usinga TypeConverter, then you need to request the TypeConverter using the TypeDescriptorclass, instead of creating the generalized TypeConverter directly.

The code fragments that follow demonstrate how to dynamically request a TypeConverterfor the Complex struct and perform conversions to and from a string.

CustomControl.Complex c = new CustomControl.Complex(2, 3);

string s = "1+1i";

if( TypeDescriptor.GetConverter(c).CanConvertTo(typeof(string)) )

s = TypeDescriptor.GetConverter(c).ConvertToString(c);

CustomControl.Complex d =

(CustomControl.Complex)TypeDescriptor.GetConverter(c).ConvertFrom(s);

The first statement creates an instance of the Complex type. The second statement defines astring that is in a format consistent with a Complex number. TypeDescriptor.GetConverterreturns the right kind of converter for the type passed to GetConverter. CanConvertTo returnsa Boolean indicating whether the conversion is supported. We know it is in this instance, andthe conversion is performed. The last statement converts the string back to a Complex struct.

TypeConverters use Reflection to perform type conversions and, as a result, will be slowerthan conversions performed by the IConvertible interface. Recall that the IConvertibleinterface will allow you to convert a type to a new type but not back again. Additionally, theTypeConverter supports design time conversion support, which is essential to implementingcomponents that behave in an intuitive way in Visual Studio .NET, as is illustrated by thePoint, Rectangle, and Image types.

Page 318: Advanced c sharp programming

Implementing a Type EditorThe previous section demonstrated the type converter. If you have been following along withthe sample code, then you know that you can define the values for a Point property by enteringa comma-delimited string in any property that represents a Point. The TypeConverter is oneaspect of implementing professional, custom controls.

Have you gotten around to wondering how those small icons are painted next to propertiesin the Properties window? Or how combo boxes, color drop-down lists, or dialog-based propertyeditors are implemented? The answer is the UITypeEditor. The UITypeEditor (for User InterfaceType Editor) is the base class for implementing editors in .NET.

If you define a custom control that has properties that cannot be managed by simple stringvalues—even if you have a TypeConverter—then you need a UITypeEditor. You need aUITypeEditor if you simply want to provide a cool way to implement editing properties atdesign time.

This section will demonstrate how to implement a dialog-based UITypeEditor, a drop-downeditor based on the NumericUpDown control, and a UITypeEditor that demonstrates custompainting in the Properties window. We will accomplish our objectives by implementing aGradientLabel. (Our GradientLabel will be a custom control that paints the label using agradient brush.) We will display a sample of the output from our label in the Text property byimplementing a GradientEditor. And, we will use two versions of an AngleEditor to demonstratethe drop-down NumericUpDown editor and a dialog-based editor. The AngleEditor will allowus to express the angle used to describe the GradientBrush.

Creating the GradientEditorOften it is useful to provide a visual clue demonstrating what role a property plays in a customcontrol. For example, if you have a Shape control, then it might be useful to describe an editorthat provides a visual cue illustrating what the shape will look like. This kind of feature addsrichness to the IDE and makes your controls appear more professional.

A GradientLabel control is implemented in the CustomControl.csproj class library. Thecontrol demonstrates the painting capabilities of a UITypeEditor. (The GradientEditor isdifficult to see in Figure 9-6, but it is represented by the boxed-in area with the “T” printedin a green and white gradient brush. In this instance you may have to take my word for it.)

Having established the goal of our UITypeEditor—to display the effect of theLinearGradientBrush in the Properties window—I will demonstrate the code in Listing 9-5.

Listing 9-5 The source code to implement the GradientEditor, which shows text based on aLinearGradientBrush in the Properties window

1: public class GradientEditor : UITypeEditor

2: {

3: public override bool GetPaintValueSupported(

4: ITypeDescriptorContext context)

5: {

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 9 1

Page 319: Advanced c sharp programming

6: return true;

7: }

8:

9: public override void PaintValue(PaintValueEventArgs e)

10: {

11: GradientLabel label = (GradientLabel) e.Context.Instance;

12: e.Graphics.DrawString(label.Text, label.Font,

13: new LinearGradientBrush(e.Bounds,

14: label.ForeColor, label.BlendColor, label.Angle),

14: e.Bounds);

15: }

16: }

You will have to add a reference to the System.Design.dll assembly to generalize theUITypeEditor (shown on line 1 of Listing 9-5). The UITypeEditor class is defined in theSystem.Drawing.Design namespace. Add a using statement at the top of the modulecontaining the code in Listing 9-5.

2 9 2 A d v a n c e d C # P r o g r a m m i n g

Figure 9-6 The GradientEditor shown in the selected property

Page 320: Advanced c sharp programming

TIPWhen you draw content in the boxed-in region, use the Bounds property of the PaintValueEventArgsargument to constrain the display region.

Our objective is to create a custom editor that displays the gradient text; hence, all wehave to do is override the GetPaintValueSupported and PaintValue methods inherited fromUITypeEditor. GetPaintValueSupported returns False by default. By returning True, on line 6of Listing 9-5, we are indicating that we are going to perform custom painting in the Propertieswindow. The PaintValueEventArgs argument for the PaintValue method on line 9 representsthe canvas—also referred to as hDC, DC, or Device Context—of the small boxed region in theProperties window (see Figure 9-6). The code in PaintValue—between lines 10 and 16—usesGDI+, DrawString, and a LinearGradientBrush to display a sample of the visual effect ofchanges in the GradientLabel.

Implementing the AngleEditorThe GradientEditor demonstrated the custom painting aspects of a UITypeEditor. TheAngleEditor demonstrates how to implement methods that support editing a value. To implementthe AngleEditor, we will override UITypeEditor.EditValue and UITypeEditor.GetEditStyle.GetEditStyle returns a UITypeEditorEditStyle enumerated value; the enumerated value returneddescribes the type of editor to display.

Listing 9-6 demonstrates the AngleEditor. A compiler directive is used to separate thecode that supports both the dialog and drop-down version of the editor. If GetEditStylereturns UITypeEditorEditStyle.DropDown, then define the compiler directive DROPDOWN.To use the modal dialog version of the AngleEditor, undefine the DROPDOWN compilerdirective.

Listing 9-6 A custom UITypeEditor that demonstrates the GetEditStyle and EditValue methodsof a custom editor

1: public class AngleEditor : UITypeEditor

2: {

3: private IWindowsFormsEditorService editorService = null;

4: private bool IsValidContext(ITypeDescriptorContext context)

5: {

6: return context != null && context.Instance != null;

7: }

8:

9: private bool IsValidProvider(IServiceProvider provider)

10: {

11: return provider != null;

12: }

13:

14: private IWindowsFormsEditorService GetService(

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 9 3

Page 321: Advanced c sharp programming

15: IServiceProvider provider)

16: {

17: return (IWindowsFormsEditorService)provider.GetService(

18: typeof(IWindowsFormsEditorService));

19: }

20:

21: public override object EditValue(ITypeDescriptorContext context,

22: IServiceProvider provider, object value)

23: {

24: if( !IsValidContext(context) || !IsValidProvider(provider))

25: return value;

26:

27: editorService = GetService(provider);

28: if( editorService == null ) return value;

29:

30: #if DROPDOWN

31:

32: NumericUpDown control = new NumericUpDown();

33: control.Maximum = 360;

34: control.Minimum = 0;

35:

36: if(value is float)

37: {

38: control.Value = Convert.ToDecimal(value);

39: editorService.DropDownControl(control);

40: }

41: return (float)control.Value;

42: #else

43: if(value is float)

44: {

45: using( FormAngle form = new FormAngle())

46: {

47: form.Angle = (float)value;

48: if(editorService.ShowDialog(form) == DialogResult.OK)

49: return form.Angle;

50: }

51: }

52:

53: return value;

54: #endif

55: }

56:

57: public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext

context)

58: {

59: return IsValidContext(context) ?

60: #if DROPDOWN

61: UITypeEditorEditStyle.DropDown : base.GetEditStyle(context);

62: #else

63: UITypeEditorEditStyle.Modal : base.GetEditStyle(context);

2 9 4 A d v a n c e d C # P r o g r a m m i n g

Page 322: Advanced c sharp programming

64: #endif

65: }

66: }

TIPConditional directives must be defined first at the top of a module.

The kind of editor displayed in this particular example hinges on the conditional directivedefined on line 1 of Listing 9-6. If DROPDOWN is defined, then we try to return theUITypeEditorEditStyle.DropDown enumerated value on line 61; otherwise we try to returnthe UITypeEditorEditStyle.Modal value on line 63. (Figure 9-7 shows the DropDownAngleEditor in the Properties window.)

The IsValidContext method invoked on line 59 of Listing 9-6 tests to determine if thecontext is valid. IsValidContext is implemented on lines 4 through 7. IsValidContext teststo determine if the PropertyDescriptor and the PropertyDescriptor container are not null.

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 9 5

Figure 9-7 A custom UITypeEditor using the UITypeEditorEditStyle.DropDown style

Page 323: Advanced c sharp programming

GetEditStyle is the first key method. The second key method is EditValue, defined onlines 21 through 55 of Listing 9-6. Line 24 implements a sentry; if we don’t have a validcontext or provider, then the method returns the argument Value. The ITypeDescriptorContextrefers to a PropertyDescriptor, most often used to perform type conversions for data, andan IServiceProvider. The IServiceProvider implements one method, GetService. TheServiceProvider we need is an IWindowsFormsEditorService. IWindowsFormsEditorServicedefines methods for showing a control to drop down or display as a modal dialog.

The DropDown version defined on lines 31 to 41 of Listing 9-6 creates an instance ofthe NumericDropDown control, setting the minimum and maximum values, and uses theIWindowsFormsEditorService to display the control. Lines 43 to 53 implement the modaldialog behavior. A using block on line 45 is used to create the form, and theIWindowsFormsEditorService is used to display the form within the context of Visual Studio.NET. All that’s left to do is to associate the custom UITypeEditor with a property.

Applying the Custom Editor with the EditorAttributeAs with many things in this chapter, we associate a custom UITypeEditor with a control usingan attribute. The EditorAttribute is defined in the System.ComponentModel namespace.

The version of the EditorAttribute we will use takes the type information of our customtype and the base type. The custom type is the AngleEditor class defined in the previoussection and the type we derived it from, the UITypeEditor. We want to associate the editorwith the Angle property of the GradientLabel. Combined with all of the attributes for theGradientLabel’s Angle property, the property with its attributes is provided in the listing next:

[Description("Describes the orientation of the gradient."),

Category("Appearance"),

Editor(typeof(AngleEditor), typeof(UITypeEditor)),

DefaultValue(45)]

public float Angle

{

get

{

return angle;

}

set

{

angle = value;

Invalidate();

}

}

The four attributes for the Angle property provide a description, category, default value, andcustom UITypeEditor. These represent significant enhancements over the property itself,resulting in a richer user experience.

2 9 6 A d v a n c e d C # P r o g r a m m i n g

Page 324: Advanced c sharp programming

Defining a Windows Forms DesignerProgramming in C# is a rewarding and fun experience. (For some fun, try the Terrariumexamples in Chapter 4.) Unfortunately, however, we have to draw this chapter to a close.This means that I have to leave some things for you to discover; otherwise, by the time Ifinish this book, it will be time to write the second edition. I would like to close this chapterwith a quick discussion of the IDesigner interface and the ControlDesigner class.

The IDesigner interface is defined in the System.ComponentModel.Design namespace. Theinterface defines Component and Verbs properties and DoDefaultAction and Initialize methods.An interesting application of the IDesigner interface is to define the ControlDesigner class.

ControlDesigner is defined in the System.Windows.Forms.Design (and there is one in theSystem.Web.UI.Design) namespace. By inheriting and extending the ControlDesigner class,you can provide extended behavior for custom controls at design time. The basic approach isquite straightforward: inherit from ControlDesigner; define a read-only Verbs property,which will become context menus for the control in Visual Studio .NET; and use theinitialized reference to the Component property to update a referenced control at design time.Finally, associate the ControlDesigner with a control using the DesignerAttribute defined inthe System.ComponentModel namespace.

The shape.cs module contained in the CustomControl.csproj project demonstrates acontrol that draws shapes. The designer displays one additional menu item, based on theimplementation in Listing 9-7—an About menu item. You can refer to the code availableonline for the Shape control. The ShapeDesigner is provided in Listing 9-7.

Listing 9-7 Implementing a ControlDesigner to extend design–time support for custom controls

1: public class ShapeDesigner : ControlDesigner

2: {

3: public override DesignerVerbCollection Verbs

4: {

5: get

6: {

7: return new DesignerVerbCollection(

8: new DesignerVerb[]{new DesignerVerb("About",

9: new EventHandler(OnAbout))});

10: }

11: }

12:

13: private void OnAbout(object sender, System.EventArgs e)

14: {

15: const string About =

16: "The C# Developer's Guide\n" +

17: "Copyright \xA9 2002. All Rights Reserved.\n" +

18: "Written by Paul Kimmel. [email protected]\n";

19:

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 9 7

Page 325: Advanced c sharp programming

2 9 8 A d v a n c e d C # P r o g r a m m i n g

20: MessageBox.Show(About, "About", MessageBoxButtons.OK,

21: MessageBoxIcon.Information);

22: }

23: }

OnAbout on lines 13 through 22 of Listing 9-7 implements the behavior of our designer.The OnAbout handler displays a simple About dialog to tell developers using my controlabout the author. (This is a perfectly valid use for a ControlDesigner, especially if you are inthe developer tools business.) Lines 3 through 11 implement the overridden, read-only Verbscollection. Essentially, a verb translates to a menu item, and with menus you generally need ahandler for the click event. Lines 7, 8, and 9 construct the DesignerVerbCollection, an arrayof DesignerVerbs, and initializes the DesignerVerb array with the menu caption and an eventhandler to respond when the verb is invoked by clicking the menu item.

TIPSeveral of the attributes in this chapter are initialized with type information. The reason for this is that bygetting the type information of any type, Reflection can be used to dynamically access members of that typewithout knowing anything about the type in advance. See Chapter 2 for information about Reflection.

You can associate the ShapeDesigner with a control by initializing the DesignerAttributewith the type information of our custom ControlDesigner. The application of the attribute isdemonstrated in the next code fragment:

[ToolboxBitmap(@"C:\temp\Shape.bmp"),

Designer(typeof(ShapeDesigner))]

public class Shape : Control

{ // implement the class here

}

SummaryThis chapter is one of those chapters that is fun to write. I am a toolsmith by nature. A toolsmithis someone who likes building parts of solutions, like components. Building custom controls isfun and rewarding, because it is often quick work with highly visible and reusable results andwithout a lot of the other things that go with developing systems, like gathering requirementsand meetings.

This chapter contains significantly more than just an EffectsLabel. I have introduced youto many of the aspects that you can use over and over to build solid, professional, and richcustom controls. You learned how to create controls, ControlDesigners, and TypeEditors,

Page 326: Advanced c sharp programming

and how to perform GDI+ custom painting operations. You also were shown how to useattributes defined in the ComponentModel to provide helpful metadata with your controls,and how to employ Merge Modules and type converters.

Support for building custom controls feels very rich in Visual Studio .NET and C#. Thehallmark of a great tool is self-extensibility. Visual Studio .NET is very extensible in C#because the Common Language Runtime is a rich and expressive framework.

C h a p t e r 9 : S p e c i a l E f f e c t s T e x t 2 9 9

Page 327: Advanced c sharp programming
Page 328: Advanced c sharp programming

CHAPTER

10Creating

Custom Attributes

301

IN THIS CHAPTER:

Demonstrated Topics

Defining the Custom Attribute Class

Implementing an Attribute Constructor

Adding Other Members to Custom Attributes

Applying the AttributeUsageAttribute

Reading Attributes

Secondary Topics

Commenting Attributes

Implementing Extender Provider

Reviewing the EditorBrowsableAttribute

Reviewing theDesignerSerializationVisibilityAttribute

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 329: Advanced c sharp programming

By now you have heard a lot about attributes. Attributes provide one of the means bywhich Microsoft has mitigated the impact of “DLL Hell.” DLL Hell is caused byversion problems related to registering and copying COM components for applications.

In the past, the information that described the COM component was written to the registryand traveled separately from the component. Part of what attributes do is to help solve thisproblem by combining information that used to be written to the registry with the assemblyitself. (An assembly is an application combined with its metadata—that is, the informationadded by the attributes.)

NOTEAny user-defined type can be used as an attribute. It is assumed that most attributes will inherit fromSystem.Attribute.

Attributes are commonly derived from the System.Attribute class, as the Attribute classwas designed for this specific purpose. Attributes support adding a wide variety of informationto code. Attributes are examined using Reflection and are employed for a wide range of purposes,including declarative security. Chapter 9 demonstrated how attributes are used to enhance design-time features for Windows Forms and to provide a description and categorization for controls.

NOTEDeclarative security is employed by associating security permissions using attributes. Imperative securityis employed by declaring instances of security classes and invoking methods on those classes at run time.

You have had several opportunities to explore Reflection (refer to the AssemblyViewer.slnfrom Chapter 2) and use a variety of attributes (refer to the control examples from Chapter 9).In this chapter I will demonstrate how to combine and extend these skills by creating customattributes.

Demonstrated TopicsThe basic skills demonstrated in this chapter relate to implementing custom attributes. Referto the “Secondary Topics” section, later in this chapter. for related additional topics coveredin this chapter. The primary subject matter of this chapter demonstrates how to

� Implement a custom attribute

� Apply the AttributeUsageAttribute

� Define a constructor for your custom attribute

� Describe positional and named arguments for your custom attributes

� Apply the custom attribute

3 0 2 A d v a n c e d C # P r o g r a m m i n g

Page 330: Advanced c sharp programming

C h a p t e r 1 0 : C r e a t i n g C u s t o m A t t r i b u t e s 3 0 3

We will accomplish these objectives by building a HelpAttribute class. The HelpAttributeclass will support associating an URL, an HTML target, and a description with code that theattribute is applied to. Any code can use Reflection to resolve the URL.

Defining the Custom Attribute ClassSuppose you are building tools. It is easy enough to associate context help for your userinterface by using the HelpProvider and a help file. What if you want to associate help withyour code? This is what developers want. You might elect to use the HelpAttribute to associatehelp with code. On the basis of the specific class in your tool that a customer is actively using,you could display help based on the code context.

Creating a custom attribute requires that you inherit from System.Attribute. You knowhow to specify an inheritance relationship in C#, so I will demonstrate the class header herewithout further elaboration.

class HelpAttribute : System.Attribute

If there is an existing attribute that provides some of the behaviors we need, then we can inheritfrom that other attribute.

Implementing an Attribute ConstructorIf you have any initialization code you need to run, then you will want to implement aconstructor. Additionally, where attributes are concerned, if you need initial values (recallthe DescriptionAttribute from Chapter 9), you will want to define a constructor.

Arguments that must be supplied because they are arguments supplied to the constructorare referred to as positional arguments. Attributes support positional and named arguments. Ielected to require that the user supply an URL and a description and, optionally, a target tag.On the basis of these decisions, we need to implement a constructor with two positionalarguments and one named argument.

Positional ArgumentsPositional arguments are constructor arguments. If I want to implement the HelpAttributewith two positional arguments, URL and description, then I need to define the constructoraccordingly. Our class as described so far has evolved as follows:

class HelpAttribute : System.Attribute

{

private string url;

private string description;

Page 331: Advanced c sharp programming

3 0 4 A d v a n c e d C # P r o g r a m m i n g

public HelpAttribute( string url, string description )

{

this.url = url;

this.description = description;

}

}

As described, the attribute must be applied with a string argument for each of the constructorarguments. Here is an example:

[Help("http://www.softconcepts.com", "SoftConcepts Home")]

Named ArgumentsOptional arguments that can be supplied to an attribute are referred to as named arguments.Named arguments are implemented as public properties in the custom attribute that have bothgetters and setters. This is opposed to positional arguments, which are typically exposed bypublic properties with getters only.

For our project, we need to implement a named argument called “target,” and we need toimplement public, read-only properties for the URL and Description fields. If we implementthe properties, then consumers of our attribute will not be able to read the state of ourcustom attribute. As can be seen here, HelpAttribute has matured to adolescence with theaddition of the following details:

class HelpAttribute : System.Attribute

{

private string url;

private string description;

private string target;

public HelpAttribute( string url, string description )

{

this.url = url;

this.description = description;

}

public string Target

{

get

{

return target;

}

set

{

target = value;

}

Page 332: Advanced c sharp programming

}

public string URL

{

get

{

return url;

}

}

public string Description

{

get

{

return description;

}

}

}

The class now has two positional arguments and one named argument. With the additionof the property methods, consumers will be able to read the value of the attributes. In addition,URL and Description are not modifiable after the attribute is created. Constrained by ourimplementation, the attribute can now be applied with two or three arguments.

[Help("http://www.amazon.com", "Books Online")]

[Help("http://www.yahoo.com", "Search Engine", Target="Maps")]

The first example uses the required positional arguments that will be passed to theconstructor. The second example demonstrates the positional arguments, which must comefirst, and the named argument. Note the use of the property name Target. As the namedargument designation suggests, use the name when supplying a value for an attribute’snamed argument.

NOTEBy examining the attribute statement, you might make some inferences about how attributes are managedby .NET. The attribute statement is read only. The name of the attribute is used to request the constructorinformation. Reflection, the attribute named, and the arguments can be used to dynamically invoke theconstructor. The property name of named arguments can be used in conjunction with Reflection to initialize theproperties of the attribute. (It would be nice to have the framework source code to explore this supposition.)

Adding Other Members to Custom AttributesCustom attributes are just classes used in a specialized way. You can add other members toyour custom attributes if you need them. The basic idea is to add members that support therole your attribute will play.

C h a p t e r 1 0 : C r e a t i n g C u s t o m A t t r i b u t e s 3 0 5

Page 333: Advanced c sharp programming

Applying the AttributeUsageAttributeThe last thing we need to do before we compile our attribute into a class library is to applythe AttributeUsageAttribute to our custom attribute. The AttributeUsageAttribute is used tospecify the kinds of entities that an attribute can be applied to, whether the attribute can beapplied multiple times, and whether the attribute is inherited or not.

The AttributeUsageAttribute has one positional argument, an AttributeTargets enumeratedvalue, and two named arguments, Inherited and Allow Multiple. We’ll review each of thearguments for the AttributeUsageAttribute after I show you the application for the HelpAttribute.Here it is, preceding the class header.

[AttributeUsage(AttributeTargets.All)]

public class HelpAttribute : Attribute

Specifying Attribute TargetsThe AttributeTargets enumeration is used as the positional argument for theAttributeUsageAttribute. If your attribute is suitable for all members, then initializethe AttributeUsageAttribute with AttributeTargets—as we did for the HelpAttribute.

If you want to limit an attribute to a specific kind of code element, then select theenumeration member that describes the element your custom attribute can be appliedto. You can combine attributes with a binary or ( | ) operator. For example, if you definean attribute that can be applied to properties and methods, then you would apply theAttributeUsageAttribute as demonstrated next.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]

Specifying If Your Attribute Is InheritedAttributeUsageAttribute accepts two named arguments. The Inherited property is a Booleanproperty that you can initialize when you apply the AttributeUsageAttribute. If you initializethe AttributeUsageAttribute with Inherited = true, then classes that generalize classes thathave your attribute applied to them will inherit your attribute too.

By default, the Inherited property is True. Here is an example of the AttributeUsageAttributedemonstrating the Inherited named argument.

[AttributeUsage(AttributeTargets.Struct, Inherited=false)]

The preceding statement indicates that the attribute can be applied to structs, and the attributeis not inherited.

Specifying If Your Attribute Can Be Applied Multiple TimesThe AllowMultiple named argument is a Boolean value that is False by default. If AllowMultipleis True, then the attribute can be applied multiple times to the same member. Attributes where

3 0 6 A d v a n c e d C # P r o g r a m m i n g

Page 334: Advanced c sharp programming

AllowMultiple is True are referred to as multi-use attributes. When AllowMultiple is False,these attributes are referred to as single-use attributes.

[AttributeUsage(AttributeTargets.All, AllowMultiple=true)]

AllowMultiple is False by default. You would want to allow an attribute to be applied morethan one time only if there is no potential for conflict by applying an attribute more than once.For example, you would not want to allow multiple security attributes to be applied andthen have to worry about conflicting security permissions. (Refer to Chapter 16 for moreinformation on security in .NET.) Listing 10-1 in the next section provides an example of acustom attribute that could reasonably allow multiples.

Reading AttributesChapter 2 demonstrated that information about a class can be read using Reflection. Oneaspect of Reflection is the ability to retrieve the custom attributes that have been applied totypes. Provided with a System.Type record for a specific type, we can request the attributesapplied to that type. To explore the attributes applied to the members of a type, we can requestthe members of the type and then request the attributes applied to each member.

Think a moment about the relationship between types and members. Attributes may beapplied to classes as well as to the members of a class. Additionally, the members of a classmay themselves be complex types, classes. Listing 10-1 is a method that iterates over all of themembers of a class and requests the custom attribute information for the class and its members.

Listing 10-1 Exploring all the attributes of a type using Reflection

1: ArrayList topics;

2: [Help(@"C:\Temp\CWP13.HTM", "Constructor")]

3: public HelpAttributeDemo()

4: {

5: topics = new ArrayList();

6: topics.AddRange( GetType().GetCustomAttributes(

7: typeof(HelpAttribute), false));

8:

9: MemberInfo[] memberInfo = GetType().GetMembers();

10: IEnumerator enumerator = memberInfo.GetEnumerator();

}

11: while( enumerator.MoveNext())

12: {

13: topics.AddRange( ((MemberInfo)

14: enumerator.Current).GetCustomAttributes(

15: typeof(HelpAttribute), false));

16: }

17: }

C h a p t e r 1 0 : C r e a t i n g C u s t o m A t t r i b u t e s 3 0 7

Page 335: Advanced c sharp programming

3 0 8 A d v a n c e d C # P r o g r a m m i n g

The ArrayList on line 1 in Listing 10-1 is used to store the reflected attributes. (This procedureis defined in the HelpAttributeDemo class in the HelpAttribute.sln available online. The .HTMhelp documents (shown in Figure 10-1) were created using the Tools, Build Comment WebPages in Visual Studio .NET.) Line 2 applies a HelpAttribute using Web Pages generatedfrom Visual Studio .NET. The description for the attribute explains that the method is theconstructor. The ArrayList is constructed on line 5.

Line 6 in Listing 10-1 invokes GetType to get the Type information for the HelpAttributeDemoclass. The Type object is used to request an array of custom attributes. By specifying thetypeof(HelpAttribute) as an argument to GetCustomAttributes, GetCustomAttributes willreturn only the HelpAttribute attributes applied to the class.

Lines 9 through 16 in Listing 10-1 request all of the MemberInfo objects for theHelpAttributeDemo type. The memberInfo array—line 10—is used to request an enumerator,and lines 13 through 16—constituting a statement—adds all of the HelpAttribute attributesapplied to all of the members of the HelpAttributeDemo to the topics ArrayList.

Returning to our scenario, once we have all of the attributes dynamically discovered, wecan display the list of descriptions. When a user can pick a specific description, there will bean URL associated with the HelpAttribute owning that description. With the URL we candisplay the help page. The HelpAttributeDemo class and the AttribueDemo.exe applicationdemonstrate precisely this behavior.

Figure 10-1 Code Comment Web Report created in Visual Studio .NET

Page 336: Advanced c sharp programming

C h a p t e r 1 0 : C r e a t i n g C u s t o m A t t r i b u t e s 3 0 9

TIPGenerate Comment Web Pages from the Tools menu in Visual Studio .NET.

The key to using Reflection is to request the Type information. With the Type informationyou can use Reflection to explore all of the members of a type, including reading attributeinstances and the properties of those instances.

Secondary TopicsThe previous section demonstrated a HelpAttribute that displayed dynamic HTML help usingReflection. A reasonable person might want to employ this attribute but also want an easyway to generate professional Web pages. You can use Tags for Documentation and VisualStudio .NET to generate the HTML comment pages automatically.

This section of the chapter demonstrates several topics. In addition to tags designed forgenerating HTML documentation for your code, this section will explore

� The ToolTip Component and how to implement an Extender Provider

� The EditorBrowsableAttribute

� The DesignerSerializationAttribute

Commenting AttributesVisual Studio .Net will generate high-quality HTML documentation for your source code.You provide the comments, and Visual Studio .Net will take care of the rest.

Table 9-1 in Chapter 9 introduced the comment XML tags for commenting source code.You can use the Visual Studio .NET help documentation as a general reference. Here I willdemonstrate a few of the commenting tags used to create the HTML documentation shownin Figure 10-1.

The description column shown in Figure 10-1 was created by using the <summary> tag.The Remarks section shown at the bottom of Figure 10-1 was created using the <remarks>tag, and the <param> and <returns> comments are shown in Figure 10-2. The Type, Name,and Description information for the method shown in Figure 10-2 is created by the <param>tag, and the Return information is added by the <returns> tag.

The Description for the topics element shown in Figure 10-1 was created by adding thefollowing comment to the topics field.

/// <summary>

/// The internal ArrayList that contains all of the HelpAttribute objects.

/// </summary>

ArrayList topics;

Page 337: Advanced c sharp programming

The Remarks in Figure 10-1 were created with the following comment applied to theHelpAttributeDemo class.

/// <remarks>

/// Demonstrates the HelpAttribute class.

/// </remarks>

[Help(@"C:\Temp\CWP11.HTM", "Class")]

public class HelpAttributeDemo

{

The parameter and return information was created by applying the <param> and <returns>tags, as demonstrated here.

/// <param name="byDescription">The description to search for.</param>

/// <returns>HelpAttribute</returns>

[Help(@"C:\Temp\CWP17.HTM", "Find Topic")]

private HelpAttribute FindTopic(string byDescription)

3 1 0 A d v a n c e d C # P r o g r a m m i n g

Figure 10-2 The parameter type and name and return type of a method added to a CodeComment Web Report using the <param> and <returns> tags

Page 338: Advanced c sharp programming

By providing text and documentation tags, you can create rich help documentation foryour source code.

Implementing Extender ProviderExtender Providers provide properties to other components. Instead of every control having itsown Hint property, an Extender Provider can provide a Hint—or ToolTip—property to controlsdynamically. There are several existing Extender Providers, including the ComponentTray,ErrorProvider, HelpProvider, LocalizationExtenderProvider, PropertyTab, and ToolTip.

For example, the ToolTip provider can be added to the Component Tray and controlsdynamically given a ToolTip control. Consumers can query Extender Providers by invokingthe single method IExtenderProvider classes must implement, CanExtend. CanExtend returnsa Boolean indicating whether or not a particular provider can extend that consumer. To createcustom Extender Providers, define a class that inherits from Component and implements theIExtenderProvider interface.

To demonstrate the IExtenderProvider interface, we’ll create a HelpProvider that extendscontrols by adding a HelpLink property to controls. There a few basic features of the .NETFramework that we will need to make use of to implement the HelpProvider. We will needto inherit from System.ComponentModel.Component, implement the System.ComponentModel.IExtenderProvider interface, and apply the System.ComponentModel.ProviderPropertyAttribute.We will explore each of these pieces in the subsections that follow and wrap up the chapterwith the complete code listing for the provider.

Specifying the Class Header for the HelpProviderThe HelpProvider will provide links to help information. We will need a collection to storethe links in. The IExtenderProvider interface and the ProviderPropertyAttribute are definedin the System.ComponentModel namespace, and we will be associating the HelpProviderwith Windows Forms controls. Thus, it will be helpful to include the namespaces containingthese capabilities in the module implementing the HelpProvider.

NOTEThe term generalize means to inherit from a class and the term realize means to implement an interface.These terms are synonyms. “Inherit” and “implement” are most often used in the context of a programminglanguage, like C#, and “generalize” and “realize” are most often used in the context of a modeling languagelike the Unified Modeling Language (UML).

To place the HelpProvider in the Component Tray, we need to generalize the System.Windows.Forms.Component class and realize the System.ComponentModel. IExtenderProvider.The using statements and class header follow:

using System;

using System.Collections;

using System.ComponentModel;

C h a p t e r 1 0 : C r e a t i n g C u s t o m A t t r i b u t e s 3 1 1

Page 339: Advanced c sharp programming

using System.Windows.Forms;

public class HelpProvider : Component, IExtenderProvider

The last statement indicates that the HelpProvider inherits from Component and implementsIExtenderProvider.

Applying the ProviderPropertyAttributeThe ProviderPropertyAttribute is used to provide a name for the property that a particularExtender Provider will provide. You can initialize the ProviderPropertyAttribute with the nameof the property provided and the name or type of the object this provider can extend. We wantour provider to extend System.Windows.Forms.Controls with a HelpLink property, whichguides the ProviderPropertyAttribute statement (shown next in relation to the class header):

[ProvideProperty("HelpLink", typeof(Control))]

public class HelpExtender : Component, IExtenderProvider

{

The ProviderPropertyAttribute indicates that our provider will be providing a property namedHelpLink and can extend Control objects. (Keep in mind that every Windows Forms controlinherits from Control.) An alternate form of the ProvidePropertyAttribute is initialized withthe name of the property and the type of the property provided.

When the HelpProvider is added to the ComponentTray, every Control will have a HelpLinkproperty unless that Control is specifically excluded by the CanExtend method.

Implement the IExtenderProvider InterfaceIExtenderProvider defines the method CanExtend. When a class realizes an interface, thatclass must implement all of the members defined by the interface. You can implement aninterface member explicitly, either by prefixing the member that satisfies the interface contractwith the name of the interface or by declaring a public member with a signature identicalto the member defined by the interface. Either of the following two method signatures willsatisfy the IExtenderProvider interface:

public bool CanExtend(object extendee)

or

bool IExtenderProvider.CanExtend(object extendee)

The public method CanExtend implicitly satisfies the IExtenderProvider interface, and thesecond example explicitly satisfies the contract.

Suppose you have a class that implements two interfaces containing one or more identicalmembers. You can use the second, explicit form of implementing an interface to indicatewhich members are implementing a specific interface.

3 1 2 A d v a n c e d C # P r o g r a m m i n g

Page 340: Advanced c sharp programming

C h a p t e r 1 0 : C r e a t i n g C u s t o m A t t r i b u t e s 3 1 3

Our control will play the role of provider to any Control; as a result, the HelpProvider.CanExtend method will return True if the type of the extendee is a Control. Here is theimplementation for the CanExtend method.

bool IExtenderProvider.CanExtend(object extendee)

{

return extendee is Control;

}

Implement the HelpProviderThe HelpProvider will provide a HelpLink property to every control that it extends. As aresult, all that remains to do is to implement the methods for getting and setting the HelpLinkand to provide a place to store all of the HelpLinks. Listing 10-2 provides the completeimplementation of the HelpProvider.

Listing 10-2 An implementation of an IExtenderProvider component that extends a control byproviding a HelpLink

1: using System;

2: using System.Collections;

3: using System.ComponentModel;

4: using System.Windows.Forms;

5:

6: namespace HelpExtender

7: {

8: [ProvideProperty("HelpLink", typeof(Control))]

9: public class HelpExtender : Component, IExtenderProvider

10: {

11:

12: private Hashtable links;

13:

14: public HelpExtender()

15: {

16: links = new Hashtable();

17: }

18:

19: public string GetHelpLink(Control control)

20: {

21: return links[control] == null ? string.Empty

22: : (string)links[control];

23: }

24:

25: public void SetHelpLink(Control control, string text)

Page 341: Advanced c sharp programming

3 1 4 A d v a n c e d C # P r o g r a m m i n g

26: {

27: if(text == null)

28: links[control] = string.Empty;

29: else

30: links[control] = text;

31: }

32:

33: bool IExtenderProvider.CanExtend(object extendee)

34: {

35: return extendee is Control;

36: }

37:

38: }

39: }

The constructor defined on lines 14 through 17 of Listing 10-2 creates an instance of aHashTable. We will rely on the GetHashCode method that every class inherits from the Objectclass to provide a means of storing unique HelpLink values. GetHelpLink and SetHelpLinkprovide the read and write behavior for the HelpLink property.

SetHelpLink on lines 25 through 31 of Listing 10-2 uses the control to index the HashTable,storing the string value of the HelpLink for a specific control at the hash index created bythe control itself. GetHelpLink performs the inverse operation. If the HashTable entry at aparticular index returns null, then the value string.Empty is returned; otherwise, the hashingfunction returns an object (which we know to be a string), and we can return the string valueas demonstrated on line 22.

Using the HelpProviderInclude the module containing the HelpProvider in a class library. Build the class libraryproject and add the HelpProvider library to the Toolbox. (Refer to “Adding the Component tothe Toolbox” in Chapter 9 for information on customizing the Toolbox in Visual Studio .NET.)

To use the HelpProvider, drag a HelpProvider from the Toolbox to a Form. The HelpProviderwill be added to the Component Tray, and the form and the controls on the form will beextended with a HelpLink property (shown in Figure 10-3).

The value of a property added by an extender lives in the extender, not in the control itextends. Normally, you would read a property value by requesting the property from a specificinstance of an object. For example, button1.Text provides access to the Text property of anobject named button1. To access the value of a property added by an extender, request thevalue from the extender’s Get method. The following statement demonstrates how to requestthe HelpLink value associated with an object named button1.

helpExtender1.GetHelpLink(button1)

In our example, the HelpLink is an URL referring to an HTML document created with theComment Web Pages. To display the Web page, we could pass the value of the HelpLinkto the Process.Start method. The HTTP:// moniker is associated with Internet Explorer

Page 342: Advanced c sharp programming

C h a p t e r 1 0 : C r e a t i n g C u s t o m A t t r i b u t e s 3 1 5

(on my machine); hence, Process.Start called with an HTTP:// address will open thedocument referred to in the URL.

Reviewing the EditorBrowsableAttributeThe EditorBrowsableAttribute is defined in the System.ComponentModel namespace.Initialized with an EditorBrowsableState enumerated value, the EditorBrowsableAttributewill determine whether a property or method is viewable in an editor. For example,[EditorBrowsable(EditorBrowsableState.Never)] will conceal the element it is appliedto from Visual Studio .NET’s Intellisense capability.

The values for EditorBrowsableState are Never, Always, and Advanced. If Hide advancedmembers is selected in the Options dialog (see Figure 10-4), then members that have anEditorBrowsableAttribute initialized with EditorBrowsableState.Advanced will not bedisplayed by Intellisense.

Figure 10-3 The HelpLink property added to the Button control by the HelpProvider

Page 343: Advanced c sharp programming

Reviewing the DesignerSerializationVisibilityAttributeThe DesignerSerializationVisibilityAttribute (that’s a mouthful) is used to describe how aproperty is saved by a designer. There are three enumerated values, Visible, Content, andHidden, defined in the DesignerSerializationVisible enumeration that you can initialize thisattribute with. The Visible value indicates that a designer can use the default behavior toserialize properties. Use Hidden to indicate that a property is not serialized and Content toindicate that the value of the property is serialized. For example, use Content to serialize theelements of a collection.

We can combine the EditorBrowsableAttribute, the BrowsableAttribute, and theDesignerSerializationAttribute to remedy a semantics deficit from Chapter 9. Chapter 9introduced an EffectsLabel. The EffectsLabel inherited from Label and created a shadoweffect by overriding the OnPaint behavior. The deficit was subtle. To create the shadoweffect, an overlay color was combined with the existing, inherited ForeColor. The deficit wasthat the ForeColor actually created the background shadow effect and that the OverlayColorproperty actually became the ForeColor. A reasonable person might be confused by thissubtle yet annoying incongruity.

To resolve this problem, we can introduce a new property called ShadowColor andhide the old property named ForeColor. As a result, the EffectsLabel has properties named

3 1 6 A d v a n c e d C # P r o g r a m m i n g

Figure 10-4 Conceal or reveal EditorBrowsableState.Advanced members with the StatementCompletion Options.

Page 344: Advanced c sharp programming

ShadowColor and OverlayColor and the role of each property is much clearer in the contextof the EffectsLabel. To complete the revision, all we have to do is add the two propertiesshown in Listing 10-3 to the EffectsLabel control class defined in Chapter 9.

Listing 10-3 Add the code in the listing to the EffectsLabel custom control from Chapter 9to hide the ForeColor property and introduce a ShadowColor property thatis more meaningful.

1: [Browsable(false),

2: EditorBrowsable(EditorBrowsableState.Never),

3: DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]

4: public override Color ForeColor

5: {

6: get

7: {

8: return base.ForeColor;

9: }

10: set

11: {

12: base.ForeColor = value;

13: }

14: }

15:

16:

17: [Category("Appearance"),

18: Description("The color that is used to create the shadow.")]

19: public Color ShadowColor

20: {

21: get

22: {

23: return ForeColor;

24: }

25: set

26: {

27: ForeColor = value;

28: }

29: }

The BrowsableAttribute on line 1 of Listing 10-3 will prevent the ForeColor from beingdisplayed in the Properties window. The EditorBrowsableAttribute as applied on line 2 willprevent Intellisense from displaying the ForeColor property in the code designer, and theDesignerSerializationVisibilityAttribute as applied will prevent the ForeColor from beingserialized by the code editor.

C h a p t e r 1 0 : C r e a t i n g C u s t o m A t t r i b u t e s 3 1 7

Page 345: Advanced c sharp programming

For all intents and purposes, the EffectsLabel revised with the addition of the code inListing 10-3 will no longer have a ForeColor property. The EffectsLabel from the perspectiveof the consumer will have a ShadowColor and an OverlayColor, which are much moremeaningful in the context of a shadow label. Of course, you and I know (from Listing 10-3)that the ShadowColor is implemented in terms of the inherited ForeColor. We have employeda bit of sleight of hand to make the EffectsLabel easier to use.

SummaryThe intent of code should be conveyed by the way you write the code. For the most part,consumers of controls and providers are going to be introduced to your code by the membersyou provide them with. Consumers may never see your internal comments and will mostlikely encounter your external documentation after exploring the code.

This chapter has demonstrated how to associate help documentation with your code bycreating a custom Attribute and an Extender Provider. The last part of the chapter demonstratedhow you can combine attributes to perform a bit of programmatic sleight of hand to concealexisting properties and expose new properties that convey greater meaning.

You can create custom attributes to add metadata to your code. Use Extender Providers toextend existing controls by adding new properties, and you can use existing attributes to conveyadditional information about your code.

3 1 8 A d v a n c e d C # P r o g r a m m i n g

Page 346: Advanced c sharp programming

CHAPTER

11Practical Reflection

319

IN THIS CHAPTER:

Demonstrated Topics

Discovering and Using Types Dynamically

Exploring the .NET Framework with Reflection

Emitting Dynamic Assemblies

Secondary Topics

Reflection and Web Services

Implementing the Metaclass Idiom

Serializing Objects

Emitting Regular Expression Assemblies

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 347: Advanced c sharp programming

Reflection is a capability that facilitates dynamically managing assemblies. It isan evolutionary enhancement of run-time type information. With Reflection youcan load and emit assemblies, you can discover and create types dynamically

with compiled and running code, and you can invoke operations on or emit members withrunning code.

Run-time type information, or RTTI, supports dynamic type discovery. Querying COMinterfaces supports dynamic interface discovery. Reflection provides support for both ofthese operations and significantly more. In addition to discovering information about atype or interface, Reflection allows you to discover information about fields, events, properties,methods, constructors, and attributes. An enhancement to discovering information aboutassemblies and types is the writing of emitters—that is, defining code generators—that writeobject-oriented intermediate language (IL). This chapter will demonstrate how to do all ofthese things.

Demonstrated TopicsThis chapter demonstrates practical applications for using Reflection. There are severalexample programs, as well as a discussion of the Assembly Viewer 2 sample applicationfrom Chapter 5. In this chapter, you will have an opportunity to learn how to

� Dynamically discover information about types using Reflection

� Load assemblies at run time

� Explore the .NET Framework programmatically

� Emit assemblies, modules, classes, and members

The secondary topics complement core Reflection topics by demonstrating someadditional uses for Reflection. In the secondary topics, I will demonstrate how to implementthe metaclass—class of a class—idiom, serialize objects with XML, serialize objects to anADO.NET DataSet, and emit compiled regular expressions.

Discovering and Using Types DynamicallyYou will likely encounter several instances where you define a generic type and initializethat generic object to a specific type at run-time as opposed to compile time. Severalexamples in this chapter will prove useful in practical applications, such as the metaclassidiom described in “Implementing the Metaclass Idiom” near the end of this chapter. Thissection will demonstrate the basic concepts for discovering type information that you willuse in routine programming and in creating objects from a class’s Type object.

3 2 0 A d v a n c e d C # P r o g r a m m i n g

Page 348: Advanced c sharp programming

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 2 1

Using Type ObjectsWhen you invoke the typeof(object) operation, an instance of a class’s Type record is returned.Like any other class, the Type class has members. The only difference is that a Type classcontains properties, fields, and methods that describe a Type. For example, if you declarea new class with the default name of Class1 and request the typeof(Class1), then you willget an instance of System.Type as the return value. Using this Type object, you can invokeoperations on the Type object with the Type.InvokeMember. Here is a brief example:

using System;

namespace TypeofDemo

{

class Class1

{

[STAThread]

static void Main(string[] args)

{

Type type = typeof(Class1);

Console.WriteLine(type.FullName);

Console.ReadLine();

}

}

}

The typeof statement returns an instance of Class1’s Type object. The FullName memberreturns the namespace and name of the type represented by the Type object. The examplewill write TypeofDemo.Class1 to the console.

Type objects contain methods about their containing assembly and the members definedby the type. There are methods to request the attributes, constructors, events, fields,properties, interfaces, methods, method parameters, nested types, containing assemblyand namespace, and the access modifiers applied to the type.

A second way to request a type is to pass the full name to the Type.GetType sharedmethod. Type.GetType(“TypeofDemo.Class1”) will return the Type object representingClass1, precisely as the typeof(Class1) statement did. All you need to do to explore a type orinvoke operations on it is obtain an instance of its Type object.

A metaclass is the class of a class. A Type class is similar to a metaclass, but the Typeclass is less specific. The Type class describes all types, and a metaclass describes a singletype. Because the Type class is generic to all types, the operations you will perform on Typeobjects are generic, requiring more information than a metaclass would. For example, invokea constructor on a metaclass and you get the correct object. Invoke a constructor on aType object, and you must invoke the constructor indirectly, passing the parameter as anarray of objects. If you are familiar with metaclasses, the differences will be obvious. In

Page 349: Advanced c sharp programming

general, think of a metaclass as a class describing a specific class and the System.Typeclass as describing all possible classes.

Instantiating Objects with ReflectionUnless you are invoking only static members on a type, you will need to construct an instance of areflected type. There are three ways to create an instance using the Type object of a type. You canuse the Activator.CreateInstance method. You can request a ConstructorInfo and instantiate aninstance with that type, or you can call Type.InvokeMember and pass the arguments that describea constructor. All three ways work reasonably well; using the static Activator.CreateInstancemethod seems to be easiest.

The three short fragments that follow demonstrate how to request a Type object from thename and create instances of that type.

// Using an activator

Type type = Type.GetType("TypeofDemo.Class1");

object o = Activator.CreateInstance(type);

type.InvokeMember("Test", BindingFlags.InvokeMethod,

null, o, new object[]{});

This example creates an instance of Class1 using the shared method Activator.CreateInstance.Pass the Type object, and CreateInstance will invoke a default constructor. There areseveral overloaded forms of CreateInstance; for example, if you pass an array of argumentsrepresenting arguments to the constructor, then CreateInstance will create an instance of thetype using the constructor that most closely matches the arguments’ parameter.

// Using a ConstructorInfo

Type type = Type.GetType("TypeofDemo.Class1");

ConstructorInfo constructorInfo = type.GetConstructor(new Type[]{});

object o = constructorInfo.Invoke(new object[]{});

type.InvokeMember("Test", BindingFlags.InvokeMethod,

null, o, new object[]{});

The second example obtains a ConstructorInfo object, which represents a single constructor. TheConstructorInfo object is used to create an instance of the type by passing an array of objectvalues. The ConstructorInfo object will match the constructor with the closest matching arguments.Passing an empty array of objects will invoke the default constructor.

// Type.InvokeMember

Type type = Type.GetType("TypeofDemo.Class1");

object o = type.InvokeMember("Class1",

BindingFlags.CreateInstance, null, null, new object[]{});

type.InvokeMember("Test", BindingFlags.InvokeMethod,

null, o, new object[]{});

3 2 2 A d v a n c e d C # P r o g r a m m i n g

Page 350: Advanced c sharp programming

The final example treats the constructor like a method and invokes the method Class1.Recall that constructors in C# have the same name as the class, and every class has a defaultconstructor whether you define one or not. Recall from the code for Class1 at the beginningof this section that we did not define a constructor explicitly; however, if you examine the ILin Figure 11-1, it is obvious that a default constructor has been defined.

Dynamic Member InvocationYou might have heard the phrases “.NET allows developers to be language agnostic”and “the .NET language you choose is a lifestyle choice.” These are true statements for acouple of reasons. The first reason is that C# and Visual Basic .NET have equal accessto the Common Language Runtime. A second reason is that there are just a few noticeabledifferences between C# and Visual Basic .NET (most of the differences are syntactical).One of the noticeable differences is that Visual Basic .NET supports implicit late binding,and C# does not.

NOTEOf course, there are several .NET languages. The basic idea is that a .NET language sits on top of the CLR.As a consequence, many languages implemented for .NET will differ most significantly by syntax.

Implicit late binding is where you define a type as a generic type like Object and assign itto an instance of a specific type. Late binding in Visual Basic .NET means that you invokeoperations based on a presumption of the members defined by the instantiated type. I willdemonstrate what this means by example. Here is the code that uses the Activator to createan instance of Class and invoke a member named Test.

// Using an activator

Type type = Type.GetType("TypeofDemo.Class1");

object o = Activator.CreateInstance(type);

type.InvokeMember("Test", BindingFlags.InvokeMethod,

null, o, new object[]{});

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 2 3

Figure 11-1 Classes are provided with a default constructor, as shown here by the IL.

Page 351: Advanced c sharp programming

Here is an identical listing in Visual Basic .NET.

Dim type As Type = type.GetType("VBTypeofDemo.Class1")

Dim o As Object = Activator.CreateInstance(type)

o.Test()

Notice that we are not required to use InvokeMember to call the Test method, even thoughwe declared o as an Object instance rather than a Class1 instance. This is late binding inVisual Basic .NET.

Defining the Member to InvokeAs is true with object-oriented frameworks in general, you are likely to find severaloverloaded methods for any method. Covering all of the variations of every method isthe function of the integrated help or a reference manual. There are three versions ofInvokeMember. The version I am going to demonstrate accepts five arguments and hasthe following method signature:

public object InvokeMember(string, BindingFlags, Binder, object, object[]);

The first argument is the name of the member to invoke. You can interact with fields, properties,events, or methods using this same—InvokeMember—method.

When you call InvokeMember, the return type is defined as an object. Of course, whenyou write your code, you will know what type to expect and can typecast the return type to theactual type returned. For example, if you InvokeMember on an event, then you can cast thereturn type to the delegate type defined by the event. (Refer to the preceding section for anexample that demonstrates invoking a member method named Test.)

Specifying the BindingFlagsThe second argument in the version of InvokeMember that we are reviewing is an OR’dparameter containing BindingFlags-enumerated values. The BindingFlags described thekind of member, including such things as the access modifiers. The BindingFlags valuesare defined in Table 11-1.

BindingFlags are OR’d together in specific combinations. Three common combinationsinclude a flag that indicates the member type, a flag that indicates the access level, and a flagthat indicates whether the member is an instance member, static member, or both. Here aresome examples of BindingFlags combinations that make sense:

BindingFlags.GetField | BindingFlags.NonPublic |

BindingFlags.Instance

BindingFlags.InvokeMember | BindingFlags.Public | BindingFlags.Static

BindingFlags.SetProperty | BindingFlags.Public |

BindingFlags.Instance

As a general rule, fields are private; hence, it makes sense to indicate that we want anon-public instance when invoking a field operation. Generally, public methods make up thepublic interface, so we might want to call a public static or instance method. (The second

3 2 4 A d v a n c e d C # P r o g r a m m i n g

Page 352: Advanced c sharp programming

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 2 5

statement demonstrates how to invoke a public static method.) The final example invokes apublic property setter method.

The code in Listing 11-1 was added to the example class Class1. The additional codeimplements a private field and a public property that represents the underlying field value.

Listing 11-1 Examples of BindingFlags combinations for getting and setting fieldsand properties

1: private string field;

2:

3: public string Property

4: {

5: get{ return field; }

6: set{ field = value; }

Member Name DescriptionCreateInstance Indicates that an instance of the invoking type should be created.

DeclaredOnly Means skip inherited members when reflecting a member.

Default No binding flags.

ExactBinding Argument types must precisely match parameter types.

FlattenHierarchy Static members are returned. (Does not include nested types.)

GetField Used to indicate reference to a Field member

GetProperty Used to indicate reference to a Property member.

IgnoreCase Ignore case when reflecting a member.

IgnoreReturn Return value can be ignored for COM Interop.

Instance Include instance members.

InvokeMethod Used when invoking a method.

NonPublic Used for non-public members. ReflectionPermission is required.

OptionalParamBinding Used for methods with default arguments and varargs.

Public Public members are included in the search. ReflectionPermission is notrequired.

PutDispProperty PROPPUT on a COM object should be invoked.

PutRefDispProperty PROPPUTREF member on a COM object should be invoked.

SetField Treat invoked member as a left-hand–side value.

SetProperty Invoke property setter.

Static Include static members in invocation.

SuppressChangeType (Help indicates that this is not implemented.)

Table 11-1 Combine BindingFlags Values to Describe the Type of Member to Invoke

Page 353: Advanced c sharp programming

7: }

8:

9: private static void DemoBindingFlags()

10: {

11: Type type = Type.GetType("TypeofDemo.Class1");

12: object o = Activator.CreateInstance(type);

13:

14: type.InvokeMember("field",

15: BindingFlags.SetField | BindingFlags.NonPublic |

16: BindingFlags.Instance, null, o,

17: new object[]{

18: "Welcome to Valhalla Tower Material Defender!"});

19:

20:

21: string s = (string)type.InvokeMember("Property",

22: BindingFlags.GetProperty | BindingFlags.Public |

23: BindingFlags.Instance, null, o,

24: new object[]{});

25:

26: Console.WriteLine(s);

27: Console.ReadLine();

28:

29: }

Lines 1 through 7 of Listing 11-1 represent the field and property that we will be reflecting.Note that the field is private and that the property is public. (The use of the name field forthe field member and property for the property member is not relevant to our discussion.)DemoBindingFlags requests the type by passing the full name to the Type.GetType methodon line 11, and line 12 uses an Activator to create an instance of the type.

Lines 14 through 24 of Listing 11-1 demonstrate a combination of BindingFlags thatwill allow us to set the private field value, and the array of object argument takes a singleargument with the same type as the field we are reflecting. The field field is a string; hence,we pass an array containing a single string element. Because field is an instance member, weneed to pass an object to invoke the reflected operation on—that is, we need memory to storethe field value in.

NOTEIf you are a beginning programmer, properties, public access modifiers, and instances may seem foreign.I respect your ambition in tackling this subject matter as a beginner, so here is a quick review of what youneed to learn for the BindingFlags to make sense. Properties are methods that act like data and have to dowith the concept of encapsulation. Public access modifiers relate to the subject of information hiding, andinstantiation has to do with the concept of classes versus objects. These are fundamental OOP concepts thatyou will have to learn.

3 2 6 A d v a n c e d C # P r o g r a m m i n g

Page 354: Advanced c sharp programming

Lines 21 through 24 of Listing 11-1 demonstrate how to get a public property value andassign the value to a temporary variable. BindingFlags.GetProperty, BindingFlags.Public,and BindingFlags.Instance become self-explanatory in this context. If you understand theconcepts of a property, public access, and an instance method, then the flags make sense.This time we are using the result of InvokeMember as a right-side value; InvokeMemberis defined to return an object, but the type is really a string, so we perform the typecaston line 21. The remaining code simply displays the text “Welcome to Valhalla TowerMaterial Defender!”

Defining a BinderWhether you pass a Binder to InvokeMember or not, you are using a Binder—theDefaultBinder. Binders are used to convert arguments you pass to InvokeMember to theformal argument types expected by the member. The easiest thing to do is pass the argumenttypes that the member you are invoking expects. However, it is likely that you will encountera situation where you want a reasonable conversion between types to work. For example,IBM’s Universal Database has a specific format for the Timestamp data type. If you passa valid .NET date/time value to a Timestamp function in an SQL statement, UDB will notaccept it. Here is a method that I wrote to convert a .NET DateTime value to a suitable stringfor the UDB Timestamp data type.

public static string ToTimeStamp(DateTime Value)

{

return string.Format("{0}-{1:0#}-{2:0#}-{3:0#}.{4:0#}.{5:0#}.{6:000000}",

Value.Year, Value.Month, Value.Day,

Value.Hour, Value.Minute, Value.Second,

Value.Millisecond);

}

NOTEIt is possible that someone at IBM is aware of a workaround; however, I tested several variationsand it seemed that the precise string format described for a DateTime value was needed to set aTimestamp column.

Following the string.Format method, the basic syntax for a UDB Timestamp isd-m-yy-hh:mm:ss.mils. In plain English, the format is the day, month, and year valuesseparated by a dash, followed by a dash and the hour, minute, second, and six-digitmillisecond value. It would be preferable if such highly specialized values accepted areasonable value for the date and time and performed the conversion for me. This is therole of a Binder.

The Binder class is an abstract class. If you want to describe custom binding, then you caninherit from the Binder class and implement BindToMethod, BindToField, SelectMethod,SelectProperty, ChangeType, and ReorderArgumentArray, all inherited from the defaultBinder class. (The Binder class is defined in the System.Reflection namespace.) Listing 11-2demonstrates a binder that will perform a conversion between a string and a DateTime valuewhen resolving a DateTime property.

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 2 7

Page 355: Advanced c sharp programming

3 2 8 A d v a n c e d C # P r o g r a m m i n g

Listing 11-2 This contains a simple Binder that will convert a string to a DateTime.

1: public class DateTimeBinder : Binder

2: {

3:

4: public override MethodBase BindToMethod(

5: BindingFlags bindingAttr,

6: MethodBase[] match,

7: ref object[] args,

8: ParameterModifier[] modifiers,

9: CultureInfo culture,

10: string[] names,

11: out object state)

12: {

13: state = null;

14: return match.Length == 1 ? match[0] : null;

15: }

16:

17: public override FieldInfo BindToField(

18: BindingFlags bindingAttr,

19: FieldInfo[] match,

20: object value,

21: CultureInfo culture)

22: {

23: return null;

24: }

25:

26: public override MethodBase SelectMethod(

27: BindingFlags bindingAttr,

28: MethodBase[] match,

29: Type[] types,

30: ParameterModifier[] modifiers)

31: {

32: return null;

33: }

34:

35: public override PropertyInfo SelectProperty(

36: BindingFlags bindingAttr,

37: PropertyInfo[] match,

38: Type returnType,

39: Type[] indexes,

40: ParameterModifier[] modifiers)

41: {

Page 356: Advanced c sharp programming

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 2 9

42: return null;

43: }

44:

45: public override object ChangeType(

46: object value,

47: Type type,

48: CultureInfo culture)

49: {

50: if ( value.GetType() != typeof(string)) return value;

51:

52: try

53: {

54: return Convert.ToDateTime(value);

55: }

56: catch

57: {

58: return value;

59: }

60: }

61:

62: public override void ReorderArgumentArray(

63: ref object[] args,

64: object state)

65: {}

66: }

Quickly scanning Listing 11-2 you can determine that most of the inherited methods are juststubbed out. All I want this converter to do is convert a string representation of a date/time toa DateTime value.

BindToMethod sets the out parameter—out parameters are not initialized by the caller butare expected to have a value when the callee returns—and the MethodBase object that wewant to bind to. My implementation returns one MethodBase object if only one was found.

The MethodBase class is the base class for MethodInfo. In our example, I passed aDateTimeBinder to a reflected property request. When properties are emitted to IL, hiddenmethods named get_property and set_property, where property is the name of the propertyassociated with these methods, are emitted to IL. When a property is reflected, the valuesare retrieved from these hidden methods. Hence, BindToMethod is called for a Binder evenwhen we are reflecting properties. (Figure 11-2 shows the emitted setter (highlighted) andgetter methods for Class1.DateOfBirth.)

To demonstrate the DateTimeBinder in Listing 11-2, a DateOfBirth property anda dateOfBirth field were added to the Class1 sample class. The code in Listing 11-3demonstrates code that implicitly invokes the BindToMethod and ChangeType methodsof the custom Binder.

Page 357: Advanced c sharp programming

Listing 11-3 Code that invokes the DateTimeBinder methods from Listing 11-2

Type type = typeof(Class1);

object o = Activator.CreateInstance(type);

type.InvokeMember( "DateOfBirth",

BindingFlags.Instance | BindingFlags.SetProperty |

BindingFlags.Public, new DateTimeBinder(),

o, new object[]{@"02/12/1966"});

The only difference from examples we have seen so far is the usage of the custom binder,DateTimeBinder, in Listing 11-3. The InvokeMember method in Listing 11-3 will usethe DateTimeBinder to bind the reflected DateOfBirth invocation. Without the custombinder, the call to InvokeMember would fail because DateOfBirth is defined as a DateTimerather than string member.

Passing Parameters to InvokeMemberIf you need to pass values to a reflected member, then pass the arguments as an array ofheterogeneous object arguments (see Listing 11-3). You can declare this value in line, asdemonstrated in Listing 11-3, or declare a temporary variable. (Try to avoid temporaryvariables. Avoiding temporary variables is argued in books on Refactoring.)

The DefaultBinder will use the order and type of the arguments in the object array toresolve the reflected member. If you implement a custom binder, then you can use theReorderArgumentArray method to facilitate resolving which member was intended bythe caller.

If you are invoking methods, property setters, or fields, then the array of object argumentsare used as parameters to a method or as right-side values for fields and property set methods.Listing 11-4 demonstrates how to invoke an indexed property and pass multiple arguments

3 3 0 A d v a n c e d C # P r o g r a m m i n g

Figure 11-2 Properties are modified by getter and setter methods in MSIL that represent the getand set block in C#.

Page 358: Advanced c sharp programming

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 3 1

to a reflected member. This is not at all intuitive unless you understand indexers and howthey are represented.

Listing 11-4 Reflecting an indexed property set method

1: DateTime[] dateTimes = new DateTime[10];

2: public DateTime this[int index]

3: {

4: get{ return dateTimes[index]; }

5: set{ dateTimes[index] = value;}

6: }

7:

8: private static void InvokeIndexedPropertyDemo()

9: {

10: Type type = typeof(Class1);

11: object o = Activator.CreateInstance(type);

12: type.InvokeMember( "Item",

13: BindingFlags.Instance |

14: BindingFlags.SetProperty | BindingFlags.Public,

15: null, o, new object[]{0, new DateTime(1966, 2, 12)});

16:

17: Console.WriteLine(((Class1)o)[0].ToString());

18: }

Line 1 of Listing 11-4 declares a field as an array of DateTime values. (If you are going todeclare a fixed-size array, then the property method should have some error handling, butthis isn’t relevant to our discussion.) Lines 2 through 6 define an indexer that supportstreating the containing object as if it were an array. The indexer is named this. Lines 8through 18 implement a test method that invokes the indexer.

Of special note: we refer to the indexer by the name “Item.” This is not especially intuitive:you just have to know indexers can be referred to by an implied property named Item.Using a default name for an indexer makes it convenient to refer to indexers in languagesthat do not directly support them. For example, Visual Basic .NET does not support indexersdirectly; however, a Visual Basic .NET consumer of a C# class can invoke the indexer byreferring to the implied Item property. Finally, note that we can pass disparate types to thearray of arguments. On line 15 of Listing 11-4, we pass an integer and a DateTime value.The integer represents the indexed value, and the DateTime value will be assigned to theunderlying field accessed via the property set method.

Loading Assemblies Using ReflectionAn assembly contains metadata and the IL representation of an application. To use anassembly dynamically, you can invoke Load or LoadFrom static methods on the Assembly

Page 359: Advanced c sharp programming

class. (In Chapter 4 you had an opportunity to learn how to use Assembly.LoadFrom, whichloads an assembly from a path and an assembly file name. I won’t repeat that informationhere; refer to Chapter 4 for examples of LoadFrom.) I will demonstrate how to load anassembly using the Assembly.Load method here.

NOTEYou can use Assembly.LoadFromPartialName to load assemblies registered in the GAC (Global AssemblyCache), but this is not a recommended practice.

The Assembly.LoadFrom method is the easiest way to load an assembly at run-time.There are seven variations of Assembly.Load. You can refer to the help documentation forinformation on the variations of Assembly.Load. One version of Assembly.Load acceptsan AssemblyName object that describes them explicitly, including the full name of theassembly, the version, culture information, and a public key token. Listing11-5 demonstrates anexample of the Assembly.LoadFrom method (for quick reference), and Listing 11-6demonstrates Assembly.Load using an AssemblyName object, demonstrating how toconstruct and initialize an AssemblyName object.

Listing 11-5 A quick reference for the Assembly.LoadFrom method

Assembly assembly = Assembly.LoadFrom(

"C:\\WINNT\\Microsoft.NET\\Framework\\" +

"V1.0.3705\\System.Windows.Forms.dll");

foreach( Type type in assembly.GetTypes())

{

Console.WriteLine(type.FullName);

}

Listing 11-5 loads an assembly by providing the complete file path to the System.Windows.Forms.dll assembly. (Check Chapter 4 for more Reflection examples.) This is the easiestway to dynamically load an assembly. The foreach statement iterates through all of the typesdefined by System.Windows.Forms.

Listing 11-6 Loading an assembly using an AssemblyName object

AssemblyName assemblyName = new AssemblyName();

assemblyName.CodeBase =

"file:///C:\\WINNT\\Microsoft.NET\\Framework\\" +

"V1.0.3705\\System.Windows.Forms.dll";

assemblyName.Version = new Version("1.0.3300.0");

assemblyName.CultureInfo = new CultureInfo("en-US");

3 3 2 A d v a n c e d C # P r o g r a m m i n g

Page 360: Advanced c sharp programming

Assembly assembly = Assembly.Load(assemblyName);

foreach( Type type in assembly.GetTypes())

{

Console.WriteLine(type.FullName);

}

The code in Listing 11-6 is equivalent to the code in Listing 11-5. We used the more verboseAssembly.Load method, passing an instance of an AssemblyName object. We could haveinitialized the AssemblyName.CodeBase argument only, as demonstrated using a file://moniker and the path. Listing 11-6 also demonstrates how to specify the Version andCultureInfo; both Version and CultureInfo are object properties. In the example, we loadedthe English-US version represented by the “en-US” string used to initialize the CultureInfoobject. We might use this more verbose version of the Load method to switch betweenculture-specific assemblies.

Generating User Interfaces with Reflected ObjectsBeing able to reflect type information means that you can figure out the type and value of anobject’s properties dynamically. Equate specific kinds of data to a specific control and it ispossible to create user interfaces dynamically. For example, assume you have an object witha string property. Equate the name of the property to a Label’s Text property, the data typestring to a TextBox control, and the value of the property to the value of the TextBox, andyou can add a Label and TextBox to a form. Repeat this process for other object properties,and you have a dynamic user interface. Employ DataBinding, and the changes in the controlare automatically reflected in the object.

Listing 11-7 combines a strongly typed collection, reflected properties of the type containedby the collection, and DataBinding to generate a user interface. Notice that the form does notknow anything about the kind of object being used to create the user interface; everything isaccomplished using Reflection.

Listing 11-7 Code that automatically generates a user interface using Reflection

1: using System;

2: using System.Drawing;

3: using System.Collections;

4: using System.ComponentModel;

5: using System.Windows.Forms;

6: using System.Data;

7: using System.Reflection;

8:

9: namespace DynamicUserInterface

10: {

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 3 3

Page 361: Advanced c sharp programming

3 3 4 A d v a n c e d C # P r o g r a m m i n g

11: public class Form1 : System.Windows.Forms.Form

12: {

13:

14: [Used outlining to hide auto-generated code]

15: [Windows Form Designer generated code]

16:

17: [STAThread]

18: static void Main()

19: {

20: Application.Run(new Form1());

21: }

22:

23: private void menuItem7_Click(object sender, System.EventArgs e)

24: {

25: const string about =

26: "Dynamic User Interface Demo\r\n" +

27: "Written by Paul Kimmel. [email protected]\r\n" +

28: "Copyright \xA9 2002. All Rights Reserved.\r\n" +

29: "Published in Advanced C# Programming\r\n";

30:

31: MessageBox.Show(about, "About",

32: MessageBoxButtons.OK, MessageBoxIcon.Information);

33: }

34:

35: private void menuItem2_Click(object sender, System.EventArgs e)

36: {

37: Application.Exit();

38: }

39:

40: private void menuItem4_Click(object sender, System.EventArgs e)

41: {

42: CompactDiscs compactDiscs = CompactDiscs.Create();

43: CreateUserInterface(compactDiscs, typeof(CompactDisc));

44: }

45:

46: private void menuItem5_Click(object sender, System.EventArgs e)

47: {

48: Customers customers = Customers.Create();

49: CreateUserInterface(customers, typeof(Customer));

50: }

51:

52: private void ClearControls()

53: {

54: panel1.Controls.Clear();

55: }

Page 362: Advanced c sharp programming

56:

57: private object dataSource;

58: private void CreateUserInterface(IList list, Type type)

59: {

60: dataSource = list;

61: ClearControls();

62:

63: PropertyInfo[] properties = type.GetProperties();

64: for( int i=0; i < properties.Length; i++)

65: {

66: AddControl( properties[i], i, list );

67: }

68: }

69:

70: private void AddControl(PropertyInfo propertyInfo, int i, IList list)

71: {

72: Label label = new Label();

73: label.AutoSize = true;

74: label.Text = propertyInfo.Name;

75: label.Location = new Point(10, (i + 1) * 25);

76: panel1.Controls.Add(label);

77:

78: TextBox textBox = new TextBox();

79: textBox.Location = new Point(250, (i + 1) * 25);

80: textBox.Width = 250;

81: panel1.Controls.Add(textBox);

82: textBox.DataBindings.Add("Text", list, propertyInfo.Name);

83: }

84:

85:

86: private void menuItem8_Popup(object sender, System.EventArgs e)

87: {

88: menuItemNext.Enabled = panel1.Controls.Count > 0;

89: menuItemPrevious.Enabled = panel1.Controls.Count > 0;

90: }

91:

92: private void menuItemNext_Click(object sender, System.EventArgs e)

93: {

94: try

95: {

96: panel1.BindingContext[dataSource].Position += 1;

97: }

98: catch{}

99: }

100:

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 3 5

Page 363: Advanced c sharp programming

3 3 6 A d v a n c e d C # P r o g r a m m i n g

101: private void menuItemPrevious_Click(object sender, System.EventArgs e)

102: {

103: try

104: {

105: panel1.BindingContext[dataSource].Position -= 1;

106: }

107: catch{}

108: }

109: }

110:}

There a few comments I need to make before we review the code. Lines 14 and 15 ofListing 11-7 represent outlined code. This code will not run as is. Download and run theUserInterfaceDemo.sln to test the code. The outlined code represents the controls, constructor,and Dispose method, which are all generated by the View Designer.

The code that performs the work is defined in CreateUserInterface and AddControl.Pass an IList and a Type to CreateUserInterface on lines 58 through 68 of Listing 11-7.CreateUserInterface iterates through all of the properties defined by Type and invokesAddControl. AddControl on lines 70 through 83 create a Label control and set the textproperty to the PropertyInfo.Name value. Then, a TextBox is created, and the IList andthe PropertyInfo.Name value are used to create a Binder—on line 82—to the TextBox’s Textproperty. The menuItemNext_Click and menuItemPreviousClick event handlers use panel1’sBindingContext to move forward and backward between the objects in the IList. Modifythe value in a TextBox and the bound object’s property is updated.

Types.cs implements two strongly typed collections containing Customer and CompactDiscobjects to demonstrate the user interface generator.

What is left to create a robust solution? Well, if I were implementing this software, I wouldconsider making the following changes:

� Insert spaces into the PropertyInfo.Name value to make the Label.Text property moreuser friendly.

� Perhaps right justify the Label control and make all Labels the same width as thewidest text value, resulting in less whitespace between the Label and TextBox (orother control).

� Use the PropertyInfo.PropertyType to determine an optimal control to render, ratherthan defaulting to a TextBox.

� Dynamically assign an event handler to facilitate validation.

� Implement a layout manager to support dynamically changing interface styles.

� Separate the code-generation code into a separate class.

Page 364: Advanced c sharp programming

� Implement the code-generation code as an interface and realize a version for WindowsForms and Web Forms.

NOTEI opened Pandora’s box a couple of decades too soon. It is possible to generate clever user interfaces usingReflection. You can also use Reflection to emit dynamic types. My concern is that some manager will readthis and think that programmer obsolescence is just around the corner. Nothing could be further from thetruth. The demands of users are greater than ever. We may be able to automate some boring tasks likecreating common kinds of input screens, but that will just leave time for more challenging problems. Thisis a pattern of evolution that all innovation seems to take. With the current evolution of object-orientedlanguages, I do think that we are on the cusp of some clever applications.

The preceding bulleted list describes the changes that come to mind for an actual application;there are probably some other good modifications, too. With a little imagination, it would bepossible to create some reasonably advanced user interfaces.

Exploring the .NET Framework with ReflectionThe AssemblyViewer.sln from Chapter 2 demonstrates code that will allow you to explorethe .NET framework. If you have been reading this book from the beginning, you aresufficiently well prepared to contrive some algorithms to discover information about typesusing Reflection. The key to working with types using Reflection is to obtain an Assemblyobject, get the types defined in that Assembly, and invoke operations on the members definedin those types.

Assembly.GetTypes will return an array of System.Type objects representing all of thetypes defined by an assembly. From the Type object, you can obtain information about anymember by requesting the kindInfo objects. For example, to get all of the members of asingle type, invoke Type.GetMembers. GetMembers returns a heterogeneous mix ofMemberInfo objects, combining constructors, properties, events, fields, and methods intoa single array. The ConstructorInfo, PropertyInfo, EventInfo, FieldInfo, and MethodInfoclasses all inherit from the MemberInfo class. All of these classes are defined in theSystem.Reflection namespace.

You can request an array of a specific type by invoking the get method for that type. Forexample, Type.GetMethods will return an array of MethodInfo objects. Type.GetMethodwill return a single MethodInfo, based on the parameters that you pass to GetMethod. It iseasier to invoke operations on members when you have a specific Info object; however,you know from the “Dynamic Member Invocation” section that you can invoke operationson members directly with a Type object.

In the next section I will demonstrate some operations you are likely to want to performin real applications relying on dynamic type interaction. We’ll review Reflection examplesfrom the AssemblyViewer.sln from Chapter 5, examples of dynamically invoked methodsand parameters, and binding to events using Reflection.

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 3 7

Page 365: Advanced c sharp programming

Returning to the CLR Reference Application, Assembly Viewer 2The AssemblyViewer.sln contains several examples of Reflection for fields, properties,methods, events, constructors, and types. As you may recall, the sample application fromChapter 5 allows you to load an assembly at run-time and adds all of the types and theirincumbent members to a database. Refer to that application for general examples ofReflection. I will refer to the AssemblyViewer.sln occasionally in this section, as well asprovide some additional new sample programs.

Requesting Type InformationAn easy way to think about Reflection is as a hierarchical arrangement of entities with anassembly at the pinnacle of the hierarchy. Inside an assembly are the types, and insideeach type are members, including nested types. The members might include enumerations,structures, classes, properties, methods, events, fields, and constructors. Thus, once youhave loaded an assembly, the next thing you will need to do is request a Type object.

There are a couple of ways to request type objects. You can use the typeof operator,which is the easiest and most convenient way to obtain a Type object, or you can use thestatic method Type.GetType. Here are a couple of syntactical examples demonstrating howto obtain type information.

typeof(type)Type.GetType(typename)

For example, typeof(Form1) will return a Type object for a class named Form1. The secondexample is a bit misleading. From the help documentation for Type.GetType, you mightassume that you can simply express the typename argument as a simple name. The tricky partis that it seems that for some types you can. For example, Type.GetType(“System.Int32”)will return a Type object for Int32, but Type.GetType(“System.Windows.Forms.Form”) willreturn null. However, if you pass the assembly qualified name to Type.GetType thenType.GetType will work correctly. Here is an example demonstrating an assembly qualifiedname for the System.Windows.Forms.Form class and why you’ll appreciate typeof.

Type.GetType("System.Windows.Forms.Form, System.Windows.Forms,

Version=1.0.3300.0, Culture=neutral,

PublicKeyToken=b77a5c561934e089");

To reflect types using the Type.GetType reliably, you will need to provide an assemblyqualified name. Here is an example that loads the System.Windows.Forms.Form class byname using an assembly qualified name. Listing 11-8 provides an example of an assemblyqualified name. The fragment was taken from the TypeDemo.sln available for download.(You can probably tell from the fragment that the user interface is composed of two Buttoncontrols and a TextBox on a form.)

3 3 8 A d v a n c e d C # P r o g r a m m i n g

Page 366: Advanced c sharp programming

Listing 11-8 Reflecting Type and MemberInfo objects, including an example of an assemblyqualified name

1: private void ReflectMembers(Type type)

2: {

3: textBox1.Clear();

4: foreach(MemberInfo memberInfo in type.GetMembers())

5: {

6: textBox1.AppendText(memberInfo.Name + "\r\n");

7: }

8: }

9:

10: private void button1_Click(object sender, System.EventArgs e)

11: {

12: ReflectMembers(typeof(System.Windows.Forms.Form));

13: }

14:

15: private void button2_Click(object sender, System.EventArgs e)

16: {

17: const string assemblyQualifiedName =

18: "System.Windows.Forms.Form, System.Windows.Forms, " +

19: "Version=1.0.3300.0, Culture=neutral, " +

20: "PublicKeyToken=b77a5c561934e089";

22: ReflectMembers(Type.GetType(assemblyQualifiedName));

23: }

ReflectMembers, beginning on line 1 of Listing 11-8, demonstrates how to iterate allmembers as a homogeneous group. This is a good strategy for quick reference. The eventhandler on lines 10 through 13 demonstrate how to obtain type information using the typeofoperator. The typeof operator will not work if you do not have a reference to the assembly.For example, if System.Windows.Forms.dll is not part of the assembly, you will not beable to obtain its type information.

The event handler on lines 15 through 23 of Listing 11-8 demonstrates how to obtain thetype information for the same type as the event handler for button1—System.Windows.Forms.Form—using an assembly qualified name. The fields in the string include the type to get theType object for, the declaring types, the version and culture information, and the public key.

A reasonable person might inquire as to how a fully qualified assembly name is obtained.The answer is that I reflected System.Windows.Forms.Form using the typeof operator andread the Type.AssemblyQualifiedName property. This isn’t very convenient. Here is anexample of a practical problem presented by the requirement for an assembly qualified name.

Geoff Caylor, a member of a team of programmers working on a C# project in Oregon, cameup with the idea of using Reflection to generate ASP.NET user interfaces. Geoff wanted to create

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 3 9

Page 367: Advanced c sharp programming

a CustomAttribute (refer to Chapter 10) to tag properties in our business objects with a hintdescribing the kind of Web control to render for a particular field. When the GUI generator readthe property, the custom attribute could be read and a good control rendered. There are a coupleof ways of solving this problem: a person could create a layout manager and dynamically resolvethe kind of control to render; a Type object of the control to render could be associated with theproperty; or a string name of the control to render could be associated—for example, “CheckBox”for a Boolean property. Geoff originally chose the latter when he was prototyping because itseemed lightweight; that is, he chose to use a string. However, you cannot obtain the typeinformation for the System.UI.Web.WebControls.CheckBox without the assembly qualifiedname, and tying the property to a specific assembly version will break as soon as the .NETFramework is updated. (Recall the assembly qualified name includes the version information.)There is a practical solution to the problem that probably includes a layout manager and an aspectof a control hint and dynamic control-type resolution.

3 4 0 A d v a n c e d C # P r o g r a m m i n g

My role in the project in Oregon I have mentioned was architect. The project managerasked me about a practical problem. On our project, there were contractors that werewell grounded in OOP principles and main frame programmers transitioning to OOPand .NET. The project manager expressed a concern about esoteric code, like userinterface generators, and the ability of less-experienced permanent employees tomaintain this kind of code. I coined the term esoterrorism, which is applied to code thatis intentionally or maliciously esoteric. (We wanted to avoid unnecessarily esotericcode.)

Our pragmatic approach was to get the project done efficiently, taking advantage ofthe best and most powerful aspects of .NET without making the code unmaintainable.The solution is that we provided models and written architectural overviews tied towell-documented patterns and refactorings, and tagged code that might seem esotericto a “junior” programmer with the name of the pattern or “Refactoring.” For example,when we used a factory method, we provided a reference to a book in the team’s librarythat described factory methods. Clearly, it is the responsibility of permanent and newstaff to use this information, but there is no practical alternative.

All of this was tied to the concept of a “junior” programmer. Junior programmermeans a novice and is also a euphemism for a poorly compensated programmer. Whatsuch a person may know or be motivated to learn is a crapshoot. We could not justifyignoring Web Services, interfaces, delegates, Reflection, inheritance, or any of thethings a junior programmer may not yet comprehend, because to do so would riskthe primary objective, which was to deliver the application. A secondary and realgoal was to acknowledge the long-term budgetary constraints of the customer. Thiscombination of technical and human factors demonstrates why software developmentcan be difficult, and why it is important to have both a seasoned architect and a projectmanager guiding application development.

Page 368: Advanced c sharp programming

Reflecting Methods and Method ParametersTo obtain method information using Reflection, you can use MemberInfo.Name andMemberInfo.MemberType or directly request the MethodInfo objects from a type. Wheremethods are concerned, it is not sufficient to know the method name only. You must acquirethe entire signature of a method to determine how to invoke a method. Hence, after yourequire the MethodInfo object, you will need to require the ParameterInfo objects that describethe parameters that must be provided to the method invocation. Listing 11-9 provides anexample that displays the approximate signature of the methods of a type.

Listing 11-9 Reflecting the method signatures of a type

1: using System;

2: using System.Reflection;

3: using System.Text;

4:

5: namespace InvokeMethod

6: {

7: class Class1

8: {

9: [STAThread]

10: static void Main(string[] args)

11: {

12: Type type = typeof(System.Windows.Forms.MessageBox);

13: MethodInfo[] methods = type.GetMethods();

14:

15: foreach( MethodInfo methodInfo in methods )

16: {

17: ApproximateSignature( methodInfo );

18: }

19:

20: Console.ReadLine();

21:

22: }

23:

24: private static void ApproximateSignature( MethodInfo methodInfo )

25: {

26: // access return_type name( paramtype1 param1

27: // [, paramtypen paramn]);

28: const string mask = "{0} {1} {2}({3})";

29:

30: Console.WriteLine( string.Format(mask,

31: GetAccessModifier(methodInfo), methodInfo.ReturnType,

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 4 1

Page 369: Advanced c sharp programming

32: methodInfo.Name, GetParameterList( methodInfo )));

33: }

34:

35: private static string GetAccessModifier(MethodInfo methodInfo)

36: {

37: return methodInfo.IsPublic ? "public" : "private";

38: }

39:

40: private static string GetParameterModifier(

41: ParameterInfo parameterInfo )

42: {

43: if( parameterInfo.IsIn ) return "[in]";

44: if( parameterInfo.IsOut) return "[out]";

45: return string.Empty;

46: }

47:

48: private static string GetParameterList(MethodInfo methodInfo)

49: {

50: // modifier type name

51: const string mask = "{0} {1}";

52: StringBuilder s = new StringBuilder();

53: ParameterInfo[] parameters = methodInfo.GetParameters();

54:

55: for( int i = 0; i < parameters.Length; i++ )

56: {

57: if( GetParameterModifier(parameters[i]) != string.Empty )

58: s.Append( GetParameterModifier(parameters[i]) );

59: s.Append( string.Format(mask,

60: parameters[i].ParameterType, parameters[i].Name ));

61: if( i < parameters.Length-1 ) s.Append(", ");

62: }

63: return s.ToString();

64: }

65: }

66: }

The code in Listing 11-9 will print the approximate method signatures. You can obtaina good visual picture of a method using this program, but it would be difficult to invokemethods in a purely dynamic way using this information, because the parameters couldrepresent any type, including types that haven’t been invented yet.

The code in Listing 11-9 begins in Main by requesting the method signatures of theSystem.Windows.Forms.MessageBox class on line 12. (The sample application for this codeis InvokeMethod.sln.) ApproximateSignature displays an access modifier, a return type,name, and parameter list. Reflection distinguishes between public and non-public methods;

3 4 2 A d v a n c e d C # P r o g r a m m i n g

Page 370: Advanced c sharp programming

thus, GetAccessModifier returns only public or private, but may, in reality, be public, protected,private, internal, or protected internal. You can dynamically invoke public and non-publicmethods based on ReflectionPermission settings. GetParameterList provides the list ofparameters, including any modifier, type, and name, as shown in lines 48 through 64.

NOTETechnologies like Reflection, including the ability to emit dynamic code (see “Emitting Dynamic Assemblies”later in this chapter), may ultimately lead to smart programs that write their own code, but, as you may beable to glean from this section, it may be challenging to write completely dynamic code. From this sectionyou can see that it may be difficult to invoke any method without knowing something about its signaturein advance.

GetParameterModifier on lines 40 through 46 of Listing 11-9 only returns [in] and [out]modifiers. You may recall that there is a ref modifier for parameters, too. This is reflected asan ampersand (&) in the type signature. Hence, if a string parameter uses the ref modifier,then the ParameterInfo.ParameterType will return System.String&. The ampersand is actuallyused in C++ to represent pass-by-reference objects. (The presence of the ampersand for refparameters provides a hint as to the underlying language used to create C#.)

With some innovation, you may be able to create a completely dynamic methodinvocation application. Reflect the MethodInfo and ParameterInfo objects for a particularmethod. Use the ParameterInfo to dynamically generate a user interface for input fields,obtaining parameters from a user and invoking the method by passing those parameters tothe method. This is a reasonable approximation of what Web Services do with .asmx pages.(Refer to the Expressions.sln Web Service from Chapter 4 for an example of the dynamicinput test page for Web Services.)

Creating the dynamic user interface and invoking the method with dynamic user inputs isleft as an exercise. If you combine the user interface form code demonstrated in the earliersection “Generating User Interfaces with Reflected Objects” with examples for dynamicallyinvoking methods provided throughout the chapter, you can derive a solution. If you arehaving difficulties, download the InvokeMethod.sln from the www.osborne.com orwww.softconcepts.com website for an example implementation. The dynamic form isdemonstrated in input.cs, and the invocation is provided in Main.cs.

TIPIf you like a challenge, try to complete the dynamic form, method and parameter reflection, and invocationof a method sample before downloading the code. Here is a hint that will help you get started: look at an.asmx page from a Web Service for inspiration. A solution is provided in InvokeMethod.sln, available fordownload.

Accessing Properties and Fields DynamicallyThe key to accessing properties and fields using Reflection is to obtain the property and fieldthrough InvokeMember, PropertyInfo, or FieldInfo, as the case may be; cast the referenceto the appropriate data type; and then simply treat the value as you would a property or field.

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 4 3

Page 371: Advanced c sharp programming

I implied that there are two ways to access properties and fields: InvokeMember or obtain aPropertyInfo for properties or a FieldInfo for fields. I will demonstrate using properties andfields as left-side and right-side values using both InvokeMember and the Info objects. Theclass used for this section is provided in Listing 11-10.

Listing 11-10 A class used to demonstrate reflecting fields and properties

public class Target

{

private string text;

public string Text

{

get{ return text; }

set{ text = value; }

}

}

The class Target is representative of any class with fields and matching properties. (Clearly,if we modify either the field or property, the related value is modified, too.) Target will beused to demonstrate reflections in the sections that follow.

Setting and Getting Fields Using InvokeMemberInvokeMember can be used to interact with any member type. The general applicationof InvokeMember is to name the member, provide the BindingFlags, specify a Binder orpass null to use the DefaultBinder, pass an object for non-static members, and an arrayof objects representing parameters to the member. Listing 11-11 provides an example ofInvokeMember on the private instance field: Target.text from Listing 11-10.

Listing 11-11 Using InvokeMember on a private instance field

Type type = typeof(Target);

object o = Activator.CreateInstance(type);

// set private field using InvokeMember

type.InvokeMember("text",

BindingFlags.SetField | BindingFlags.NonPublic |

BindingFlags.Instance, null, o,

new object[]{"Advanced C# Programming"});

// get private field using InvokeMember

string text = (string)type.InvokeMember("text",

BindingFlags.GetField | BindingFlags.NonPublic |

3 4 4 A d v a n c e d C # P r o g r a m m i n g

Page 372: Advanced c sharp programming

BindingFlags.Instance, null, o, new object[]{});

MessageBox.Show(text);

The first two statements obtain the Type object and create an instance of the Target type.The first use of InvokeMember indicates the field name, text, and specifies the BindingFlagsthat help pinpoint the field. We want to set the field value, so we pass BindingFlags.SetField.The field is private, so we OR—represented by the | operator—BindingFlags.NonPublic toBindingFlags.SetField; Target.text is private, which is represented by ORing BindingFlags.Instance to our BindingFlags argument. We can use the Type.DefaultBinder for the textfield; therefore, passing null will implicitly instruct InvokeMember to use the DefaultBinder.The o argument represents the instance of Target returned by the Activator.CreateInstancemethod, and, finally, we are setting a field, which only needs a single value. The fieldTarget.text is assigned the string “Advanced C# Programming”.

The second call to InvokeMember is used to get the Target.text field value. To invokethe set method for text, we need to make a few changes to the parameters to InvokeMember.Instead of BindingFlags.SetField, we OR BindingFlags.GetField into the BindingFlagsargument. Because we are invoking what is effectively a function, we can pass an emptyarray to InvokeMember. Finally, cast the return value of InvokeMember to the type we aregetting. The MessageBox.Show method will display “Advanced C# Programming” whenthe final statement executes.

Setting and Getting Fields Using a FieldInfoUsing a FieldInfo object is much simpler than using InvokeMember to perform the sameoperation. The reason is that when we obtain a FieldInfo object, we have set the context tofields, resulting in the need to provide less information (see Listing 11-12).

Listing 11-12 Reflecting a field with a FieldInfo object

Type type = typeof(Target);

object o = Activator.CreateInstance(type);

//set and get a field using a FieldInfo

FieldInfo field = type.GetField("text",

BindingFlags.NonPublic | BindingFlags.Instance);

field.SetValue(o, "www.codeguru.com");

text = (string)field.GetValue(o);

MessageBox.Show(text);

The first two statements in Listing 11-12 create an instance of the Target object. The thirdstatement obtains a FieldInfo for the Target.text field. Because the field is private, we need to usethe BindingFlags to pinpoint the field. Notice that we do not need to use BindingFlags.GetField

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 4 5

Page 373: Advanced c sharp programming

3 4 6 A d v a n c e d C # P r o g r a m m i n g

BindingFlags .SetField; simply specifying the access type and whether we want an instance orstatic member is sufficient. The final two statements set and get the value of the field.

Setting and Getting a Property Using InvokeMemberGetting and setting a property using InvokeMember is very similar to getting and setting afield. The only noticeable difference between using InvokeMember on fields and propertiesis the use of BindingFlags. We need to use BindingFlags.SetProperty and BindingFlags.GetProperty, as demonstrated in Listing 11-13.

Listing 11-13 Invoking operations on properties

Type type = typeof(Target);

object o = Activator.CreateInstance(type);

// set and get a property using InvokeMember

type.InvokeMember("Text",

BindingFlags.SetProperty | BindingFlags.Instance |

BindingFlags.Public, null, o,

new object[]{"Windows Developer Magazine"});

// get property using invoke member

text = (string)type.InvokeMember("Text",

BindingFlags.GetProperty | BindingFlags.Instance |

BindingFlags.Public, null, o, new object[]{});

MessageBox.Show(text);

If you are reflecting an indexed property, you will need to pass the index in the arrayof objects passed to InvokeMember. (Refer to the earlier section “Passing Parameters toInvokeMember” for an example of calling InvokeMember on an indexed property.)

Setting and Getting a Property Using a PropertyInfoSetting and getting properties using a PropertyInfo object is similar to working with FieldInfoobjects using Reflection, as Listing 11-14 demonstrates.

Listing 11-14 Using PropertyInfo objects

Type type = typeof(Target);

object o = Activator.CreateInstance(type);

//get and set a property using a PropertyInfo

PropertyInfo property = type.GetProperty("Text");

property.SetValue(o, "www.softconcepts.com", null);

Page 374: Advanced c sharp programming

text = (string)property.GetValue(o, null);

MessageBox.Show(text);

The PropertyInfo object in Listing 11-14 accounts for a possible index property by accepting athird argument on SetValue and a second argument on GetValue. PropertyInfo .GetValue,as demonstrated in Listing 11-14, passes null to GetValue, as Target.Text is not an indexedproperty. Pass an argument specifying the index of the value you want to modify forindexed properties.

Binding to Events DynamicallyYou can bind an event handler to a dynamically reflected EventInfo. Obtain the EventInfoobject by name from the Type object and invoke EventInfo.AddEventHandler to associatethe event handler with an instance of the reflected object. I added a Changed event to theTarget class to demonstrate this. Listing 11-15 contains the revised Target class and afragment of code that demonstrates how to associate an event handler to the Changed event.

Listing 11-15 Binding to a reflected event member

1: public class Target

2: {

3: private string text;

4:

5: public string Text

6: {

7: get{ return text; }

8: set

9: {

10: text = value;

11: OnChanged();

12: }

13: }

14:

15: private void OnChanged()

16: {

17: if( Changed != null )

18: Changed(this, System.EventArgs.Empty);

19: }

20:

21: public event EventHandler Changed;

22: }

23:

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 4 7

Page 375: Advanced c sharp programming

3 4 8 A d v a n c e d C # P r o g r a m m i n g

24: public class Class1

25: {

26: [STAThread]

27: static void Main( string[] args )

28: {

29: Type type = typeof(Target);

30: object o = Activator.CreateInstance(type);

31: EventInfo eventInfo = type.GetEvent("Changed");

32: eventInfo.AddEventHandler(o, new EventHandler(Class1.OnChanged));

33:

34: ((Target)o).Text = "New Value";

35: }

36:

37:

38: private static void OnChanged(object sender , System.EventArgs e)

39: {

40: MessageBox.Show(((Target)sender).Text);

41: }

42: }

The class Target is revised on lines 1 through 22 of Listing 11-15 and now contains anOnChanged method that raises the Changed event if Changed is not null. OnChanged iscalled when the value of the Text property is changed on line 11.

The static method Class1.Main obtains the Type object for Target and creates an instanceof Target on lines 29 and 30 of Listing 11-15. Target’s Type object is used to obtain anEventInfo object that represents Target.Changed on line 31. The EventInfo.AddEventHandlermethod is called to associate Class1.OnChanged with the Target.OnChanged. (We don’tneed an instance of Class1, since we are using a static event handler.) We take a shortcut online 34 to change Target.Text, which will raise the Changed event. The OnChanged handleron lines 38 through 41 will display the Text property of the Target object represented by thevariable o.

The fundamental operations for using objects directly and for using reflected objects arethe same. The only difference is that you have to write verbose code to specify the types andmembers that you want to interact with.

Emitting Dynamic AssembliesA powerful aspect of .NET programming with a significant amount of untapped potential isthe ability to use the Reflection.Emit namespace to generate code.

Microsoft .NET compiles code to MSIL (Microsoft Intermediate Language). MSILapproximates assembly language but is not tied to a specific operating system. The net effectof MSIL is that we are likely to see .NET applications running on other platforms besides

Page 376: Advanced c sharp programming

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 4 9

Windows- and Intel-based computers. IL code is Just-In-Time compiled (JITted) to machinelanguage before it is executed. I suppose someone is already working on .NET for the AppleMacintosh or Unix platforms, but a benefit we have today is the ability to emit IL code tomemory and disk from a running application. The System.Reflection.Emit namespace supportsthis capability. In this section, I will review salient aspects of the Emit namespace anddemonstrate how to emit an assembly, class, and code and dynamically load and executethe assembly.

The fundamental steps required to emit an assembly and type with code are analogous tothose for writing code from scratch. You have to define the same elements; the difference isthat you have to write code to do the job rather than create the entities with the keyboard andeditor. The basic steps for creating a dynamic assembly and dynamic types includethe following:

� Emitting an assembly to memory and disk

� Emitting a module

� Emitting a dynamic type (a class)

� Emitting members, including constructors, methods, properties, and fields

� Saving, loading, and executing dynamic assemblies

All of the code that demonstrates the topics in this section is available for download in theEmitter.sln. The code demonstrates emitting a strongly typed collection that inherits fromSystem.Collections.CollectionBase and a Test collected object. Between the two sampleclasses that emit IL there are examples of emitting fields, properties, methods, constructors,classes, modules, and assemblies. There are Reflection.Emit examples that demonstratedeclaring local variables, performing type casting, applying the DefaultMemberAttribute,and creating a class indexer. Several of these topics are explored, but you are encouraged todownload the Emitter.sln for the complete sample application.

There are a couple of important and easy-to-follow strategies that will help you write codethat emits MSIL. If you remember that you have to perform the same kinds of things withyour emitter code that you do if you were interacting with the IDE, you will be less likelyto miss steps. If you keep in mind that reading a lot of code will help you write code andapply this concept to emitting MSIL, you will find writing emitters more enjoyable. Finally,remember that because you can write code in C#, compile the code, and view the MSIL withthe ildasm.exe utility, you will have an excellent resource for creating, viewing, and learningMSIL from your own code.

Emitting Assemblies in MemoryOne of the steps you need to perform when emitting MSIL is to create an assembly.Assemblies have names represented by the AssemblyName class and an Assembly objectrepresented by the AssemblyBuilder class.

Page 377: Advanced c sharp programming

3 5 0 A d v a n c e d C # P r o g r a m m i n g

NOTEEntities emitted in MSIL have analogous classes that are designed specifically for generating these entities.These classes end with the suffix Builder. Hence, to emit an assembly, you will need an AssemblyBuilder.To emit a class or structure, you will need a TypeBuilder, and so on. Look at the System.Reflection.Emitnamespace for all of the Builder classes.

The code samples for this section are contained in Emitter.sln, as previously mentioned.The Emitter.sln contains a module Emitter.cs that provides an implementation of an emitterthat writes a strongly typed collection derived from the CollectionBase class and also containsa sample class that can be used as a collected class. (Refer to Chapter 6 for more on stronglytyped collections.) The EmitTypedCollection creates a derived class that generalizesSystem.Collections.CollectionBase for any type that you use to initialize an emitter. Forexample, if you have a Music class that represents your favorite artists and titles, you canemit a typed collection at run-time without writing any additional code.

The first step in creating our emitter is to create a dynamic assembly to contain the typedcollection. EmitTypedCollection.GetClass is a static factory method that is the starting pointfor the emitter. GetClass returns a Type, which is, in fact, the dynamically created type.GetClass requires a string and Type argument. The string argument represents the name ofthe typed collection class, the module, and the assembly, and the Type argument will be thestrong type stored by the dynamic class.

GetClass has two forms. The first form accepts the string and Type argument, and thesecond version accepts a string, Type, AppDomain, and AssemblyBuilderAccess-enumeratedvalue. All of this information is necessary to create the AssemblyName and Assembly objects,the first objects we have to create. Listing 11-16 shows the implementation of the code thatwill yield a dynamic assembly, albeit an empty one at this point.

TIPIt is important to remember that the orchestration of the code is up to the individual. You certainly couldconsolidate the emitter code into fewer methods, but you would end up with some extremely long methods.

Listing 11-16 Essential ingredients for emitting a dynamic assembly

1: public class EmitTypedCollection

2: {

3: private string name;

4: private Type collectedType;

5: private AssemblyName assemblyName;

6: private AssemblyBuilder assemblyBuilder;

7: private ModuleBuilder moduleBuilder;

8: private TypeBuilder typeBuilder;

9: private MethodBuilder addMethod;

10: private PropertyBuilder indexerProperty;

11:

Page 378: Advanced c sharp programming

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 5 1

12: public EmitTypedCollection(string name, Type collectedType)

13: {

14: this.name = name;

15: this.collectedType = collectedType;

16: }

17:

18: public static Type GetClass(string name,

19: Type collectedType )

20: {

21: return GetClass(name, collectedType,

22: Thread.GetDomain(), AssemblyBuilderAccess.Run);

23: }

24:

25: public static Type GetClass(string name,

26: Type collectedType, AppDomain domain,

27: AssemblyBuilderAccess access)

28: {

29: EmitTypedCollection o = new EmitTypedCollection(name,

30: collectedType);

31:

32: return o.CreateClass(domain, access);

33: }

34:

35: private Type CreateClass(AppDomain domain,

36: AssemblyBuilderAccess access)

37: {

38: assemblyName = CreateAssemblyName();

39: assemblyBuilder = CreateAssemblyBuilder(domain, assemblyName, access);

40: moduleBuilder = CreateModuleBuilder(assemblyBuilder, access);

41: typeBuilder = CreateTypeBuilder(moduleBuilder);

42: EmitCode(typeBuilder);

43:

44: return typeBuilder.CreateType();

45: }

46:

47: private AssemblyName CreateAssemblyName()

48: {

49: AssemblyName aName = new AssemblyName();

50: aName.Name = name;

51: return aName;

52: }

53:

54: private AssemblyBuilder CreateAssemblyBuilder(AppDomain domain,

55: AssemblyName assemblyName, AssemblyBuilderAccess access)

Page 379: Advanced c sharp programming

3 5 2 A d v a n c e d C # P r o g r a m m i n g

56: {

57: return domain.DefineDynamicAssembly(assemblyName, access);

58: }

59: // more code needs to be added here

60: }

Decomposed as demonstrated in Listing 11-16, the code should be easier to follow. CallGetClass(name, collectedType) to get started. The overloaded GetClass is called, adding theAppDomain and AssemblyBuilderAccess-enumerated value to the mix. AppDomains exist toprovide application isolation. We will be emitting an assembly, and assemblies are isolatedby an AppDomain. In the listing we use the AppDomain of the application this code runs in,Emitter.exe. AssemblyBuilderAccess has three enumerated values: Run, RunAndSave, andSave. Use Run for dynamic assemblies that won’t be written to disk, use RunAndSave torun in memory and save, and use Save to write an assembly to disk but not to run in memory.(You can run a saved assembly by referencing it in an application in the usual way.)

The second GetClass creates an instance of our EmitTypedCollection class and callsCreateClass. CreateClass is the nexus of control for the emitter. Just as promised, CreateClasscreates an AssemblyName, an AssemblyBuilder, a ModuleBuilder, and a TypeBuilder, andfinally emits the code contained in that type. (The module, Type, and code-building code isnot shown yet, intentionally.)

You know how to construct an AssemblyName; thus, the code on lines 47 through 52of Listing 11-16 should be no surprise. Finally, the AssemblyBuilder code is quitestraightforward. The call to domain.DefineDynamicAssembly(assemblyName, access) isseparated into the CreateAssemblyBuilder method—on lines 54 through 59—to provide awell-named method describing what is going on. More important is the recurring patternthat is introduced here.

When you are writing code everything you do is context dependent. The same is true foremitting code. To end up with an assembly, you create a new project. Modules are addedin Visual Studio .NET to the project. When you are emitting code, you emit the assemblydirectly, but modules still need to be added to the assembly. This contextually dependentbehavior is evident when we use AppDomain.DefineDynamicAssembly on line 57. The codeis understood to mean that the dynamic assembly belongs to the AppDomain. You will seethis recurring pattern everywhere. AppDomains contain assemblies, which contain modules,which contain types, which contain members, which contain code.

The boldfaced code in Listing 11-16 is described in the upcoming section “Using BuilderObjects to Create IL.”

Saving Emitted Assemblies to FileThe Assembly class contains a Save method. If you invoke AppDomain.DefineDynamicAssembly with either of the AssemblyBuilderAccess.Save or AssemblyBuilderAccess

Page 380: Advanced c sharp programming

.RunAndSave values (see Listing 11-16, line 22, for an example of AssemblyBuilderAccess

.Run), you can write a dynamic assembly to disk.The static factory method EmitTypedCollection.SaveAssembly and the private instance

method EmitTypedCollection.SaveAssembly demonstrate how to emit a Test class and astrongly typed collection class containing Test objects to disk. You can load and experimentwith the Test.dll application just as you would an assembly hand-crafted in the VisualStudio .NET IDE. The code used to create the typed collection is the same code definedin EmitTypeCollection, and EmitType provides an example of a simple class containinga field, a property, and a constructor.

To experiment with the Test.dll assembly, follow the numbered steps:

1. Download and run the Emitter.sln.

2. Click the Write Assembly button shown in Figure 11-3.

3. Close the application and open the Emitter.sln project.

4. Add a reference to the Test.dll assembly in the Emitter.csproj. (Test.dll should be inthe same directory containing the compiled Emitter.sln).

5. Find the button3_Click event handler. This is the event handler for the Test Writtenbutton shown in Figure 11-3. Change the compiler #if conditional to True, enablingthe code for testing Test.dll.

6. Run Emitter.sln again and click the Test Written button.

Following the numbered steps will allow you to try the emitted code in Test.dll. The testcode will create an instance of the Test collection, add a TestData object to the collection,demonstrate the foreach construct, and bind the strongly typed collection to the DataGrid(shown in Figure 11-3).

If you want to modify the code that emits Test.dll, remove the reference to Test.dll, disablethe conditional code, modify the EmitTypedCollection or EmitType code, and repeat thenumbered steps to test your changes. Of course, you can step through the code to explorehow everything works.

Using Builder Objects to Create ILBuilder objects are beneficial for emitting entities, including dynamic assemblies. Lines 40,41, 42, and 44 of Listing 11-16 exemplify the importance of Builders. On line 40, we create aModuleBuilder, and on line 41 a TypeBuilder. Ultimately, we want to get to our TypeBuilder,which represents the class we will be emitting. Listing 11-17 shows the CreateModuleBuilderand CreateTypeBuilder methods that I implemented. (These are part of theEmitTypedCollection class from Listing 11-16.)

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 5 3

Page 381: Advanced c sharp programming

3 5 4 A d v a n c e d C # P r o g r a m m i n g

Listing 11-17 EmitTypedCollection methods create the ModuleBuilder and TypeBuilder,representing a dynamic module and class, respectively.

private ModuleBuilder CreateModuleBuilder(AssemblyBuilder assemblyBuilder,

AssemblyBuilderAccess access)

{

if( access == AssemblyBuilderAccess.Run)

return assemblyBuilder.DefineDynamicModule(name);

else

return assemblyBuilder.DefineDynamicModule(name + ".mod",

NameToFileName);

}

private TypeBuilder CreateTypeBuilder(ModuleBuilder moduleBuilder)

{

TypeBuilder t = moduleBuilder.DefineType(name,

TypeAttributes.Public | TypeAttributes.BeforeFieldInit,

typeof(System.Collections.CollectionBase));

Type type = typeof(DefaultMemberAttribute);

ConstructorInfo constructorInfo =

type.GetConstructor(new Type[]{typeof(string)});

CustomAttributeBuilder customAttributeBuilder =

new CustomAttributeBuilder(constructorInfo, new Object[]{"Item"});

Figure 11-3 Click Write Assembly to write the Test.dll Assembly containing an emitted type andtyped collection.

Page 382: Advanced c sharp programming

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 5 5

t.SetCustomAttribute(customAttributeBuilder);

return t;

}

The code in Listing 11-17 looks more daunting than it is, but it does suggest a tendencytoward complexity as we get to the emitter code that writes lines of code. CreateModuleBuilderemits a dynamic module name if we are going to run the assembly in memory or a validassembly name and module if we are going to save the assembly and module to disk. The“.mod” extension indicates a module containing compiled executable code, like .lib files.The .mod file is an intermediate file between the compiling and linking process. TheNameToFileName method was implemented by me; NameToFileName simply adds a “.dll”extension to the name passed to the emitter.

CreateTypeBuilder is defining and emitting the class for us. ModuleBuilder.DefineTypecreates a TypeBuilder object and emits the type code. The arguments passed to DefineTypeindicate that we want a public class derived from the CollectionBase class, demonstratinghow to emit a subclass. TypeAttributes.BeforeFieldInit initializes the class before any staticmembers are accessed. The default type to emit is a class, but we could have specified thisby ORing TypeAttributes.Class into the TypeAttributes parameter.

After we define the type, the remaining statements demonstrate how to emit an attributewith our class. Because we are emitting a typed collection, we want an indexer. By emittingthe DefaultMemberAttribute, we are signifying that Item will be our default member. EmittingItem with the DefaultMemberAttribute is identical to defining a this indexer property. Thefirst statement gets the Type object of the attribute we want to emit. The second statementobtains DefaultMemberAttribute’s ConstructorInfo for the constructor that accepts a singlestring named “argument.” Then, we request a CustomAttributeBuilder—another Builder thatfacilitates emitting entities, in this case, an attribute—by constructing the object from theConstructorInfo. Finally, we invoke TypeBuilder.SetCustomAttribute to emit our attributeand return the TypeBuilder.

NOTEThere is approximately an order of magnitude difference in lines of code that is hand-written and linesof code that emits IL. For example, it takes me about 15 minutes and 10 lines of code to implement astrongly typed collection in C#. It took me about 30 hours and 400 hundred lines of code to write theemitter examples in Emitter.sln. (I am getting faster, though.)

At this point, we are at EmitCode on line 42 of Listing 11-16. We have an assembly witha module and a type. We are ready to emit members and lines of code. The listings can bequite long at this point.

Writing Lines of OpCodesWriting lines of code requires that we have Builders that need lines of code. Events andfields are easier to emit than methods, since properties, fields, and events are represented by

Page 383: Advanced c sharp programming

simple statements. Methods, which include property methods, can be very long and complexbecause you have to write C# that emits IL almost as granular as assembly language code.Hence, if you are used to writing two or three lines of C# in your methods, then you willwrite about 25 lines of C# code to emit IL equivalent to three lines of C#. Contrarily, if youare used to writing 25-line methods or greater, your emitting methods will be hundreds oflines long.

We can look at a run-of-the-mill typed collection to figure out what members we haveto emit to IL to dynamically create a typed collection. Listing 11-18 contains a prototypicaltyped collection we can use as a roadmap.

Listing 11-18 A typed collection that contains instances of a class named Prototype

public class PrototypeCollection : CollectionBase

{

public PrototypeCollection(){}

public Prototype this[int index]

{ get{ return (Prototype)List[index];}

set{ List[index] = value; }

}

public int Add(Prototype value)

{

return List.Add(value);

}

}

The 14 lines of emitted code equates to approximately 266 lines of emitter code. With someRefactoring, I am sure I could reduce this amount substantially, but at half that number, we’restill talking about an order of magnitude more emitter code. The question is whether I will getat least ten times the payout. In simpler terms, will I or another developer create more than 10typed collections. The answer is yes, so arguably it is worth writing the emitter once.

Clearly, from the code listing we can determine that we need to emit a type that inheritsfrom CollectionBase. (We did that in Listing 11-17.) We might want to emit a constructor.Although our constructor doesn’t do anything, I will demonstrate this process. We need toemit an indexer property with a set and get method. The get method is surprisingly complexbecause we have to emit a dynamic type case. And, if we want our typed collection toserialize correctly, we will need to emit an Add method. The following list describes thesteps we still need to complete.

� Emit a constructor that calls our base class.

� Emit an index property that will be our default property.

� Emit the get and set methods for the indexer.

3 5 6 A d v a n c e d C # P r o g r a m m i n g

Page 384: Advanced c sharp programming

� Emit the Add method.

Summarizing, we need to emit one constructor, three methods, and a property. The EmitCodemethod in Listing 11-19 shows how I organized the remaining steps.

Listing 11-19 The EmitCode method organizes emitting the remaining parts of our typedcollection emitter.

private void EmitCode(TypeBuilder typeBuilder)

{

EmitMethod(typeBuilder);

EmitProperty(typeBuilder);

EmitConstructor(typeBuilder);

}

EmitMethod emits the Add method. EmitProperty emits the index property and the setand get methods that support it, and EmitConstructor emits our do-nothing constructor.

Emitting the Add MethodConsistent with the examples thus far, we need to obtain a Builder. Methods reside in types;as a result, we need to request a MethodBuilder from the TypeBuilder representing the typethat will contain the method. Listing 11-20 contains the EmitMethod code that emits the Addmethod. This code demonstrates several important facets of emitting IL.

Listing 11-20 Emitting a method

1: private void EmitMethod(TypeBuilder typeBuilder)

2: {

3: addMethod = typeBuilder.DefineMethod(

4: "Add", MethodAttributes.Public |

5: MethodAttributes.HideBySig, CallingConventions.HasThis,

6: typeof(int),

7: new Type[]{ collectedType });

8:

9: ILGenerator generator = addMethod.GetILGenerator();

10: generator.DeclareLocal(typeof(int));

11:

12: generator.Emit(OpCodes.Ldarg_0);

13: Type type = typeof(System.Collections.CollectionBase);

14:

15: MethodInfo methodInfo = type.GetMethod("get_List",

16: BindingFlags.InvokeMethod | BindingFlags.NonPublic |

17: BindingFlags.Instance,

18: null, Type.EmptyTypes, null);

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 5 7

Page 385: Advanced c sharp programming

19:

20: generator.Emit(OpCodes.Call, methodInfo);

21: generator.Emit(OpCodes.Ldarg_1);

22:

23: type = typeof(System.Collections.IList);

24: methodInfo = type.GetMethod("Add");

25: generator.Emit(OpCodes.Callvirt, methodInfo);

26: generator.Emit(OpCodes.Stloc_0);

27: Label label = generator.DefineLabel();

28: generator.Emit( OpCodes.Br_S, label);

29: generator.MarkLabel(label);

30: generator.Emit(OpCodes.Ldloc_0);

31: generator.Emit(OpCodes.Ret);

32: }

The code in Listing 11-20 is part of the EmitTypedCollection class, and the line numberswere added for reference only.

Lines 3 through 7 of Listing 11-20 define a new method from the TypeBuilder.DefineMethodmethod. The first argument will be the method name, and the second argument will be the methodattributes. The third argument indicates that we are passing a reference to the owning object,the ever-present this object; the fourth argument indicates that the return type is an integer;and the fifth argument indicates that a single parameter will be whatever type we will bestoring in this collection. The MethodAttributes used indicate that the method will be apublic method and that the method hides by name and signature, describing how informationhiding is implemented.

The ILGenerator class is introduced on line 9 of Listing 11-20. When we want to writeindividual lines of code, we get a contextually dependent ILGenerator. Line 10 declares alocal variable that is used as a temporary variable to hold the index of the item added to ourcollection. The MSIL for the Add method in the Test.dll is shown in Figure 11-4. Line 10emits the .locals statement shown in Figure 11-4.

Line 12 of Listing 11-20 loads the first argument onto the evaluation stack, emittingldarg.0. This is the silent this reference. Line 13 obtains the Type object for CollectionBase;we need it to get the get method—get_List—for the List property inherited from CollectionBase.(You have to understand properties and their relationships to get and set methods to get thisfar.) Using the MethodInfo object, we can emit the code that invokes the List property’sget method, returning the List object. The reference to the List is placed on the evaluationstack on line 21. Having the List object inherited from CollectionBase, we can obtain theMethodInfo for the inherited IList.Add method on lines 23 and 24. Because we are dealingwith an interface, we have to use the polymorphic call to get the Add method actuallyimplemented by CollectionBase, occurring on line 25.

Finally, we store the return integer from Add into our local variable (line 26), define alabel (the infamous goto alive and well in low-level code on line 27), and emit a branchstatement (branching to our label on lines 28 and 29—the Br_S is the goto statement). We

3 5 8 A d v a n c e d C # P r o g r a m m i n g

Page 386: Advanced c sharp programming

load the integer returned by our inherited call to add onto the stack to be obtained by thecaller when the method returns—lines 30 and 31, respectively.

A significant portion of emitting code at this level is very similar: obtain an ILGeneratorand write the individual lines of OpCodes that consummate your algorithm.

Emitting an Indexer Property and Property MethodsListing 11-21 demonstrates code that will emit a property to a type. The PropertyBuilderis straightforward but is reliant on the get and set methods that actually implement theproperty. The get and set methods are simply methods associated with a property. Youcan use Listing 11-20 as an example of implementing methods, but I have included theproperty get method because it demonstrates dynamic typecasting via emitted code. (Referto Emitter.cs for the set method.)

Listing 11-21 Emitting a property and a property get method

1: private void EmitProperty(TypeBuilder typeBuilder)

2: {

3: indexerProperty = typeBuilder.DefineProperty("Item",

4: PropertyAttributes.None, collectedType,

5: new Type[]{typeof(int)});

6:

7: indexerProperty.SetSetMethod(EmitSetter(typeBuilder));

8: indexerProperty.SetGetMethod(EmitGetter(typeBuilder));

9: }

10:

11: private MethodBuilder EmitGetter(TypeBuilder typeBuilder)

12: {

13:

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 5 9

Figure 11-4 One instance of the MSIL emitted by the Add method described in Listing 11-20

Page 387: Advanced c sharp programming

14: MethodBuilder getBuilder = typeBuilder.DefineMethod(

15: "get_Item",

16: MethodAttributes.Public | MethodAttributes.HideBySig |

17: MethodAttributes.SpecialName, collectedType,

18: new Type[]{typeof(int)});

19:

20: ILGenerator generator = getBuilder.GetILGenerator();

21: generator.DeclareLocal(collectedType);

22: generator.Emit(OpCodes.Ldarg_0);

23:

24: Type type = typeof(System.Collections.CollectionBase);

25: MethodInfo methodInfo = type.GetMethod("get_List",

26: BindingFlags.InvokeMethod | BindingFlags.NonPublic |

27: BindingFlags.Instance,

28: null, Type.EmptyTypes, null);

29:

30: generator.Emit(OpCodes.Call, methodInfo);

31: generator.Emit(OpCodes.Ldarg_1);

32:

33: type = typeof(System.Collections.IList);

34: methodInfo = type.GetMethod("get_Item",

35: BindingFlags.Instance | BindingFlags.Public |

36: BindingFlags.InvokeMethod,

37: null, new Type[]{typeof(int)}, null);

38:

39: generator.Emit(OpCodes.Callvirt, methodInfo);

40: generator.Emit(OpCodes.Castclass, collectedType);

41: generator.Emit(OpCodes.Stloc_0);

42: Label label = generator.DefineLabel();

43: generator.Emit(OpCodes.Br_S, label);

44: generator.MarkLabel( label );

45: generator.Emit(OpCodes.Ldloc_0);

46: generator.Emit(OpCodes.Ret);

47: return getBuilder;

48: }

The EmitProperty method in Listing 11-21 is easy. Request a PropertyBuilder from theTypeBuilder; if you indicate a get method only, the property is a read-only property. Includea set method, and you have a read-write property. Because we are implementing an indexedproperty, we need to specify a return type and a parameter type. The return type is the type ofthe object we are collecting, and the argument type is the indexer, an integer.

As I mentioned, get methods are simply methods. The EmitGetter method is mechanicallysimilar to every other method: you are writing code to emit the algorithm. I will not repeatparts that are similar to the Add method, but I do want to point out a few salient points. By

3 6 0 A d v a n c e d C # P r o g r a m m i n g

Page 388: Advanced c sharp programming

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 6 1

convention, we use get_ and set_ prefixes followed by the property name for propertygetters and setters, respectively, as demonstrated on line 15 of Listing 11-21. Generally, wedon’t make these directly available to consumers because getters and setters represent theimplementation details of a property. However, I made the getter public so you could see itin the emitted object. The SpecialName method attribute signifies that we are dealing witha special-purpose method.

The final piece of new information is the type cast emitted on line 40 of Listing 11-21.Remember, one of the benefits of a strongly typed collection is that we get the stronglytyped object from our indexer rather than littering our code with type casts; we do it in oneplace, the indexer. Line 40 demonstrates how to emit a type cast. Line 40 emits code thatis equivalent to the cast part of (type)List[index], where type represents the type stored inour collection.

All of the OpCodes can be looked up in the Visual Studio .NET help documentation.

Emitting a Constructor That Calls a Base Class ConstructorFinally, we wrap things up by examining our constructor briefly. Most constructors exist toinitialize fields. You know how to initialize fields from parameters because it is the same asinitializing fields passed to methods. (If you need an example, download the Emitter.cs classand look at the constructor emitter for the EmitType class.) Listing 11-22 demonstrates howto invoke a base class constructor; in our example, we are calling the CollectionBaseconstructor explicitly.

Listing 11-22 Emitting a constructor that calls a base class constructor

1: private void EmitConstructor(TypeBuilder typeBuilder)

2: {

3: ConstructorBuilder constructor =

4: CreateConstructorBuilder(typeBuilder, new Type[]{});

5:

6: ILGenerator generator = constructor.GetILGenerator();

7: EmitConstructorCode(generator);

8: }

9:

10: private void EmitConstructorCode(ILGenerator generator)

11: {

12: generator.Emit(OpCodes.Ldarg_0);

13: Type type = typeof(System.Collections.CollectionBase);

14: ConstructorInfo parent =

15: type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,

16: null, Type.EmptyTypes, null);

17:

18: generator.Emit(OpCodes.Call, parent);

19: generator.Emit(OpCodes.Ret);

20: }

Page 389: Advanced c sharp programming

In Listing 11-22, EmitConstructor creates a ConstructorBuilder directly by invoking itsconstructor. An ILGenerator is requested to create context for code emitted to our constructor,and EmitConstructorCode writes the lines of code. (This all happens on lines 1 through 8 ofListing 11-22.)

As with the code for a method, we need to emit individual lines of code that representour algorithm. Our only stated purpose is to explicitly invoke our base class’s constructor.Line 12 of Listing 11-20 loads the this pointer. Line 13 gets the Type object for theCollectionBase class, which can be used to get the Collection class ConstructorInfo object.We generate a call to the parent constructor on line 18 and return on line 19.

Constructors should be simple. A general rule of thumb for constructors is to call a parentconstructor, store parameters into fields, and call an Initialize method if necessary. Long,complex constructors can significantly slow down an application that creates a lot of aparticular type of object.

Loading and Executing Dynamic AssembliesI provided several types of examples that demonstrate how to load assemblies and createinstances of types defined in those assemblies. I won’t repeat that information again here.The biggest difference between an assembly on disk and one your code has emitted tomemory is that the emitted assembly is already loaded. This is what our GetClass methodindicates.

When GetClass returns, we have an assembly and type already in memory. All you needto do is create an instance of it. The Emitter.sln sample application contains a form (shownin Figure 11-3). Click the Test In-Memory button to create strongly typed collection basedon a simple Music type. The dynamic collection will be bound to the DataGrid (also shownin Figure 11-3).

Listing 11-23 Creating and using an instance of the in-memory assembly containing a stronglytyped Music Collection

Type type = EmitTypedCollection.GetClass(

"MusicCollection", typeof(Music));

// Test construction

IList list = (IList)Activator.CreateInstance(type);

// Test Add

list.Add(new Music("Charlie Pride", "16 Biggest Hits"));

list.Add(new Music("Ted Nugent", "Great Gonzos"));

// Test indexer

for( int i =0; i < list.Count; i++)

{

3 6 2 A d v a n c e d C # P r o g r a m m i n g

Page 390: Advanced c sharp programming

Music m = (Music)list[i];

Debug.WriteLine(m.Artist + ", " + m.Title);

}

// General test

dataGrid1.DataSource = list;

Listing 11-23 emits the type and uses an Activator to create an instance of the MusicCollectiontype. Because the type is created dynamically, we have to use a compatible type for theinstance, IList; however, we could declare the type as a MusicCollection object if the type wereemitted to disk.

A couple of Music objects are added to the collection. We use a for loop to test ourindexer, and, finally, we bind the list to the DataGrid to demonstrate in fact that it is astrongly typed collection. (Recall from Chapter 6 that a strongly typed collection implementsIList, IListSource, and IEnumerable, making typed collections very convenient to use.)

There is a lot to emitting dynamic IL. If you can imagine it as C# code, then you canprobably write an emitter that will generate that code. There is support for all of theconstructs that you write when you type C# code into Visual Studio .NET. This includesthings that I didn’t show, like exception handlers. Emitting is a huge subject that could easilyfill a book all by itself. Until a comprehensive book on .NET emitting is published I hope Ihave provided you with a suitable way to get started.

Secondary TopicsI will wrap this chapter up by showing you a couple of neat things that you can do withReflection and IL. I am going to keep them short because you already know the mechanicsfrom earlier discussions, particularly in Chapter 6.

Reflection and Web ServicesReflection is used in Web Services. A Web Service serializes objects to support returning anobject from a Web Method. The object returned is a flattened version of the object requested.For example, if you request that a strongly typed collection be returned from a Web Method—like the MusicCollection—what you will actually get is an array of Music objects ratherthan the actual MusicCollection object. This is still useful but is not as powerful as gettingthe fully reconstituted MusicCollection object would be. (See Chapter 12 for more onthis subject.)

How does .NET return objects from a Web Service? The answer is Xml Serializationand Reflection. These two technologies combined support obtaining aggregate data froman application across a network like the Internet.

Web Services, Xml Serialization, and Reflection are three compelling reasons to switchto .NET.

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 6 3

Page 391: Advanced c sharp programming

3 6 4 A d v a n c e d C # P r o g r a m m i n g

Implementing the Metaclass IdiomA metaclass is a class of a class. Or, phrased another way, a metaclass is a class that is treatedas an object. .NET does not support the metaclass idiom directly. (Borland’s Delphi does, forexample.) However, you implement the metaclass idiom using Reflection.

To use a class like a metaclass, you can pass a class’s Type object as a parameter to amethod. With the type object, you can request Info objects, like MethodInfo objects. Createan instance of the class using an Activator and dynamically invoke operations using theType.InvokeMember method and—voila!—you have a metaclass. Listing 11-24 containsan example from the Metaclass.sln sample program.

Listing 11-24 Treating a class like an object, a metaclass

private void CreateControl(Type type)

{

Control control = (Control)Activator.CreateInstance(type);

control.Text = "Dynamic";

control.Location = new Point(10, 35 * (Controls.Count - 2));

Controls.Add(control);

}

private void button1_Click(object sender, System.EventArgs e)

{

CreateControl(typeof(CheckBox));

}

private void button2_Click(object sender, System.EventArgs e)

{

CreateControl(typeof(TextBox));

}

In Listing 11-24, we are passing the Type object to CreateControl. Clearly, an observantperson will see that the same result could be achieved by passing an instance of a control toCreateControl. There are two deficits we are correcting. The first is that we are eliminatingthe need for consumers of CreateControl to have to create an instance, which is what wewould have to do if we passed a Control type. The second is presented when the consumerdoesn’t know which type to create.

Not knowing is especially useful when implementing the factory pattern. For example, wecould implement a code generator that has a factory method capable of creating a dynamiccontrol on demand. The caller doesn’t have to know what control to create; the caller mayonly know about the data. The factory pattern could examine the type of the data and returnan instance of a control based on the data. The caller simply accepts what it gets.

Further refining the approach, we could ask all of the possible subtypes if the datarepresented the kind of data the subtype is interested in. By using the metaclass idiom,

Page 392: Advanced c sharp programming

we could ask the subtype without ever creating an instance of the type. How? By usingInvokeMember on a static member. Instead of creating every possible subtype, we couldsimply use InvokeMember on a static method, passing the data. If the subtype is interested inthe data, we could create an instance of the subtype. This won’t work with .NET controlsas they exist today, because they weren’t implemented this way. However, you can writenew code to play the role of metaclass.

Serializing ObjectsObjects can be serialized to XML. Reflection plays a role in XML serialization because theXML serializer uses Reflection to determine the properties that need to be serialized. Youcan write custom serialization to serialize any data you want, but the default behavior workspretty well, too.

In the last line of Listing 11-23, we bound a strongly typed collection to a DataGrid.DataSource. What if you wanted to convert a typed collection into a DataSet and persistthat data to a database? XML serialization, in conjunction with Reflection, supports thisbehavior. Listing 11-25 demonstrates how to serialize the MusicCollection from Listing11-23 directly into a DataTable inside of a DataSet.

Listing 11-25 Serializing a typed collection to an ADO.NET DataSet

private void SerializeToDataSet()

{

PrototypeCollection c = new PrototypeCollection();

c.Add(new Prototype("Item 1"));

c.Add(new Prototype("Item 2"));

c.Add(new Prototype("Item 3"));

XmlSerializer serializer =

new XmlSerializer(typeof(PrototypeCollection));

MemoryStream stream = new MemoryStream();

serializer.Serialize(stream, c);

stream.Position = 0;

DataSet dataSet = new DataSet("Data");

dataSet.ReadXml(stream);

dataGrid1.DataSource = dataSet.Tables[0];

}

CAUTIONYou can serialize a dynamic assembly as demonstrated in Listing 11-25. Write an assembly to disk, and thenyou can serialize objects in the assembly.

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 6 5

Page 393: Advanced c sharp programming

Listing 11-25 creates an instance of the PrototypeCollection and adds three elements to it.After we create the collection, we create an instance of the XmlSerializer based on the typeof the PrototypeCollection. To serialize an object, a class must implement ISerializable orhave the Serializable attribute applied to the class. CollectionBase has the Serializableattribute applied to it, so our derivative collections are serializable.

After we create an XmlSerializer based on the PrototypeCollection, we create aMemoryStream as a place to receive the serialized object. We invoke XmlSerlializer.Serialize, writing the serialized form of the collection to the MemoryStream. Finally, were-position the stream, create a DataSet, and read the XML from the stream into the DataSet.

When the last line of Listing 11-25 completes, the DataSet object contains a serialized formof the PrototypeCollection. You can observe the results in the Emitter.sln sample application.

There are a couple of things you need to be aware of to get the code to work. You willneed to include the System.IO namespace to use the MemoryStream. (Other streams, likethe FileStream, will work, too.) You will need to include the System.Xml.Serializationnamespace because it contains the XmlSerializer class, and, finally, your collected objectswill need a default constructor that accepts no arguments. The reason for this last requirementis that deserialization constructs an object by invoking the default constructor and then usesthe Reflection and the properties to reassign the underlying field values. Without the defaultconstructor, the serializer cannot create a serialized copy of the object. (The code for thePrototype and PrototypeCollection classes is available for download in the Emitter.slnassembly.)

Emitting Regular Expression AssembliesOne emitter that exists and is already available for use is the emitter represented by the Regex.CompileToAssembly method. CompileToAssembly emits a DLL assembly with a class thatgeneralizes the Regex class. The emitted regular expression class contains the initializationstring that represents your regular expression.

I will demonstrate how to emit the assembly in a moment. Regular expressions have theirown grammar, representing a language, albeit a terse language (see Chapters 4 and 6 forexamples of regular expressions). Regular expressions are comprised of literal charactersand metacharacters that make up the regular expression language. .NET regular expressionsuse a Nondeterministic Finite Automation engine similar to those used by Perl, Python,Emacs, and Td, and is compatible with Perl 5. You can also compile regular expressions toan assembly, resulting in a slower load time but faster execution; it takes longer to load theassembly, but, when compiled to an assembly, the regular expression is converted to .NETcode and executes faster than when executed by the regular expression engine. In limitedtest cases, I have noticed significant speed improvements with compiled regular expressions,and the Visual Studio .NET help documentation also indicates that this is the case.

You can use the CompileToAssembly method to dynamically compile and emit a compiledregular expression to an assembly. Fortunately someone has written the emitter, making itrelatively easy to use. Listing 11-26 contains part of the EmitExpressionDemo.sln that emitsa compiled regular expression to an assembly. (The whole sample application supports inputtingand testing regular expressions before emitting them to an assembly.)

3 6 6 A d v a n c e d C # P r o g r a m m i n g

Page 394: Advanced c sharp programming

Listing 11-26 Code that demonstrates emitting a compiled regular expression to an assembly

private string InputString

{ get{ return textBox2.Text; }}

private string Expression

{ get{ return textBox1.Text; }}

private void Compile()

{

RegexCompilationInfo[] CompilationInfo =

new RegexCompilationInfo[]{

new RegexCompilationInfo(Expression,

RegexOptions.Compiled,

"Regex", "CompiledExpressions", true)};

AssemblyName assemblyName = new AssemblyName();

assemblyName.Name = "Regex";

Regex.CompileToAssembly(CompilationInfo, assemblyName);

}

To create a class that generalizes the System.Text.RegularExpression.Regex class,representing an immutable regular expression, create an array of RegexCompilationInfoobjects and an AssemblyName, and invoke the Regex.CompileToAssembly method.Pass the array of RegexCompilationInfo objects to CompileToAssembly. To convertthe regular expression to .NET code, pass the RegexOptions.Compiled option to theRegexCompilationInfo constructor.

SummaryIf Reflection were an option on a car, you might consider buying a car simply becauseof the rich features introduced by Reflection. Reflection is an evolution of run-time typeinformation and is central to XML Serialization, discovering type information, dynamicbinding of types and members, and emitting code at run-time. There are several examplesthat demonstrate these topics associated with this chapter.

One of the by-products of Reflection is the Regex class’s capability to emit a regularexpression to an assembly. In addition to emitters supporting Web Services and ADO.NET,I expect that there will be new cottage industry, like the component industry in the 1990s,dedicated to creating and selling .NET emitters.

C h a p t e r 1 1 : P r a c t i c a l R e f l e c t i o n 3 6 7

Page 395: Advanced c sharp programming

This page intentionally left blank.

Page 396: Advanced c sharp programming

PART

IIIWeb Applications—

IBUYSPY Portal

OBJECTIVES

� Learn to implement synchronous andasynchronous Web Services in C#

� Learn how to implement every aspect ofthe IBuySpy portal

� Learn how to create custom server anduser controls

� Explore and hone your ADO.NET skills,including stored procedures

� Learn about forms and Windows authentication

� Read the introduction to code base security

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 397: Advanced c sharp programming

This page intentionally left blank.

Page 398: Advanced c sharp programming

CHAPTER

12ImplementingWeb Services

371

IN THIS CHAPTER:

Demonstrated Topics

Web Services: Discovery and Description

Testing Web Services

Creating a Simple Web Service

Returning Complex Data from a Web Service

Returning a DataSet from a Web Service

Secondary Topics

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 399: Advanced c sharp programming

3 7 2 A d v a n c e d C # P r o g r a m m i n g

There are several technologies that support XML Web Services. I am going tointroduce these technologies in this chapter. However, if you think of Web Servicesin the context of all of these supporting utilities and technologies, you might think

that using XML Web Services is difficult. Nothing could be further from the truth. Creatingand consuming Web Services is easier because of these technologies.

You may have heard of the acronyms XML, UDDI, WSDL, DISCO, HTTP, and SOAPin conversations about XML Web Services. This plethora of tools may make Web Servicesseem complicated. I will talk about the role of these technologies and tools as they relate toWeb Services, but it is important to know that these technologies and tools work seamlesslyand, for the most part, in the background. If you and I had to master XML, UDDI, WSDL,or SOAP before we were able to create a Web Service, Web Services would be challenging.Fortunately, we do not. C# and Visual Studio .NET allow us to emphasize designing asolution and writing the code.

Demonstrated TopicsProgramming XML Web Services with C# and Visual Studio .NET allows you to focus onthe C# code. There are several technologies working in the background, so we’ll cover those,too. In this chapter you will learn about the following:

� The supporting role of WSDL, DISCO, UDDI, XML, HTTP, and SOAP

� Creating, testing, and consuming Web Services

� Returning complex data types from Web Services

� Object serialization

Objects are serialized as XML and transported over the wire. For the most part, the XMLand serialization are automatic and occur without your intercession; however, there arecircumstances where you may want to take control of the tools and the serialization process.The secondary section will demonstrate how to implement the ISerializable interface andmanage XML serialization yourself.

Web Services: Discovery and DescriptionThe best description of UDDI is the one I heard from Harry Pierson from Microsoft’s NationalTechnology Team. UDDI is the Web Services’ Yellow Pages. A longer explanation is derivedfrom the initials UDDI. UDDI stands for Universal Description, Discovery, and Integration.A couple of companies host UDDI servers. Two UDDI directory hosts that I know of areMicrosoft and IBM. You can browse http://uddi.microsoft.com for a list of hosted WebServices at Microsoft.

Page 400: Advanced c sharp programming

A convenient tool for browsing for Web Services is the Add Web Reference dialog,shown in Figure 12-1. (Notice the UDDI Directory link shown in Figure 12-1.) If youwant to find a Web Service located at a specific location, type in the URL to the WebService in the address bar. The Web Service is represented by .asmx files. For example,if you create a Web Service named WebService1, uncomment the sample code for theHelloWorld Web Method, and compile the Web Service, you would be able to browseto it at http://localhost/WebService1/ and select the Service1.asmx file.

The .asmx page will be displayed in the Add Web Reference dialog, just as if we hadbrowsed to it with Internet Explorer. (We can test the Web Methods in the Add WebReference dialog.) After we have selected the .asmx file, the Add Reference button will beenabled. Click the Add Reference button (shown in Figure 12-1) to incorporate the WebService into a project.

UDDI has made it easy to find and consume Web Services. The only real skills youneed are those you would use to browse to your favorite website. (One of mine is definitelyAmazon.com.)

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 7 3

Figure 12-1 The Add Web Reference dialog makes it easy to consume Web Services.

Page 401: Advanced c sharp programming

3 7 4 A d v a n c e d C # P r o g r a m m i n g

DISCO Is BackTechies do have a sense of humor. Disco.exe is the name of a utility that is used by the AddWeb Reference dialog to create a .MAP file, a .disco file, and a .wsdl file. (You may also runthe disco.exe utility from the Visual Studio .NET Command Prompt.)

TIPSelect Start | Program Files | Microsoft Visual Studio .NET | Visual Studio .NET Tools | Visual Studio .NETCommand Prompt to open a command window configured to allow you ready access to .NET framework utilities.

The Reference.map contains the URL reference to the DISCO and the WSDL files. TheDISCO file is the discovery file and the WSDL file describes the schema for the Web Serviceclass. (We’ll talk more about the WSDL file in a moment.) Listing 12-1 provides a listing fora Reference.map file. Listing 12-2 provides an example listing for the DISCO file for thesame Web Service.

Listing 12-1 A MAP file that contains a reference to the DISCO and WSDL files for aWeb Service

<?xml version="1.0" encoding="utf-8"?>

<DiscoveryClientResultsFile xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<Results>

<DiscoveryClientResult

referenceType="System.Web.Services.Discovery.ContractReference"

url="http://localhost/TemperatureConverter/Service1.asmx?wsdl"

filename="Service1.wsdl" />

<DiscoveryClientResult

referenceType="System.Web.Services.Discovery.DiscoveryDocumentReference"

url="http://localhost/TemperatureConverter/Service1.asmx?disco"

filename="Service1.disco" />

</Results>

</DiscoveryClientResultsFile>

Listing 12-2 A DISCO file that contains a reference to the Web Service contract file, theWSDL file

<?xml version="1.0" encoding="utf-8"?>

<discovery xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns="http://schemas.xmlsoap.org/disco/">

<contractRef ref="http://localhost/TemperatureConverter/Service1.asmx?wsdl"

docRef="http://localhost/TemperatureConverter/Service1.asmx"

xmlns="http://schemas.xmlsoap.org/disco/scl/" />

<soap address="http://localhost/TemperatureConverter/Service1.asmx"

xmlns:q1="http://tempuri.org/" binding="q1:Service1Soap"

xmlns="http://schemas.xmlsoap.org/disco/soap/" />

</discovery>

Page 402: Advanced c sharp programming

The MAP and DISCO files are implemented in XML, demonstrating another aspect ofhow XML is used in .NET. Fortunately, these files are generated by the DISCO tool.

If you mentally filter the XML, you will note that the MAP file contains URLs that referto the commands that generate the WSDL and DISCO files and the name of the DISCO andWSDL files. You can request the WSDL, for example, by entering the name of the WebService followed by the ?WSDL query in Internet Explorer. For example, to return the WSDLfor the temperature converter, we can type http://localhost/TemperatureConverter/Service1.asmx?wsdl.

NOTEThe TemperatureConverter Web Service was originally implemented for an article I wrote in the VB TechNotes section of www.codeguru.com. You can find the code there. I re-implemented in C# in the nextsection, but there is no requirement that Web Services have to be in the same language as the one youare developing in. XML is the common language shared between Web Service producers and consumers.

WSDLIf you write a Web Service, you will know what the methods and classes are that are definedby that Web Service. However, if you are consuming a Web Service from an external vendor,you have to have some source of information that tells you what is in the Web Service. Thisincludes the signature of the Web Methods provided by the service and the types that arereturned by those Web Methods.

WSDL (Web Services Description Language), pronounced whiz-dul, describes what is inthe Web Service and provides you with the information you will need to use those Web Methods.Listing 12-3 provides an implementation of a temperature converter in C# and part of theWSDL that describes the Web Method in Listing 12-4.

Listing 12-3 A Web Method that converts temperatures from Fahrenheit to Celsius

[WebMethod]

public double FahrenheitToCelsius(double temperature)

{

return (temperature - 32) * 5 / 9;

}

We’ll talk about the steps for creating Web Services in a moment. From Listing 12-3,you might be able to infer that one of the obvious differences between a method and a WebMethod is the application of the WebMethodAttribute.

Listing 12-4 An excerpt from the WSDL file for the FahrenheitToCelsius Web Method

- <types>

- <s:schema elementFormDefault="qualified"

targetNamespace="http://tempuri.org/">

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 7 5

Page 403: Advanced c sharp programming

- <s:element name="FahrenheitToCelsius">

- <s:complexType>

- <s:sequence>

<s:element minOccurs="1" maxOccurs="1" name="temperature" type="s:double" />

</s:sequence>

</s:complexType>

</s:element>

- <s:element name="FahrenheitToCelsiusResponse">

- <s:complexType>

- <s:sequence>

<s:element minOccurs="1" maxOccurs="1" name="FahrenheitToCelsiusResult"

type="s:double" />

</s:sequence>

</s:complexType>

</s:element>

<s:element name="double" type="s:double" />

</s:schema>

</types>

The XML indicates that there is an element named FahrenheitToCelisus that accepts asingle parameter (a double) named temperature. The second element name tag describes themethod response, named FahrenheitToCelsiusResponse. FahrenheitToCelsiusResponse has areturn value named FahrenheitToCelsiusResult that is a double, too.

When we consume the Web Method in an application, we invoke it as shown in Listing 12-3.All of the XML used to describe the Web Service is concealed from us as consumers. Whenyou add a reference to a Web Service, a proxy class is created. You can create an instanceof the Web Service class and invoke the Web Methods as if the Web Service class werecontained in the consuming application.

Consuming Web ServicesTo consume a Web Service, select Add Web Reference from the Project menu or the contextmenu for the Solution Explorer. Use the Add Web Reference dialog to browse to a UDDIdirectory service or a Web site containing the service you want to link to.

Figure 12-2 shows the ConvertTemperature Web Service added to theTestConvertTemperature.csproj (both the Web Service and test application are availablefor download). The name of the host is added to the Web References folder and plays therole of namespace. For example, to refer to the Web Service class, we can add a usingstatement to the module containing the Web Service or we can use the fully qualifiedpath to the Web Service class. Listing 12-5 contains code for a console application thatdemonstrates how to create an instance of the ConvertTemperature Web Service.

Listing 12-5 Consuming the ConvertTemperature Web Service

using System;

using Service = TestConvertTemperature.localhost;

3 7 6 A d v a n c e d C # P r o g r a m m i n g

Page 404: Advanced c sharp programming

namespace TestConvertTemperature

{

class Class1

{

[STAThread]

static void Main(string[] args)

{

Service.Convert convert = new Service.Convert();

Console.WriteLine(convert.FahrenheitToCelsius(32));

Console.ReadLine();

}

}

}

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 7 7

Figure 12-2 The Solution Explorer with a Web Reference to the ConvertTemperature Web Service

Page 405: Advanced c sharp programming

3 7 8 A d v a n c e d C # P r o g r a m m i n g

TIPI used the alias = namespace form of the using statement to shorten the namespace of the proxy class to analias, Service.

Listing 12-5 shows how to refer to the host-derived namespace, TestConvertTemperature.localhost. However, the System namespace contains the Convert class, so we can resolve theambiguity by declaring the Convert class using the full name. Notice that in our code it looksjust as though the Convert class is defined in a local assembly.

For programmers, this is an example of the power and ease of XML Web Services. Allof the technology and tools are working silently in the background to get us to the pointwhere the Web Service looks just like any class in any assembly. Quietly, HTTP or SOAPand XML are being used in the background to marshal calls across a network. (Of course,in our example the network is my laptop.)

When you incorporate a Web Service, a proxy class is created. You don’t have to worryabout this when you are using Visual Studio .NET, but you can use the wsdl.exe utility togenerate the proxy source code to see what it looks like. Run the wsdl.exe utility, passingthe URL of the Web Service to the WSDL tool. For example, on my laptop—PCs andlaptops are excellent places to test Web Services—I can generate the proxy file for theConvertTemperature Web Service with the following command line:

wsdl http://localhost/ConvertTemperature/ConvertTemperature.asmx

The generated Convert.cs module—Convert is the name of the Web Service class thatcontains the Web Method from Listing 12-3—contains a class named Convert thatinherits from System.Web.Services.Protocols.SoapHttpClientProtocol, a constructor, andthree methods. The proxy method FahrenheitToCelsius uses a synchronous call to theSoapHttpClientProtocol.Invoke method to invoke the Web Service. The other two methodssupport asynchronous Web Method invocation.

Again, it is important to know that proxy classes are created using WSDL, but thesethings happen automatically. For the general case, you can simply add a Web referenceand create an instance of the Web Service as if it were a class in an assembly referencedby your project.

Reviewing Web Services Wire FormatsThere are three wire formats for Web Services. There is HTTP GET, HTTP POST, andSOAP. HTTP GET uses a name-and-value pair–encoded URL. For example, we can invokethe Web Service with an HTTP GET URL–encoded address:

http://localhost/converttemperature/ConvertTemperature.asmx/

FahrenheitToCelsius?temperature=32

Page 406: Advanced c sharp programming

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 7 9

HTTP POST uses name-and-value pair encoding, but the parameters are encoded in theheader of the message. SOAP messages are encoded as XML. You can navigate to thecommand page to see samples of SOAP, HTTP GET, and HTTP POST messages andresponses if you are curious.

http://localhost/converttemperature/ConvertTemperature.asmx?op=

FahrenheitToCelsius

If you send an HTTP GET message (as shown in the first example in this section) andcompare the result to the HTTP GET response encoding, you will see that the responseencoding describes the format of the response from the Web Service. Listing 12-6 shows theformat of the HTTP GET response and an actual response from the FahrenheitToCelsiusWeb Service.

Listing 12-6 The HTTP GET request and response format, taken from the .asmx page

GET

/converttemperature/ConvertTemperature.asmx/FahrenheitToCelsius?temperature=

string

<?xml version="1.0" encoding="utf-8"?>

<double xmlns="http://tempuri.org/">double</double>

The first statement demonstrates the syntax of the HTTP GET message, and the remainingstatements demonstrate the response text. An actual response will have a value in place of theword “double.” Again, this is the supporting information for Web Service technology. Whenwe are consuming the Web Service as a part of our application, message request and responseformats are concealed from us.

Testing Web ServicesThere are several ways to test Web Services. You can test a Web Service using the integrateddebugger in Visual Studio .NET. You can use the “build and browse” method, by running theWeb Service application and entering data in the test page generated by browsing to the WebService .asmx page. Or, you can browse to the Web Service outside of the IDE and use anHTTP GET–encoded URL to invoke the Web Methods. (As an alternative, you can also usean HTTP POST message in an HTML document to invoke the Web Service.)

Integrated debugging in Visual Studio .NET is the most convenient way to test the WebService. Place a breakpoint on the code in the .asmx.cs source file containing the code thatyou want to test and build and run the Web Service application. When the Web Service isinvoked and your breakpoint is reached, the debugger will switch the code view to VisualStudio .NET, and you can step through your code and debug as you would any other assembly.(Ultimately, a Web Service is a DLL assembly and can be tested as such.)

Page 407: Advanced c sharp programming

Creating a Simple Web ServiceTo create Web Services on your PC, you will need to be connected to a network with accessto an IIS server or have IIS installed on your PC. Creating the project can be accomplishedby selecting the ASP.NET Web Service applet in the New Project dialog. The Wizard Enginewill create a Web Service project from the CSharpAddWebServiceWiz wizard templates.Table 12-1 describes the files that will be added to your Web Service and the role each ofthese files plays in the Web Service.

All of these files may seem a bit daunting, but you can implement a Web Serviceby adding public methods to the .asmx.cs file and tagging those methods with theWebMethodAttribute. You learned how to add metadata to an AssemblyInfo.cs file inChapter 2. The Web.config can be modified to enable tracing or cookie support, and theGlobal.asax file can be modified to respond to application-level events.

The application level contains a Global class that inherits from System.Web.HttpApplication.HttpApplication events and methods can be explored in depth in the Visual Studio .NET helpdocumentation.

Returning Simple DataThe easiest kind of Web Service to create is one that returns simple data, like a double, asdemonstrated in the ConvertTemperature Web Service. We’ll look at the process of creating

3 8 0 A d v a n c e d C # P r o g r a m m i n g

File Name/Extension Description.dll The Web Service assembly compiled to a DLL

.pdb Contains symbolic debug information

.sln The Web Service solution file

.csproj The Web Service project file

.csproj.webinfo Contains the project path on the server or PC

AssemblyInfo.cs Contains assembly metadata for the project

.asmx Contains the Web Service processing directive; used as an entry pointto the Web Service

.asmx.cs The code-behind file for your Web Service. This is where your WebMethods are coded and paired with the .asmx and .asmx.resx files

.asmx.resx Resource file

.vsdisco Discovery file that describes searchable paths for Web Services

Global.asax Contains application-level event handlers

Global.asax.cs The code-behind page for application-level events

Web.config Configuration information for ASP.NET applications

Table 12-1 Project Files for an ASP.NET Web Service

Page 408: Advanced c sharp programming

a simple Web Service, indicating a Namespace, adding a Description, and handling WebService exceptions. Fortunately, we can return complex types from Web Services, whichwe’ll look at in the next section.

To create a Web Service, create a new ASP.NET Web Service project in the New Projectdialog. The Web Service project will be created in c:\inetpub\wwwroot\webservicename.This directory will be located on your PC if you create the project using the localhost hostname. The .asmx.cs code-behind file will contain a class named Service1 (by default) orsome variation. The class will inherit from System.Web.Services.WebService. It is thisinheritance that makes the solution a Web Service. To provide some behavior, you need todefine a public method with the WebMethodAttribute applied.

You can have as many Web Methods and supporting methods as you need. A good modelfor Web Services is to implement a solution and then implement a Web Service and WebMethods that expose aspects of the solution that a consumer might want. As is true withobject-oriented programming in general, if you keep the number and variety of public WebMethods to a small number, your Web Services will be easier to use. Provide too few WebMethods and your Web Services won’t be compelling enough to attract attention. Happily,.NET’s Web Services technology affords us the luxury of focusing on the solution withouthaving to wrangle with the technical nuts and bolts.

The code in Listing 12-7 will determine whether a number is a prime number by usingan approximation of the Sieve of Eratosthenes. The listing is provided next, followed by asummary of the code.

Listing 12-7 Calculate prime numbers using the Sieve of Eratosthenes

1: using System;

2: using System.Collections;

3: using System.ComponentModel;

4: using System.Data;

5: using System.Diagnostics;

6: using System.Web;

7: using System.Web.Services;

8:

9: namespace Primes

10: {

11: [WebService(Namespace="http://www.softconcepts.com",

12: Description="Calculate primes using the Sieve of Eratosthenes")]

13: public class Primes : System.Web.Services.WebService

14: {

15: public Primes()

16: {

17: //CODEGEN: This call is required by the

18: //ASP.NET Web Services Designer

19: InitializeComponent();

20: }

21:

22: [region Component Designer generated code]

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 8 1

Page 409: Advanced c sharp programming

23:

24: [WebMethod]

25: public long[] PrimesTo(long max)

26: {

27: return (new PrimeEngine(max)).Primes;

28: }

29:

30: [WebMethod]

31: public bool IsPrime(long number)

32: {

33: return (new PrimeEngine(number)).IsPrime(number);

34: }

35: }

36:

37: class PrimeEngine

38: {

39:

40: private ArrayList primes;

41:

42: /// <summary>

43: /// Calculates prime numbers up to max

44: /// using an approximation of the 'Sieve of Eratosthenes'

45: /// </summary>

46:

47: public PrimeEngine(long max)

48: {

49: primes = new ArrayList();

50: primes.Add(2L);

51: BuildPrimes(max);

52: }

53:

54: private bool TestPrime( long number )

55: {

56: foreach(long l in Primes)

57: {

58: if( number % l == 0) return false;

59: if( l >= Math.Sqrt(number)) return true;

60: }

61:

62: return true;

63: }

64:

65: public void BuildPrimes(long max)

66: {

67: if( max < 3) return;

68:

69: for( long i=3; i<= max; i++)

70: {

71: if(TestPrime(i)) primes.Add((long)i);

72: }

3 8 2 A d v a n c e d C # P r o g r a m m i n g

Page 410: Advanced c sharp programming

73: }

74:

75: public bool IsPrime(long number)

76: {

77: return primes.IndexOf(number) > -1;

78: }

79:

80: public long[] Primes

81: {

82: get{ return (long[])primes.ToArray(typeof(long)); }

83: }

84: }

85: }

NOTECalculating large primes is time consuming. The Sieve of Eratosthenes will be extremely slow for very largeprime numbers. Because large prime numbers take time to calculate, they are excellent for encryption. Thebasic idea is that by the time an information thief has solved a large prime, the opportunity to exploit thedata will have passed.

The WebService class Primes exposes the capabilities of the PrimeEngine. The WebService allows the consumer to inquire whether a number is a prime and will return an arrayof long integers up to the max value specified. Based on the implementation in Listing 12-7,calculating very large prime numbers will be slow.

All integers are a product of primes. A prime number is a number that is divisible only byitself and 1. Eratosthenes was a respected scholar and librarian in Alexandria about twenty-twohundred years ago. Eratosthenes resolved that prime numbers could be calculated by checkingto see if a number were divisible by all of the prime numbers less than the square root of thecandidate number. (We only have to check numbers up to the square root because, if a numbergreater than the square root is a divisor, then a number less than the square root will be adivisor, too.)

If we seed the array of primes with the first prime, 2, then we can use 2 to calculate allof the primes between 2 and the candidate number. The PrimeEngine constructor seeds thearray with the lowest known prime, 2, and calculates all of the primes up to max. (We don’tcalculate more primes than we need, since calculating large primes using Eratosthenes’algorithm will be slow for huge primes.) BuildPrimes checks all of the integers between 3and the maximum value on lines 65 to 73. TestPrime—on lines 54 to 63 of Listing 12-7—iterates through all of the primes calculated less than the number. If the number is evenlydivisible by one of the primes, then the candidate is not a prime and we return false on line 58.If we reach a number greater than or equal to the square root of the candidate number, thenwe have a prime number, and we can stop testing.

I prefer to implement solutions as classes. Then, if I want to expose a solution to a specifickind of consumer, I can implement the solution in terms of the existing class. For example,instead of implementing solutions in the Web Methods, I prefer to implement them in termsof a class that solves the general problem. As a result, the solution can be exposed to a variety

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 8 3

Page 411: Advanced c sharp programming

3 8 4 A d v a n c e d C # P r o g r a m m i n g

of consumers. It is worth noting that you can add a reference to a Web Service DLL, createinstances of the WebService-derived class, and invoke the methods directly. Fundamentally,a WebService is a class and can be used and treated like any other class, as long as you haveaccess to the physical DLL.

Providing a Namespace and DescriptionWeb Service classes must be unique. The Namespace is used to uniquely describe the WebService. When you are testing your Web Service, you can use the default tempuri.org. Beforeyou deploy your Web Service, you can apply the WebServiceAttribute to the Web Serviceclass, providing named argument values for the Namespace, and a Description to let consumersknow what the Web Service does. Lines 11 and 12 in Listing 12-7 demonstrate how to usethe WebServiceAttribute. (For more information on named arguments and custom attributes,refer to Chapter 10.)

Handling Web Service ExceptionsYou can handle or throw exceptions from Web Services. The consumer will actually receivea SoapException. The SoapException class will contain contents that indicate the underlyingexception, but the class of the exception will be a SoapException. To handle exceptionsthrown from Web Services, use a catch block or a catch block that specifies the SoapException.Here is a syntactic example of a try catch block that handles a SoapException.

try

{

//invoke Web Method

}

catch(SoapException x)

{

// handle exception

}

If you want to throw an exception in your Web Service code, you can throw a specificclass of an exception, and it will be converted to a SoapException. For example, if a consumerpasses a number less than 2 to the Primes Web Service, we could raise an ArgumentException.The following two methods can be added to the Web Service in Listing 12-7 to raise anexception for an invalid argument.

private void ThrowException(long number)

{

const string error = "Number ({0}) must an integer greater than 2";

throw new ArgumentException(string.Format(error, number));

}

private void Validate(long number)

{

Page 412: Advanced c sharp programming

if(number < 2) ThrowException(number);

}

When a consumer receives a Web Service exception, you can try to convert it to a specificexception based on the contents of the SoapException, but this seems like an unnecessaryexercise. Use the exception content to provide an error handler or notify the provider of theerror in the Web Service.

Invoking Web Services AsynchronouslyThere are two ways that you can invoke Web Methods. The first way is to invoke a WebMethod synchronously: invoke the Web Method and wait until the response comes back.The second way is to invoke the Web Method, do some other processing, and handle theresponse from the Web Method at a later time. This second way is referred to as asynchronousprocessing. .NET supports asynchronous processing for a large part of the framework,including Windows Forms, ASP.NET Web Forms, and ASP.NET Web Services, amongother aspects of .NET.

When you add a reference to a Web Service, the wsdl.exe utility generates a proxy class.Inside of the proxy class are two methods, BeginXXXX and EndXXXX, for each Web Methodin your Web Service. For example, our IsPrime method will be represented by a synchronousmethod, IsPrime, and an asynchronous pair of methods, BeginIsPrime and EndIsPrime. Thelatter two methods are wrappers for a call to the SoapHttpClientProtocol.BeginInvoke andSoapHttpClientProtocol.EndInvoke asynchronous methods. By using the asynchronousmethods, you can invoke an operation on a Web Method, perform some additional processing,and handle the result asynchronously when the data is ready. Listing 12-8 demonstrates aConsole Application client that invokes the IsPrime Web Method asynchronously. In thismanner, the same client can send several requests to IsPrime and display the results whenthey are available.

Listing 12-8 Invoking a Web Service asynchronously

1: using System;

2: using TestPrimes.localhost;

3:

4: namespace TestPrimes

5: {

6:

7: class Class1

8: {

9: [STAThread]

10: static void Main(string[] args)

11: {

12: TestAsynch();

13:

14: Console.WriteLine("Press any key...");

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 8 5

Page 413: Advanced c sharp programming

15: Console.ReadLine();

16: }

17:

18:

19: private static void TestAsynch()

20: {

21: while(true)

22: {

23: Console.WriteLine("Enter a number (Q=Quit):");

24: string value = Console.ReadLine();

25: if( value.ToUpper() == "Q" ) return;

26: try

27: {

28: TestPrime(Convert.ToInt64(value));

29: }

30: catch

31: {

32: Console.WriteLine(value.ToString() + " is not a number");

33: }

34: }

35: }

36:

37:

38: private class Data

39: {

40: public long number;

41: public DateTime dateTime;

42:

43: public Data(long number)

44: {

45: this.number = number;

46: dateTime = DateTime.Now;

47: }

48:

49: public TimeSpan Elapsed()

50: {

51: return DateTime.Now.Subtract(dateTime);

52: }

53: }

54:

55: private static void TestPrime(long prime)

56: {

57: IAsyncResult result =

58: p.BeginIsPrime(prime, new AsyncCallback(Class1.Callback),

59: new Data(prime));

3 8 6 A d v a n c e d C # P r o g r a m m i n g

Page 414: Advanced c sharp programming

60: }

61:

62: static Primes p = new Primes();

63: private static void Callback(IAsyncResult result)

64: {

65: if( result.IsCompleted )

66: WriteResult(result);

67: }

68:

69: private static void WriteResult(IAsyncResult result)

70: {

71: WriteResult((Data)result.AsyncState, p.EndIsPrime(result));

72: }

73:

74: private static void WriteResult( Data data, bool isPrime)

75: {

76: const string mask = "{0} {1} prime";

77: Console.WriteLine(string.Format(mask, data.number,

78: new string[]{"is not", "is"}[Convert.ToInt16(isPrime)]));

79: Console.WriteLine("Calculated in " + data.Elapsed());

80: }

81: }

82: }

NOTEThe use of the static modifier is not relevant to the solution. Static methods were used for convenience. Theonly difference is that I would have to create instances of Class1 if I didn’t use static methods. Using instancemethods neither adds to nor detracts from the example.

Lines 19 through 35 of Listing 12-8 implement a while loop representing a simple userinterface. Enter a number to test for primeness or Q to quit. TestPrime is implemented onlines 55 through 60. Because TestPrime invokes the Web Method IsPrime asynchronously,by calling BeginIsPrime, it returns immediately.

Call asynchronous methods by passing an AsyncCallback delegate. The wrapper BeginIsPrimeaccepts the number to test, the AsyncCallback delegate, and an optional third argument usedto pass data to the AsyncCallback delegate. In our example, we pass an instance of an internalclass, Data. Data stores the tested number and a seeded start time. (We can use the seededstart time to calculate the elapsed time.) The Data class demonstrates the nested class idiomand how to pass complex data to asynchronous delegates.

The callback method was named Callback to make it easy to find. (We have to use theclass name, Class1, simply because the callback is static.) TestPrime returns immediately.We know that the asynchronous data is ready when the delegate, Callback, is called.Callback tests the IAsyncResult argument to make sure the method completed, on line 64

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 8 7

Page 415: Advanced c sharp programming

of Listing 12-8. If the asynchronous call completed, then we write the result of the IsPrimemethod to the Console with the overloaded WriteResult method.

We could have created any user interface for the asynchronous example. The importantpoint is that we use the wrapper methods for BeginInvoke and EndInvoke, and we use theEndInvoke method to obtain the data when the asynchronous data is ready through a delegate.This basic use of asynchronous methods is consistent, whether we are using Windows Formsor Web Forms or implementing the asynchronous Web Method with some other user interface.

You can use the IAsyncResult object returned from BeginInvoke—in our exampleBeginIsPrime, the wrapper for BeginInvoke—and obtain a WaitHandle through theAsyncWaitHandle property. The WaitHandle can be used to block by invokingWaitHandle.WaitOne, WaitHandle.WaitAll, or WaitHandle.WaitAny. Your codewill block if you call EndInvoke, too.

Combining the techniques discussed so far, you can test IAsyncResult.IsCompleted in aloop, use a WaitHandle, or use EndInvoke—EndIsPrime in our example—to block until anasynchronous method invocation returns. For example, you could call BeginInvoke, performsome other operations, and call EndInvoke to block until the asynchronous call returns.

Returning Complex Data from a Web ServiceYou can return simple data from a Web Service, and, more important, you can also returncomplex data from a Web Service. Web Services create a proxy type for classes defined inthe Web Service. The proxy type flattens the original type, converting properties to publicfields and providing you with the equivalent of a data-only record containing the values ofthe original object.

TIPThe SOAP protocol supports returning complex data types from Web Services. If you use either the HTTP GETor HTTP POST protocol, you are limited to simple name-and-value pairs of data.

For example, suppose you have a Web Service that returns basic statistics about aprofessional sports team. You might store the name, city, state, and game statistics in theWeb Service, as well as provide some basic operations. We can name the class “Team.” Theclass contained in the Web Service will be a fully constituted Team object, but, when youreturn the Team object from the Web Service, it will look flat. Listing 12-9 contains a WebService and the Team class. Listing 12-10 shows what the Team class will look like afterthe wsdl.exe utility has generated a proxy class for the Team class. (I used sproxy.exe tomanually generate the Team proxy generated automatically when you add a Web reference.)

Listing 12-9 A Web Service and a class defined in a Web Service representing informationabout sports teams

1: using System;

2: using System.Collections;

3 8 8 A d v a n c e d C # P r o g r a m m i n g

Page 416: Advanced c sharp programming

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 8 9

3: using System.ComponentModel;

4: using System.Data;

5: using System.Diagnostics;

6: using System.Web;

7: using System.Web.Services;

8:

9: namespace ComplexDataDemo

10: {

11: public class Service1 : System.Web.Services.WebService

12: {

13: public Service1()

14: {

15: //CODEGEN: Required by the ASP.NET Web Services Designer

16: InitializeComponent();

17: }

18:

19: [Component Designer generated code]

20:

21:

22: [WebMethod]

23: public Team CreateTeam(string name)

24: {

25: return new Team(name);

26: }

27:

28: }

29:

30: public class Team

31: {

32: private string name;

33: private string city;

34: private string state;

35: private bool isProfessional;

36: private string sport;

37: private int season;

38: private int wins;

39: private int losses;

40: private int ties;

41:

42: public Team(){}

43: public Team(string name)

44: {

45: this.name = name;

46: }

Page 417: Advanced c sharp programming

3 9 0 A d v a n c e d C # P r o g r a m m i n g

47:

48: public string Name

49: {

50: get{ return name; }

51: set{ name = value; }

52: }

53:

54: public string City

55: {

56: get{ return city; }

57: set{ city = value; }

58: }

59:

60: public string State

61: {

62: get{ return state; }

63: set{ state = value; }

64: }

65:

66: public bool IsProfessional

67: {

68: get{ return isProfessional; }

69: set{ isProfessional = value; }

70: }

71:

72: public string Sport

73: {

74: get{ return sport; }

75: set{ sport = value; }

76: }

77:

78: public int Season

79: {

80: get{ return season; }

81: set{ season = value; }

82: }

83:

84: public int Wins

85: {

86: get{ return wins; }

87: set{ wins = value; }

88: }

89:

Page 418: Advanced c sharp programming

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 9 1

90: public int Losses

91: {

92: get{ return losses; }

93: set{ losses = value; }

94: }

95:

96: public int Ties

97: {

98: get{ return ties; }

99: set{ ties = value; }

100: }

101:

102: public override string ToString()

103: {

104: const string mask =

105: "Name: {0}\r\n" +

106: "City/State: {1}, {2}\r\n" +

107: "Sport: {3}\r\n" +

108: "IsProfessional: {4}\r\n";

109:

110: return string.Format(mask,

111: name, city, state, sport, isProfessional);

112: }

113: }

114: }

The WebMethod in line 23 of Listing 12-9 creates and returns an instance of Team.The Team class—lines 30 through 113—contains several fields, properties, and a method,ToString, representing a capability of the Team class. It is valuable to return aggregate datafrom a Web Service—you do need to know that the Team object in the assembly containingthe Web Service will not be the same Team object that the client receives.

When you add a reference to the Web Service shown in Listing 12-9, you will get aflattened team object. Listing 12-10 contains the Team class that a client will get from theWeb Service.

Listing 12-10 Complex types returned from Web Services do not contain methods but havefields that mirror public properties.

/// <remarks/>

[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://tempuri.org/")]

public class Team {

/// <remarks/>

Page 419: Advanced c sharp programming

3 9 2 A d v a n c e d C # P r o g r a m m i n g

public string Name;

/// <remarks/>

public string City;

/// <remarks/>

public string State;

/// <remarks/>

public bool IsProfessional;

/// <remarks/>

public string Sport;

/// <remarks/>

public int Season;

/// <remarks/>

public int Wins;

/// <remarks/>

public int Losses;

/// <remarks/>

public int Ties;

}

Notice in Listing 12-10 that the fields have been removed and the properties have beenreplaced by public fields. There is no sign of our ToString method.

XML serialization is used to convert complex data into an XML representation of objectsreturned from Web Services.

Returning a DataSet from a Web ServiceAn excellent way to return data from a Web Service is to return a DataSet. On both sides of aWeb Service—the Service and the client—the DataSet is a complete object. This means youcan return a DataSet that contains multiple tables, table relations, and mapping relationships.Chapter 5 discusses ADO.NET and the DataSet; how to return a DataSet from a Web Serviceis demonstrated in the Chapter 5 section “Returning a DataSet from a Web Service.” Chapter 11,in the section “Serializing Objects,” demonstrates how to serialize objects into a DataSet.Both of these sections provide additional Web Service examples.

The DataSet was coded by Microsoft to support being consumable by Web Serviceswithout intervention from the programmer. The DataSet uses a DiffGram to serialize theoriginal and current row values of a DataSet. Web Service consumers don’t have to worryabout serialization and deserialization of DataSets; DataSets are fully reconstituted acrossWeb Service boundaries.

Page 420: Advanced c sharp programming

Secondary TopicsObjects can be serialized as XML documents or in a binary or SOAP format. You can use theXmlSerializer class to convert an object to an Xml document, or you can use the Serializableattribute to support serializing to a binary or SOAP format. You can also implement theSystem.Runtime.Serialization.ISerializable interface to control the serialization process whenusing a SOAP or binary formatter.

Chapter 11, in the section “Serializing Objects,” demonstrates how to use the XmlSerializer.In this section, I will demonstrate how to use the SoapFormatter to serialize an object andhow to implement the ISerializable interface to allow you to manually control how theBinaryFormatter and SoapFormatter serialize an object.

Using the SoapFormatterThe SoapFormatter will format an object as a Soap document. The SoapFormatter classimplements the System.Runtime.Remoting.Messaging.IRemotingFormatter. We can declarethe SoapFormatter as an IRemotingFormatter and serialize an object much as we did withthe XmlSerializer in Chapter 11. Listing 12-11 demonstrates how to serialize the Team classto a Soap document.

Listing 12-11 Serializing an object to a Soap document

// include these additional namespaces

using System.Runtime.Remoting.Messaging;

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 9 3

Web Services can consume a DataSet. In simple terms, this means that if you pass aDataSet to a Web Service or return a DataSet from a Web Service, then you get aSystem.Data.DataSet object. If you consume a custom class in a Web Service, then youget a flattened class (see Listing 12-9 and Listing 12-10). For example, the Team classin Listing 12-9 was flattened to the Team class in Listing 12-10 when it was returnedfrom the Web Service.

Interestingly, if I add a reference to the Web Service in Listing 12-9, I can modifythe reference.cs proxy file. By removing the flattened version of the Team class inListing 12-10 and adding a using statement referencing an assembly that contains thereal Team, the Web Service returns a completely initialized version of Team, methodsincluded.

I don’t know the answer, but it does seem that DataSets are consumable as objectssimply because of the reference to System.Data in the Reference.cs wsdl-generated file.Unfortunately, changing the Reference.cs file manually—in the example, removing thegenerated Team class and adding a using statement and a reference to a Teams.dllassembly—is not very extensible because the Reference.cs file is regenerated any timethe Web Reference is updated.

Page 421: Advanced c sharp programming

3 9 4 A d v a n c e d C # P r o g r a m m i n g

using System.Runtime.Serialization.Formatters.Soap;

using System.IO;

public void SerializeToSoap()

{

Teams.Team team = new Teams.Team("Red Wings");

team.IsProfessional = true;

team.City = "Detroit";

team.State = "Michigan";

team.Sport = "Hockey";

IRemotingFormatter formatter = new SoapFormatter();

MemoryStream stream = new MemoryStream();

formatter.Serialize(stream, team);

stream.Position = 0;

textBox1.Text =

System.Text.ASCIIEncoding.ASCII.GetString(stream.ToArray());

}

(The example in Listing 12-11 can be found in the ComplexDataDemo.csproj sample program.)Here we create an instance of the Team class directly (found in the Teams.csproj project). Wecreate an instance of the SoapFormatter and a MemoryStream. (The MemoryStream is definedin System.IO.) We instruct the formatter to Serialize to the object to the stream, reposition thestream, and use the System.Text.ASCIIEncoding.ASCII.GetString method to convert the streamto a string.

We can invoke stream.ToArray to return an array of bytes (byte[ ]), and the ASCII.GetStringmethod can convert the bytes to an ASCII string. When the last statement runs, the object willappear as a SOAP message, as shown in Figure 12-3.

The System.SerializableAttribute applied to the Team class is what allows theSoapFormatter and BinaryFormatter to serialize the Team class. (This is the original Teamclass, not the Team class returned by the Web Service.) If you want to manually controlthe serialization process, you can implement the ISerializable interface in conjunction withthe Serializable attribute.

Implementing the ISerializable InterfaceSystem.Runtime.Serialization.ISerializable requires that you implement a GetObjectDatamethod to support serializing an object using the BinaryFormatter or the SoapFormatter andthat you implement a protected constructor, taking the same arguments as GetObjectData,to support deserializing an object using the BinaryFormatter and SoapFormatter.

Page 422: Advanced c sharp programming

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 9 5

The modifications shown in Listing 12-12 demonstrate how to implement ISerializable forthe Team class defined in Teams.csproj. The balance of the Team code is shown in Listing 12-9.

Listing 12-12 Implementing ISerializable to support SOAP and binary serialization

using System;

using System.Runtime.Serialization;

using System.Diagnostics;

namespace Teams

{

[Serializable]

public class Team : ISerializable

{

// Original code from listing 12-9 goes here.

protected Team(SerializationInfo info,

StreamingContext context)

Figure 12-3 An object serialized as a SOAP document using the SoapFormatter

Page 423: Advanced c sharp programming

3 9 6 A d v a n c e d C # P r o g r a m m i n g

{

name = (string)info.GetValue("name", typeof(string));

city = (string)info.GetValue("city", typeof(string));

state = (string)info.GetValue("state", typeof(string));

isProfessional = (bool)info.GetValue("isProfessional", typeof(bool));

sport = (string)info.GetValue("sport", typeof(string));

season = (int)info.GetValue("season", typeof(int));

wins = (int)info.GetValue("wins", typeof(int));

losses = (int)info.GetValue("losses", typeof(int));

ties = (int)info.GetValue("ties", typeof(int));

DateTime dateTime = (DateTime)info.GetValue("DateTime",

typeof(DateTime));

Debug.WriteLine("Serialized at: " + dateTime.ToString());

Debug.WriteLine("Deserialized at: " + DateTime.Now.ToString());

}

public void GetObjectData(SerializationInfo info,

StreamingContext context)

{

info.AddValue("name", name);

info.AddValue("city", city);

info.AddValue("state", state);

info.AddValue("isProfessional", isProfessional);

info.AddValue("sport", sport);

info.AddValue("season", season);

info.AddValue("wins", wins);

info.AddValue("losses", losses);

info.AddValue("ties", ties);

info.AddValue("DateTime", DateTime.Now);

Debug.WriteLine("Serialized at: " + DateTime.Now.ToString());

}

}

}

The basic implementation of ISerializable will invoke GetObjectData when you callSoapFormatter.Serialize or BinaryFormatter.Serialize. GetObjectData writes the field valuesyou want to serialize to the SerializationInfo object. The constructor allows a caller toinitialize an instance of the object—in this case, Team—with a SerializationInfo object.

If you are only going to write the members represented by public properties, you don’tneed to implement ISerializable. To demonstrate, our implementation of ISerializable adds aDateTime value indicating when the object was serialized and deserialized. Practical use ofISerializable would be to serialize data that makes up an object’s state and that may be storedin private fields having no associated public properties.

The StreamingContext object contains information about the source or destinationof the transmitted data. For example, you can poll context.State == StreamingContextStates.CrossMachine to determine whether the context is a different computer.

Page 424: Advanced c sharp programming

For more information on using the SoapFormatter or BinaryFormatter, explore the VisualStudio .NET help documentation for information about .NET Remoting. Remoting is anotherway that .NET supports communications between different operating system processes, onthe same or different computers, and is a non-proprietary replacement for Microsoft’s DCOMtechnology.

SummaryI have read at least one article and at least one book that suggests that Microsoft’s WebServices are “not quite ready for prime time” or that there is a lot of noise surroundingWeb Services without substance. Nothing could be further from the truth.

In this chapter, I demonstrated how to return simple and complex data from Web Services.Clearly, the open standards of XML and SOAP imply that this is not a Microsoft-onlytechnology, and, also clearly, we can make requests that Web Services can satisfy, as wellas obtain advanced data types. These operations can be performed both synchronouslyand asynchronously.

The only difficulty I have found so far is returning what I refer to as a completelyreconstituted object. When a custom object is returned, the default behavior is to flattenthe object into a fields-only shallow image of the actual object residing in the Web Service.(In a note earlier in the chapter, I described one way of getting around even this modestlimitation—although it is questionable whether this behavior represents a limitation or anecessity. When a type is returned from a Web Service, there is clearly no guarantee thatthe type or any of its sub-types exist on the client machine.) My suspicion is that perceivedlimitations and the perception of over-hype may be caused by limitations in understandingof some of the core technologies that support Web Services. There is a lot to .NET, andit may be a while before there is both a broad and deep understanding across a broadpopulation of developers.

I do believe that open standards (XML and SOAP) are better than a proprietary standard(DCOM) and that XML Web Services represent a powerful and compelling means of sharingdata between systems and across networks.

C h a p t e r 1 2 : I m p l e m e n t i n g W e b S e r v i c e s 3 9 7

Page 425: Advanced c sharp programming

This page intentionally left blank.

Page 426: Advanced c sharp programming

CHAPTER

13IBuySpy and Dynamic

User Interfaces inASP.NET

399

IN THIS CHAPTER:

Demonstrated Topics

Creating a Cascading Style Sheet

Implementing the Portal Banner

Secondary Topics

Administrating the Portal

Debugging the IBUYSPY Portal

Introducing Mobile Modules

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 427: Advanced c sharp programming

This section of the book is significant because the next four chapters containexamples taken by express permission from the IBuySpy portal sample applicationimplemented by Microsoft.

IBuySpy.com (see the following logo) is an online Web storefront application that sellshumorous and fictitious spy goods. More important, it is a complete working Web applicationthat employs Microsoft’s ASP.NET and demonstrates Visual Basic .NET, C#, security, andMicrosoft’s Mobile Internet Toolkit, thereby blazing a trail for developers. You can download,visually customize (using the Administration tools), or programmatically customize the portalapplication, borrowing from practical examples and best practices demonstrated in production-quality code.

The entire IBuySpy portal can be downloaded from www.ibuyspy.com. This includes theapplication, database, documentation, and source code. You may install and configure theIBuySpy portal to run on your PC or Intranet, and Microsoft extends permission to you to useany of the code and many of the figures as aids in building your own portal application. Thischapter and the three that follow, which are made possible courtesy of Microsoft, will helpyou work with the IBuySpy portal code as well as build your own ASP.NET applications.

To download and run the IBuySpy Portal on your PC, you will need

� Windows 2000 or Windows XP server or professional

� SQL Server or MSDE 2000 or higher

� .NET Framework SDK or Visual Studio .NET

� Microsoft Internet Explorer 5.5 or higher

To install the portal, you will need to download the IBuySpy portal from www.ibuyspy.com(PortalCS_VS_0204.exe) and run the self-extracting archive. Ensure that you have therequisite configuration in your target environment and follow the instructions for the portalsetup and configuration.

Demonstrated TopicsThe IBuySpy portal demonstrates everything you need to know to build a Web portalapplication. IBuySpy includes dynamic Web pages, an SQL Server database, security, pagesfor mobile devices, code-behind, stored procedures, dynamic content, administration, andmuch more.

4 0 0 A d v a n c e d C # P r o g r a m m i n g

Page 428: Advanced c sharp programming

This chapter emphasizes the way in which ASP.NET, custom UserControls, and codewere combined to implement an environment with a dynamic user interface. In the firstsection of the chapter, we will explore

� How to create a cascading style sheet

� How to implement the IBuySpy portal banner

� Portal modules and the dynamic nature of the portal

� The stored procedures used to support the dynamic interface design

� Roles-based security for the portal

Consistent with the rest of the book, the “Secondary Topics” section will provide supplementalmaterial relevant to the “Demonstrated Topics” and the IBuySpy portal, including discussionsof how to administer the portal and debug the IBuySpy portal application, and an introductionto Microsoft’s Mobile Internet Toolkit.

Creating a Cascading Style SheetCascading styles sheets (CSS files) have been around awhile. Cascading style sheets allowyou to describe the appearance of HTML and ASP.NET Web pages in named styles and in asingle location and to apply those styles to your content. The result is that it becomes easierto manage the appearance of the content separately from the actual content. The IBuySpyportal has a distinctive appearance (see Figure 13-1), which is controlled primarily by theportal.css cascading style sheet.

NOTEThe view in Figure 13-1 shows the IBuySpy portal application before a user has been authenticated. Endusers will have access to features, and administrative users will have an additional Admin tab and links thatallow dynamic content management of the portal.

Almost everything you see in Figure 13-1 is dynamic content represented by UserControlsor the portal style sheet. You can create a style sheet by selecting File | New | File and selectingthe Style Sheet applet from the New File dialog (see Figure 13-2). The portal.css style sheetalready exists, and we can explore and customize it by selecting the portal.css file from thesolution explorer.

Navigating Style SheetsAs is true with many aspects of Visual Studio .NET, the view you are presented with is relatedto the context in which you are working. When you open a .css file to modify it, you will getthe text that is the style sheet and the CSS Outline—the document outline describing thecontent of the style sheet (see Figure 13-3).

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 0 1

Page 429: Advanced c sharp programming

4 0 2 A d v a n c e d C # P r o g r a m m i n g

Figure 13-1 The visual result shown in the IBuySpy portal application is controlled by thecentralized values described in the portal.css cascading style sheet.

Figure 13-2 Add a cascading style sheet to an ASP.NET project by selecting File | New | Fileand picking the Style Sheet applet from the New File dialog.

Page 430: Advanced c sharp programming

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 0 3

TIPYou can also open a Document Outline for the content of an ASP.NET page by selecting the .aspx file andchoosing View | Other Windows | Document Outline.

The CSS outline, as well as any outline view, helps you quickly navigate the style sheetdocument without having to scroll over every line of text. To navigate to a specific element,expand the folder containing that element and click the element name in the CSS Outline.Items in the Elements folder include things like styles for Horizontal Rules <HR>, links, andlists <LI>. Items in the Classes folder include styles for named classes that you create.

You can add, edit, or build styles by using the context menu in the CSS Outline view or bymanually editing the text in the actual style sheet document. The Style Builder (see Figure 13-4)provides a categorized way to manage styles, and the .css document itself employs Intellisenseto make it easier to add attributes to style elements correctly (see Figure 13-5).

TIPYou can press CTRL+SPACEBAR to display the list of available attributes for an element or begin typing anattribute name.

Figure 13-3 The Visual Studio .NET view when you are editing the content of a style sheet

Page 431: Advanced c sharp programming

4 0 4 A d v a n c e d C # P r o g r a m m i n g

Figure 13-4 The Style Builder dialog opened from the CSS Outline context menu item Build Stylewith the SiteTitle style focused

Figure 13-5 Intellisense showing a list of available style items for the SiteTitle style

Page 432: Advanced c sharp programming

Defining ElementsElements like the horizontal rule tag have a default appearance. You can modify the defaultappearance adding a style rule for the <HR> element. The following steps describe the visualprocess for adding an element style rule for the horizontal rule element.

1. In the CSS Outline, right-click the Elements folder and select Add Style Rule from thecontext menu. (The Add Style Rule dialog will be displayed, as shown in Figure 13-6.)

2. Select the Element radio button and pick the HR element.

3. Click OK to add the style rule to the .css document.

If you selected the HR element, you will see the following text in the style sheet document:

HR

{

}

The example is referred to as a style block. By adding declarations to the style block, you cancontrol how that style will look throughout the pages that use the style sheet containing thestyle block.

After you have added the style block, you can manually enter the declarations in the styleblock or use the Style Builder mentioned earlier to define the style. The horizontal rule forthe IBuySpy portal is defined as follows:

HR {

color: dimgrey;

height:1pt;

text-align:left

}

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 0 5

Figure 13-6 You can use the Add Style Rule dialog to define new styles.

Page 433: Advanced c sharp programming

4 0 6 A d v a n c e d C # P r o g r a m m i n g

The style indicates that the horizontal rule will be dimgrey in color and be 1 point high, withtext left-aligned. Examples of the horizontal rule can be seen in Figure 13-2, which shows themain page of the IBuySpy portal. Change the color declaration to color: red and build andrun the portal, and you will see that all instances of horizontal rules will now be red. This issignificantly more convenient than performing a global search-and-replace in every .aspx and.ascx page.

Defining ClassesClasses are added and defined similarly to elements. A class defines a new style rule that youwill associate with controls in your Web pages. To add a new class, right-click the Classesfolder in the CSS Outline view and select Add Style Rule. This time, choose the Class nameradio button and type the name of the class. For example, if you want to create the style rulefor the portal site, you might name the class SiteTitle. Click OK, and the SiteTitle style blockwill be added to the .css document.

You can use the Build Style dialog to visually describe the style or enter the declarationsmanually, using Intellisense to guide you. The Build Style dialog is especially helpful whenselecting items like color. If you pick an RGB color, it may be easier to pick a visual color(see Figure 13-7) in the Style Builder rather than guessing. Here is the style block for theSiteTitle.

.SiteTitle {

font-family: Verdana Bold, Helvetica, sans-serif;

font-size: 20px;

font-weight: bold;

color:#cccc99;

}

TIPI added the semicolon for the last—color—declaration, but you don’t need a semicolon for the lastdeclaration in a style rule.

Experiment with the Elements and Classes in the portal.css style sheet to see the effectthese changes have on the portal pages.

Using Elements and ClassesWhen you add style rules for elements like the horizontal rule, these rules are applied wheneveryou use that element. If you want to associate a style rule described by a class, you will haveto associate that class with a CssClass property of a control or by manually editing the HTMLthat describes the control; it is easier to modify the properties in the Properties window thanit is to modify the HTML directly.

The SiteTitle was actually applied only once in the portal application. The SiteTitle isapplied to the DesktopPortalBanner.ascx (UserControl). The banner control contains a labelwith the “IBuySpy Portal” text. The ASP that sets the CssClass attribute for the title label is

Page 434: Advanced c sharp programming

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 0 7

shown next. The Properties window containing the modified CssClass property for the Labelis shown in Figure 13-8.

Figure 13-7 Visually picking some declaration values is much easier using the Build Style dialog,as demonstrated here by the Color Picker dialog.

Figure 13-8 The CssClass property for the Label control demonstrates an instance of applying astyle rule from the portal.css style sheet.

Page 435: Advanced c sharp programming

<asp:label id="siteName" CssClass="SiteTitle" EnableViewState="false"

runat="server" />

Whenever you want to apply a class in the style sheet to a control, you will need to definethe style rule, find the CssClass property for that object, and type the name of the style rulein the Properties window (or directly in the HTML text). Several controls, like the DataList,have nested properties that also contain a CssClass property. That is, you can apply differentstyles for different elements of the control. For example, the DataList control has a CssClassproperty, as well as the nested properties EditItemStyle.CssClass, FooterStyle.CssClass,HeaderStyle.CssClass, ItemStyle.CssClass, SelectedItemStyle.CssClass, and the SeparatorStyle.CssClass properties. Each can have their own style rule or will inherit the rule applied to thecontrol itself.

Implementing the Portal BannerASP.NET UserControls are heavily used in the IBuySpy portal. The benefit of UserControlsis clearly reuse. It is much easier to describe a general layout and plug in a UserControl thanit is to hardwire every aspect of the user interface. For example, creating one banner meansthat every page simply needs to refer to the banner in order to display the banner. Even if youuse only a piece of a page one time, it is still beneficial to create UserControls, because aUserControl can easily be reused. Figure 13-9 shows the DesktopPortalBanner.ascx UserControlbefore it is rendered, and Figure 13-1 (at the top) shows the dramatic difference after thecontrol has been rendered in a Web page.

The basic approach followed throughout the IBuySpy portal is that the page and controlsdefine the basic layout of the portal pages, and the style sheets and code determine whichelements to display. The result is a complete and elaborate Web application with a relativelysmall number of .aspx Web pages and an equal number of reusable UserControls.

Designing User ControlsThe fundamental steps for creating UserControls for Web pages are similar to those outlinedin Chapter 8, which introduced the topic of creating UserControls. When you create a WebUserControl you will be inheriting from System.Web.UI.UserControl instead of System.Windows .Forms.UserControl. You can add a Web UserControl to a project by selectingthe Web UserControl applet from the Add New Item dialog. The DesktopPortalBanneralready exists, so we will commence with the description of its implementation (shown inFigure 13-9).

TIPYou can convert a Web Forms page to a UserControl by removing the <html>, <body>, and <form>elements from the page and changing any @Page directive to an @Control directive. Make sure youremove @Page attributes that are not applicable to @Control directives.

4 0 8 A d v a n c e d C # P r o g r a m m i n g

Page 436: Advanced c sharp programming

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 0 9

From Figure 13-9, it is clear that the design-time effort spent on the DesktopPortalBannerwas minimal. The control layout is described by a <table>, and there are a couple of Labelcontrols, HTML links, and a DataList. We will explore the various aspects of implementingthe DesktopPortalBanner.ascx UserControl in the sections that follow.

Working with the Design ViewAfter we have added a Web UserControl to the project, we can use the designer or theHTML editor to create the control layout. The ASP and HTML that implement theDesktopPortalBanner is shown in Listing 13-1 for reference. (It is difficult to format HTMLboth in this text and the HTML designer, which is one reason why it is beneficial to use theDocument Outline to navigate the text.)

Listing 13-1 The HTML that implements the DesktopPortalBanner.ascx UserControl

1: <%@ Control CodeBehind="DesktopPortalBanner.ascx.cs"

2: Language="c#" AutoEventWireup="false"

3: Inherits="ASPNetPortal.DesktopPortalBanner" %>

4: <%@ Import Namespace="ASPNetPortal" %>

Figure 13-9 The DesktopPortalBanner.ascx UserControl before rendering

Page 437: Advanced c sharp programming

4 1 0 A d v a n c e d C # P r o g r a m m i n g

5: <%--

6:

7: The DesktopPortalBanner User Control is responsible for

8: displaying the standard Portal

9: banner at the top of each .aspx page.

10:

11: The DesktopPortalBanner uses the Portal Configuration

12: System to obtain a list of the portal's sitename and

13: tab settings. It then renders this content into the page.

14:

15: --%>

16: <table width="100%" cellspacing="0" class="HeadBg" border="0">

17: <tr valign="top">

18: <td colspan="3" class="SiteLink"

19: background="<%= Request.ApplicationPath %>/images/bars.gif"

20: align="right">

21: <asp:label id="WelcomeMessage" forecolor="#eeeeee" runat="server" />

22: <a href="<%= Request.ApplicationPath %>"

23: class="SiteLink">Portal Home</a>

24: <span class="Accent">

25: |</span>

26: <a href="<%= Request.ApplicationPath %>/Docs/Docs.htm"

27: target="_blank" class="SiteLink">

28: Portal Documentation</a>

29: <%= LogoffLink %>

30: &nbsp;&nbsp;

31: </td>

32: </tr>

33: <tr>

34: <td width="10" rowspan="2">

35: &nbsp;

36: </td>

37: <td height="40">

38: <asp:label id="siteName" CssClass="SiteTitle"

39: EnableViewState="false" runat="server" />

40: </td>

41: <td align="middle" rowspan="2">

42: <a href="http://www.asp.net"><img id="logo"

43: src="<%=Request.ApplicationPath%>/images/poweredby_simple.gif"

44: border="0"></a>

45: </td>

46: </tr>

47: <tr>

Page 438: Advanced c sharp programming

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 1 1

48: <td>

49: <asp:datalist id="tabs" cssclass="OtherTabsBg"

50: repeatdirection="horizontal" ItemStyle-Height="25"

51: SelectedItemStyle-CssClass="TabBg"

52: ItemStyle-BorderWidth="1" EnableViewState="false" runat="server">

53: <ItemTemplate>

54: &nbsp;<a href='<%= Request.ApplicationPath

%>/DesktopDefault.aspx?tabindex=<%# Container.ItemIndex %>&tabid=<%#

((TabStripDetails) Container.DataItem).TabId %>' class="OtherTabs"><%#

((TabStripDetails) Container.DataItem).TabName %></a>&nbsp;

55: </ItemTemplate>

56: <SelectedItemTemplate>

57: &nbsp;<span class="SelectedTab"><%# ((TabStripDetails)

Container.DataItem).TabName %></span>&nbsp;

58: </SelectedItemTemplate>

59: </asp:datalist>

60: </td>

61: </tr>

62: </table>

A UserControl has many of the same HTML elements as a Web page. From the previoussection, we know that a UserControl does not have the <form>, <html>, and <body> elements,and the code-behind is associated with the control using an @Control directive instead of an@Page directive.

Using an HTML Table to Manage LayoutAn excellent and lightweight way to manage the layout of a UserControl (and a page) is toadd an HTML table to the control in order to subdivide the real estate of the control usingrows and data cells. The <table> in Listing 13-1 extends from line 16 to line 62, intertwinedwith the controls added to that table. The Document Outline view is the best way to navigateindividual elements of the table, like the three row elements <tr>, and you can use the contextmenu to add rows and cells to a table. To create very elaborate layouts, you can also nestHTML <table> elements.

To create a <table> element that has a layout similar to the DesktopPortalBanner, followthese steps:

1. Create a new Web UserControl.

2. Select the HTML tab in the toolbox and drag the Table control to the UserControldesigner. (By default, there are three columns and rows rendered in an HTML table;we will modify the default number.)

3. Right-click the top right cell and select Delete | Cells from the context menu. Repeatthis step again, deleting the middle data cell.

Page 439: Advanced c sharp programming

4. Press F4 to display the Properties window and change the colspan property of the<TD> (data cell) element to 3. This step will create a top row like that used to displaythe Portal Home and Portal Documentation links shown in Figure 13-1.

5. To create the bottom two rows of the DesktopPortalBanner, delete two cells from thebottom row using the context menu again.

6. Select the middle-left data cell and change the rowspan property to 2.

7. Repeat step 6 for the middle-right cell.

8. Move the mouse over one vertical edge of either of the center data cells, and drag themouse until the second and third row middle cells consume most of the horizontalspace for the control.

9. Finally, select the two-rowspan rightmost data cell, and press F4 to open the Propertieswindow. Change the cell width to about 50.

The result of the previous nine steps creates the table layout shown in Figure 13-10 and thecode shown in Listing 13-2.

4 1 2 A d v a n c e d C # P r o g r a m m i n g

Figure 13-10 A table layout that approximates the layout of the DesktopPortalBannerUserControl

Page 440: Advanced c sharp programming

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 1 3

Listing 13-2 An HTML table control that is similar to the layout for the DesktopPortalBanner

<%@ Control Language="c#" AutoEventWireup="false"

Codebehind="WebUserControl1.ascx.cs"

Inherits="ASPNetPortal.WebUserControl1"

TargetSchema="http://schemas.microsoft.com/intellisense/ie3-2nav3-0"%>

<TABLE id="Table1" height="96"

cellSpacing="1" cellPadding="1" width="784" border="1">

<TR>

<TD colSpan="3"></TD>

</TR>

<TR>

<TD rowSpan="2"></TD>

<TD width="739"></TD>

<TD width="50" rowSpan="2"></TD>

</TR>

<TR>

<TD width="739"></TD>

</TR>

</TABLE>

A strategy I employ when using a table to manage layout is to leave the border value greaterthan 0, so I can see the cell boundaries as I am adding controls to the layout. To complete theDesktopPortalBanner, we need to add some controls that act as placeholders for the data.

Adding Controls to the TableNow that we have the data cells, we can add controls to create the UserControl design bydragging and dropping controls onto the UserControl cells. For example, the vertical bars arecreated by adding a background image to the <TD> element. This is demonstrated by line 19of Listing 13-1; notice the use of the function invocation to Request.ApplicationPath appendedto the relative path /images/bars.gif.

Using the server-side script block allows you to insert script inline. The Request.ApplicationPath call will be replaced with the actual application path. (Using the server-sidescript makes it possible to easily move the portal application to your PC or deploy it.) Thetop row of the banner contains a label that will be replaced with a welcome message whenyou log in. There are two hyperlinks that allow the user to navigate to the portal home pageor portal documentation and the server-side script to display the LoggoffLink field. Again,when you log in, the LogoffLink field will return HTML that renders the Logoff link in thePage Load event if the user is authenticated. Here is the DesktopPortalBanner.ascx.cs code-behind file that initializes the LogoffLink field.

if (Context.User.Identity.AuthenticationType == "Forms")

{

LogoffLink =

Page 441: Advanced c sharp programming

"<" + "span class=\"Accent\">|</span>\n" + "<" +

"a href=" + Request.ApplicationPath + "/Admin/Logoff.aspx class=SiteLink>

Logoff" + "<" + "/a>";

}

The LogoffLink string creates the following HTML text:

<span class="Accent">|</span>

<a href=/PortalCSVS/Admin/Logoff.aspx class=SiteLink> Logoff</a>

If you view the pages and UserControls in the IBuySpy portal, you will see server-sidescript used frequently to create a dynamic relationship between the content displayed and thecode that manages the content.

Adding a DataList to Implement the Navigation TabsThe DataList control is added from the Web Forms tab of the toolbox and can be bound tothe same kinds of objects that you would bind a DataGrid to, for example, an ArrayList orDataSet. The DataList is a data-bound template-derived control that supports viewing,updating, and deleting data in the list. Cleverly, the data list is used to bind tab names todisplay available tabs for the IBuySpy portal based on the authentication state of the portal;there are more items displayed for administrative users and fewer tab items for end users.

The DataList can be edited from the Edit Template context menu item. There are severaltemplates you can modify to create the desired result for a DataList, including: Header andFooter Templates, Item Templates, and a Separator Template. The Item Templates includethe ItemTemplate, AlternatingItemTemplate, SelectedItemTemplate, and EditItemTemplate.The Header and Footer templates describe the appearance of the header and footer, and theSeparator template describes the content between DataList items. The Item Templates describethe appearance of the data-bound content of the DataList. The ItemTemplate describes all itemsin the DataList. The AlternatingItemTemplate describes the appearance of every other item inthe DataList. The SelectedItemTemplate describes the appearance of the DataList item that isselected, and the EditItemTemplate describes the element that is being edited in the list. Defineeach of these template items if you want to customize the appearance of the DataList in anyparticular state.

If you open the Item Templates editor for the DataList for the DesktopPortalBanner, itmay be difficult to tell which templates were employed and how they were defined, so wewill turn to the code. (Of course, you can modify the DataList and other ASP.NET controlsby turning directly to the HTML editor or the Properties window, too.) Listing 13-3 containsthe ASP.NET code that describes the DataList for the banner.

Listing 13-3 The ASP.NET code that describes the DataList used to create the navigation tabsfor the IBuySpy portal

1: <asp:datalist id="tabs" runat="server"

2: EnableViewState="false" ItemStyle-BorderWidth="1"

3: SelectedItemStyle-CssClass="TabBg"

4: ItemStyle-Height="25" repeatdirection="horizontal"

4 1 4 A d v a n c e d C # P r o g r a m m i n g

Page 442: Advanced c sharp programming

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 1 5

5: cssclass="OtherTabsBg">

6: <ItemTemplate>

7: &nbsp; <a href='<%= Request.ApplicationPath

%>/DesktopDefault.aspx?tabindex=<%# Container.ItemIndex %>&tabid=<%#

((TabStripDetails) Container.DataItem).TabId %>'

8: class="OtherTabs">

9: <%# ((TabStripDetails) Container.DataItem).TabName %>

10: </a>&nbsp;

11: </ItemTemplate>

12: <SelectedItemTemplate>

13: &nbsp;<span class="SelectedTab"><%# ((TabStripDetails)

Container.DataItem).TabName %>

14: </span>&nbsp;

15: </SelectedItemTemplate>

16: </asp:datalist>

(ASP.NET contains some very long text. Instead of arbitrarily breaking the statements intoformattable chunks, I elected to display the code that describes the DataList roughly as it willappear in the HTML editor, and the line numbers were added for reference.)

The DataList itself is defined by the <asp:datalist> and </asp:datalist> tags. Basically,ASP.NET controls are rendered as text, which supports building ASP.NET pages in any texteditor. (I wouldn’t do this now, but ASP.NET was released before Visual Studio .NET andthis is how early beta developers may have demonstrated building Web pages.) Lines 1through 5 describe the DataList. The runat attribute indicates that the DataList code is to beprocessed on the server. The EnableViewState=“false” attribute indicates that the view stateinformation for the DataList will not be stored on the page. (View state information lookslike garbage text but is actually encrypted state information stored in the clear.) TheSelectedItemStyle-CssClass represents the DataList.SelectedItemStyle.CssClass propertyand is assigned the rule TabBg defined in the portal.css cascading style sheet. The ItemStyle-Height is 25; the items will be displayed horizontally; and the general style rule for theDataList is the OtherTabsBg style. (You can examine the portal.css to see the styles definedby these rules. You’ll find that the styles define the background color, which is suggested bythe Bg suffix used in the style name.)

The &nbsp; (non-breaking space) is used to force a space between the DataBound elementsin the DataList. This tag is used at the left and right sides of the DataBound elements in boththe ItemTemplate and SelectedItemTemplate to produce a uniform spacing between elements.

Again, the DataList is using the server-side script tags. The DataList tab items are obtainedprogrammatically in the code-behind page, which we will discuss in a moment. The DataListin the DesktopPortalBanner demonstrates how to programmatically bind data at run-time,resulting in the tabs shown in Figure 13-1.

Programming in the Code-Behind ModuleA convenient way to demonstrate the code-behind module is to describe how the tabs—DataList in the DesktopPortalBanner—are generated in the portal application.

Page 443: Advanced c sharp programming

ASP.NET is more challenging a tool than Windows Forms for creating graphical userinterfaces because there are more ways to create the interface. In Windows Forms, we havethe Form designer and the code-behind. In ASP.NET, we have the Page designer, HTMLeditor, and the code. Fortunately, the code-behind page does make it easier to associate codewith ASP.NET Web pages in a manner that is more consistent with how we have beenimplementing non-Web applications since Alan Cooper invented Visual Basic.

You can press F7 to switch from the Page designer view to the code-behind view andSHIFT+F7 to switch back to the Page designer view. By convention, the ASP.NET page hasan .aspx extension, and the code-behind file has the .aspx.cs extension, but the associationhappens in the Codebehind attribute of the @Page and @Control statements (see Listing13-2 for an example.)

You might assume that the code-behind code runs first, but this isn’t necessarily the case.The code in the Global.asax page is executed first. I will describe the order of executionbeginning with the Global.asax page and ending with data being bound to the DataListcontrol named tabs.

Implementing Event Handlers in the Global.asax ModuleThe Global.asax module inherits from HttpApplication and provides you with a convenientplace to add event handlers for responding to application level events. The Global.asax filecannot be requested directly by a user, but the code in it is run when an application-level event isrequested. In the IBuySpy portal application, the Global.asax file implements two eventhandlers: BeginRequest and AuthenticateRequest. (There are about a dozen event handlersyou can implement; writing any code in the application file is optional.)

In the Portal, BeginRequest uses the TabIndex and TabId in the URL-encoded messagestring to build the PortalSettings object each time a request is made. The AuthenticateRequestevent handler is implemented to determine whether the user has been authenticated and whatroles that person has been assigned. Listing 13-4 contains the code for the Application_BeginRequest event handler (you can refer to Chapter 16 for more information on roles-based security).

Listing 13-4 The BeginRequest event handler is used to initialize the PortalSettings based on theURL-encoded request.

1: protected void Application_BeginRequest(Object sender, EventArgs e)

2: {

3: int tabIndex = 0;

4: int tabId = 0;

5:

6: // Get TabIndex from querystring

7:

8: if (Request.Params["tabindex"] != null) {

9: tabIndex = Int32.Parse(Request.Params["tabindex"]);

10: }

11:

4 1 6 A d v a n c e d C # P r o g r a m m i n g

Page 444: Advanced c sharp programming

12: // Get TabID from querystring

13:

14: if (Request.Params["tabid"] != null) {

15: tabId = Int32.Parse(Request.Params["tabid"]);

16: }

17:

18: Context.Items.Add("PortalSettings", new PortalSettings(tabIndex,

tabId));

19: }

When you select an item in the tab navigator, the hyperlink in the ItemTemplate creates aURL-encoded request string, such as the one demonstrated here:

http://localhost/PortalCSVS/DesktopDefault.aspx?tabindex=1&tabid=2

The parameters in the request can be read from the Request.Params property, using the parametername as an indexer, as demonstrated on lines 9 and 15 of Listing 13-4. The parameters tabindexand tabid are used as arguments to the constructor for the PortalSettings. Each time a requestis made, the PortalSettings object is re-created. The PortalSettings object is essential in theIBuySpy portal, providing information about the presentation of the Web application. ThePortalSettings are stored in the application’s Context object, which is accessible anywhere inthe application during the life of a single request.

Reading the Portal Settings from the DatabaseThe PortalSettings class is defined in the configuration.cs module. The PortalSettings areread from the portal database using the tabindex and tabid parameters as arguments to theGetPortalSettings stored procedure. In this section, we will look at the constructor for thePortalSettings class, including how to create, invoke, and obtain information from storedprocedures using ADO.NET.

Implementing a Stored Procedure The PortalSettings class is initialized from the storedprocedure shown in Listing 13-5. When you create the portal, the script PortalDB.sql isrun to create the portal database. One of the steps the script takes is to generate theGetPortalSettings stored procedure, and the procedure shown in Listing 13-5 modifiesthat stored procedure slightly and then retrieves specific portal setting values from thedatabase in the PortalSettings constructor.

Listing 13-5 A stored procedure that changes slightly based on portal context and is used toretrieve portal settings

1: ALTER PROCEDURE dbo.GetPortalSettings

2: (

3: @PortalAlias nvarchar(50),

4: @TabID int,

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 1 7

Page 445: Advanced c sharp programming

5: @PortalID int OUTPUT,

6: @PortalName nvarchar(128) OUTPUT,

7: @AlwaysShowEditButton bit OUTPUT,

8: @TabName nvarchar (50) OUTPUT,

9: @TabOrder int OUTPUT,

10: @MobileTabName nvarchar (50) OUTPUT,

11: @AuthRoles nvarchar (256) OUTPUT,

12: @ShowMobile bit OUTPUT

13: )

14: AS

15:

16: /* First, get Out Params */

17: IF @TabID = 0

18: SELECT TOP 1

19: @PortalID = Portals.PortalID,

20: @PortalName = Portals.PortalName,

21: @AlwaysShowEditButton = Portals.AlwaysShowEditButton,

22: @TabID = Tabs.TabID,

23: @TabOrder = Tabs.TabOrder,

24: @TabName = Tabs.TabName,

25: @MobileTabName = Tabs.MobileTabName,

26: @AuthRoles = Tabs.AuthorizedRoles,

27: @ShowMobile = Tabs.ShowMobile

28:

29: FROM

30: Tabs

31: INNER JOIN

32: Portals ON Tabs.PortalID = Portals.PortalID

33:

34: WHERE

35: PortalAlias=@PortalAlias

36:

37: ORDER BY

38: TabOrder

39:

40: ELSE

41: SELECT

42: @PortalID = Portals.PortalID,

43: @PortalName = Portals.PortalName,

44: @AlwaysShowEditButton = Portals.AlwaysShowEditButton,

45: @TabName = Tabs.TabName,

46: @TabOrder = Tabs.TabOrder,

47: @MobileTabName = Tabs.MobileTabName,

48: @AuthRoles = Tabs.AuthorizedRoles,

49: @ShowMobile = Tabs.ShowMobile

4 1 8 A d v a n c e d C # P r o g r a m m i n g

Page 446: Advanced c sharp programming

50:

51: FROM

52: Tabs

53: INNER JOIN

54: Portals ON Tabs.PortalID = Portals.PortalID

55:

56: WHERE

57: TabID=@TabID

58:

59: /* Get Tabs list */

60: SELECT

61: TabName,

62: AuthorizedRoles,

63: TabID,

64: TabOrder

65:

66: FROM

67: Tabs

68:

69: WHERE

70: PortalID = @PortalID

71:

72: ORDER BY

73: TabOrder

74:

75: /* Get Mobile Tabs list */

76: SELECT

77: MobileTabName,

78: AuthorizedRoles,

79: TabID,

80: ShowMobile

81:

82: FROM

83: Tabs

84:

85: WHERE

86: PortalID = @PortalID

87: AND

88: ShowMobile = 1

89:

90: ORDER BY

91: TabOrder

92:

93: /* Then, get the DataTable of module info */

94: SELECT

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 1 9

Page 447: Advanced c sharp programming

4 2 0 A d v a n c e d C # P r o g r a m m i n g

95: *

96:

97: FROM

98: Modules

99: INNER JOIN

100: ModuleDefinitions ON Modules.ModuleDefID =

ModuleDefinitions.ModuleDefID

101:

102: WHERE

103: TabID = @TabID

104:

105: ORDER BY

106: ModuleOrder

We can’t fit a stored procedure tutorial in the space available. A good SQL Serverprogrammer’s guide, such as SQL Server 2000 Developer’s Guide by Michael Otey andPaul Conty (McGraw-Hill/Osborne), is an essential part of every database developer’stoolkit. In addition, there are a couple of dozen stored procedures in the IBuySpy portalthat you can borrow or learn from. I will provide you with a brief overview explaininghow the stored procedures work and how they were used in the portal.

Stored procedures accept up to 2,100 parameters and can return parameters. MicrosoftSQL Server’s stored procedures can contain any number of T-SQL statements. The result isthat you can package a large request for many bits of related data into a stored procedure,which, in turn, are all run in one group on the server and selectively return the data in the formof the output parameters that you are interested in. ALTER PROCEDURE dbo.GetPortalSettingsdefines two input parameters and eight output parameters—that is, we are getting eightpieces of data from the database from this one stored procedure.

The stored procedure in Listing 13-5 modifies GetPortalSettings based on the TabIdpassed in the URL-encoded request that I mentioned earlier in “Implementing EventHandlers in the Global.asax Module.” A SELECT statement is added to the stored procedurebased on the T-SQL If Else test on lines 17 through 58. Lines 59 through 73 obtain the tabslist. Lines 75 through 91 obtain the tabs list for the mobile device pages, and lines 93 through106 obtain the module information.

Defining Input and Output Parameters for Stored Procedures GetPortalSettings and other storedprocedures represent a kind of encapsulation as a general strategy for enhancing performance.The stored procedure encapsulates several SELECT statements into a single, simpler package,and all of the SELECT queries run on the database server as a single request from the portalapplication. This reduces frequency of requests between the portal application and thedatabase server, resulting in a tuned performance.

Now that we know the portal is using stored procedures to simplify programming andimprove performance, we need to know how to invoke stored procedures using ADO.NET.(See Chapter 5 for an introduction to ADO.NET.) Listing 13-6 shows the constructor for the

Page 448: Advanced c sharp programming

PortalSettings class, which is initialized by invoking the GetPortalSettings stored procedurefrom Listing 13-5.

Listing 13-6 The PortalSettings constructor demonstrates how stored procedures are used in theIBuySpy portal.

1: //*********************************************************************

2: //

3: // PortalSettings Constructor

4: //

5: // The PortalSettings Constructor encapsulates all of the logic

6: // necessary to obtain configuration settings necessary to render

7: // a Portal Tab view for a given request.

8: //

9: // These Portal Settings are stored within a SQL database, and are

10: // fetched below by calling the "GetPortalSettings" stored procedure.

11: // This stored procedure returns values as SPROC output parameters,

12: // and using three result sets.

13: //

14: //**********************************************************************

15:

16: public PortalSettings(int tabIndex, int tabId) {

17:

18: // Create Instance of Connection and Command Object

19: SqlConnection myConnection = new SqlConnection(

20: ConfigurationSettings.AppSettings["connectionString"]);

21: SqlCommand myCommand = new SqlCommand("GetPortalSettings", myConnection);

22:

23: // Mark the Command as a SPROC

24: myCommand.CommandType = CommandType.StoredProcedure;

25:

26: // Add Parameters to SPROC

27: SqlParameter parameterPortalAlias =

28: new SqlParameter("@PortalAlias", SqlDbType.NVarChar, 50);

29: parameterPortalAlias.Value = "p_default";

30: myCommand.Parameters.Add(parameterPortalAlias);

31:

32: SqlParameter parameterTabId =

33: new SqlParameter("@TabId", SqlDbType.Int, 4);

34: parameterTabId.Value = tabId;

35: myCommand.Parameters.Add(parameterTabId);

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 2 1

Page 449: Advanced c sharp programming

4 2 2 A d v a n c e d C # P r o g r a m m i n g

36:

37: // Add out parameters to Sproc

38: SqlParameter parameterPortalID =

39: new SqlParameter("@PortalID", SqlDbType.Int, 4);

40: parameterPortalID.Direction = ParameterDirection.Output;

41: myCommand.Parameters.Add(parameterPortalID);

42:

43: SqlParameter parameterPortalName =

44: new SqlParameter("@PortalName", SqlDbType.NVarChar, 128);

45: parameterPortalName.Direction = ParameterDirection.Output;

46: myCommand.Parameters.Add(parameterPortalName);

47:

48: SqlParameter parameterEditButton =

49: new SqlParameter("@AlwaysShowEditButton", SqlDbType.Bit, 1);

50: parameterEditButton.Direction = ParameterDirection.Output;

51: myCommand.Parameters.Add(parameterEditButton);

52:

53: SqlParameter parameterTabName =

54: new SqlParameter("@TabName", SqlDbType.NVarChar, 50);

55: parameterTabName.Direction = ParameterDirection.Output;

56: myCommand.Parameters.Add(parameterTabName);

57:

58: SqlParameter parameterTabOrder =

59: new SqlParameter("@TabOrder", SqlDbType.Int, 4);

60: parameterTabOrder.Direction = ParameterDirection.Output;

61: myCommand.Parameters.Add(parameterTabOrder);

62:

63: SqlParameter parameterMobileTabName =

64: new SqlParameter("@MobileTabName", SqlDbType.NVarChar, 50);

65: parameterMobileTabName.Direction = ParameterDirection.Output;

66: myCommand.Parameters.Add(parameterMobileTabName);

67:

68: SqlParameter parameterAuthRoles =

69: new SqlParameter("@AuthRoles", SqlDbType.NVarChar, 256);

70: parameterAuthRoles.Direction = ParameterDirection.Output;

71: myCommand.Parameters.Add(parameterAuthRoles);

72:

73: SqlParameter parameterShowMobile =

74: new SqlParameter("@ShowMobile", SqlDbType.Bit, 1);

75: parameterShowMobile.Direction = ParameterDirection.Output;

76: myCommand.Parameters.Add(parameterShowMobile);

77:

Page 450: Advanced c sharp programming

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 2 3

78: // Open the database connection and execute the command

79: myConnection.Open();

80: SqlDataReader result = myCommand.ExecuteReader();

81:

82: // Read the first resultset -- Desktop Tab Information

83: while(result.Read()) {

84:

85: TabStripDetails tabDetails = new TabStripDetails();

86: tabDetails.TabId = (int) result["TabId"];

87: tabDetails.TabName = (String) result["TabName"];

88: tabDetails.TabOrder = (int) result["TabOrder"];

89: tabDetails.AuthorizedRoles =

90: (String) result["AuthorizedRoles"];

91:

92: this.DesktopTabs.Add(tabDetails);

93: }

94:

95: if (this.ActiveTab.TabId == 0) {

96: this.ActiveTab.TabId =

97: ((TabStripDetails) this.DesktopTabs[0]).TabId;

98: }

99:

100: // Read the second result -- Mobile Tab Information

101: result.NextResult();

102:

103: while(result.Read()) {

104:

105: TabStripDetails tabDetails = new TabStripDetails();

106: tabDetails.TabId = (int) result["TabId"];

107: tabDetails.TabName = (String) result["MobileTabName"];

108: tabDetails.AuthorizedRoles =

109: (String) result["AuthorizedRoles"];

110:

111: this.MobileTabs.Add(tabDetails);

112: }

113:

114: // Read the third result -- Module Tab Information

115: result.NextResult();

116:

117: while(result.Read()) {

Page 451: Advanced c sharp programming

118:

119: ModuleSettings m = new ModuleSettings();

120: m.ModuleId = (int) result["ModuleID"];

121: m.ModuleDefId = (int) result["ModuleDefID"];

122: m.TabId = (int) result["TabID"];

123: m.PaneName = (String) result["PaneName"];

124: m.ModuleTitle = (String) result["ModuleTitle"];

125: m.AuthorizedEditRoles = (String) result["AuthorizedEditRoles"];

126: m.CacheTime = (int) result["CacheTime"];

127: m.ModuleOrder = (int) result["ModuleOrder"];

128: m.ShowMobile = (bool) result["ShowMobile"];

129: m.DesktopSrc = (String) result["DesktopSrc"];

130: m.MobileSrc = (String) result["MobileSrc"];

131:

132: this.ActiveTab.Modules.Add(m);

133: }

134:

135: // Now read Portal out params

136: result.NextResult();

137:

138: this.PortalId = (int) parameterPortalID.Value;

139: this.PortalName = (String) parameterPortalName.Value;

140: this.AlwaysShowEditButton = (bool) parameterEditButton.Value;

141: this.ActiveTab.TabIndex = tabIndex;

142: this.ActiveTab.TabId = tabId;

143: this.ActiveTab.TabOrder = (int) parameterTabOrder.Value;

144: this.ActiveTab.MobileTabName = (String) parameterMobileTabName.Value;

145: this.ActiveTab.AuthorizedRoles = (String) parameterAuthRoles.Value;

146: this.ActiveTab.TabName = (String) parameterTabName.Value;

147: this.ActiveTab.ShowMobile = (bool) parameterShowMobile.Value;

148:

149: myConnection.Close();

150:}

NOTEThe code in Chapters 13, 14, 15, and 16 is provided by permission from Microsoft Corporation and is listedas it actually occurs in the IBuySpy portal, with only very modest changes for pagination purposes. Thestyle reflects that of the author of the portal code, and the format and content was left intact to preventunintentional modification. I will be referring to the PortalSettings Constructor in Listing 13-6 in this sectionand the next.

In Chapter 5 you learned about creating a connection object. The PortalSettings constructorcreates an SQL provider connection object on lines 19 and 20 of Listing 13-6. One differencefrom Chapter 5 is the way in which the portal stores application information. The connection

4 2 4 A d v a n c e d C # P r o g r a m m i n g

Page 452: Advanced c sharp programming

string is stored in the web.config file (see Listing 13-7). The ConfigurationSettings class’sshared property AppSettings allows you to store and retrieve application settings from the<appSettings> element of the web.config file.

Listing 13-7 The appSettings portion of the web.config file for the IBuySpy portal, accessiblevia the ConfigurationSettings class

<!-- application specific settings -->

<appSettings>

<add key="ConnectionString"

value="server=localhost;Trusted_Connection=true;database=Portal" />

</appSettings>

Next, the constructor creates an SqlCommand object used to represent the stored procedure(see line 21 of Listing 13-6). On line 24, we indicate that the command type will be a storedprocedure—CommandType.StoredProcedure. These first couple of steps are consistent withrecurring behavior for executing stored procedures in general. The next general step is toexpress the in and out parameters for the stored procedure, followed by executing the storedprocedure and retrieving any output values. (We’ll look at running the stored procedure andretrieving output parameters in the next section.)

Parameters for stored procedures, like everything else in the .NET framework, are expressedas instances of classes. To create and send a parameter to a stored procedure, we need tocreate an instance of the SqlParameter (OleDbParameter for OLEDB providers) class and addthe parameter object to the SqlCommand object’s Parameters collection. Lines 26 through 30of Listing 13-6 demonstrate an input parameter, the PortalAlias, and lines 38 through 40demonstrate an output parameter. (There are several examples of both kinds of parameters inListing 13-6.)

The SqlParameter constructor shown on line 28 of Listing 13-6 is initialized withthe parameter name, data type, and size. (There are six overloaded constructors for theSqlParameter class.) Line 29 provides an input value for the PortalAlias input parameter,and line 30 adds the parameter to the SqlCommand.Parameters collection.

Output parameters are initialized similarly. Instead of providing a SqlCommand.Valuerepresenting the input argument for output parameters, we modify the SqlParameter.Directionvalue to be ParameterDirection.Output. An example of the difference between an input andan output parameter is shown on line 40 of Listing 13-6. When we have all of the input andoutput parameters expressed, we can invoke the stored procedure.

Invoking the GetPortalSettings Stored Procedure After creating the connection, command, andparameter objects, we are ready to connect to the Portal, the default name of the IBuySpydatabase, and retrieve the results. Line 79 of Listing 13-6 opens the connection, and we usethe SqlCommand object to execute the stored procedure. As we are only reading data, we canuse an SqlDataReader to retrieve the output parameters. The SqlDataReader is returned fromthe SqlCommand.ExecuteReader method (line 80); then an SqlDataReader is a unidirectional,read-only cursor and yields a better performance than a DataSet.

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 2 5

Page 453: Advanced c sharp programming

4 2 6 A d v a n c e d C # P r o g r a m m i n g

Consistent with the order of the SELECT statements, we retrieve the result data by executingthe Read to retrieve all of the desktop tab information. When all of the TabDetail objects areinitialized from the resultset—lines 83 through 93, we move onto the next resultset by invokingSqlDataReader.NextResult, as demonstrated on line 101. This sequence of events is repeateduntil we have read all of the resultsets, and we wrap up the constructor by closing the connection(see line 150).

Notice that where there were possibly many rows of data returned in an individual resultset,we used a while loop and SqlDataReader.Read to retrieve all rows, and the output parameterswere read using the parameter objects on lines 138, 139, 140, 143, 144, 145, 146, and 147.(The values tabIndex and tabId—lines 141 and 142—were passed into our constructor; wejust store them in the PortalSettings.ActiveTab object.)

Debugging, Creating, and Modifying Stored Procedures Using stored procedures and ADO.NETrepresents a strategy you will see used consistently and repeatedly in the IBuySpy portal.Stored procedures are an optimal way to manage data and the disconnected ADO.NET modelsupports tremendous scalability. Understanding how one object is instantiated using storedprocedures will allow you to work with all objects in the portal and any other application thatemploys this strategy. For more examples of classes that use this strategy in the portal, referto the .cs source code files in the Components folder of the portal.

The final thing you need to know how to do is to write, modify, and debug stored procedures.The Visual Studio .NET IDE supports creating, modifying, and debugging stored proceduresthrough the Server Explorer window (see Figure 13-11).

To create a new stored procedure, right-click the Stored Procedures node (see Figure 13-11) orany of the individual stored procedures, and select New Stored Procedure from the contextmenu. This step will create a template-stored procedure if you have the SQL Server EnterpriseManager installed. (The menu item will not be displayed if the Enterprise Manager is notinstalled on your workstation.)

To edit an existing stored procedure, select the stored procedure you want to edit and pickEdit Stored Procedure from the right-click context menu. You can also run or step into aparticular stored procedure from the same menu. You might get an error (see Figure 13-12) ifyou try to debug a stored procedure and don’t have adequate permissions to do so. Contactyour administrator or click the Help button, shown in Figure 13-12, for instructions on resolvingthis problem. When you are stepping into a stored procedure, you can use breakpoints andwatch values just as you would for debugging C# code.

Implementing the Page Load Init HandlerAfter the BeginRequest event is handled, the Page_Init event (followed by Page_Load) eventis raised. The portal uses the Page_Init event handler to initialize the portal settings read fromthe stored procedure and stored in the HttpContext object at the end of the BeginRequestevent. The Page_Init event handler for the DesktopDefault.aspx Web page is shown inListing 13-8.

Page 454: Advanced c sharp programming

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 2 7

Figure 13-11 The Server Explorer expanded to show the alphabetically ordered StoredProcedures for the IBuySpy portal

Figure 13-12 Trying to debug a stored procedure from the Server Explorer without adequatepermissions

Page 455: Advanced c sharp programming

Listing 13-8 The Page_Init event handler for the DesktopDefault.aspx Web page uses thePortalSettings object to initialize the page dynamically.

1: private void Page_Init(object sender, EventArgs e) {

2: //

3: // CODEGEN: This call is required by the ASP.NET Web

4: // Form Designer.

5: //

6: InitializeComponent();

7:

8: //********************************************************************

9: //

10: // Page_Init Event Handler

11: //

12: // The Page_Init event handler executes at the very beginning

13: // of each page request (immediately before Page_Load).

14: //

15: // The Page_Init event handler below determines the tab

16: // index of the currently

17: // requested portal view, and then calls the

18: // PopulatePortalSection utility

19: // method to dynamically populate the left,

20: // center and right hand sections of the portal tab.

21: //

22: //********************************************************************

23:

24: // Obtain PortalSettings from Current Context

25: PortalSettings portalSettings =

26: (PortalSettings) HttpContext.Current.Items["PortalSettings"];

27:

28: // Ensure that the visiting user has access to the current page

29: if (PortalSecurity.IsInRoles(

30: portalSettings.ActiveTab.AuthorizedRoles) == false) {

31: Response.Redirect("~/Admin/AccessDenied.aspx");

32: }

33:

34: // Dynamically inject a signin login module into the top

35: // left-hand corner of the home page if the

36: // client is not yet authenticated

37: if ((Request.IsAuthenticated == false)

38: && (portalSettings.ActiveTab.TabIndex == 0)) {

39: LeftPane.Controls.Add(

40: Page.LoadControl("~/DesktopModules/SignIn.ascx"));

41: LeftPane.Visible = true;

42: }

43:

4 2 8 A d v a n c e d C # P r o g r a m m i n g

Page 456: Advanced c sharp programming

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 2 9

44: // Dynamically Populate the Left, Center and Right

45: // pane sections of the portal page

46: if (portalSettings.ActiveTab.Modules.Count > 0) {

47:

48: // Loop through each entry in the configuration system for this tab

49: foreach (ModuleSettings _moduleSettings in

50: portalSettings.ActiveTab.Modules) {

51:

52: Control parent = Page.FindControl(_moduleSettings.PaneName);

53:

54: // If no caching is specified, create the user control

55: // instance and dynamically

56: // inject it into the page. Otherwise, create a cached

57: // module instance that

58: // may or may not optionally inject the module into the tree

59:

60: if ((_moduleSettings.CacheTime) == 0) {

61:

62: PortalModuleControl portalModule =

63: (PortalModuleControl) Page.LoadControl(

64: _moduleSettings.DesktopSrc);

65:

66: portalModule.PortalId = portalSettings.PortalId;

67: portalModule.ModuleConfiguration = _moduleSettings;

68:

69: parent.Controls.Add(portalModule);

70: }

71: else {

72:

73: CachedPortalModuleControl portalModule =

74: new CachedPortalModuleControl();

75:

76: portalModule.PortalId = portalSettings.PortalId;

77: portalModule.ModuleConfiguration = _moduleSettings;

78:

79: parent.Controls.Add(portalModule);

80: }

81:

82: // Dynamically inject separator break between portal modules

82: parent.Controls.Add(new LiteralControl("<" + "br" + ">"));

83: parent.Visible = true;

84: }

85: }

86: }

Page 457: Advanced c sharp programming

The default Web page, DesktopDefault.aspx, has a very simple visual design, as shown inFigure 13-13. Essentially, the DesktopDefault.aspx Web page is composed of an instance ofthe DesktopPortalBanner.ascx UserControl and an HTML table. The rest of what you see(refer to Figure 13-1) in the portal is incorporated dynamically in the Page_Init event handler.(In fact, Listing 13-8 comprises all of the user-defined code for the DesktopDefault.aspxpage, too.) Simplicity of design and flexibility of employing UserControls dynamically arebenefits of programming with ASP.NET.

The user-defined code starts on lines 25 and 26 of Listing 13-8. Lines 25 and 26 obtain alocal copy of the PortalSettings object we created in the BeginRequest event handler in theglobal.asax file. Lines 29 through 32 employ role-based security to determine whether wehave permission to view the page indicated as the ActiveTab. If we don’t, we are redirectedto the AccessDenied.aspx page on line 31. (The tilde (~) indicates that the URL is relative tothe root of the application. If you look in the admin folder in the project, you can view thedesign of the AccessDenied page.)

4 3 0 A d v a n c e d C # P r o g r a m m i n g

Figure 13-13 The main page of the IBuySpy portal has a very basic design that includes aUserControl and an HTML table.

Page 458: Advanced c sharp programming

If the user has not been authenticated, the SignIn.ascx control is added to the top-left pane(LeftPane) of the HTML table, as shown in Figure 13-1. Because of the way the portal isdesigned, The SignIn control will show up every time the DesktopDefault.aspx page isshown and we have not signed in.

Finally, the remainder of the code on lines 46 through 86 of Listing 13-8 iteratesthrough all of the modules—these are dynamic controls associated with each tab, read from thedatabase—and places these modules on the various data cells (called panes in the application).The PaneName is stored in the PortalSettings.ActiveTab.Modules collection—see line 49,and the ModuleSettings object has the PaneName property. Page.FindControl (ModuleSettings.PaneName) is used to locate a specific User Control on one of the HTML data cells on thepage (see line 52).

TIPKeep in mind that “module” is a term local to the IBuySpy portal. The portal uses “module” when referringto UserControls that make up the portal dynamic interface.

The pane name is resolved on line 52 of Listing 13-8. If the module has not been cached—line 60—then we dynamically load the module with Page.LoadControl, set the PortalId andModuleSettings, and finish up by adding the module to the page’s Controls collection.(These steps occur on lines 62 through 69.)

If the module has been cached, then an instance of the portal-defined CachedPortalModuleControl is created, initialized, and added to the page’s Controls collection. For moreinformation on caching, refer to Chapter 15.

After each module is added, a line separator—the <BR> tag—is inserted into the pagedynamically. Line 82 of Listing 13-8 demonstrates how to dynamically insert a LiteralControlinto a Web page. (Refer to Chapter 14 for more information on creating custom servercontrols.) The loop continues until all modules have been added to the dynamic page. Theresult of dynamic page creation for the Home tab can be seen in Figure 13-1.

Reviewing the Portal DesignClearly, there is a lot more going on in the portal than the simple design of theDesktopDefault.aspx page and the code in Listing 13-8. Knowing this is essential tounderstanding the elegance of the portal design.

The are a relatively few number of IBuySpy pages, yet the user experience is very rich.The key is that the Web pages are fairly generic, and it is the UserControls, referred to asmodules, that make up the rich and dynamic nature of the IBuySpy portal. This is as it shouldbe. Essentially, the developers of the portal are demonstrating best practices that are consistentwith good OOP practices; that is, make the individual elements self-responsible through theclass idiom. For example, the Sign-In module is an independent class that knows aboutaccepting user names and passwords and authentication. Consequently, the modules can beplugged in anywhere they are needed—in effect, reused.

A significant amount of what you see in the portal is a combination of UserControls andcascading styles. Refer to Chapter 14 for more information on creating the UserControls thatmake up the rich and dynamic client experience.

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 3 1

Page 459: Advanced c sharp programming

Secondary TopicsThe IBuySpy is a diverse playground for exploring .NET technologies. We have demonstratedmany of the facets of .NET used to create this rich Web portal application, and I would liketo take this opportunity to showcase some of the supporting cast of features that you canincorporate into your own projects or that will make your interaction with the portal asuccessful experience.

Administrating the PortalThe IBuySpy portal supports dynamic administration. As shown in Figure 13-1, the portalsupports user authentication. The kind of security exemplified is role-based security managedby the portal itself. You can add new users with the sign-in module or log in with user=guestand password=guest. (Refer to Chapter 16 for more information on portal and .NET security.)

If you sign in with the default administrative password, “guest,” or any other administrativepassword, the portal will display an Admin tab and additional administrative links. The Admintab and administrative links allow non-programmers to customize the portal (see Figure 13-14).More important, these features provide a clear path (with some exploration) that demonstrateshow you can implement a Web application that supports management and extensibility by aremote user, even non-programmers.

4 3 2 A d v a n c e d C # P r o g r a m m i n g

Figure 13-14 Admin tab and administrative links that support the dynamic and remotecustomizations

Page 460: Advanced c sharp programming

C h a p t e r 1 3 : I B u y S p y a n d D y n a m i c U s e r I n t e r f a c e s i n A S P . N E T 4 3 3

TIPRemember that the IBuySpy portal represents an intranet application for corporate employees.

For example, notice that the portal shown in Figure 13-14 contains an Example Image:this week’s special, the Document Transport System. Knowing that there was a significantsurplus of Night Vision goggles, a portal administrative user elects to make the surplusgoggles this week’s special. By logging in as an administrator and clicking the Edit link by“This Week’s Special” (see Figure 13-14), the administrative user can quickly update theweekly special reminder directly from the portal.

To modify the portal, click the aforementioned edit link. Modify the text and <img src>tag to refer to the content representing a new promotion for Night Vision Goggles. (SeeFigure 13-15 for an example of the Administrative management facilities demonstrated inIBuySpy.) Click the Update link when you have finished, and the revisions will be made. Ifyou look at the initial page content and the revised content in Figure 13-15, you will noticethat I updated some of the text content and the image link. (Being a conscientious manager, Iwould, of course, go look up the productID, too.)

Figure 13-15 Update the portal dynamically using the built-in administrative capabilities.

Page 461: Advanced c sharp programming

4 3 4 A d v a n c e d C # P r o g r a m m i n g

NOTENotice that clicking the hyperlink for the product information takes us to the IBuySpy store. The store representsa separate application. I asked Susan Warren, and she said that the portal was the more interesting of the twoapplications for developers. You can check out the IBuySpy store online at www.ibuyspy.com.

Debugging the IBUYSPY PortalWhen you download the Portal, you will not be immediately able to step through theapplication. The portal code online was modified slightly to support a product-ready state.The portal installs and sets up very smoothly, but you will need to make one additionalchange to debug the portal.

To debug the IBuySpy portal code in Visual Studio .NET, you will need to open theweb.config file, find the <compilation debug=false> tag, and change the value to True.(The new value will be <compilation debug=true>.) After the change, you will be able toeasily explore and debug all of the portal code.

Introducing Mobile ModulesThe IBuySpy portal demonstrates how to use Microsoft’s Mobile Internet Toolkit to leveragethe same core code for Web-enabled mobile devices like WAP phones and PDAs. If youdownload and install the Microsoft Mobile Internet Toolkit (MMIT) from www.microsoft.com,you will be able to experiment with the pages for mobile devices that ship with the portal code.

MMIT is beyond the scope of this book, but it is worth repeating that a well-architectedapplication will allow you to leverage the same basic object-oriented code for mobile deviceswith .NET. Separate the business objects from the interface and the mobile toolkit will allowyou to reuse the same code for your mobile application development. (My book .NET MobileApplication Development (Wiley), due out later this year, will explore the Microsoft MobileInternet Toolkit and .NET in depth.)

SummaryThe IBuySpy portal is an excellent resource for developers trying to get the most out ofASP.NET and C# as early as possible. Microsoft has provided this portal application andmade the code available for learning or outright use by developers.

In addition to gaining an understanding of aspects of .NET by exploring the IBuySpyportal, in this chapter you learned more about ADO.NET, T-SQL, stored procedures, creatingdynamic user interfaces, cascading style sheets, the DataList control, and managing applicationevents in the global.asax module.

Chapters 14, 15, and 16 explore Web UserControls, caching, and security as they areemployed in the IBuySpy portal. Borrowing from the best practices made available to us byMicrosoft and Susan Warren, you will be building scalable, dynamic enterprise applicationsin ASP.NET and C# right out of the gate.

Page 462: Advanced c sharp programming

CHAPTER

14Creating Custom

Web Controls

435

IN THIS CHAPTER:

Demonstrated Topics

Rendering Controls Dynamically

Reviewing the PortalModuleControl Base Control

Binding the Tabs Data

Visually Designing the Sign-In Module

Creating the Image Module

Creating the Links Module

Implementing the XML/XSL Transform Module

Creating a Custom Portal Module

Secondary Topics

Using the HttpServerUtility

Using the Repeater Control

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 463: Advanced c sharp programming

Controls are simply classes with a visual aspect. From this perspective, a control isat least as powerful as a class, and their visual representation makes controls morepowerful than the average class. As powerful as visual classes are, it is just as

important to design your application in an object-oriented way when designing the businessobjects as well as the graphical user interface. This is the approach the IBuySpy portal takes.

In the IBuySpy portal, controls are central to the implementation of the portal. TheIBuySpy portal implements a common UserControl that is the base class for many of themodules in the portal application. This chapter will explore the user controls—referred to as“modules”—in the IBuySpy portal and demonstrate how to extend the portal applicationconsistent with the architectural strategy used to build the IBuySpy portal.

Demonstrated TopicsThis chapter zooms in on the IBuySpy portal controls and the .NET classes used to implementthese controls. There are seven basic modules that ship with the Portal example, affording awide variety of dynamically generated Web pages. This chapter will describe inheriting fromand implementing a custom UserControl and will demonstrate how to

� Render controls dynamically using an HtmlWriter

� Generalize the PortalModuleControl

� Bind data to the DataList control

� Create the links module

� Use an XSL transform to format XML data, separating the data from the appearance ofthe data

The “Secondary Topics” section explores the HttpServerUtility class and introduces theRepeater control. The Repeater control aids us in presenting read-only data on a Web page.

Rendering Controls DynamicallyThe Render method is roughly the equivalent of a Window’s Form Paint method. Instead ofa GDI+ Graphics object, you get an HtmlTextWriter. The HtmlTextWriter represents theoutput stream for a server control as it is being rendered to a Web page. You can override theRender method in a custom server control to enhance the appearance of server controls oradd content as the control is being rendered.

The portal uses a Render method in a couple of distinct places. We’ll look at the portal’susage of the Render method after we spend a moment exploring how to create custom servercontrols.

4 3 6 A d v a n c e d C # P r o g r a m m i n g

Page 464: Advanced c sharp programming

Creating a Custom Server ControlYou can create a custom server control by creating a new class library and defining a newclass that inherits from System.Web.UI.Control. Override the Render method inherited fromControl, and you have a custom server control.

NOTECreating a custom server control from the Control class is the last thing you want to do. The reason is simple.You want to reuse as much code as possible and do the job of creating the control as efficiently as possible.Consequently, you will want to start with the control that provides as much of the functionality as possiblealready. If you need to create a compositional control, then the best starting place is the User Control. SusanWarren at Microsoft said that a control derived from the User Control is almost always the best choice. Thisperspective is reflected in the IBuySpy portal.

Listing 14-1 implements a custom user control that has one Text property and simplydisplays the value of the Text property. The HelloWorld control is a server control becauseit inherits from System.Web.UI.Control, and is a simple approximation of a Label.

Listing 14-1 A basic custom server control inherits from System.Web.UI.Control and overridesthe Render method.

1: using System;

2: using System.Web;

3: using System.Web.UI;

4:

5: namespace ServerControl

6: {

7: public class HelloWorld : Control

8: {

9: private string text;

10: public string Text

11: {

12: get{ return text; }

13: set{ text=value; }

14: }

15:

16: protected override void Render(HtmlTextWriter writer)

17: {

18: writer.RenderBeginTag("i");

19: writer.Write(Text);

20: writer.RenderEndTag();

21: }

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 3 7

Page 465: Advanced c sharp programming

22: }

23: }

Lines 2 and 3 of Listing 14-1 add using statements for namespaces containing code weare likely to need for any server control. Line 5 provides a namespace for the control, whichwe’ll need in a moment to register our user control. Line 7 demonstrates inheritance fromSystem.Web.UI.Control, making this simple class a server control. Lines 9 through 14introduce the single additional field and related property, and lines 16 through 21 implementthe overridden Render method.

Render receives a single HtmlTextWriter, representing our IO stream of HTML content.As the page is rendered, the various controls are processed. This is our chance to describe ourcontrol as HTML. The HtmlTextWriter.RenderBeginTag writes the tag brackets for us. Forexample, writer.RenderBeginTag(“i”) will add the italicize tag, <i>, to the stream. TheWrite method on line 19 of Listing 14-1 sends the literal text to the HTML stream, and line20 closes the previously opened tag. Assigning the HelloWorld.Text property the literal“Hello, World!”, the rendered HTML is <i>Hello, World!</i>, and the rendered output isHello, World!

You could employ variations on the Render method. For example, you could write theentire literal content to the stream as follows:

writer.Write("<i>Hello, World!</i>");

For very simple output this approach works well, but the number of tags and variety ofattributes can be difficult to manage as literal text.

There are a couple of dozen methods defined by the HtmlTextWriter for renderingHTML. You can render simple or complex compositional controls by defining a customserver control. You will find that it is much easier to create compositional controls using theUser Control, and new controls can more easily be contrived from an existing control than bystarting from scratch.

Incorporating the Custom Server Control into a Web PageIn the preceding section, we defined a custom control as a class defined in a DLL assembly.To incorporate the control into an ASP.NET application, we will need to reference the DLL,register the assembly, and include the control in the <body> of our ASP.NET Web page.

I created the HelloWorld control in the ServerControl.csproj, which is available fordownload at www.osborne.com (or www.softconcepts.com). I also created theConsumeServerControl.csproj. ConsumeServerControl is a Web application containing a singlepage that demonstrates how to register the control and add an instance of the server control tothe page. Listing 14-2 provides a complete sample Web page, providing an example of theHTML that shows the additional steps necessary to consume the custom control.

TIPThe Register statement is case sensitive.

4 3 8 A d v a n c e d C # P r o g r a m m i n g

Page 466: Advanced c sharp programming

Listing 14-2 Registering the custom control and adding a single instance to the otherwiseblank Web page

1: <%@ Page language="c#" Codebehind="WebForm1.aspx.cs"

AutoEventWireup="false" Inherits="ConsumerServerControl.WebForm1" %>

2: <%@ Register TagPrefix="ACSP" Namespace="ServerControl"

Assembly="ServerControl"%>

3: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

4: <HTML>

5: <HEAD>

6: <title>WebForm1</title>

7: <meta content="Microsoft Visual Studio 7.0" name="GENERATOR">

8: <meta content="C#" name="CODE_LANGUAGE">

9: <meta content="JavaScript" name="vs_defaultClientScript">

10: <meta content="http://schemas.microsoft.com/intellisense/ie5"

name="vs_targetSchema">

11: </HEAD>

12: <body MS_POSITIONING="GridLayout">

13: <ACSP:HelloWorld runat="server" id="HelloWorld1" Text="Hello,

World!" />

14: </body>

15: </HTML>

The boldfaced statements indicate the part of the code that I added. Line 2 of Listing 14-2implements the @Register statement. The @Register statement associates the alias with anactual namespace. In the example, the alias ACSP (Advanced C# Programming) is associatedwith the namespace ServerControl. TagPrefix defines the alias. Namespace provides thecomplete real namespace name, and Assembly provides the name of the assembly containingthe namespace. (If you recall, when we create a class library, by default the assembly andnamespace names are identical.)

Line 13 of Listing 14-2 demonstrates how to add an instance of the control to the page.The runat attribute indicates that this control should be processed on the server. The idattribute provides a unique name for the control. If we wanted to refer to this control in thecode-behind page, we would need to add a field in the code-behind page with the same nameas the one indicated in the id attribute. The Text attribute sets the value of the Text property.Listing 14-3 shows an example of a code-behind page that defines the HelloWorld1 field,allowing us to programmatically modify the HelloWorld control.

Listing 14-3 Adding a matching field in the code-behind page allows us to programmaticallyinteract with the custom control.

1: using System;

2: using System.Collections;

3: using System.ComponentModel;

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 3 9

Page 467: Advanced c sharp programming

4: using System.Data;

5: using System.Drawing;

6: using System.Web;

7: using System.Web.SessionState;

8: using System.Web.UI;

9: using System.Web.UI.WebControls;

10: using System.Web.UI.HtmlControls;

11:

12: namespace ConsumerServerControl

13: {

14: /// <summary>

15: /// Summary description for WebForm1.

16: /// </summary>

17: public class WebForm1 : System.Web.UI.Page

18: {

19:

20: protected ServerControl.HelloWorld HelloWorld1;

21: private void Page_Load(object sender, System.EventArgs e)

22: {

23: // Put user code to initialize the page here

24: }

25:

26: [Web Form Designer generated code]

27:

28: private void WebForm1_Init(object sender, System.EventArgs e)

29: {

30: HelloWorld1.Text = "Hello, World!";

31: }

32: }

33: }

The boldfaced case statement on line 20 of Listing 14-3 contains the field statement Iadded to facilitate referring to the HelloWorld1 control programmatically. For example, line30 initializes the HelloWorld1.Text property each time the page is initialized.

As defined, our control will always be initialized to have the “Hello, World!” text. Thisbehavior is consistent with a static text Label control. What if we wanted to create a controlthat was modified by the user and maintain its value when we post the control back to theserver? We would have to code behavior to allow a custom server control to save stateinformation between post-backs.

Saving Control StateFor a custom control to maintain state between page posts, we need to implement theIPostBackDataHandler in the custom control class. IPostBackDataHandler requires that we

4 4 0 A d v a n c e d C # P r o g r a m m i n g

Page 468: Advanced c sharp programming

implement LoadPostData and RaisePostDataChangedEvent methods. LoadPostData will letus read the value from the ViewState, and RaisePostDataChangedEvent gives us anopportunity to raise a changed event.

Retaining Control Value Between Form PostsSometimes you will want to retain state information between form posts and other times not.For example, you would more than likely want to save state information for the user nameportion of a login control, but not for the password information. That is, you would not wantto populate the password field on post-back and thereby possibly allow someone else toborrow another user’s password.

To support this dynamic relationship, we could implement a custom server control thatdisplays and supports post-backs for the user name but not the password. Combining statictext with an <input> tag in a base control, we can implement the basic behavior that displaysa prompt and an input box. Listing 14-4 provides an implementation of a custom control anda generalized version of the custom control that implements the password behavior.(Remember the password does not support the post-back behavior.)

Listing 14-4 A base class that renders a prompt and input control, and a subclass thatimplements the password control

1: using System;

2: using System.Web;

3: using System.Web.UI;

4: using System.Web.UI.WebControls;

5: using System.Collections.Specialized;

6:

7: namespace ServerControl

8: {

9: public class InputBox : Control

10: {

11: private string fieldValue;

12: private string prompt;

13: private string type;

14:

15: public string FieldValue

16: {

17: get{ return fieldValue; }

18: set{ fieldValue = value; }

19: }

20:

21: public string Prompt

22: {

23: get{ return prompt; }

24: set{ prompt = value; }

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 4 1

Page 469: Advanced c sharp programming

4 4 2 A d v a n c e d C # P r o g r a m m i n g

25: }

26:

27: public string Type

28: {

29: get{ return type; }

30: set{ type = value; }

31: }

32:

33: protected override void Render(HtmlTextWriter writer)

34: {

35: writer.AddStyleAttribute("width", "125");

36: writer.RenderBeginTag("b");

37: writer.Write(prompt);

38: writer.RenderEndTag();

39: writer.AddAttribute("type", type);

40: writer.AddAttribute("value", fieldValue);

41: writer.AddAttribute("Name", UniqueID);

42: writer.AddStyleAttribute("width", "125");

43: writer.RenderBeginTag("input");

44: writer.RenderEndTag();

45: }

46: }

47:

48: public class PasswordText: InputBox

49: {

50: public PasswordText() : base()

51: {

52: Prompt = "Password: ";

53: Type = "password";

54: }

55: }

56: }

Lines 9 through 46 of Listing 14-4 implement the base class, InputBox. InputBox allows theuser to express the text value of the prompt and the type of the input tag. The Prompt propertywill be the value displayed in the label, and the type will be used as a Type attribute for the<input> tag. (Initialize Type with “text” and we get a basic HTML text box. Initialize Typewith “password” and we get a basic password textbox, which masks the input characters.)

Lines 48 through 54 of Listing 14-4 implement the PasswordText custom control.PasswordText provides a simple constructor that initializes the control to have a prompt and<input> tag type of “password”—all suitable for a control used to enter passwords. With thePasswordText control, we don’t want to post the value of the PasswordText control back to

Page 470: Advanced c sharp programming

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 4 3

the form. On the other hand, it would be appropriate to post back a non-password value, likea user name.

Posting Form DataListing 14-5 demonstrates how to implement the IPostBackEventHandler. TheIPostBackEventHandler interface is used to implement the UserNameText control.UserNameText generalizes InputBox and realizes the IPostBackDataHandler.

Listing 14-5 Supporting form post-back state retention

1: public class UserNameText: InputBox, IPostBackDataHandler

2: {

3: public UserNameText() : base()

4: {

5: Prompt = "User Name: ";

6: Type = "text";

7: }

8:

9: public virtual bool LoadPostData(string key,

10: NameValueCollection values)

11: {

12: if (FieldValue == null || !FieldValue.Equals(values[key]))

13: {

14: FieldValue = values[key];

15: return true;

16: }

17: return false;

18: }

19:

20: public virtual void RaisePostDataChangedEvent(){}

21: }

Listing 14-5 implements the IPostBackDataHandler.LoadPostData method. TheNameValueCollection class is defined in the System.Collections.Specialized namespace.The NameValueCollection represents all of the incoming names and associated values whena page is posted back. The code on lines 9 through 18 obtains this value and assigns it to theFieldValue property if the form’s value is different from the object’s value.

The RaisePostDataChangedEvent can be used to call a method that raises an event indicatingthat the value has changed. If you need to use this aspect of the IPostBackDataHandler, add anevent statement to the class and implement a method with an On prefix that raises the event, forexample, OnDataChanged. Then, call OnDataChanged from the RaisePostDataChangedEventmethod, calling the event handler through the event added to the control class.

Page 471: Advanced c sharp programming

4 4 4 A d v a n c e d C # P r o g r a m m i n g

Creating a Composite Custom ControlA composite control is a custom control composed of multiple child controls. An excellentway to build composite controls is to inherit from and add controls to the UserControl class.However, you can create a composite control programmatically by inheriting from theSystem.Web.UI.Control class, realizing the INamingContainer interface and overriding theCreateChildControls method.

The INamingContainer interface does not require you to implement any members. What itdoes for you is ensure that child controls have unique names on a page, even if you use morethan one instance of the composite control on the same page.

The CreateChildControls method adds all of the child controls that make up the compositeto the custom control’s Controls collection. To demonstrate a composite control we can makea new custom control from the UserNameText and PasswordText custom controls. Becauseboth of these controls already know how to render themselves, our composite control doesnot need to implement a Render method. Listing 14-6 implements the custom server controlthat I named SignIn.

Listing 14-6 Creating a composite custom server control, SignIn, comprised of two nested childcontrols

1: public class SignIn : Control, INamingContainer

2: {

3: private UserNameText GetUserNameTextBox()

4: {

5: return (UserNameText)Controls[1];

6: }

7:

8: private PasswordText GetPasswordText()

9: {

10: return (PasswordText)Controls[3];

11: }

12:

13: public string UserName

14: {

15: get{ return GetUserNameTextBox().FieldValue; }

16: set{ GetUserNameTextBox().FieldValue = value; }

17: }

18:

19: public string Password

20: {

21: get{ return GetPasswordText().FieldValue; }

22: set{ GetPasswordText().FieldValue = value; }

23: }

Page 472: Advanced c sharp programming

24:

25: protected override void CreateChildControls()

26: {

27: Controls.Add(new LiteralControl("<hr>"));

28: Controls.Add(new UserNameText());

29: Controls.Add(new LiteralControl("<br>"));

30: Controls.Add(new PasswordText());

31: }

32: }

As far as classes go, SignIn is not very complex. Inheriting from System.Web.UI.Controland overriding CreateChildControl makes it a custom server control, and adding more thanone child control makes it a composite control. Throwing in the INamingContainer interfacemanages unique names for the child controls. Figure 14-1 shows the SignIn control renderedon a Web page.

The composite control is incorporated in the Web page in the same way as every othercustom control. Add a Register directive at the top of the page and include the tag referenceto the composite control. (Refer to Listing 14-2 for a syntactical example, demonstrating howto add custom controls.)

The composite custom server control SignIn exemplifies an important strategic wayto program. By slowly building up controls—beginning with the InputBox—the compositecontrol was very easy to create. The same strategy is used to create a beautiful finish onfurniture or compose complex symphonies. Several light applications yield a finish onfurniture that appears to have depth. Complex symphonies begin with a few simple notes,

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 4 5

Figure 14-1 The rendered custom server control, SignIn

Page 473: Advanced c sharp programming

4 4 6 A d v a n c e d C # P r o g r a m m i n g

layering in complexity and instruments as the piece progresses. The same is true of software.Layering in complexity and adding changes in small increments is simpler than trying tomanage code created in monolithic leaps and bounds.

Using the WebControl Library Project TemplateThe System.Web.UI.WebControls.WebControl class is a good starting place for creatingcustom server controls. The WebControl class contains properties for managing theappearance of a server control, public events for responding to interaction with the control,and methods that you might expect to find in a server control. For example, WebControlintroduces a DataBind method that can be called to bind a server control to a data source.You will find that many Web controls are derived from the WebControl class, which extractsout the behavior common to most Web controls.

If you want to customize the rendering behavior of a control derived from the WebControl,then override the RenderContent method.

When you want to create a custom server control, create a new class library containing aclass that inherits from the WebControl class. You can easily accomplish this by selecting theWeb Control Library template from the New Project dialog. In general, consider theseguidelines when creating new Web controls:

� Implement a new Web control by inheriting from the UserControl. (This approachshould work in most cases.)

� If you need a finer degree of control, consider using the WebControl.

� If you want to start from scratch, inherit from the Control class.

Reviewing Custom Controls in the PortalThe only control that derives directly from System.Web.UI.Control in the portal is theCachedPortalModuleControl. This is consistent with the idea that you can inherit from aless-general control in many instances. The CachedPortalModuleControl is defined tosupport caching of modules in the IBuySpy portal. As this control emphasizes caching, wewill defer exploration of the CachedPortalModuleControl until the thorough treatment ofcaching in Chapter 15.

Reviewing the PortalModuleControl Base ControlThe specific use of the term “module” in reference to IBuySpy refers to UserControlsthat are derived from the PortalModuleControl, defined in the IBuySpy portal. ThePortalModuleControl is a UserControl, defined in the Components\DesktopControls.cs file,that defines portal-specific properties that are used to manage module presentation.

Page 474: Advanced c sharp programming

Exploring Base Control PropertiesThe PortalModuleControl introduces five public properties that every module will inherit.These properties are ModuleId, PortalId, IsEditable, ModuleConfiguration, and Settings. TheModuleId is a key value that is central to navigating the data in the Portal database. ThePortalId is used to identify specific instances of the portal. The IsEditable property is usedto determine whether modules can be modified. The ModuleConfiguration property returns aModuleSettings object that defines detailed information for a specific tab. The Settingsproperty returns a Hashtable containing specific module settings based on the ModuleId. Themodule settings are retrieved from PortalSettings.GetModuleSettings, which invokes theGetModuleSettings stored procedure. The GetModuleSettings stored procedure returnsinformation from the ModuleSettings table. For example, the ModuleSettings table containssetting names and values that point to external information, such as the image for “ThisWeek’s Special” found in the ImageModule.ascx control.

The benefit of sharing these properties in a base class is that they act as a common stringerthat provides the infrastructure for orchestrating existing and new modules on a Web pageadded to the portal application. We’ll return to these properties in the upcoming section“Creating a Custom Portal Module.”

Using Control AttributesThe PortalModuleControl applies two attributes to every property defined by the customUserControl: they are the BrowsableAttribute and the DesignerSerializationVisibilityAttribute.The BrowsableAttribute prevents a property from showing up in the Properties window. TheDesignerSerializationVisibilityAttribute is used to describe how a property is serialized. Youcan read more about these attributes in Chapter 10.

NOTEIn spoken languages, we support the notion of people specializing in linguistics. I consider myself anobject-oriented linguist. By exploring common idioms in various languages, I have discovered that it helpsto understand how aspects of language work by finding related idioms in other languages. Borland has anexcellent language, Delphi. The Delphi language introduces the concept of the published access modifier.Properties marked as published in Delphi show up in Delphi’s properties window. C# does not support apublished access modifier; instead, C# uses attributes to accomplish the same thing. Public properties willshow in the Properties window of Visual Studio .NET unless the Browsable Attribute is initialized with False.

Binding the Tabs DataThe DesktopPortalBanner module uses a DataList to display the tabs for the portal. TheDataList.DataSource property can be bound to any object that implements the System.Collections.IEnumerable interface.

The DesktopPortalBanner iterates over the PortalSettings.DesktopTabs and determineswhether a specific tab is supported by the user’s role. If the tab has features supported by the

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 4 7

Page 475: Advanced c sharp programming

4 4 8 A d v a n c e d C # P r o g r a m m i n g

user’s role, the object representing the tab details are added to an ArrayList. The ArrayListcontaining the tab details is assigned to the DataList.DataSource property, and theDataList.DataBind method is called. This aspect of the IBuySpy portal can be explored in thePage_Load event for the DesktopPortalBanner module (see Listing 14-7).

Listing 14-7 An example that binds an ArrayList to a DataList control, dynamically creating theDesktopPortalBanner tabs

1: private void Page_Load(object sender, System.EventArgs e)

2: {

3: // Obtain PortalSettings from Current Context

4: PortalSettings portalSettings = (PortalSettings)

5: HttpContext.Current.Items["PortalSettings"];

6:

7: // Dynamically Populate the Portal Site Name

8: siteName.Text = portalSettings.PortalName;

9:

10: // If user logged in, customize welcome message

11: if (Request.IsAuthenticated == true) {

12:

13: WelcomeMessage.Text = "Welcome " +

14: Context.User.Identity.Name + "! <"

15: + "span class=Accent" + ">|<" + "/span" + ">";

16:

17: // if authentication mode is Cookie, provide a logoff link

18: if (Context.User.Identity.AuthenticationType == "Forms") {

19: LogoffLink = "<" + "span class=\"Accent\">|</span>\n" +

20: "<" + "a href=" + Request.ApplicationPath

21: + "/Admin/Logoff.aspx class=SiteLink> Logoff" + "<" + "/a>";

22: }

23: }

24:

25: // Dynamically render portal tab strip

23: if (ShowTabs == true) {

24: tabIndex = portalSettings.ActiveTab.TabIndex;

25:

26: // Build list of tabs to be shown to user

27: ArrayList authorizedTabs = new ArrayList();

28: int addedTabs = 0;

29:

30: for (int i=0; i < portalSettings.DesktopTabs.Count; i++) {

31: TabStripDetails tab =

32: (TabStripDetails)portalSettings.DesktopTabs[i];

Page 476: Advanced c sharp programming

33:

34: if (PortalSecurity.IsInRoles(tab.AuthorizedRoles)) {

35: authorizedTabs.Add(tab);

36: }

37:

38: if (addedTabs == tabIndex) {

39: tabs.SelectedIndex = addedTabs;

40: }

41:

42: addedTabs++;

43: }

44:

45: // Populate Tab List at Top of the Page with authorized tabs

47: tabs.DataSource = authorizedTabs;

48: tabs.DataBind();

49: }

50: }

The PortalSettings are retrieved from the HttpContext on lines 4 and 5 of Listing 14-7.If the user is authenticated—lines 11 through 23—then a welcome message and a Logofflink is displayed. Lines 23 through 43 iterate through the possible tabs, examining the roleassociated with the tab and the role of the current user; if the tab and user roles coincide, thenthe tab is added to the ArrayList, authorizedTabs. Finally, authorizedTabs is assigned to theDataList’s DataSource property, and the DataBind method is called.

The DesktopPortalBanner demonstrates how you can use controls and control bindingto bind to collections of objects that are not part of ADO.NET. This means you can takeadvantage of ease-of-use ADO.NET without trying to force all objects to look like databaseobjects. (We’ll discuss the role-based security strategy employed in Listing 14-7 and theportal in Chapter 16.)

Visually Designing the SignIn ModuleAll modules in the IBuySpy portal use a basic approach to design. The controls and layoutare quite simple (see Figure 14-2). The fit and finish is created by applying the portal.csscascading style sheet and adding programmatic customizations.

The SignIn control is made up of three spans, two TextBox controls, one CheckBoxcontrol, an HTML table, and two images. Account Login, Email, and Password are definedusing the <span> tag. The <span> tag allows you to select a region of an HTML documentand wrap it in the tag. You may then apply attributes or a style sheet class to the controlsor text contained within the span. For example, the Account Login text is wrapped in thefollowing <span> tag.

<span class="SubSubHead" style="HEIGHT: 20px">Account Login</span>

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 4 9

Page 477: Advanced c sharp programming

4 5 0 A d v a n c e d C # P r o g r a m m i n g

The cascading style sheet class is SubSubHead, and the height of the span is 20 pixels. Youcan look in the portal.css for the style rule for the SubSubHead class.

NOTEThe Insert | Span menu item will not be enabled if the document’s targetSchema is set to Internet Explorer3.02/Navigator 3.0 in the Properties window. However, you can enter the <span> tag manually into theHTML text. You will get a warning indicating that it is not supported by the current schema.

The TextBox and CheckBox controls are ASP.NET Web Controls, and the sign-in andregister links are actually images that are associated with a hyperlink. The rendered SignIn.ascxcontrol is shown in Figure 14-3. The code for the Signin.ascx module is relatively short,demonstrating forms authentication and using cookies. We’ll defer a discussion on these topicsuntil Chapter 16.

Figure 14-2 The design-time view of the SignIn.ascx module

Page 478: Advanced c sharp programming

Creating the Image ModuleCustom controls do not have to be complex to be useful. Keeping custom controls short andsweet will actually allow greater reuse and create fewer defects. The ImageModule.ascx moduleexemplifies this notion. This very simple module is composed of the DesktopModuleTitle.ascx control added to a second user control with an ASP.NET image control. (The imagecontrol is defined in the System.Web.UI.WebControls namespace.)

Recall that in administration mode you can dynamically change the image referred to by thismodule. This humble module will eliminate the need to implement the layout of an image andrelated text more than just one time. Figure 14-4 shows the rendered ImageModule displayingthe title Night Vision and the related image. Listing 14-8 shows the Page_Load event handler,which is responsible for managing the width and height of the image.

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 5 1

Figure 14-3 The rendered SignIn.ascx module

Figure 14-4 The ImageModule manages a simple image and title and an edit link inadministration mode (not shown).

Page 479: Advanced c sharp programming

4 5 2 A d v a n c e d C # P r o g r a m m i n g

Listing 14-8 Code that manages the ImageModule’s image height and width

private void Page_Load(object sender, System.EventArgs e) {

String imageSrc = (String) Settings["src"];

String imageHeight = (String) Settings["height"];

String imageWidth = (String) Settings["width"];

// Set Image Source, Width and Height Properties

if ((imageSrc != null) && (imageSrc != "")) {

Image1.ImageUrl = imageSrc;

}

if ((imageWidth != null) && (imageWidth != "")) {

Image1.Width = Int32.Parse(imageWidth);

}

if ((imageHeight != null) && (imageHeight != "")) {

Image1.Height = Int32.Parse(imageHeight);

}

}

The dynamic image path, height, and width are read from the inherited Settings property. (Wediscussed the Settings property in the earlier section “Exploring Base Control Properties.”) TheSettings property is initialized by calling the PortalSettings.GetModuleSettings method. ThePortalSettings.GetModuleSettings method calls a stored procedure named GetModuleSettings,which reads the module settings, including the image source from the ModuleSettings table.In the default installation, there were no values for the height and width properties.

Creating the Links ModuleThe Links.ascx module is another example of a control that uses the DataList control. TheLinks module provides the hyperlinks, similar to those used to create the Quick Launch portionshown on the home page of the portal. (The Quick Launch section is actually supported by asimpler control named QuickLinks.ascx.) The Links module provides us with another modulethat uses a DataList. This time, we’ll examine how we can describe the presentation of theDataList data from the perspective of the DataBinder and script blocks in the HTML source.

NOTEThis section describes the Links.ascx control and code. The actual control used by default in the IBuySpyportal is the QuickLinks.ascx control. The two controls produce a similar result using slightly different designand coding techniques. You swap out the QuickLinks.ascx control with the Links.ascx control by modifyingthe Modules table. Change the ModuleDefID to 7 from 8 where Modules.ModuleID is 1. I picked theLinks.ascx module because it demonstrated more code than the revised QuickLinks.ascx module.

Page 480: Advanced c sharp programming

Coding Script Blocks in the HTML CodeThe DataList control, like the Repeater and DataGrid, is a template control. The presentationof the control is described by templates. Indicate the way a single element on the controlappears as a template tag, and all items displayed will be defined visually by the template.You can define these templates by writing the script by hand in the HTML editor, or youcan use the template editor in the designer. In this section, I will describe the ASP thatdefines the template for the Links.ascx module, and the next section will demonstrate howto use the template editor. The ASP code for the Links.ascx module DataList is shown inListing 14-9.

Listing 14-9 The DataList is described by providing ASP.NET code that describes how elementswill be displayed, as shown in the listing from the Links.ascx module.

1: <asp:DataList id="myDataList" CellPadding="4" Width="100%"

runat="server">

2: <ItemTemplate>

3: <SPAN class="Normal">

4: <asp:HyperLink id=editLink runat="server"

5: NavigateUrl='<%# "~/DesktopModules/EditLinks.aspx?ItemID=" +

DataBinder.Eval(Container.DataItem,"ItemID") + "&amp;mid=" + ModuleId

%>'

6: ImageUrl="<%# linkImage %>">

7: </asp:HyperLink>

8: <asp:HyperLink id=HyperLink1 runat="server" NavigateUrl='<%#

DataBinder.Eval(Container.DataItem,"Url") %>'

9: Target="_new" ToolTip='<%#

DataBinder.Eval(Container.DataItem,"Description") %>'

10: Text='<%# DataBinder.Eval(Container.DataItem,"Title") %>'>

11: </asp:HyperLink>

12: </SPAN>

13: </ItemTemplate>

14: </asp:DataList>

When you paint a DataList control from the Web Forms tab of the toolbox onto a UserControl of Web Form, you get the code shown on lines 1 through 14 of Listing 14-9. The<asp:DataList> tag describes the DataList. The description of the template is defined betweenlines 2 and 13. The Links.ascx module defines an ItemTemplate only. You can defineseparator, header, and footer templates in a manner similar to how the ItemTemplateis defined. (We’ll come back to this in a moment.)

The first thing that happens in the ItemTemplate is the inclusion of a <span>. Recall thatspan can be used as a convenient means of applying a cascading style sheet. The Normalclass from the portal.css file is applied to the elements between the span on lines 3 and 12.The remaining code on lines 4 through 11 define one element in the DataList.

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 5 3

Page 481: Advanced c sharp programming

The controls used to describe the appearance of a DataList element include two hyperlinks.The first HyperLink control is defined by lines 4 through 7, and the second is defined by lines 8through 11. The first link represents a link for editing the link itself, and the second actuallywill navigate the user to the described location.

Using NavigateUrlLine 5 of Listing 14-9 introduces the NavigateUrl property of the HyperLink control. Allof the code on line 5 creates an URL-encoded path. Recall that the first hyperlink refers to alocation that will allow an administrative-role user to edit the link itself. All of the code online 5 simply describes this path dynamically.

NavigateUrl='<%# "~/DesktopModules/EditLinks.aspx?ItemID=" +

DataBinder.Eval(Container.DataItem,"ItemID") + "&amp;mid=" + ModuleId

%>'

The NavigateUrl will be ~/DesktopModules/EditLinks.aspx?ItemID=value&mid=value.The tilde (~) is the root-relative indicator, which means that DesktopModules is a folderrelative to the root of the Web site. EditLinks.aspx is an ASP.NET Web page, and ItemIDand mid are names in the URL-encoded string. The actual ItemID is dynamically read fromthe DataSource associated with the DataList, and ModuleId is inherited from thePortalModuleControl class.

The second HyperLink control uses a similar technique to dynamically associate an URLwith the HyperLink.NavigateUrl property. The first link navigates to the dynamic URLrepresenting an edit page for this control if the logged-in user is an administrator, or an errorpage, if the user has not been authenticated. The second link navigates to the dynamicallyassociated Web address.

Using Block ScriptThe dynamic code is defined in script blocks denoted by <% and %>. For example, the firstHyperLink control is visually represented by the value linkImage defined in the script blockfor the HyperLink.ImageUrl property. From line 6 of Listing 14-9,

ImageUrl="<%# linkImage %>">

we set the ImageUrl of the first HyperLink to one of ~/images/edit.gif or ~/images/ navlink.gif, depending on whether the PortalModuleControl.IsEditable inherited property is True orFalse. This logic occurs in the Page_Load event for the Links.ascx module, which modifiesthe linkImage field.

The second HyperLink control displays a Tooltip and a Text value for the link. (The firstlink is named editLink, and the second link is named HyperLink1—lines 4 and 8 of Listing14-9, respectively.) The second HyperLink control navigates to the value for the Url field ofthe DataSource. Outside of the script block for the second HyperLink, we find the Targetattribute _new on line 9, indicating that a new page will be opened. The second script blockfor the second HyperLink begins on line 9. This script block reads a Description from theDataSource, assigning this value to the HyperLink.ToolTip property; and the final script

4 5 4 A d v a n c e d C # P r o g r a m m i n g

Page 482: Advanced c sharp programming

block, beginning on line 10, assigns the DataSource’s Title value to the HyperLink.Textproperty. The last script block represents the display value of the HyperLink.

When combined, the two HyperLinks will create a blue arrow image, which navigates toan error message screen, for non-authenticated users, or creates a pencil that navigates to anedit screen, for authenticated administrators. The second part of the link will display a titlefor the link and an actual target URL. For example, when you aren’t signed in, you will seea blue arrow graphic and the text “ASP.NET Site.” Click the ASP.NET Site text and yourbrowser will attempt to navigate to the www.asp.net website.

Employing the DataBinderThe DataBinder class is used to evaluate and parse data binding expressions. The staticmethod DataBinder.Eval (shown in Listing 14-9) is used to dynamically bind to fields in theDataList.DataSource property. There are two versions of the static Eval method. The firstversion of Eval allows you to express the source of the data and the field name of the data.For example, Container.DataItem represents the DataList.DataListItem in the DataList, andItemID indicates the ItemID field.

The second form of DataBinder.Eval is a string representing a formatting expression. Forexample, if we were displaying values that represented dollar amounts, we might write

DataBinder(Container.DataItem, "Amount", "{0:c}")

where “Amount” is the name of a field in the DataSource and “{0:c}” is the actual formatstring for displaying values as currency. You can try the following statement in a Consoleapplication to experiment with formatting strings:

Console.WriteLine("{0:c}", 13567);

The preceding statement will write $13,567.00 to the console. You can use the sameformatting expressions in DataBinder.Eval statements that you would use for Windows orConsole applications.

If you encounter a DataBinder statement in script, then you can expect to find a relatedDataSource and field in the code-behind module. You can find the DataSource code for theLinks.ascx module in the Page_Load event handler in the code-behind module. The nextfragment shows the code that provides the dynamic DataSource for the Links.ascx module’sDataList.

ASPNetPortal.LinkDB links = new ASPNetPortal.LinkDB();

myDataList.DataSource = links.GetLinks(ModuleId);

myDataList.DataBind();

The LinksDB class is referred to as a façade pattern. The façade pattern provides asimplified interface for performing operations that constitute part of the business rules of anapplication. You can find the code for the Links.ascx module in LinksDB.cs. LinkDB.GetLinksreturns a SQLDataReader, which can be bound directly to the DataList, as demonstrated.

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 5 5

Page 483: Advanced c sharp programming

Creating Script Blocks Using the Template EditorAn alternate way to modify the templates of a DataList is to use the Template Editor in thedesigner. The Template Editor allows you to drag and drop controls directly into the designer,as shown in Figure 14-5, and to visually modify the appearance of the controls that make up thetemplate by changing values in the Properties window.

To modify a template visually, right-click over the control and select Edit Template and theparticular template region you wish to modify. When you have finished, display the contextmenu again and select End Template Editing. For example, you could add a horizontal rulebetween DataList elements by editing the SeparatorTemplate and dragging the horizontal rulecontrol into the field representing the SeparatorTemplate template. The visual appearance of theSeparatorTemplate is controlled by the SeparatorTemplateStyle tag. (You can modify this valuein the HTML editor or the Properties window.)

4 5 6 A d v a n c e d C # P r o g r a m m i n g

Figure 14-5 The Template Editor can be used to visually design control templates.

Page 484: Advanced c sharp programming

Implementing the XML/XSL Transform ModuleXML contains self-describing data. You can apply an XSL/T transform style sheet, separatingdata from the format. By changing the XSL/T sheet, you can change the appearance of the datawithout changing the data in the XML document. XSL/T (or XSLT) is a practical alternative tousing cascading style sheets.

The IBuySpy portal demonstrates XML and XSLT in the XmlModule control. Oneexample demonstrating the XmlModule can be found by default in the lower-right corner ofthe home page containing the title Top Movers. The XmlModule module is created using theDesktopModuleTitle.ascx control and the Xml control found on the Web Forms tab of thetoolbox.

The Xml control makes it easy to indicate an XML document and an XSLT document.Specify the Xml.DocumentSource property and the Xml control will display the contentsof the XML document. Include an XSL/T file for the Xml.TransformSource property andthe XML document will be displayed in the formatted style consistent with the transformdescribed in the .xsl file. Listing 14-10 contains the sample XML file for the Top Moversdata, and Listing 14-11 contains the XSL transform file applied to create the layout shownin Figure 14-6.

Reviewing the XML DocumentListing 14-10 contains the XML document representing revenue trends for the IBuySpy.comstore. I will review the format of an XML file, which is pretty straightforward, after Listing 14-10.

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 5 7

Figure 14-6 The Top Movers section created using an XML document containing data and anXSL/T document describing the appearance

Page 485: Advanced c sharp programming

Listing 14-10 The sales.xml document, representing revenue trends in the IBuySpy.com store

1: <?xml version='1.0'?>

2: <sales xmlns:HTML="http://www.w3.org/Profiles/XHTML-transitional">

3: <product id='Travel'>

4: <revenue>60</revenue>

5: <growth>8</growth>

6: </product>

7: <product id='Communications'>

8: <revenue>45</revenue>

9: <growth>-7.8</growth>

10: </product>

11: <product id='Deception'>

12: <revenue>10</revenue>

13: <growth>9</growth>

14: </product>

15: <product id='Munitions'>

16: <revenue>4</revenue>

17: <growth>-3</growth>

18: </product>

19: </sales>

The XML document is consistent with data that might be returned by a Web Service, forexample. The first statement on line 1 of Listing 14-10 is the XML declaration. It contains amandatory version number and optional character encoding information. The second statementon line 2 defines a namespace, uniquely identifying the sales element with an xmlns (XMLnamespace) statement. The namespace tag uniquely identifies the sales tag in the event thatanother sales tag existed but was intended to convey some other meaning. The rest of thedocument in Listing 14-10 describes named product objects containing revenue and growthvalues. Each product tag represents one product object.

This data formatted as XML could have originated from a DataSet or a collection or arrayof product objects. We could use a text editor to create such a document or any tool or programthat is capable of exporting to an XML document.

XML is used as the underlying framework for persisting data, including data represented byADO.NET objects. For this reason, non-traditional kinds of data can be used as data sources.ADO.NET or user-defined objects can be serialized to and from XML. For example, you cansave the view state information of an object by using an XmlSerializer and reassembling theobject from XML. (Refer to the Emitter.sln in Chapter 11 for an example that demonstratesbuilding a DataSet from an XML document.)

Note that I did not make any mention of the presentation of the XML defined in Listing14-10. That is the role of the XSL/T document.

4 5 8 A d v a n c e d C # P r o g r a m m i n g

Page 486: Advanced c sharp programming

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 5 9

Reviewing the XSL DocumentThe XSL/T document supports separating document data from data presentation. The XMLdocument contains the data, and the XSL/T document contains the transform describing howthe data should be selected and presented. Listing 14-11 contains the sales.xsl document forthe XmlModule.ascx control, followed by a brief synopsis.

Listing 14-11 The sales.xsl XSL/T document describes the presentation of the sales.xml data,separating content from presentation.

1: <xsl:stylesheet version='1.0'2: xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>

3: <xsl:template match="/">4: <table width="210" border="1pt" cellspacing="0" cellpadding="3"5: bordercolor="#dddddd" style="border-collapse:collapse;">6: <tr>7: <th align="left">Product <br/>Category</th>8: <th>Revenue (Millions)</th>9: <th>Growth</th>10: </tr>11: <xsl:for-each select='sales/product'>12: <tr>13: <td class="Normal" width="100">14: <i><xsl:value-of select='@id'/></i>15: </td>16: <td class="Normal">17: <CENTER>18: <xsl:value-of select='revenue'/>19: </CENTER>20: </td>21: <td class="Normal">22: <xsl:if test='growth &lt; 0'>23: <xsl:attribute name='style'>24: <xsl:text>color:red</xsl:text>25: </xsl:attribute>26: </xsl:if>27: <CENTER>28: <xsl:value-of select='growth'/>29: </CENTER>30: </td>31: </tr>32: </xsl:for-each>33: </table>34: </xsl:template>35: </xsl:stylesheet>

Page 487: Advanced c sharp programming

NOTEXML and XSL are complete technologies incorporated in, yet independent of, .NET. You are encouraged toacquire at least one whole book on XML and XSL/T if you will be creating custom documents extensively. TheXSLT Developer’s Guide in the Visual Studio .NET at ms-help://MS.VSCC/MS.MSDNVS/xmlsdk30/htm/xmconxsldevelopersguide.htm is a good starter resource for Microsoft XML 3.0.

The XSL/T document creates a new XML tree independent of the XML data. Many ofthe tags used in our document are recognizable HTML tags, such as <table>, <th>, and <td>.Other elements are unique to XSL/T documents. For example, the for-each select beginningon line 11 and ending on line 32 of Listing 14-11 indicates that all of the rows in the XMLdocument representing product objects—where sales/product represents the namespace salesand product represents the product items—should be returned. The values selected are therevenue and growth values, as indicated on lines 18 and 28, respectively. And, if the growthnumber is negative—represented by the if test on line 22—then the text will be displayedusing a red font.

As with any language, simple elements of XSL/T like for-each select and if teststatements help provide tremendous power and flexibility. For this reason, you areencouraged to acquire a book that that will serve as a reference source for XML.McGraw-Hill/ Osborne has an excellent book, XML: The Complete Reference by HeatherWilliamson. We can perform a couple of additional transforms on the XML document, whichI will briefly describe next.

Sorting XMLWe can apply a sort transform to order our XML data. Adding the following XSL/T tosales.xsl shown in Listing 14-11 after line 11 will sort based on the growth value.

<xsl:sort select='growth' data-type='number' order='descending' />

By default, the value is treated as a string. By adding the data-type=‘number’ attribute, thegrowth value will be treated as a numeric type, yielding the correct result. The select=‘growth’attribute indicates the column we are sorting on, and the order attribute indicates the order.Replace ‘descending’ with ‘ascending’ to reverse the sort order.

Selecting Data ConditionallyAnother option we might be interested in is to select only a specific number of elements orthose that pass a set of minimum criteria. For example, we may only be interested in positivegrowth when presenting to investors, but the sales people should still also know what needsimprovement. We could modify the sales.xsl document, inserting <xsl:if test=‘growth &gt;0’> after line 11 and </xsl:if> before line 32 of Listing 14-11. The end result is that we wouldget only the positive top movers displayed without changing the sales.xml file.

Loading the XML and XSL Documents with the Xml ControlThe XmlModule.ascx control is initialized in the Page_Load handler. Both the values for the.xml and .xsl files are read from the PortalModuleControl.Settings inherited property. The

4 6 0 A d v a n c e d C # P r o g r a m m i n g

Page 488: Advanced c sharp programming

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 6 1

PortalModuleControl.Settings property returns a Hashtable. The Settings Hashtable isinitialized by the PortalSettings.GetModuleSettings method. The actual XML and XSLdocuments are read by invoking the GetModuleSettings stored procedure, which reads thevalues from the ModuleSettings table.

The SettingName column contains xmlsrc and xslsrc in two separate rows and the relativepath to the two data files in the SettingValue column. For example, the first statement in theXmlModule.Page_Load handler passes the indexer “xmlsrc” to the Settings property, whichis used as a lookup value into the ModuleSettings table. Here is the statement:

String xmlsrc = (String) Settings["xmlsrc"];

This statement initially runs the GetModuleSettings stored procedure, returning the value~/data/sales.xml from the database. The HttpServerUtility object inherited by the UserControlclass contains a method MapPath, which takes the relative path and maps it to a physical path.If the document exists, it is loaded. The same technique is used to load the XSL file. Listing14-12 shows the code used to safely load the XML document. (An identical technique was usedto load the XSL document.)

Listing 14-12 Safely loading a relative path document

1: String xmlsrc = (String) Settings["xmlsrc"];

2: if ((xmlsrc != null) && (xmlsrc != "")) {

3: if (File.Exists(Server.MapPath(xmlsrc))) {

4: xml1.DocumentSource = xmlsrc;

5: }

6: else {

7: Controls.Add(new LiteralControl("<" + "br" + "><" + "span

8: class=NormalRed" + ">" + "File " + xmlsrc +

9: " not found.<" + "br" + ">"));

10: }

11: }

The first statement demonstrates a string indexer in use. The consumer of the Settingsproperty does not need to know that this Hashtable is originally initialized from a storedprocedure. It is a good practice to hide these details precisely as demonstrated in the portal.The code on line 3 of Listing 14-12 maps a relative path to a physical path, using theHttpServerUtility represented by the Server object. The result is that the portal can bephysically relocated without breaking code, while allowing for reasonable validation suchas file existence checks. If the file exists, the Xml.DocumentSource property is assigned therelative path value. (File.Exists does not work with URL relative path strings.) If the filewas not found, a dynamically created Literal control is created instead. If you examine thePage_Load event, you will find almost identical code for the XSL file.

Page 489: Advanced c sharp programming

TIPThe block for loading the XML and XSL files could be refactored into a single method inside theXmlModule.ascx file, passing the string indexer to a method containing the code in common.

Creating a Custom Portal ModuleWith most software, the greatest cost will be incurred when you try to grow the software.Employees and contractors are more likely to be asked to maintain an existing program thanto write a new program from scratch. In fact, I personally have worked for many companieswhere the applications are “made available” rather than “released.” Instead of getting to goon to the next big thing, the developers immediately begin a maintenance cycle that involvesmoving bugs around that everyone knew were in the software when it was made available.(Generally, these environments aren’t very fun to work in.)

NOTEAll except the most trivial applications should have UML models, written architectural overviews, a documentedconvention, and preferably an evolution plan, describing the best way to grow the software. The alternative isthat software decays much more quickly relative to the number of hands that modify it. The basic idea is thatthe greater your employee turnover, the faster your company’s software will decay into spaghetti code andbecome unmaintainable—unless you have the artifacts described in the first sentence.

Regardless of the reason for working on existing software, one of the first things to do isfigure out how it was implemented and how to extend the software. After the software wasoriginally hacked together, usually more hacking occurs, resulting in software that is neverreally quite done. Having an architecture, using UML models and written architecturaloverviews, and following an established set of best practices constitute the best ways toslow the decay of software quality. For example, the IBuySpy portal provides accessibleexplanations in writing—which is not the same thing as inferring from the code—that describehow to modify and maintain the portal, and it adheres to best practices that Microsoft ispromoting with respect to the architecture of .NET. As a result, we can adhere to the styledemonstrated by the portal and follow the published documentation, resulting in a seamlessintegration of our customizations.

Creating a Custom Portal ModuleAll programmers have heard or used the word “extensible.” An extensible system is one thatencourages and promotes consistent enhancement. If you have to use a crowbar to wedgerevisions in, the software is not extensible.

The IBuySpy is very extensible. You can create a custom portal module and integrate thecustomization in the portal by following these steps.

1. With the portal solution open in Visual Studio .NET, find the DesktopModules folderin the Solution Explorer.

4 6 2 A d v a n c e d C # P r o g r a m m i n g

Page 490: Advanced c sharp programming

2. Right-click the DesktopModules folder and select Add | Add Web User Control fromthe context menu.

3. In the Name field in the Add New Item dialog, enter a file name for your custommodule. (I named the example module CustomModule.ascx.)

4. Click Open. This will close the dialog, adding CustomModule.ascx to your copy of theIBuySpy portal.

5. Double-click the new module and switch to the Code Designer view. Change the classheader to inherit from ASPNetportal.PortalModuleControl and save the file.

6. Using SHIFT+F7, switch back to the Form Designer view. Click the HTML tab at thebottom to switch to the HTML editor. Define a <span> and add the class attribute tothe span, indicating that you want to use the Normal style class for your control. Thespan statement follows:

<span class="Normal"></span>

7. Click the Design tab to switch back to the Designer view. Drag theDesktopModuleTitle.ascx control from the Server Explorer to the Designer viewof the new control. (This will allow an administrator to name the module when it isadded as a page.)

8. Finally, add some controls, text, or HTML to define the appearance of thecustom control.

I planned to use the new control to announce book releases. To make the control trulyflexible, we could use a DataList and read the book release from the database. (You can useother controls like the Links.ascx or QuickLinks.ascx controls to make the control use datafrom the Portal database.) To finish up, I simply added some literal text announcing therelease of this book.

The final step is to incorporate the custom control into the portal. We could create a newtab and page or incorporate the CustomModule.ascx control into an existing tab. I chose thelatter. The next section walks you through incorporating the CustomModule.ascx control intothe Home page of the IBuySpy portal.

Integrating the Custom Module into the IBuySpy PortalTo incorporate a control into the IBuySpy portal, log in using an administrative password.The default administrative Email and password are both “guest.” After you log in as guest,the Admin tab of the portal will be displayed. Navigate to the Admin tab and you canincorporate the new control without writing any additional code. Follow the steps tocomplete the process, using the referenced images as a graphical guide.

1. Ensure that you are logged in with an administrative role. (Email equals guest andPassword equals guest for the default administrative login.) Also, make sure you are onthe Admin tab (shown in Figure 14-7).

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 6 3

Page 491: Advanced c sharp programming

2. Click Add New Module Type at the bottom-right of the Admin page (also shown inFigure 14-7). The Module Type Definition view will open.

3. Specify a Friendly Name and Desktop Source for the new module. (You can use Figure14-8 as a visual guide. The module name is the relative path and file name of the filecontaining the new User Control.)

4. Click Update to save the changes.

5. Again, on the Admin tab, in the Tabs section, select the Home tab and click the icongraphic of the pencil to edit the Tab definition for the Home tab.

6. Modify the Tab Name and Layout view, using Figure 14-9 as a visual guide. Select theCustom Portal Module (or whatever Friendly Name you used in step 3). For the ModuleType, provide a Module Name, which will be displayed in the DesktopTitleModule.ascxcontrol, and click the Add to “Organize Modules” Below link.

7. By default, the module will be added to the Content Pane. You can use the bluedirectional arrows to move the module to an alternate pane. (I stuck with theContentPane.)

8. Click the Apply Changes link when you are satisfied and navigate to the Home tab toview the new module.

4 6 4 A d v a n c e d C # P r o g r a m m i n g

Figure 14-7 Select Add New Module Type on the Admin tab to incorporate a new module intothe IBuySpy.

Page 492: Advanced c sharp programming

In eight fairly simple steps, we customized the IBuySpy portal in a very non-technicalway. We certainly could have opened up the database tables and tried to figure out whichcolumns and rows to change, but the administrative tool is a significant improvement overthat. The method of managing a website using plug-and-play controls and an administrativefront end is very maintainable and extensible.

Secondary TopicsChapters on C# and the .NET Framework could go on interminably, as evidenced by thehundred or so books that have been published already. I have mixed feelings about everychapter. There is more I would have liked to share, but in each it is necessary to draw to aclose if the book is ever to get to press.

I mentioned a couple of things that are part of the IBuySpy portal that I would like toexplore a little more before we wrap up this chapter. Specifically, I’d like to explore theHttpServerUtility class a bit more and offer an example that demonstrates the Repeater control.

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 6 5

Figure 14-8 Provide a Module Type Definition, where the Friendly Name will be the name yourefer to when administrating the portal and the Desktop Source is the relative UserControl file path.

Page 493: Advanced c sharp programming

Using the HttpServerUtilityThe HttpServerUtility provides support services. This class is represented by the Serverobject, which is a property of the System.Web.UI.Page control. The Server object can beused to complete tasks like requesting the machine name of the server, transferring control toa new page, or mapping the relative path of a page to a physical file path. (Listing 14-12demonstrates how mapping a relative to a physical path can be used to ensure that files exist,by invoking the HttpServerUtility.MapPath method.) You can obtain the machine name byaccessing the HttpServerUtility.MachineName property, and the HttpServerUtility.Transfermethod stops the execution of the current page and transfers execution to a page passed to theTransfer method.

TIPThe HttpServerUtility.Transfer method can be used as a substitute for the Response.Redirect method.Response.Redirect occurs on the client, and the HttpServerUtility.Transfer method occurs on the server.

4 6 6 A d v a n c e d C # P r o g r a m m i n g

Figure 14-9 The CustomModule.ascx control added to the IBuySpy portal

Page 494: Advanced c sharp programming

C h a p t e r 1 4 : C r e a t i n g C u s t o m W e b C o n t r o l s 4 6 7

Using the Repeater ControlThe Repeater control is a data-bound, template control that provides no default layout or styleinformation but does allow you to split tags across templates. For example, a <table> tag canstart in the <ItemTemplate> and end in the <FooterTemplate>, spanning <AlternatingItem>and <SeparatorTemplate> tags in between. However, you must specify an ItemTemplate at aminimum to display data in the DataSource. Listing 14-13 shows the <table> tag, spanningboth the <ItemTemplate> and <AlternatingItemTemplate>, and the <TR> tag, crossing bothtemplates, resulting in a red and black checkerboard pattern.

Listing 14-13 Tags spanning templates in the Repeater control

<asp:Repeater id="Repeater1">

<table>

<ItemTemplate>

<tr>

<td bgcolor=black>

&nbsp;

</td>

</ItemTemplate>

<AlternatingItemTemplate>

<td bgcolor=red>

&nbsp;

</td>

</tr>

</AlternatingItemTemplate>

</table>

</asp:Repeater>

The <table> tag wraps the <ItemTemplate> and the <AlternatingItemTemplate>. Noticethat the <tr> (row) tag spans both the <ItemTemplate> and the <AlternatingItemTemplate>.Listing 14-13 results in a checkerboard pattern where alternating DataSource items areactually displayed in the same row.

Unfortunately, you may get a smattering of errors in the Task List, indicating that the activeschema does not support a particular element, for example, the <AternatingItemTemplate>. Theerror report may be inaccurate or only partially correct. With a little experimentation, you canproduce some creative presentations with the Repeater control.

Page 495: Advanced c sharp programming

SummaryThe IBuySpy portal is maintained through the administrative pages and can be extended bycreating modules—UserControls—that are easily plugged into the portal using theadministrative pages.

This chapter expands on what you already learned from Chapter 8, demonstrating how tobuild UserControls for the Web. This is a developer’s guide, so I showed you how to createUserControls that fit into a specific application, the IBuySpy portal. Clearly, you don’t haveto inherit from the PortalModuleControl for any other application than the IBuySpy portal.Rather, the portal application provides you with an implicit best practice: define the basicinfrastructure of your application and “genericize” that aspect, allowing all participants indevelopment to integrate their code seamlessly.

The IBuySpy portal was implemented by Microsoft to blaze a trail for developers.ADO.NET and stored procedures can be used to organize and retrieve data efficiently, andthe UserControl is an excellent way to create a dynamic presentation layer. As a combinedstrategy, the stored procedure and the UserControl allow you to focus on the business rules,the middle tier.

4 6 8 A d v a n c e d C # P r o g r a m m i n g

Page 496: Advanced c sharp programming

CHAPTER

15Output Cachingand Persisting

State Information

469

IN THIS CHAPTER:

Demonstrated Topics

Output Caching Pages

Caching Partial Pages

Caching Data

Using the Session Cache

Using the Application Cache

Secondary Topics

Configuring the Session State Server

Configuring the SQL Server for SessionManagement

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 497: Advanced c sharp programming

If you compare a Web application to a Windows application, you are likely to detect anoticeable sluggishness in the Web application relative to the Windows application.Web applications are hosted in a browser and have to make trips between the client and

server, with the greatest part of the code residing on the server. Anything you can do to enhanceperformance and responsiveness should be considered.

In terms of strategy, one of the best things you can do is a little process re-engineeringbefore you begin developing your application. Consider the flow of your application from theperspective of how the user will complete tasks and minimize trips to the Web server. Afteryou have resolved ways in which you can simplify task completion, you can minimize thenumber of times pages have to be rendered and data has to be retrieved from a database.(For our purposes, we are assuming there will be a database, although the data repositorycould be something else.)

This chapter is about enhancing the performance of your Web applications by relying onstrategies that reduce the frequency that code has to run to render pages and the frequencythat databases must be accessed. This information is covered with the IBuySpy portal as thebackdrop. However, every application will not use all techniques; hence, I will be showingyou useful techniques that are used not only by the portal but also by others.

Demonstrated TopicsThere are several kinds of caching that we can take advantage of to optimize the performanceof our Web applications. These are not the same kinds of optimizations that we refer to whenwe talk about optimizing code; that kind of optimization has to occur, too. As a reminder,tune your code after your application is working correctly. And, you can treat the materialpresented in this chapter as tuning and optimizing strategies or you can implement thesestrategies when you initially write the code. In this chapter I will demonstrate

� How to cache entire pages to reduce the number of times a page is rendered, includinghow to use declarative and programmatic caching

� How to use UserControls to cache partial pages

� The various ways that you can cache data, as an alternative to caching pages

� How to administer the expiration policy

One way in which you can cache data is to use the Session cache. There are two generalways in which data is cached. There is the default in-process memory cache and the out-of-process caches. The in-process cache is fast but does not maintain state if the server is restarted,and there are two out-of-process caches that maintain cache data between server restarts, andas a result, they are more robust. If you plan to deploy a Web farm, you will need to use anout-of-process cache server. The “Secondary Topics” section will demonstrate how toconfigure and use out-of-process caching.

4 7 0 A d v a n c e d C # P r o g r a m m i n g

Page 498: Advanced c sharp programming

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 7 1

Output Caching PagesWhen you request a page, the page is Just-In-Time compiled on the server and the code-behind is executed to render the page. All of this takes time. If the content of a page doesn’tchange very often, you can cache the page. Subsequent requests for the page will be readfrom the cache, avoiding the overhead of JITting and rendering the page for each request.

You can cache pages declaratively or programmatically. Declarative caching, using the @OutputCache directive, will work in most circumstances, but you can obtain a finer degree ofcontrol using programmatic page caching. Let’s take a look at declarative page caching as itis used in the IBuySpy portal, and we’ll explore programmatic caching that results in thesame behavior.

Using Declarative CachingThe @ OutputCache directive is added directly to the HTML text when you define a Webpage. You need to specify a duration attribute and a value for the VaryByParam attribute at aminimum. There are variations on these attributes that we will talk about in a minute. Thebasic declarative output caching statement will appear in the HTML text, as in the following:

<%@ OutputCache Duration="60" VaryByParam="None" %>

The directive is OutputCache. Duration expresses the amount of time to hold the cached pagein seconds. Duration=“60” means that the page should be held for 60 seconds. The VaryByParamparameter is required even if the value is “None”. The basic directive will cause the wholepage to be cached.

Consider the page shown in Figure 15-1, which is based on the Xml control and theIBuySpy portal’s sales.xml and sales.xsl files. Without caching, the page is compiled andrun each time a user requests the page. (The Submit button represents a page request.) Clickthe Submit button and the page is posted to the server. The date and time stamp will change,reflecting the fact that the page code has been run.

NOTEA quick test is to add a breakpoint in the code-behind page or observe the date and time stamp at thebottom of the page as you click the Submit button. You will see that the breakpoint is reached (and the dateand time stamp value changes) almost every time you click the Submit button.

If we examine the sales.xml and sales.xsl, we quickly realize that this is static data. So,what is the benefit of regenerating the page for every query? The answer is that there is nobenefit. After the first request, every page will contain the same data and our users will payfor unnecessary CPU cycles. This is a perfect opportunity to enhance the performance of theWeb site vis-à-vis this page by caching the rendered sales data. Adding the OutputCachedirective to the HTML text—declaratively, using page caching—will inhibit the page fromrendering for every requestor.

Page 499: Advanced c sharp programming

4 7 2 A d v a n c e d C # P r o g r a m m i n g

Add the basic OutputCache directive demonstrated and the page will be rendered onceapproximately every 60 seconds, and all users will simply be served the cached version ofthe page. Consider the case of dozens or hundreds of requestors in 60 seconds, and you willrealize performance savings scaled by the number of requestors who don’t need to wait forthe page to render. Add the OutputCache directive, as shown in boldface in Listing 15-1, andre-run the DeclarativeCaching.sln Web application. You will see that the date and time stampis updated after every minute or so. (You can open and close the Web browser to simulatemultiple users.)

Listing 15-1 The HTML text for the WebForm1.aspx page in the DeclarativeCaching.sln

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false"

Inherits="DeclarativeCaching.WebForm1" %>

<%@ OutputCache Duration="60" VaryByParam="None" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

<HTML>

<HEAD>

<title>WebForm1</title>

<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">

<meta name="CODE_LANGUAGE" Content="C#">

<meta name="vs_defaultClientScript" content="JavaScript">

<meta name="vs_targetSchema"

content="http://schemas.microsoft.com/intellisense/ie5">

<link href="portal.css" type="text/css" rel="stylesheet">

</HEAD>

Figure 15-1 A simple page that shows some data from an XML file each time the page isrequested

Page 500: Advanced c sharp programming

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 7 3

<body MS_POSITIONING="GridLayout">

<form id="Form1" method="post" runat="server">

<table class="Normal" height="100%" width="100%" align="left">

<tr>

<td valign="top" style="WIDTH: 343px; HEIGHT: 166px">

<asp:Xml id="Xml1" runat="server"></asp:Xml>

</td>

<td style="HEIGHT: 166px" vAlign="top">

<asp:Button id="Button1" runat="server"

Text="Submit"></asp:Button>

</td>

</tr>

<tr>

<td valign="top" colspan="2">

<asp:Label id="Label1" style="Z-INDEX: 101"

runat="server">Label</asp:Label>

</td>

</tr>

</table>

</form>

<P></P>

</body>

</HTML>

A reasonable person might want to see some evidence that page caching has providedsome performance improvements before committing resources to optimizing pages withoutput caching. If you have to convince other developers or managers, then visuals mighthelp. Before we continue our discussion of the OutputCache directive, let’s explore a meansof evaluating performance improvements by taking a quick tour of Microsoft’s ApplicationCenter Test (ACT).

Examining Performance HeuristicsMicrosoft Visual Studio .NET Enterprise and Enterprise Architect ship with MicrosoftApplication Center Test (ACT). ACT is an excellent tool for simulating multiple, simultaneousclient connections.

ACT allows you to record or write a custom VBScript that interacts with your ASP.NETapplication. After you have defined one or more tests, you can configure the tests, includingthe number of users, and ACT will spin up that number of HTTP connections to the Web server.

ACT is completely configurable and provides you with visual and textual feedbackduring the test run and after the test has completed. To demonstrate ACT, I will show youhow to record, configure, and run a test that can be used to compare the differences inDeclarativeCaching.sln with and without the @ OutputCache directive.

Page 501: Advanced c sharp programming

4 7 4 A d v a n c e d C # P r o g r a m m i n g

Recording the Test To record a test, start ACT by selecting Start | Program Files | MicrosoftVisual Studio .NET | Visual Studio .NET Enterprise Features | Microsoft Application CenterTest. Follow the numbered steps to record a test script for the DeclarativeCaching.sln.

1. In ACT, select File | New Project to create a new test project. The sample test project isnamed test.act and is available for download.

2. After you have created the test project, you can add one or more tests to the project byselecting Actions | New test. The New Test Wizard starts, which we will use to recorda script.

3. After the introduction screen, select Record A New Test.

4. Select the script language (VBScript) and click Next; the Browser Record step in thewizard will let you start recording. Click the Start recording button.

5. When you start recording, the browser will be opened to a blank page. Everythingyou do in the browser from this point until you click Stop Recording will be recordedas VBScript.

6. Navigate the browser to http://localhost/declarativecaching/webform1.aspx andclick the Submit button several times. (This step assumes that you are running thesample Web application on your PC or on the same computer you are running ACTon.) The first time you click the Submit button, you should see the date and time stampchange, if you have added the @ OutputCache directive to the page.

7. After you have clicked the Submit button a couple of times, close the browser.

8. Click the Stop Recording button in the wizard and click through the remaining steps inthe ACT wizard. (One of the steps is to provide a name for the test. I named the test Icreated Submit.)

After you have finished recording the test, you can select it from the navigator windowin ACT, shown in Figure 15-2. You can create as many tests as you want to in a similarmanner.

Configuring the Test After recording the test, we can configure the test run. To configure thetest, we can select a specific test from the navigator window and select Actions | SubmitProperties (see Figure 15-3). In the Submit Properties window, you can indicate the numberof simultaneous connections, warm-up time, and run-time. You can also specify how usersare generated and add performance counters to measure. (You can read more about performancecounters in Chapter 7.)

For the test I created, I selected 10 simultaneous browser connections, a warm-up periodof 20 seconds, and test duration of 5 minutes. (These settings are shown in Figure 15-3 on theGeneral tab of the Properties window.) I let ACT automatically generate users, and I pickedthe Interrupt Time and Disk Read Time performance counters (see Figure 15-4). The InterruptTime counter tracks the time the processor spends servicing hardware interrupts, and theDisk Read Time is the amount of time the drive spends performing reads.

Page 502: Advanced c sharp programming

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 7 5

Figure 15-2 ACT showing the VBScript created after recording an interaction with theDeclarativeCacing.sln

Figure 15-3 The Submit Properties window is used to configure the test.

Page 503: Advanced c sharp programming

TIPClick the Explain button in the Browse Performance Counters dialog for a description of eachperformance counter.

Running the Test After we have configured the test, we are ready to run. Select the test andActions | Start Test to begin running the test. ACT will display the test status as the test isrunning (see Figure 15-5). You can observe test performance during execution and view allof the results after the test has completed.

When the test has completed, you can click the Results folder in the navigator and view awide variety of test results. The Summary results (shown in Figure 15-6) in our test showhow much time was spent retrieving the portal.css style sheet and the Web page. You cancompare the results between tests run with and without page caching and tune the Webapplication to enhance the performance characteristics you are interested in.

Caching by ParameterAfter our brief detour, we are back to caching. The simplest way to cache a page is to expressa duration and the VaryByParam=“None” attribute. You can also express literal parameternames for the VaryByParam name. If the page request includes parameters, you can use thisattribute to indicate that a new page needs to be rendered if the value of the parameters havechanged, even if the page requested hasn’t changed.

Express the parameters between quotes in a semicolon-delimited list. Our example doesn’tuse parameters; however, we could have implemented the page so that the user could indicate

4 7 6 A d v a n c e d C # P r o g r a m m i n g

Figure 15-4 The Browse Performance Counters dialog

Page 504: Advanced c sharp programming

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 7 7

Figure 15-5 The Submit test in progress with details showing

Figure 15-6 The Request Summary report for the Submit test

Page 505: Advanced c sharp programming

the XML file to view. For example, we could have used a parameter file and listed all of theavailable files the user could pick from. The URL-encoded request might be http://localhost/DeclarativeCaching/webform1.aspx?file=sales.xml. On the initial request, the page wouldbe cached by the value of the file parameter, and for each unique value of file a differentversion of the page would be cached. The @ OutputCache directive expressing that we wantto vary by the value of the file parameter would be added to the HTML, as demonstrated next:

<%@ OutputCache Duration="60" VaryByParam="file" %>

If we want to vary by a second parameter, we can insert a semicolon after the word “file”and insert additional parameter names.

Caching by Custom String and by HeaderDeclaratively, there are two other ways to indicate that a cached page needs to be regenerated.We can cache by a custom string and by the page header; these attributes are VaryByCustomand VaryByHeader, respectively. You still have to include the VaryByParam attribute even ifyou set its value to None.

Caching by Custom String Suppose you implement a Web page that contains browser-specificcapabilities. You can cache by a custom string—VaryByCustom—and a new page will berendered if the value of that string changes. That is, the string value becomes the cachedeterminator.

VaryByCustom is combined with the required Duration and VaryByParam attributes,even if the VaryByParam attribute value is None. To declaratively cache by custom string,add the VaryByParam attribute and a unique string. This string can be compared to anargument passed to the HttpApplication.GetVaryByCustomString method, which you mustoverride to work with the VaryByCustom attribute. Listing 15-2 shows the @ OutputCachedirective with the VaryByCustom attribute, and Listing 15-3 shows the GetVaryByCustomStringmethod implemented in the global.asax file.

Listing 15-2 An @ OutputCache directive with the VaryByCustom attribute shown in the firstfew lines of an aspx page

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false"

Inherits="DeclarativeCaching.WebForm1" %>

<%@ OutputCache Duration="360" VaryByParam="None" VaryByCustom="cookies"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

<HTML>

<HEAD>

<title>WebForm1</title>

<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">

<meta name="CODE_LANGUAGE" Content="C#">

4 7 8 A d v a n c e d C # P r o g r a m m i n g

Page 506: Advanced c sharp programming

The string we will compare to in the Global.asax file is the value of the VaryByCustomattribute “cookies”. Listing 15-3 shows a complete Global.asax file with the overriddenGetVaryByCustomString method.

Listing 15-3 Implement the GetVaryByCustomString method to manage page caching by astring value.

1: using System;

2: using System.Collections;

3: using System.ComponentModel;

4: using System.Web;

5: using System.Web.SessionState;

6:

7: namespace DeclarativeCaching

8: {

9: /// <summary>

10: /// Summary description for Global.

11: /// </summary>

12: public class Global : System.Web.HttpApplication

13: {

14: public Global()

15: {

16: InitializeComponent();

17: }

18:

19: protected void Application_Start(Object sender, EventArgs e)

20: {

21:

22: }

23:

24: protected void Session_Start(Object sender, EventArgs e)

25: {

26:

27: }

28:

29: protected void Application_BeginRequest(Object sender, EventArgs e)

30: {

31:

32: }

33:

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 7 9

Page 507: Advanced c sharp programming

4 8 0 A d v a n c e d C # P r o g r a m m i n g

34: protected void Application_EndRequest(Object sender, EventArgs e)

35: {

36:

37: }

38:

39: protected void Application_AuthenticateRequest(Object sender,

40: EventArgs e)

41: {

42: }

43:

44: protected void Application_Error(Object sender, EventArgs e)

45: {

46:

47: }

48:

49: protected void Session_End(Object sender, EventArgs e)

50: {

51:

52: }

53:

54: protected void Application_End(Object sender, EventArgs e)

55: {

56:

57: }

58:

59: public override string GetVaryByCustomString(HttpContext context,

60: string arg

61: {

62: return arg == "cookies" ?

63: context.Request.Browser.Cookies.ToString() : string.Empty;

64: }

65:

66: #region Web Form Designer generated code

67: /// <summary>

68: /// Required method for Designer support - do not modify

69: /// the contents of this method with the code editor.

70: /// </summary>

71: private void InitializeComponent()

72: {

73: }

Page 508: Advanced c sharp programming

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 8 1

74: #endregion

75: }

76: }

The Global.asax file contains application-level and session-level event handlers that youcan implement. (Refer to Chapter 13, the section “Implementing Event Handlers in theGlobal.asax Module,” for more information.) We provided an overridden implementationof the HttpApplication.GetVaryByCustomString method on lines 59 to 64 of Listing 15-3.The code uses a ternary operator to evaluate the string argument arg. Reading the ternaryoperator, we understand lines 62 and 63 to mean if arg is equal to the literal string “cookies”then return the string value of the HttpBrowserCapabilities.Cookies property. If the currentbrowser supports cookies, then the string text “true” will be used as the custom string thatdecides whether the page is cached. Caching with “true” and a subsequent test that returns“false” will cause a new page to be rendered. This is in addition to the timeout expressed inthe @ OutputCache Duration attribute in Listing 15-2.

Unlike the VaryByParam attribute, VaryByCustom uses only one string value. For example,VaryByCustom=“cookies;frames” does not treat cookies as one string and frames as another.

Caching by Header HTTP requests send header information that describes the client-browser,supported languages, host information, and a bevy of other details about the HTTP request.You can cache based on any single key, combination of keys, or all keys in the HTTP header.

To cache based on HTTP header information adds the VaryByHeader attribute, passingthe name of the header key. If you want to cache based on multiple keys, then you canseparate header keys by a semicolon or use the asterisk to cache based on all header keys.Here are three declarative @ OutputCache directives that demonstrate caching on a singlevalue in the header, multiple values, and all values.

<%@ OutputCache Duration="10" VaryByParam="None" VaryByCustom="cookies"

VaryByHeader="user-agent"%>

<%@ OutputCache Duration="10" VaryByParam="None" VaryByCustom="cookies"

VaryByHeader="user-agent;host"%>

<%@ OutputCache Duration="10" VaryByParam="None" VaryByCustom="cookies"

VaryByHeader="*"%>

The first statement caches the page for 10 seconds, ignores URL-encoded parameters, andwill render the request if made by a client that switches between cookie support and nocookie support or if the user-agent changes. The second @ OutputCache directive differsonly by the VaryByHeader attribute—it takes the host into account. The last directive willvary if any of the header values differs. Varying by all headers—or any large variety ofcaching configurations—will cause your Web server to cache a version of the page basedon each variation attribute until the page expires.

Page 509: Advanced c sharp programming

Caching Examples in the IBuySpy PortalThe IBuySpy portal uses declarative caching in pages that will not change significantlybetween requests. Specifically, the portal caches pages that convey error information to a user.

TIPA version of grep ships with rotor. To search all of the files in the portal, change directories to the PortalCSVSdirectory and enter the following text at a command prompt: grep –i –d OutputCache *.aspx. The –iswitch means a case-insensitive search, and –d indicates that subfolders should be searched.

If you perform a grep—a source file text search—on the source files in the portal, you willsee that the AccessDenied.aspx, EditAccessDenied.aspx, and the NotImplemented.aspx pages usedeclarative caching. Here is the OutputCache directive for the AccessDenied.aspx page.

<%@ OutputCache Duration="36000" VaryByParam="none" %>

According to the Duration attribute, this page won’t expire for ten hours. You can experimentwith Duration settings in the DurationDemo.sln available for download. EditAccessDenied.aspxuses OutputCache settings identical to AccessDenied.aspx. NotImplemented.aspx demonstratesa shorter duration and an argument for VaryByParam.

NOTEThe beta version of ASP.NET had a five-minute cache limitation. The deployed version no longer has thislimitation. Set the duration for longer than 5 minutes, and the page will be cached for a greater period oftime. You can download and try the DurationDemo.sln available at www.osborne.com to experiment.

NotImplemented.aspx is displayed when you select an option that is not completed.(This is an excellent strategy for Windows and Web applications.) The OutputCachedirective for the NotImplemented.aspx page is implemented as follows:

<%@ OutputCache Duration="600" VaryByParam="title" %>

The NotImplemented.aspx page will be compiled and run when the ten-minute durationperiod expires and for each title parameter. That is, every time the URL-encoded value of“title” changes, a new page will be cached. The NotImplemented.aspx page exists for linksthat are not part of the sample data for the portal.

Using Programmatic CachingProgrammatic page caching provides features identical to declarative caching. For everydayuse, declarative caching should work fine. If you want slightly more control or need to changecaching policies at run-time, then you can manage page caching programmatically throughthe HttpCachePolicy.

An HttpCachePolicy object is accessible through the HttpResponse.Cache property;an HttpResponse object is, in turn, accessible through the Page.Response property. Page.Response.Cache will provide you access to a page’s HttpCachePolicy. The portal doesn’t

4 8 2 A d v a n c e d C # P r o g r a m m i n g

Page 510: Advanced c sharp programming

use programmatic output page caching, so we’ll take a little detour and examine programmaticcaching that is analogous to the declarative caching used by the portal.

Specifying a Cache ExpirationThe OutputCache Duration attribute specifies how long the page will be cached. The equivalentprogrammatic command is HttpCachePolicy.SetExpires. Pass the time that the cached pagewill expire. For example, you can invoke the DateTime.Now property and add a numberof seconds, minutes, or hours to the current time. Here is an example taken from theProgrammaticCacheDemo.sln.

Page.Response.Cache.SetExpires(DateTime.Now.AddSeconds(30));

(The Page object is not required, since you are in the Page when this code is executed.)DateTime.Now returns the current date and time, and AddSeconds(30) adds 30 seconds tothe current time. That is, the page will expire 30 seconds from the time this code is executed.

You need more than just the SetExpires method to use programmatic output caching.From online sources and books, you might be led to believe that you only need SetExpiresand SetCacheability. Unfortunately, this information seems to be incomplete. To cache apage programmatically, you need to supply several pieces of information; Listing 15-4provides you with an example that programmatically caches a page.

Listing 15-4 Programmatically caching a page

Page.Response.Cache.SetExpires(DateTime.Now.AddSeconds(30));

Page.Response.Cache.VaryByParams["None"] = true;

Page.Response.Cache.SetCacheability(HttpCacheability.Public);

Page.Response.Cache.SetValidUntilExpires(true);

The preceding example indicates when the cached page will expire. In the example, thepage will expire 30 seconds after the statement is executed. With the @ OutputCache directive,we need to specify VaryByParams[“None”] = true. I didn’t find any mention of this in thehelp documentation, but you can try it. Without a VaryByParams statement, the page will notcache. The SetCacheability statement sets the Cache-Control header (see Table 15-1), and

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 8 3

Value DescriptionNoCache Sets the Cache-Control header to no cache. The page will not be cached.

Private The default value sets the Cache-Control header to private, indicating that the pageis cached on the client.

Public Sets the Cache-Control header to public, indicating the page is cached by clients andproxy caches.

Server The page is cached on the server.

Table 15-1 HttpCacheability-Enumerated Values for Programmatic Caching

Page 511: Advanced c sharp programming

SetValidUntilExpires(true) instructs the ASP.NET to ignore HTTP Cache-Control headersthat invalidate the cache. SetValidUntilExpires is implicitly set to true when you use the@ OutputCache directive. If you don’t use SetValidUntilExpires(true), then the browsermay send header information that causes the cache to be invalidated. The four statements inconjunction seem to work most reliably and consistently with the @ OutputCache directivethat expresses a Duration and a VaryByParam equal to “None”.

Caching by ParametersUse the VaryByParams property to express the URL-encoded parameters that controlpage caching. Express the name of the parameter in the subscript operator and the valueon the right side of the equals-sign operator. For example, to vary by the title parameterprogrammatically—which is what the portal does declaratively—place the word “title” inquotes in the subscript operator and assign it the value true.

Page.Response.Cache.VaryByParams["title"] = true;

If you want to ignore parameters, then run this code:

Page.Response.Cache.VaryByParams.IgnoreParams = true;

As you can determine, it takes a little extra effort to cache pages programmatically, butdoing so will allow you to dynamically change page caching behavior after the code iscompiled and deployed. Generally, you can use declarative caching and revert to programmaticcaching if cache behavior is dynamic.

� Response.Cache.SetExpires(DateTime.Now.AddSeconds(60))

� Response.Cache.SetCacheability(HttpCacheability.Public)

� Response.Cache.SetValidUntilExpires(True)

Caching Partial PagesPartial page caching—also known as fragment caching—is managed via user controls. Youcan cache part of a page by adding the @ OutputCache directive to the user control, as youwould for an entire page, or you can apply the PartialCachingAttribute to the class in thecode-behind page. You already saw how to use the @ OutputCache directive in the precedingsection; let’s focus on the just-introduced PartialCachingAttribute.

CAUTIONIf you enable output caching on a user control, you will not be able to refer to that user control in code.

System.Web.UI contains the definition for the PartialCachingAttribute. The concepts forpartially caching a user control are similar to those for caching a page. The biggest differencesare syntactical, and you can vary caching based on the properties of constituent controlsdefined on the user control.

4 8 4 A d v a n c e d C # P r o g r a m m i n g

Page 512: Advanced c sharp programming

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 8 5

There are two forms of the PartialCachingAttribute. The first accepts a duration expressinghow long to hold the control cached in memory. The second version of the constructor takesfour arguments. The first is the duration, followed by a semicolon-delimited list of parametersto vary by, followed by a semicolon-delimited list of constituent control properties and avalue equivalent to the declarative VaryByCustom attribute. (If you use the VaryByCustomargument, you will need to override GetVaryByCustomString, as demonstrated in the earliersection “Caching by Custom String and by Header.”)

The PartialPageCachingDemo.sln provides an example that demonstrates using thePartialCachingAttribute (see Listing 15-5). The solution implements a UserControl that hasthe PartialCachingAttribute applied to the user control. When you create an instance of aUserControl that uses the PartialCachingAttribute, the type of the actual object created will bePartialCachingControl, not the specific user control you defined. The PartialCachingControldoes not have a public Controls collection. As a result, you cannot refer to constituent controlson the UserControl. This makes sense, because what you may be seeing on the client is likelyto be the cached UserControl, rather than the actual object.

Listing 15-5 The UserControl that uses the PartialCachingAttribute

1: namespace PartialPageCachingDemo

2: {

3: using System;

4: using System.Data;

5: using System.Drawing;

6: using System.Web;

7: using System.Web.Caching;

8: using System.Web.UI;

9: using System.Web.UI.WebControls;

10: using System.Web.UI.HtmlControls;

11:

12: [PartialCaching(36000)]

13: public abstract class CachedControl : System.Web.UI.UserControl

14: {

15: protected System.Web.UI.WebControls.Label Label1;

16: protected System.Web.UI.WebControls.Button Button1;

17: protected System.Web.UI.HtmlControls.HtmlGenericControl P1;

18: protected System.Web.UI.WebControls.Xml Xml1;

19:

20: private void Page_Load(object sender, System.EventArgs e)

21: {

22: Label1.Text = "Control updated: " + DateTime.Now.ToString();

23: if( !IsPostBack )

24: Initialize();

25: }

Page 513: Advanced c sharp programming

4 8 6 A d v a n c e d C # P r o g r a m m i n g

26:

27: private void Initialize()

28: {

29: SetData("sales.xml", "sales.xsl");

30: }

31:

32: public void SetData( string document, string transform)

33: {

34: Xml1.DocumentSource = document;

35: Xml1.TransformSource = transform;

36: }

37:

38: [Web Form Designer generated code]

39:

40: }

41: }

NOTEWe can’t simply add the PartialCachingAttribute to the XmlModule.ascx control in the IBuySpy portal. Thereason this particular revision wouldn’t work in the portal is that the XmlModule.ascx inherits from thePortalModuleControl, which is a UserControl. A weird side effect of the PartialCachingAttribute is that theUserControl is wrapped inside of the PartialCachingControl class, which does not inherit from UserControlor the PortalModuleControl. When the portal dynamically loads the control, it attempts to cast it toPortalModuleControl on line 72 of the DesktopDefault.aspx page. This cast is invalid because the loadedcontrol is actually a PartialCachingControl.

The PartialCachingAttribute on line 12 of Listing 15-5 indicates that the page will becached for 36,000 seconds, or 10 hours. The rest of the code is plain-vanilla UserControlcode. This code, which is similar to the XmlModule.ascx in the IBuySpy portal, displays thecontents of the sales.xml XML document, formatted using the sales.xsl XSL transform document.

Caching DataThus far, I have demonstrated how to cache whole pages and fragments of pages. Sometimesit is the data you want to cache instead of the page. For example, data that is read from adatabase and is used across multiple pages can be cached in the Cache property of a Page.(The Cache class is defined in System.Web.Caching if you are interested in exploring thehelp documentation.)

The classic example of using a cache for storing data is storing a user’s shopping cart as theuser travels around the Web site. The IBuySpy portal uses the Cache for a different purpose.The portal caches the user controls that make up various pages in the portal application.

The IBuySpy defines a custom control, CachedPortalModuleControl. The CachedPortalModuleControl represents the data that a portal module is composed of. If you set the

Page 514: Advanced c sharp programming

Modules table, CacheTime column—either directly in the database or in the Module Settingsadministration page in the portal (see Figure 15-7)—then the portal will use the Cache. Pageswill be cached and subsequent requests for the same page will be created out of the Cache.

Portal CachingThe election to load a new control or create one from the Cache occurs in the Page_Initmethod for the IBuySpy portal shown in Listing 15-6.

Listing 15-6 The portal will load a new page or use cached pages depending on theModules.CacheTime column in the portal database.

1: private void Page_Init(object sender, EventArgs e) {

2: //

3: // CODEGEN: This call is required by the ASP.NET Web Form Designer.

4: //

5: InitializeComponent();

6:

7: //*********************************************************************

8: //

9: // Page_Init Event Handler

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 8 7

Figure 15-7 Use the Module Settings page in the portal to indicate that a page should becached.

Page 515: Advanced c sharp programming

4 8 8 A d v a n c e d C # P r o g r a m m i n g

10: //

11: // The Page_Init event handler executes at the very beginning

12: // of each page request (immediately before Page_Load).

13: //

14: // The Page_Init event handler below determines the tab index

15: // of the currently requested portal view, and then calls the

16: // PopulatePortalSection utility method to

17: // dynamically populate the left, center and right hand sections

18: // of the portal tab.

19: //*********************************************************************

20:

21: // Obtain PortalSettings from Current Context

22: PortalSettings portalSettings = (PortalSettings)

23: HttpContext.Current.Items["PortalSettings"];

24: // Ensure that the visiting user has access to the current page

25: if PortalSecurity.IsInRoles(portalSettings.ActiveTab.AuthorizedRoles)

26: == false) {

27: Response.Redirect("~/Admin/AccessDenied.aspx");

28: }

29: // Dynamically inject a signin login module into the top left-hand

30: // corner of the home page if the client is not yet authenticated

31: if ((Request.IsAuthenticated == false) && (portalSettings.ActiveTab.TabIndex == 0)) {

32: LeftPane.Controls.Add(

33: Page.LoadControl("~/DesktopModules/SignIn.ascx"));

34: LeftPane.Visible = true;

35: }

36: // Dynamically Populate the Left, Center and Right pane sections

37: if (portalSettings.ActiveTab.Modules.Count > 0) {

38:

39: // Loop through each entry in the configuration system for this tab

40: foreach (ModuleSettings _moduleSettings in

41: portalSettings.ActiveTab.Modules) {

42: Control parent = Page.FindControl(_moduleSettings.PaneName);

43:

44: // If no caching is specified, create the user control instance

45: // and dynamically inject it into the page. Otherwise, create a

46: // cached module instance that may or may not optionally

47: // inject the module

48: if ((_moduleSettings.CacheTime) == 0) {

49: PortalModuleControl portalModule =

50: (PortalModuleControl)

51: Page.LoadControl(_moduleSettings.DesktopSrc);

52: portalModule.PortalId = portalSettings.PortalId;

Page 516: Advanced c sharp programming

53: portalModule.ModuleConfiguration = _moduleSettings;

54:

55: parent.Controls.Add(portalModule);

56: }

57: else {

58: CachedPortalModuleControl portalModule = new

59: CachedPortalModuleControl();

60:

61: portalModule.PortalId = portalSettings.PortalId;

62: portalModule.ModuleConfiguration = _moduleSettings;

63:

64: parent.Controls.Add(portalModule);

65: }

66:

67: // Dynamically inject separator break between portal modules

68: parent.Controls.Add(new LiteralControl("<" + "br" + ">"));

69: parent.Visible = true;

70: }

71: }

72: }

If Modules.CacheTime is 0, then we create a new instance of a PortalModuleControl byloading a page that inherits from that UserControl. This occurs in the single statement spanninglines 49, 50, and 51 of Listing 15-6. However, the else condition kicks in—boldfaced on lines57 through 65—if the CacheTime is greater than 0. That is, if the control is cached, we willcreate a control by constructing an instance of the CachedPortalModuleControl wrapper.

The CachedPortalModuleControl determines whether the requested control has actuallybeen cached. If it has not, then CachedPortalModuleControl loads the page in the samemanner as shown on lines 49, 50, and 51 of Listing 15-6. However, if the control is cached,then the control is created from the cache. The CachedPortalModuleControl is provided inListing 15-7. (Both the PortalModuleControl and the CachedPortalModuleControl aredefined in DesktopControls.cs.)

Listing 15-7 The CachedPortalModuleControl acts as a wrapper for cached portal modules,loading the control if it hasn’t been cached yet.

1: public class CachedPortalModuleControl : Control {

2:

3: // Private field variables

4:

5: private ModuleSettings _moduleConfiguration;

6: private String _cachedOutput = "";

7: private int _portalId = 0;

8:

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 8 9

Page 517: Advanced c sharp programming

9:

10: // Public property accessors

11:

12: public ModuleSettings ModuleConfiguration {

13:

14: get {

15: return _moduleConfiguration;

16: }

17: set {

18: _moduleConfiguration = value;

19: }

20: }

21:

22: public int ModuleId {

23:

24: get {

25: return _moduleConfiguration.ModuleId;

26: }

27: }

28:

29: public int PortalId {

30:

31: get {

32: return _portalId;

33: }

34: set {

35: _portalId = value;

36: }

37: }

38:

39: //*******************************************************************

40: //

41: // CacheKey Property

42: //

43: // The CacheKey property is used to calculate a "unique" cache key

44: // entry to be used to store/retrieve the portal module's content

45: // from the ASP.NET Cache.

46: //

47: //*******************************************************************

48:

49: public String CacheKey {

50:

51: get {

52: return "Key:" + this.GetType().ToString() +

4 9 0 A d v a n c e d C # P r o g r a m m i n g

Page 518: Advanced c sharp programming

53: this.ModuleId + PortalSecurity.IsInRoles(

54: _moduleConfiguration.AuthorizedEditRoles);

55: }

56: }

57:

58: //*******************************************************************

59: //

60: // CreateChildControls Method

61: //

62: // The CreateChildControls method is called when the ASP.NET Page

63: // Framework determines that it is time to instantiate a

64: // server control.

65: // The CachedPortalModuleControl control overrides this method

66: // and attempts to resolve any previously cached

67: // output of the portal module from the ASP.NET cache.

68: //

69: // If it doesn't find cached output from a previous request, then the

70: // CachedPortalModuleControl will instantiate and add the portal

71: // module's User Control instance into the page tree.

72: //

73: //*******************************************************************

74:

75: protected override void CreateChildControls() {

76:

77: // Attempt to resolve previously cached content

78: // from the ASP.NET Cache

79: if (_moduleConfiguration.CacheTime > 0) {

80: _cachedOutput = (String) Context.Cache[CacheKey];

81: }

82:

83: // If no cached content is found, then instantiate and add

84: // the portal module user control into the portal's

85: // page server control tree

86: if (_cachedOutput == null) {

87:

88: base.CreateChildControls();

89:

90: PortalModuleControl module = (PortalModuleControl)

91: Page.LoadControl(_moduleConfiguration.DesktopSrc);

92:

93: module.ModuleConfiguration = this.ModuleConfiguration;

94: module.PortalId = this.PortalId;

95:

96: this.Controls.Add(module);

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 9 1

Page 519: Advanced c sharp programming

97: }

98: }

99:

100: //*******************************************************************

101: //

102: // Render Method

103: //

104: // The Render method is called when the ASP.NET Page Framework

105: // determines that it is time to render content into the page

106: // output stream.

107: // The CachedPortalModuleControl control overrides this method

108: // and captures the output generated by the portal module user

109: // control. It then adds this content into the

110: // ASP.NET Cache for future requests.

111: //*******************************************************************

112:

113: protected override void Render(HtmlTextWriter output) {

114:

115: // If no caching is specified, render the child tree and return

116:

117: if (_moduleConfiguration.CacheTime == 0) {

118: base.Render(output);

119: return;

120: }

121:

122: // If no cached output was found from a previous request, render

123: // child controls into a TextWriter, and then cache the results

124: // in the ASP.NET Cache for future requests.

125:

126: if (_cachedOutput == null) {

127:

128: TextWriter tempWriter = new StringWriter();

129: base.Render(new HtmlTextWriter(tempWriter));

130: _cachedOutput = tempWriter.ToString();

131:

132: Context.Cache.Insert(CacheKey, _cachedOutput,

133: null, DateTime.Now.AddSeconds(_moduleConfiguration.CacheTime),

134: TimeSpan.Zero);

135: }

136: // Output the user control's content

137:

138: output.Write(_cachedOutput);

139: }

140:}

4 9 2 A d v a n c e d C # P r o g r a m m i n g

Page 520: Advanced c sharp programming

In Listing 15-7, the two methods that implement the CachedControlPortalModuleControlare the overridden CreateChildControls and Render methods. CreateChildControls—lines 75through 98 of Listing 15-7—is responsible for reading the cache and loading the controlmodule if the module has not been cached once already. Subsequent requests will come fromthe cache because the Render method of the control is added to the cache as a text stream(using the StringWriter class on line 128. Line 132) actually stuffs the HTML representingthe UserControl into the Cache. If you examine the value of the Cache, you will see that thevalue placed in the Cache is the literal HTML text that makes up the page.

It is the HTML text representing our control that is the data we are interested in caching.This data could just as easily be an ADO.NET DataSet or some other data. The manner inwhich we express how it is stored in the Cache is the same.

Adding Data to the CacheThe reference to the Context object on line 132 of Listing 15-7 represents an HttpContextobject (or the objectification of a single HTTP request). Context.Cache returns an instanceof the Cache object. Line 132 demonstrates the basic step for inserting an object into theCache. There are four overloaded versions of Cache.Insert that you can reference in the helpdocument for Visual Studio .NET. The example used in the portal is one of the more complex,including everything except a priority and a callback delegate for notification when cacheditems are removed.

The first argument, as demonstrated in Listing 15-7, is the key name that will be usedto identify the object. The second argument is the object to cache. This argument is typedas an object. The third argument—null, in the example—is a cache dependency, which wewill talk about in a moment. The fourth argument is the absolute expiration time, and thefinal argument is the sliding expiration interval. In the example, the sliding expiration isTimeSpan.Zero. This means that the items are not cached on a sliding expiration; as a result,the cached item will be purged after the absolute expiration period has passed. Had weexpressed a non-zero TimeSpan, then the cached item would be removed after an intervalequal to the last access time plus the TimeSpan. For example, if TimeSpan were equal to10 minutes, then the object would be removed from the cache after the expiration periodplus the sliding expiration period had elapsed.

Creating a File DependencyYou can create a CacheDependency on cached items. For example, you could cache dataread from an external file and create a dependency on that file. If the file is modified, theCache would be updated. The portal does not employ the CacheDependency object, so wewill return to our PartialPageCachingDemo.sln to demonstrate.

The DependentControl.ascx UserControl implements caching using a dependency basedon a file. If any of the external files used to create the dependency change, then the cacheditem associated with the dependency is dumped. The way the code is written in Listing 15-8,if either the XSL/T or XML documents change, the cache will be flushed.

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 9 3

Page 521: Advanced c sharp programming

Listing 15-8 Cache dependencies based on an external file

1: private void Page_Load(object sender, System.EventArgs e)

2: {

3: Label1.Text = "CacheDependency: " + DateTime.Now.ToString();

4:

5:

6: if( Context.Cache["transform"] == null )

7: {

8: XslTransform transform = new XslTransform();

9: transform.Load(Server.MapPath("sales.xsl"));

10: Xml1.Transform = transform;

11: Context.Cache.Insert("transform", Xml1.Transform,

12: new CacheDependency(Server.MapPath("sales.xsl")));

13: }

14: else

15: Xml1.Transform = (XslTransform)Cache["transform"];

16:

17: if(Context.Cache["document"] == null)

18: {

19: Xml1.DocumentSource= "sales.xml";

20: Context.Cache.Insert("document", Xml1.Document,

21: new CacheDependency(Server.MapPath("sales.xml")));

22:

23: }

24: else

25: Xml1.Document = (XmlDocument)Cache["document"];

26: }

In Listing 15-8, there are two cached items that are associated with a CacheDependency.The first is an XslTransform object and the second is an XmlDocument. The first time thiscode is run, an XslTransform (line 8 of Listing 15-8) and an XmlDocument (line 19) arecreated. The XslTransform is created explicitly on lines 8 and 9 and cached on line 11.The XmlDocument is created implicitly when we provide an Xml.DocumentSource on

line 19. (It is necessary to map a file name to a physical server path, as demonstrated on lines12 and 21.) Both objects are added to the Cache employing a CacheDependency based on theexistence of the respective files. As long as the files remain the same, the cached objects arevalid. If we edit either sales.xml or sales.xsl, the cached object is dumped and the if part ofthe conditional statement will run.

Try the PartialPageCachingDemo.sln. You will see that the UserControl that usesthe PartialCachingAttribute will be out of date if you change the sales.xml file, but theDependentControl UserControl will automatically update the data when the page issubmitted.

4 9 4 A d v a n c e d C # P r o g r a m m i n g

Page 522: Advanced c sharp programming

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 9 5

Each approach to caching has its uses. Choose between output, partial, or data cachingaccording to what would suit your needs. Cached pages, partial pages, and data are notspecifically tied to individual users or pages. This data is shared across all requests. Ifyou want to cache data relative to a user, you can use the Session cache.

Using the Session CacheEvery user connection is represented by a session. You can cache data associated withindividual user sessions by storing objects in the Session cache. The Session cache works intwo modes. A cookie containing a unique session identifier is stored on the client’s machine.The session identifier uniquely identifies the client between trips to the server. This is thedefault mode. In some situations, cookies may be disabled, and devices such as cell phonesthat do not support cookies will have to rely on cookieless sessions. Cookieless sessionswork by embedding the session identifier into the URL; the session identifier is parsed outof the URL when it is submitted.

NOTEI am going to demonstrate session caching that relies on cookies. You convert to cookieless session states bysetting the <sessionState> tag’s cookieless attribute to true in the web.config file. Try experimenting withthis mode to see the session identifier mixed in with the URL. (Refer to Figure 15-8 for an example of acookieless session URL. The Web page is from the SessionDemo.sln.)

Figure 15-8 The session identifier passed as part of the URL in a cookieless session

Page 523: Advanced c sharp programming

By default, items cached in the Session are associated with a single session and arecached in memory. When the session ends or the server restarts, the session information islost. In-process session management is fast, since it runs in memory, but it is not scalable,because the session information does not survive a server reboot and cannot be used in Webfarms. Fortunately, there are two other ways to store session information, using an externalsession server and the SQL Server database. These other methods will survive IIS serverrestarts and, because they work well in Web farms, are very scalable. I will talk aboutconfiguring out-of-process state management in the sections “Configuring the SessionState Server” and “Configuring the SQL Server for Session Management.” For now, I willdemonstrate how to use the Session cache in general, regardless of whether the informationis cached in or out of process.

Caching Data in the SessionCaching data in the Session cache is simply a matter of indexing the Session argument witha unique name for the datum and assigning the data to the Session object. The data can beretrieved from the Session cache by placing the name-indexed Session object on the rightside of an assignment, casting the return value to the known type.

For example, we can store the Text property of a TextBox control in the Session objectand retrieve that data at some other point during the same session. Storing some text in theSession object can be accomplished by assigning a literal string, as demonstrated here:

Session["text"] = "If you've got the grits, serve 'em.";

The same data can be retrieved and assigned to the Text property of a control namedTextBox1. Here is an example that reads the data out of the Session cache.

TextBox1.Text = (string)Session["text"];

There is no point in storing the information in a control using the Session, because youcan accomplish the same thing much more easily by enabling the view state. For example,TextBox1.EnableViewState = true will instruct the page to store the value of TextBox1 in ahidden field within the page. Listing 15-9 shows the view state information in boldface forthe Web page in the SessionDemo.sln.

Listing 15-9 ViewState information stored in the page in a hidden field

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

<HTML>

<HEAD>

<title>WebForm1</title>

<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">

<meta name="CODE_LANGUAGE" Content="C#">

<meta name="vs_defaultClientScript" content="JavaScript">

<meta name="vs_targetSchema"

4 9 6 A d v a n c e d C # P r o g r a m m i n g

Page 524: Advanced c sharp programming

content="http://schemas.microsoft.com/intellisense/ie5">

</HEAD>

<body MS_POSITIONING="GridLayout">

<form name="Form1" method="post" action="WebForm1.aspx" id="Form1">

<input type="hidden" name="__VIEWSTATE"

value="dDwtNDM5MzM1ODM2Ozs+kKb/ejZfdsDgE9O70iAWcI8CLro=" />

<input type="submit" name="Button1" value="Store" id="Button1"

style="width:86px;Z-INDEX: 101; LEFT: 458px; POSITION: absolute; TOP:

43px" />

<input name="TextBox1" type="text" value="If you've got the grits, serve

'em!" id="TextBox1" style="width:397px;Z-INDEX: 102; LEFT: 36px;

POSITION: absolute; TOP: 41px" />

<input type="submit" name="Button2" value="Retrieve" id="Button2"

style="Z-INDEX: 103; LEFT: 458px; POSITION: absolute; TOP: 84px" />

</form>

</body>

</HTML>

However, if you want to pass information from one page to the next, then view state won’thelp you. Stuff the information in the Session.

An example of when you might elect to use the Session cache is illustrated on a Web sitelike that of Federal Express. Shipping a document spans a couple of pages. You select yourshipping information to create a shipment. After you have created the shipment, you can electto schedule a courier. The courier information is on a separate page, but when scheduling acourier, FedEx needs to know who is shipping what. You could use Session state to track anobject representing a user’s shipment, and that information would be available when the usernavigated from creating a shipment to scheduling a pickup.

Of course, the implication here is that you can put complex objects into the Sessioncache—and, in fact, you can. You can put custom objects or objects like an ADO.NETDataSet into the Session, making it easier to maintain complex information between roundtrips to the server or even in the stateless world of the Web.

Caching Objects in the Session CacheWhen your application is on one page, you might be gathering information about a user,such as information that describes a service request. When the page is submitted to the server,you could post the information to a database and track the user by a cookie on the user’sworkstation. Moving to the next step, you could retrieve the information back from thedatabase—remember, object-oriented code runs on the server to render the page and thendisappears—and use that information to continue servicing the user’s request. A scenariousing the FedEx example would be to create a shipment and schedule a shipment pickup.The result is that there is a lot of back and forth between the IIS server and the database.

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 9 7

Page 525: Advanced c sharp programming

As an alternative, you could store the user’s information in the Session cache, and thenyou wouldn’t have to make all of those expensive extra trips to the database server. When theuser has finished, you could update the database all at one time. The end result is that objectsrepresenting what your users are doing are in memory rather than in the database.

A reasonable person might surmise that objects representing a user’s interaction don’tsound very robust. Recall, though, that we can use the default in-process Session server ora more robust out-of-process server that can withstand IIS restarts. As a result, we can usemultiple Web servers, and the Session information can withstand IIS restarts needing code toconstantly write pieces of information to the application’s database. (Refer to the “SecondaryTopics” section for more information.)

You can add and remove objects in the Session cache in precisely the same way as youadd and remove simple data from the Session cache. Refer to the Session cache property,index it with a unique string identifier, and use object assignment. Listing 15-10 demonstratescode that stores an ArrayList of strings into the Session cache. The ArrayList could just aseasily be a DataSet.

Listing 15-10 Storing a complex object in the Session cache

1: using System;

2: using System.Collections;

3: using System.ComponentModel;

4: using System.Data;

5: using System.Drawing;

6: using System.Web;

7: using System.Web.SessionState;

8: using System.Web.UI;

9: using System.Web.UI.WebControls;

10: using System.Web.UI.HtmlControls;

11:

12: namespace SessionDemo

13: {

14: /// <summary>

15: /// Summary description for WebForm1.

16: /// </summary>

17: public class WebForm1 : System.Web.UI.Page

18: {

19: protected System.Web.UI.WebControls.Button Button1;

20: protected System.Web.UI.WebControls.TextBox TextBox1;

21: protected System.Web.UI.WebControls.TextBox TextBox2;

22: protected System.Web.UI.WebControls.Label Label1;

23: protected System.Web.UI.WebControls.Button Button2;

24:

25: const string name = "text";

26:

4 9 8 A d v a n c e d C # P r o g r a m m i n g

Page 526: Advanced c sharp programming

27: [ Web Form Designer generated code]

28:

29: private ArrayList Text

30: {

31: get

32: {

33: if( Session[name] == null )

34: Session[name] = new ArrayList();

35: return (ArrayList)Session[name];

36: }

37: }

38:

39: private void Button1_Click(object sender, System.EventArgs e)

40: {

41: Text.Add(TextBox1.Text);

42: }

43:

44: private void Button2_Click(object sender, System.EventArgs e)

45: {

46: try

47: {

48: TextBox1.Text = (string)Text[Int32.Parse(TextBox2.Text)];

49: }

50: catch

51: {

52: TextBox1.Text = "Not found, enter a valid number less than " +

53: Text.Count.ToString();

54: }

55: }

56: }

57: }

If you examine the code in Listing 15-10, you might have to look twice before it isobvious that this is code-behind for an ASP.NET Web page. It is. This is one of the beautiesof .NET. The code-behind for building Web applications is fairly similar to the code inWindows or Console applications.

A style I prefer is to hide the Session indexing code in one place, localizing Sessioninitialization and references in case I want to modify something related to the Session object.Additionally, referring to a property named Text makes the code more readable than havingSession[name] and conditional code that checks for null all over the place.

Lines 29 through 37 of Listing 15-10 use lazy instantiation to create the ArrayList ondemand and localize all references to the Session cache to that single location in the page.The rest of the code is very manageable.

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 4 9 9

Page 527: Advanced c sharp programming

5 0 0 A d v a n c e d C # P r o g r a m m i n g

Managing the Session CacheThe Session object is an instance of the HttpSessionState class. There are several methods formanaging the Session state. You can determine the number of items in the Session cachethrough the Count property. You can request the SessionID, obtain a list of all Keys, Abandonthe current session, or Clear all items in the cache. Refer to the help documentation on theHttpSessionState class for a complete reference.

Using the Application CacheThe Application cache works syntactically just like the Session cache. You can store simpledata or complex objects in the Application cache. The question is: when would you putsomething in the Application cache? The answer: when you want to cache some data that isshared at the application level, that is, by all users.

An enterprise application I worked on had a commissary feature. Every consumer couldpurchase items from a small commissary that was available to everyone. Instead of readingthe commissary items for every user, we read the commissary items when the applicationstarted Global.asax Application_Start event. The items were always available without furtherhits on the database.

Another option might have been to render the page and use output caching to store therendered page, also avoiding additional database hits. However, we actually picked items fromthe commissary list and used those items to create orders. Hence, instead of static data, weneeded dynamic objects, and we resolved on the Application cache. To demonstrate using theApplication cache, replace Application with Session on lines 33, 34, and 35 of Listing 15-10.Open two or more Web pages, and you will see that all pages—representing sessions—cansee all of the strings in the ArrayList. Switch back to Session, and each page will be able tosee only the strings entered for that session.

Use the Session cache when the data is relevant to a single user represented by a singlesession, and use the Application cache when the data is relevant all users.

Secondary TopicsEvery application will not need to use all of the capabilities of .NET. It is valuable to knowas much as possible about what is available in .NET and selectively use those features.

The IBuySpy portal demonstrates how to use cookies, output page caching, and the Cacheobject. However, the portal does not use Session or Application caching; yet, with a littleexperimentation, you can quickly determine that the portal is a powerful, maintainable, andextensible Web application. (Keep in mind that portal code is available as a starting point foryour Web applications.)

We talked about the Session and Application caches even though they are not in the portal.I want to wrap up this chapter by demonstrating how to configure the out-of-process state

Page 528: Advanced c sharp programming

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 5 0 1

servers. These two states servers operate more slowly than the in-process state managementbut are more robust and support optimal Web scalability. That is, you can use one of theout-of-process state servers in a Web farm, and they are capable of maintaining state evenif IIS restarts.

All three state servers have the Web.config file in common. You can modify the<sessionState> tag to indicate which session state server you will be using and thenconfigure the server accordingly. Listing 15-11 contains a default <sessionState> tagtaken from the Web.config file for the SessionDemo.sln.

Listing 15-11 An example Web.config file’s <sessionState> tag

<sessionState mode="InProc"

stateConnectionString="tcpip=127.0.0.1:42424"

sqlConnectionString="data source=127.0.0.1;user id=sa;password="

cookieless="false"

timeout="20"

/>

In Listing 15-11, the mode attribute indicates the session state server that will be used.The four possible values are InProc, Off, SQLServer, and StateServer. InProc is thein-process server used by default. Off indicates that session state is disabled. SQLServerindicates that you will be using SQL Server out-of-process to manage state, and StateServeris the ASP.NET State Server represented by the asponet_state.exe Windows serviceapplication.

If you specify SQLServer for mode, the sqlConnectionString will be used to connect tothat state server. If you specify StateServer for mode, the stateConnectionString will beused on the loopback IP 127.0.0.1 on port 42424 to talk to the aspnet_state.exe service.The cookieless attribute is false, indicating that the session identifier is stored as a cookie;change it to true to pass the session identifier in the URL. The last value, timeout, indicatesthe number of minutes the session can remain idle before the session information is abandoned.

The InProc and Off mode state configurations require no extra steps. Read the remainingtwo sections for instructions on how to configure the two out-of-process state servers.

Configuring the Session State ServerThe ASP.NET State Server ships with the .NET framework and is installed automaticallywhen the .NET framework is installed. You can configure any server to manage session stateusing the ASP.NET State Server. Complete the numbered steps to use the out-of-processstate server.

Page 529: Advanced c sharp programming

5 0 2 A d v a n c e d C # P r o g r a m m i n g

1. Modify the Web.config file for the application that will be using the aspnet_state.exeWindows service.

2. Change mode to StateServer and the tcpip value of the stateConnectionString to the IPaddress of the server running the ASP.NET State Service. You can leave the defaultvalue if the Web application and the aspnet_state.exe service are running on the samemachine. (The service listens on port 42424, so be sure to leave this value.)

3. Open a command window (or the Services console) and start the aspnet_state.exeservice. To start the service from the command prompt, enter net start aspnet_state.

TIPYou can stop and start any Windows service with net stop servicename and net start servicename,respectively, if you know the name of the service application.

You can start and stop the service from the Services console, shown in Figure 15-9, or usethe net start and net stop commands. To stop the service from a command prompt, type netstop aspnet_state.

Figure 15-9 The Services console, used to start and stop Windows services, focused here on theASP.NET State Service application

Page 530: Advanced c sharp programming

Configuring the SQL Server for Session ManagementUsing SQL Server to manage state is the most robust form of state management. SQL Serverruns out of process, providing for support of Web farms; you can also cluster multipleinstances of SQL Server together. If one SQL Server instance goes down, other servers pickup the slack. Follow the numbered steps to set up a single SQL Server instance to managethe session state.

1. Modify the Web.config file mode attribute to SQLServer, leaving everything else as is.

2. Run Microsoft’s SQL Server Query Analyzer and run the InstallSqlState.sql script(shown in Figure 15-10). This script will create the SQL Server Session state database.(The InstalSqlState.sql script is installed with the .NET framework by default in theWinnt\Microsoft.Net\Framework\version\ directory, where version is the latest versionof the framework.)

C h a p t e r 1 5 : O u t p u t C a c h i n g a n d P e r s i s t i n g S t a t e I n f o r m a t i o n 5 0 3

Figure 15-10 The SQL Query Analyzer can be used to run the InstallSqlState.sql script,which builds the ASPState database.

Page 531: Advanced c sharp programming

3. Run the InstallSqlState.sql script logged in with SQL Server authentication and thesame account. The script will create an SQL Server Instance named ASPState onthe machine you logged into and ran the InstallSqlState.sql script against.

That’s all there is to it.

SummaryThere are several ways to manage state information in ASP.NET. Some of them are usedin the IBuySpy portal application, and all of them are available for C# and ASP.NETdevelopers. Let’s wrap up the chapter by reviewing the basic ways we can store stateinformation in ASP.NET.

You can use cookies for small pieces of information up to 4K. Cookies are used for sessionidentifiers if you are using Session caching, and are commonly used to store informationsuch as an authenticated user identifier. View state is supported in Web forms controls. Youcan enable view state information if you will be recreating a page, or disable it if not. If youenable view state, then controls will maintain their individual values between round trips tothe server, and you won’t need to cache this information. View state is maintained in anencrypted hidden field that is sent to the server. If you will be rendering a specific page eachtime, then disable view state to avoid the extra work of restoring information that will beoverwritten with new data. You also have the option of passing state information in anURL-encoded request as parameters. Output page caching and partial page output cachingcan be employed to save an entire page or individual UserControls on a page. The Cache canbe used to cache data that is accessible to everyone. Output page and data caching are goodways to optimize your application by eliminating the need for JITting and rendering pages ordata that are redundant across sessions.

Finally, the Application and Session caches can be used to cache data at the applicationor individual session level. If you want any user running your application to have access toshared data, then you can stuff objects into the Application cache, reserving the Sessioncache for data relevant to a single session. Cache information can be stored in-process forsingle-server ASP.NET applications with optimal responsiveness, or out-of-process cachingcan be configured for maximum scalability and robustness.

Web applications are stateless by nature, but it is clear that we do need to track informationabout the users and the state of an application—and there are a lot of possibilities to choosefrom in ASP.NET.

5 0 4 A d v a n c e d C # P r o g r a m m i n g

Page 532: Advanced c sharp programming

CHAPTER

16Security and

Authentication

505

IN THIS CHAPTER:

Demonstrated Topics

Using Windows NTLM Authentication

Implementing Forms Authenticationwith Cookies

Implementing the SignIn Module

Administering Users

Secondary Topics

Implementing Code Access Security

Summary

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 533: Advanced c sharp programming

There are two kinds of general security that we will talk about in this chapter. Thereis the security associated with a user and role, and there is the security associatedwith code. The first form of security is used in the IBuySpy portal and is manifested

as roles-based security (or Windows security). Another kind of security is referred to asCode Access Security and is new in .NET. The portal uses the former kind of security; hence,we will talk about roles-based security in the “Demonstrated Topics” section and CodeAccess Security in the “Secondary Topics” section.

Roles-based security is the kind of security that you are probably most familiar with. Definea role, assign user names and passwords to users, and allow them to perform some operationsbased on their authenticated role. For Web applications running behind a firewall—intranets—we can use Windows authentication. For extranets and the Internet, we can employ roles-based authentication with cookies. The IBuySpy portal is capable of allowing you to useWindows authentication if you are running the portal as an intranet application or Formsauthentication if not.

The second kind of security, Code Access Security, is new in .NET. Code Access Securityis not designed to be used instead of the roles-based or Windows security. Code AccessSecurity was designed to complement traditional security models. Roles-based or Windowssecurity authenticates the user, and Code Access Security authenticates the code, as it were.

The key is not to choose one over the other. Nor is it necessary to use both all of the time.Instead, you can use roles-based and Windows authentication for many kinds of applicationsand rely on the extra security provided for managed code—.NET applications—by defaultwith Code Access Security. The default configurations are pretty good. Where Code AccessSecurity needs extra scrutiny is on the World Wide Web. If you are running applications thatare interchanging data across the Web, then you will need to more closely scrutinize theCode Access Security model, in conjunction with traditional security steps.

Demonstrated TopicsThe IBuySpy portal supports Windows and Forms authentication, using cookies to store anauthenticated user name and password. This chapter will demonstrate how to use cookies,modify the Web.config file for Windows and Forms authentication, implement the SignInmodule, and administer the IBuySpy portal.

All of these skills are valid for intranet, extranet, and Internet ASP.NET applicationdevelopment. This section will show you how to

� Implement the SignIn module and authenticate portal users

� Modify a Web.config file for Windows and Forms authentication

� Program with cookies

� Administer IBuySpy portal users

The “Secondary Topics” section will introduce the Code Access Security model, newwith .NET. These topics include programming with imperative and declarative Code Access

5 0 6 A d v a n c e d C # P r o g r a m m i n g

Page 534: Advanced c sharp programming

Security and how to administer the Code Access Security policy. When you combinetraditional roles-based or Windows security with the complementary Code Access Security,you can diminish the security threat from the riskiest avenue of attack, the Web.

Using Windows NTLM AuthenticationYou can build ASP.NET applications using simple Windows NT Lan Manager (NTLM)authentication. Relying on internal network Windows user accounts is a pretty reasonablemodel for ASP.NET intranet applications.

As a reminder, intranet applications are internal network applications that run on a browserover a TCP/IP network. However, they reside behind your firewall, so only authenticatedusers and PCs wired into your network should have access to these sites. The portal supportsthis mode of operation.

NOTEThe Web.config file is not served by IIS. As a result, users cannot request the Web.config file and modify theapplication settings contained in it.

You can modify the IBuySpy portal’s Web.config file (or any ASP.NET application’s),setting the user security authentication model by changing the attributes for the <authentication>tag. The IBuySpy portal is a teaching tool and conveniently has this metadata commentedout inside of the Web.config file. All we have to do is modify the Web.config file.

Modifying the Web.config File for Windows AuthenticationBy default, the IBuySpy portal uses Forms authentication. We have to comment out the tagsthat set the authentication mode to Forms and uncomment the code that switches to Windowsauthentication. Listing 16-1 shows the default authentication settings for the portal. (We willcome back to Forms authentication in a moment.)

Listing 16-1 An excerpt from the Web.config file that defines the authentication mode for theIBuySpy portal

<!--IBuySpy Portal supports either Forms authentication (Internet)

or Windows authentication (for intranets). Forms Authentication is

the default. To change to Windows authentication, comment the

<authentication mode="Forms"> section below, and uncomment the

<authentication mode="Windows"> section. -->

<authentication mode="Forms">

<forms name=".ASPXAUTH" protection="All" timeout="60" />

</authentication>

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 0 7

Page 535: Advanced c sharp programming

5 0 8 A d v a n c e d C # P r o g r a m m i n g

<!--<authentication mode="Windows" />

<authorization>

<deny users="?" />

</authorization>-->

TIPThe <!- - begins a comment block in HTML and XML, and the - -> ends the comment block.

Uncomment the <authentication> and <authorization> tags in boldface and comment the<authentication> tag, setting the default mode to Forms. When you run the portal applicationwith Windows authentication, you will n see the SignIotn module.

The authentication tag with attribute mode=“Windows” instructs the application to usethe Windows user name and password to authenticate the user. If you are logged into yourcomputer, then you are logged into the application. The portal will still authenticate your username against the users and roles configuration data in the portal database, but this happensautomatically without your having to specifically log into the portal. (The portal authenticatesthe user the same way internally, regardless of the authentication mode in the Web.configfile. We’ll explore the authentication code in the next section, “Implementing FormsAuthentication with Cookies.”)

The <authorization> tag supports two subtags, <deny> and <allow>, which each havetwo attributes. Let’s look next at the purpose of <deny> and <allow>.

Denying and Allowing AuthorizationThe Windows authentication mode in the portal uses the <deny> tag. The <deny> tag has twoattributes: Users and Roles. We can deny specific users or roles by providing a comma-delimited list of users or roles or use one of two wildcard values, * or ?.

The asterisk (*) refers to all users and the question mark (?) refers to anonymous users.For example, if you use

<deny users="?" />

then anonymous users will not be allowed to access the Web site. A comma-delimited list ofroles or the ? or * wildcard can be used for the <allow> tag too.

When the Web application runs, the <deny> and <allow> tags are read from top to bottomuntil it finds the first access rule that is relevant to a user. For example, if you have

<authorization>

<allow Roles="Admins" />

<deny Users="*" />

</authorization>

then users in the administrative role will be allowed access, and all other users will be deniedaccess to the Web application. (Refer to the AuthenticationDemo.sln available for downloadfrom www.osborne.com.)

Page 536: Advanced c sharp programming

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 0 9

TIPThe Machine.config file is installed in the C:\WINNT\Microsoft.NET\ Framework\ [version]\ CONFIG\machine.config folder by default.

The top-level .config file is the Machine.config file. By default, Machine.config uses the<Allow Users=“*” /> tag, providing access to all users on the machine. This is the defaultconfiguration. You can implement one or more Web.config files for each directory in yourWeb application. The Web.config file will be examined before access to files in that directoryare allowed access, and the authorization rule will be examined before allowing access tousers. Thus, you can subdivide your Web application by folders, providing different levelsof access for different areas of your Web application.

Implementing Forms Authentication with CookiesThe IBuySpy portal authenticates users regardless of the form of authentication that you use.In this section, we will begin by looking at the configuration settings for Forms authenticationand then walk through the code that is used to authenticate users for both Windows and Formsauthentication.

Modifying the Web.config File for Forms AuthenticationThe IBuySpy portal defaults to Forms authentication. The default Web.config settings for theportal are provided in Listing 16-2.

Listing 16-2 The Forms authentication configuration settings

<authentication mode="Forms">

<forms name=".ASPXAUTH" protection="All" timeout="60" />

</authentication>

The four possible values for the <authentication> tag’s mode attribute are Windows, Forms,Passport, and None. Windows mode authenticates the user based on the authenticationinformation supplied to log into Windows. Forms authentication means that you will supply alogin form to authenticate the user, and the Passport mode means that a Passport authenticationserver will be used to authenticate the user. (None simply meansthat no authentication will take place.) The portal defaults to Forms authentication, so we’llexplore the Web.config file and the IBuySpy authentication code in that context.

The <forms> subtag allows you to supply Name, loginUrl, Protection, Timeout, and Pathattributes. IBuySpy uses Name, Protection, and Timeout. (We’ll look at the other attributes inthe upcoming section, “Defining Forms Attributes.”)

The Name attribute is the HTTP cookie name to use for authentication. By default, thename of this cookie is .ASPXAUTH. The Protection attribute supports four possible values,including All, None, Encryption, and Validation. The All Protection attribute indicates

Page 537: Advanced c sharp programming

that the cookie is protected by encryption and data validation. The Timeout attribute indicatesthe number of minutes until the cookie will expire. A portal login expires after 60 minutesby default.

Defining Forms AttributesThe two attributes not used in the portal authentication element are the loginUrl and thePath attribute. The Path attribute indicates the path for cookies used by the application.The Path attribute defaults to the root—equivalent to a slash (/)—so we don’t need to supplythat Path attribute unless we want to use a path other than the root.

The loginUrl will let you provide the URL of a login page. If an unauthenticated userattempts to browse to a page that requires authentication, then the user will be redirected tothe page indicated in the loginUrl. The portal displays the login form as a UserControl on theHome page and redirects the user to the AccessDenied.aspx administration page if an attemptis made to navigate to a page that requires authentication. For this reason, the loginUrl is notsupplied. However, the portal could redirect the user to the Home page if such an attemptwere made.

TIPIf no loginUrl attribute is provided, the default value is default.aspx.

The AuthenticationDemo.sln provides a simple login screen and Web.config file designedto use Forms authentication. The two Web pages are Main.aspx and BrowseToMe.aspx. Ifyou attempt to browse directly to BrowseToMe.aspx, you will be redirected to the pageindicated in the loginUrl attribute. The redirected URL will be approximately http://localhost/AuthenticationDemo/Main.aspx?ReturnUrl=%2fauthenticationDemo%2fBrowseToMe.aspx. After the user has been authenticated, the ReturnUrl parameter can be usedto redirect the user back to the originally requested page.

CAUTIONWhen users sign out after having been authenticated, they may still be able to browse to pages that requireauthentication. They will need to close their browser to sign all the way out. For example, you could log inas an IBuySpy administrator, log out, and still return to the Admin page until the browser is closed.

Supplying User CredentialsThe <forms> tag accepts a <credentials> tag. The <credentials> tag enables you to supplyuser name and password credentials in the Web.config file. You can supply these valuesusing an encryption scheme or in the clear. Listing 16-3 puts the <authentication> tag alltogether, providing you with an example of the <forms> and <credentials> tags. (The listingis taken from the AuthenticationDemo.sln sample application.)

5 1 0 A d v a n c e d C # P r o g r a m m i n g

Page 538: Advanced c sharp programming

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 1 1

Listing 16-3 A complete authentication example from the AuthenticationDemo.sln’sWeb.config file

<authentication mode="Forms">

<forms name=".ASPXAUTH"

protection="Encryption"

loginUrl="Main.aspx"

path="/">

<credentials passwordFormat="Clear">

<user name="DThoreau" password="Walden" />

</credentials>

</forms>

</authentication>

Listing 16-3 demonstrates how to express Forms authentication using the default cookiename, .ASPXAUTH. The authentication cookie will be encrypted. The loginUrl is Main.aspx,which means the user will be redirected to Main.aspx until authenticated and the defaultcookie path is expressed. A set of credentials is provided in the clear, expressed by thepasswordFormat attribute “Clear”.

When a user requests a Web page, the HttpApplication.AuthenticateRequest event israised. (The handler for this event is defined in the Global.asax file.) At this point, you havean opportunity to provide custom authentication behavior, which is exactly what the IBuySpyportal does.

Authenticating UsersRegardless of whether you are running the portal with Windows or Forms authentication, theportal takes over in the Global.asax’s HttpApplication.AuthenticateRequest event handler toprovide custom authentication.

If a request from any user is authenticated, the portal replaces the intrinsic User role withan IPrincipal security object that permits the portal to compare the user to roles that arerelevant to the application. The AuthenticateRequest event handler is provided in Listing 16-4,followed by a synopsis.

Listing 16-4 The HttpApplication.AuthenticateRequest event permits you to provide customIPrincipal security roles.

1: protected void Application_AuthenticateRequest(Object sender,

2: EventArgs e)

Page 539: Advanced c sharp programming

3: {

4: if (Request.IsAuthenticated == true) {

5: String[] roles;

6:

7: // Create the roles cookie if it doesn't exist for this session.

8: if ((Request.Cookies["portalroles"] == null) ||

9: (Request.Cookies["portalroles"].Value == "")) {

10: // Get roles from UserRoles table, and add to cookie

11: UsersDB user = new UsersDB();

12: roles = user.GetRoles(User.Identity.Name);

13:

14: // Create a string to persist the roles

15: String roleStr = "";

16: foreach (String role in roles) {

17: roleStr += role;

18: roleStr += ";";

19: }

20:

21: // Create a cookie authentication ticket.

22: FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(

23: 1, // version

24: Context.User.Identity.Name, // user name

25: DateTime.Now, // issue time

26: DateTime.Now.AddHours(1), // expires every hour

27: false, // don't persist cookie

28: roleStr // roles

29: );

30:

31: // Encrypt the ticket

32: String cookieStr = FormsAuthentication.Encrypt(ticket);

33:

34: // Send the cookie to the client

35: Response.Cookies["portalroles"].Value = cookieStr;

36: Response.Cookies["portalroles"].Path = "/";

37: Response.Cookies["portalroles"].Expires =

38: DateTime.Now.AddMinutes(1);

39: } else {

40:

41: // Get roles from roles cookie

42: FormsAuthenticationTicket ticket =

43: FormsAuthentication.Decrypt(

44: Context.Request.Cookies["portalroles"].Value);

45: //convert the role into a string array

5 1 2 A d v a n c e d C # P r o g r a m m i n g

Page 540: Advanced c sharp programming

46: ArrayList userRoles = new ArrayList();

47: foreach(String role in ticket.UserData.Split( new char[] {';'} ))

48: {

49: userRoles.Add(role);

50: }

51: roles = (String[]) userRoles.ToArray(typeof(String));

52: }

53:

54: // Add our own custom principal to the request containing the roles

55: Context.User = new GenericPrincipal(Context.User.Identity, roles);

56: }

57: }

Listing 16-4 contains the AuthenticateRequest event handler defined in the portal’sGlobal.asax file. The event handler contains a sentinel: if ( Request.IsAuthenticated == true).If the user is not authenticated then the event handler simply exits.

NOTESentinel is a cute name for a conditional check that simply bars entry to the code if it fails. A positivesentinel is: if condition is true, then do something. I prefer to write the negative sentinel: if condition isfalse, let’s get out of Dodge. Revising Listing 16-4 to use a negative sentinel, Line 4 would be rewrittenas if (!Request.IsAuthenticated) return; It would then be immediately apparent to the reader that if thecondition were not met, the method needs no further evaluation. This is especially useful when reading codeor debugging. By using a positive sentinel—if ( Request.IsAuthenticated == true)—we have to read therest of the method to make sure some other code isn’t running later on. The sentinel permits us to take ashortcut when the negative condition is true.

From Listing 16-4 we can determine two things. The first is that nothing happens unlessthe user is authenticated, and the second is that no matter how the user is authenticated, thecode runs. The user may be authenticated in two ways: Windows authentication could beused (the user is authenticated when he or she logged into Windows), or the user couldprovide an e-mail address (which serves as the user name in the portal) and a password,which can be authenticated against the Users table in the portal database. If the user isauthenticated in either of these ways, the Request.IsAuthenticated will evaluate to True.

NOTEWhen Windows authentication is employed, Windows will provide the authentication. When we are usingForms authentication, the user is authenticated (or not) when he or she signs in or registers with the portal.A user is considered authenticated by invoking FormsAuthentication.SetAuthCookie. This step stores anauthentication cookie that represents the user as having been authenticated. Refer to “Implementing theSignIn Module” later in this chapter for the specifics.

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 1 3

Page 541: Advanced c sharp programming

If the request made by the user is authenticated, the AuthenticateRequest event handlerconverts general authentication into user roles that are relevant to the IBuySpy portal. Thisoccurs on lines 5 through 55 of Listing 16-4.

The conversion between authentication and portal roles occurs in one of two ways. If the“portalroles” cookie has not been saved—see lines 8 and 9 of Listing 16-4—then we readthe roles out of the database using the UsersDB class, storing the roles in the array of stringsnamed “roles.” Each role is read and added to a semicolon-delimited string (lines 16 through 19).The string of roles is stored in a FormsAuthentication ticket object, encrypted as a string,and stuffed into the “portalroles” cookie. (Next time a request is made, assuming the cookiehasn’t expired, the else condition code is run.)

If the “portalroles” cookie has already been saved, then the FormsAuthenticationTicketis reconstituted by decrypting the stored cookie (see lines 43 and 44 of Listing 16-4).The semicolon-delimited list of roles is split apart on lines 46 through 50 and assignedto the String[ ] roles on line 51. In either case, we end up with an array of the String classby the time we reach line 55.

Line 55 of Listing 16-4 uses the array of string-roles to construct a GenericPrincipal object.GenericPrincipal is initialized with the identity of the user and the roles. GenericPrincipal alsoimplements the IPrincipal interface, which allows the portal to use the roles’ information toperform checks to determine whether a user is in a certain role.

After we have created the object that realizes the IPrincipal interface, GenericPrincipal,and associated that object with the Context.User property, we can perform simple tests todetermine whether a user is in a role such as the Admins role. Checks for user roles arecentralized in the Security.cs module.

Security.cs implements the static methods PortalSecurity.IsInRole and PortalSecurity.IsInRoles, which accept a single, string argument. The string is used as an argument to theHttpContext.Current.User.IsInRole method, returning a Boolean value that indicates whetherthe user is in a particular role represented by the argument string. If you perform a grep—oran equivalent search—you will see that PortalSecurity.IsInRoles is used to quickly determinehow the portal should be configured relative to a request. For example, the DesktopPortalBanner.ascx control uses PortalSecurity.IsInRoles to determine which tabs to display.

Programming with the FormsAuthentication ClassThe FormsAuthentication class provides functions for managing a ticket. Tickets representthe authentication information stored in a cookie. By exploring Listing 16-4, we can examinehow the FormsAuthentication class is used in the portal.

Line 22 of Listing 16-4 creates an instance of the authentication ticket indicating the user’sidentity on line 24, the time the ticket was issued on line 25, when the ticket expires on line 26,whether the cookie is persisted on line 27, and a string representing the user roles on line 28.After the ticket is created, the static method FormsAuthentication.Encrypt is invoked toencrypt the cookie to a string, and the cookie is written to the client. Lines 35 through 38store the cookie. (Cookies are stored as strings. Refer to the upcoming section “Programmingwith Cookies” for more information.)

Whether the portal is caching the roles information for the first time—when Request.IsAuthenticated == true—or the cookie already exists—the else condition beginning on

5 1 4 A d v a n c e d C # P r o g r a m m i n g

Page 542: Advanced c sharp programming

line 39 of Listing 16-4—the array of string-roles is populated, indicating the user roles. Theroles information is used to create an IPrincipal object, which permits us to perform tests todetermine whether the user has permissions to participate in a particular facet of the portal.

The basic idea is that if the user is authenticated, whether via Windows or Formsauthentication, the user’s roles are stored in a cookie. The cookie is retrieved when theuser’s roles need to be evaluated. The roles are stored in an IPrincipal object, which existsto provide a convenient way to examine those roles.

Programming with the IPrinicpal InterfaceThe IPrincipal interface defines a property, Identity, and a method, IsInRole. Classes thatimplement IPrincipal can be examined to determine the identity and roles of the user. TheIdentity property implements the IIdentity interface. Classes that implement IIdentity includeFormsIdentity, GenericIdentity, PassportIdentity, and WindowsIdentity. Each of these classescontains members that realize the IIdentity interface, in addition to members essential to aspecific kind of identification type.

IIdentity classes must define the AuthenticationType, IsAuthenticated, and Name properties.AuthenticationType is a string indicating the kind of authentication used. IsAuthenticated is aBoolean that indicates whether the user has been authenticated or not, and Name is the stringname of the user. IIdentity classes let us know if and how a user was authenticated and whothat user is.

Once we know the user is allowed to do something, IPrincipal classes tell us what the useris specifically allowed to do. The IsInRoles method is defined to accept a string and return aBoolean. The portal uses the GenericPrincipal, which realizes IPrincipal and implements theIdentity property and IsInRoles method for us. Thus, we can pass any string data to IsInRolesto evaluate whether or not that particular string represents a valid role in the application, gettinga Boolean result. The IBuySpy portal encapsulates the IPrincipal interaction in the Security.csmodule’s PortalSecurity class. An excerpt from the PortalSecurity class is provided inListing 16-5.

Listing 16-5 The PortalSecurity class uses the IPrincipal interface implemented by theContext.User property to test whether a user is in a specific role.

1: public static bool IsInRole(String role) {

2: return HttpContext.Current.User.IsInRole(role);

3: }

4:

5: public static bool IsInRoles(String roles) {

6:

7: HttpContext context = HttpContext.Current;

8:

9: foreach (String role in roles.Split( new char[] {';'} )) {

10:

11: if (role != "" && role != null && ((role == "All Users") ||

12: (context.User.IsInRole(role)))) {

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 1 5

Page 543: Advanced c sharp programming

5 1 6 A d v a n c e d C # P r o g r a m m i n g

13: return true;

14: }

15: }

16: return false;

17: }

In Listing 16-5, PortalSecurity implements IsInRole in terms of the IPrincipal.IsInRolemethod. The HttpContext.User property represents the object that implements IPrincipal.(This assignment was made on line 55 of Listing 16-4.) GenericPrincipal is queried todetermine whether the user is defined as a member in a specific role on line 2 and in theOr predicate on line 12 of listing 16-5.

The fundamental design of the portal relies on the premise that when a user is authenticated,and as the user navigates through the portal, the user’s IPrincipal information assigned to theHttpContext.User property can be examined to determine what the user is allowed to do.

Programming with CookiesI have mentioned cookies several times with the assumption that you have heard about themand that the concept is not completely foreign to you. Let’s take a moment to explore whatcookies are and how they are used, and, more important, what are appropriate uses for cookies.

Cookies are small pieces of information that are stored in memory while the browser isopen or in text files on the client workstation. Cookies are useful for storing bits of informationon the client’s computer. Cookies are easy to use, as you will see in a moment, but they presenta few minor stumbling blocks that you should be aware of.

Cookies can store only up to 4KB of information and, as a result, cannot be used to storeobjects like a DataSet. While most browsers support Cookies, the end user can delete cookiefiles (see Figure 16-1) or disable cookies (see Figure 16-2). With all of the security concernsthese days, it is likely that Internet users may have disabled cookies from the browsers.

What practical use is left for cookies then? Well, you can use cookies to store smallpieces of information that will make the user’s interaction with your Web application a biteasier without making it critical that cookies be enabled. For example, you can store userauthentication information, so that when a user returns to your Web site, you can acknowledgethe user’s return visit. What you shouldn’t do is store critical information like credit cardinformation, social security numbers, or other private information that adds to the generalconcern about using cookies. Cookies are reasonably secure because they are sent only tothe originating computer, and the information is not stored as clear text in the cookie file.

The IBuySpy portal uses cookies to store information that authenticates the user. Ifyou create an account in the portal, you can tell the portal to remember your accountinformation. The account information is stored as a cookie on the PC until you explicitlylog off. (Closing the browser does not delete the cookie.) Each time you return to the portal,you are authenticated using the cookie information (if the cookie exists). If you click theLogoff link in the portal, the cookie is deleted (see Figure 16-3).

Page 544: Advanced c sharp programming

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 1 7

Figure 16-1 Internet Explorer 6.0 allows you to delete cookies via the browser’s InternetOptions dialog.

Figure 16-2 Internet Explorer 6.0 allows you to disable cookies via the Advanced PrivacySettings dialog.

Page 545: Advanced c sharp programming

Managing CookiesCookies are represented by the System.Net.Cookie class in .NET. You can manage cookiesthrough the HttpResponse.Cookies property. The HttpResponse.Cookies property is read-onlyand is in an HttpCookieCollection derived from NameObjectCollectionBase. The Cookiesproperty supports adding and removing cookies from the collection and indexing cookieswith a string, representing the cookie key, or an integer value.

To store or retrieve a cookie, use the name of the cookie in parentheses as an argument tothe Response.Cookies property. For example, Response.Cookies[“portalroles”] will create oraccess an existing cookie if used as an l-value, and Request.Cookies[“portalroles”] will returnthe “portalroles” HttpCookie that will be sent to the server when used as an r-value. Whenyou have indexed a cookie, you can perform operations that are provided by the System.Net.Cookie class. These include providing comments and setting the expiration date and time,as well as a name, port, path, and value.

Using Cookies in the PortalThe portal uses the cookies to store portal-encrypted role information. This information isstored and retrieved to implement the IPrincipal object. From Listing 16-4, lines 35 through 38,we can see a typical use of the Cookies collection (see Listing 16-6).

Listing 16-6 An excerpt from Listing 16-4, focusing on the use of cookies in the portal

32: String cookieStr = FormsAuthentication.Encrypt(ticket);

33:

34: // Send the cookie to the client

35: Response.Cookies["portalroles"].Value = cookieStr;

36: Response.Cookies["portalroles"].Path = "/";

37: Response.Cookies["portalroles"].Expires = DateTime.Now.AddMinutes(1);

5 1 8 A d v a n c e d C # P r o g r a m m i n g

Figure 16-3 The Logoff link will log you out of the portal application and delete a cookie used toauthenticate you.

Page 546: Advanced c sharp programming

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 1 9

Line 32 of Listing 16-6 uses the static method to encrypt the cookie represented by theticket to a string. Line 35 adds the cookie to the Cookies collection. The cookie will bereferred to as “portalroles.” The encrypted value of the cookie is assigned to the Cookie.Valueproperty on line 35; the Cookie path is set to the root, indicating the URI on the server thatthis cookie applies to; and the cookie is set to expire in one minute from the time the cookieis added to the collection.

TIPIf the cookie path—see Listing 16-6, line 36—were set to a specific path, then the cookie would beavailable only in requests to that path.

NOTEIf you don’t express an expiration time for the cookie, then it will not be written to disk and will disappearwhen the browser is closed. Cookies without an expiration date and time are referred to as session cookies.Cookies with an expiration date and time are referred to as persistent cookies (they are “persisted” to disk).

The Logoff.aspx page contains code that responds to the user logging off and demonstrateshow to sign the user out and clear a cookie. Listing 16-7 contains the Page_Load event handlerfrom Logoff.aspx. (A user logs off by clicking the Logoff link, shown in Figure 16-3.)

Listing 16-7 The Logoff.aspx code that runs when the user logs off

1: private void Page_Load(object sender, System.EventArgs e)

2: {

3:

4: // Log User Off from Cookie Authentication System

5: FormsAuthentication.SignOut();

6:

7: // Invalidate roles token

8: Response.Cookies["portalroles"].Value = null;

9: Response.Cookies["portalroles"].Expires =

10: new System.DateTime(1999, 10, 12);

11: Response.Cookies["portalroles"].Path = "/";

12:

13: // Redirect user back to the Portal Home Page

14: Response.Redirect(Request.ApplicationPath);

15: }

The static method FormsAuthentication.SignOut logs the user out of the cookie authenticationsystem on line 5 of Listing 16-7. Line 8 sets the “portalroles” cookie value to null, andsetting the expiration to some time in the past ensures that the cookie is expired. After

Page 547: Advanced c sharp programming

5 2 0 A d v a n c e d C # P r o g r a m m i n g

logging off, the user is redirected to the default home page (line 14). (You can download anexperiment with the CookieDemo.sln, available at www.osborne.com, to practice readingand writing cookies.)

Implementing the SignIn ModuleSigning out of the portal is managed by the Logoff.aspx page. Logging in is handled by theSignIn.ascx module. We have talked about the SignIn.ascx control relative to the visual design;now, let’s take a few minutes to explore the SignIn.ascx code, which demonstrates howauthentication cookies are created. Listing 16-8 contains the code for the SignIn.ascx control.

Listing 16-8 This code implements the SignIn.ascx control, which authenticates a user andcreates an authentication ticket.

1: using System;

2: using System.Collections;

3: using System.ComponentModel;

4: using System.Data;

5: using System.Drawing;

6: using System.Web;

7: using System.Web.UI;

8: using System.Web.UI.WebControls;

9: using System.Web.UI.HtmlControls;

10: using System.Web.Security;

11:

12: namespace ASPNetPortal {

13:

14: public abstract class Signin : ASPNetPortal.PortalModuleControl {

15: protected System.Web.UI.WebControls.TextBox email;

16: protected System.Web.UI.WebControls.TextBox password;

17: protected System.Web.UI.WebControls.CheckBox RememberCheckbox;

18: protected System.Web.UI.WebControls.ImageButton SigninBtn;

19: protected System.Web.UI.WebControls.Label Message;

20:

21:

22: private void LoginBtn_Click(Object sender, ImageClickEventArgs e) {

23:

24: // Attempt to Validate User Credentials using UsersDB

25: UsersDB accountSystem = new UsersDB();

26: String userId = accountSystem.Login(email.Text, password.Text);

27:

28: if ((userId != null) && (userId != "")) {

29: // Use security system to set the UserID within a client-side

30: // Cookie

Page 548: Advanced c sharp programming

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 2 1

31: FormsAuthentication.SetAuthCookie(email.Text,

32: RememberCheckbox.Checked);

33: // Redirect browser back to originating page

34: Response.Redirect(Request.ApplicationPath);

35: }

36: else {

37: Message.Text =

38: "<" + "br" + ">Login Failed!" + "<" + "br" + ">";

39: }

40: }

41: public Signin() {

42: this.Init += new System.EventHandler(Page_Init);

43: }

44:

45: private void Page_Init(object sender, EventArgs e) {

46: //

47: // CODEGEN: This call is required by the ASP.NET Web Form

48: // Designer.

49: InitializeComponent();

50: }

51:

52: [Web Form Designer generated code]

53: }

54: }

The portal roles-based security works by authenticating the user from the Portal database’sUser table.The User table is encapsulated in the UserDB class created on line 25 of Listing 16-8.The user name, represented by the TextBox control named e-mail, and the password are sentto the UserDB.Login method on line 26. If the user is authenticated by the portal and theUser table, a UserID is returned. This constitutes roles-based authentication.

To indicate that the user has been authenticated—in this case, against the User table—the FormsAuthentication.SetAuthCookie method is invoked to create a cookie indicatingthat this specific user has already been authenticated. The Global.asax page uses this datawhen Request.IsAuthenticated is called; IsAuthenticated is True when SetAuthCookie hasbeen invoked. Finally, the authenticated user is redirected to the home page.

If the user does not exist in the portal, the else condition displays the text “Login Failed!”at the bottom of the SignIn.ascx module.

Administering UsersThe portal is user friendly from an administrative perspective. New users can register byclicking the register link in the SignIn module. The register link will redirect the user to theAdmin/Register.aspx page and create a new row in the User table in the Portal database.

Page 549: Advanced c sharp programming

5 2 2 A d v a n c e d C # P r o g r a m m i n g

Administrative users—sign in with e-mail-guest and password=guest, by default—cancreate new roles, add users to roles, modify page and module views, edit permissions, createtabs, and manage tab permissions.

To manage the portal, log in as a user who is in an Admin role and navigate to the Admintab. The portal has pretty good instructions for managing the portal and represents a goodmodel for a Web application that has a lightweight extensibility model.

Secondary TopicsThere as yet has been no mention of Code Access Security. The reason is that the portaldoesn’t use Code Access Security. I don’t want to diminish the importance of Code AccessSecurity for the obvious reason that it is important in .NET, and especially because Microsoftthinks it is important. What I do want is to provide some perspective.

Clearly, we have been building Web applications for several years without CodeAccess Security, and the IBuySpy portal is a great Web application that doesn’t useCode Access Security—so what is it and why is it important?

Traditionally, applications have provided security at the user level. If a user wasauthenticated, that user could perform the functions assigned to the role of the user. CodeAccess Security is complementary. Code Access Security is designed to authenticate thecode. Putting it in perspective, though, you do not have to use Code Access Security in everyapplication to program in .NET: unmanaged code—non-.NET applications—don’t use CodeAccess Security, and the Code Access Security policy that is configured by default is prettygood. The riskiest point of access for applications vis-à-vis malicious users is the Internet,and the other risky possibility is that an internal user accidentally performs an operation thatwreaks havoc on a system. The upshot is that you can focus your attention on code that couldpossibly cause problems if misused or accessed by an unauthorized user. These are bigproblems if you are writing code that is or can be used by other developers—specifically,class libraries that provide services for other developers.

If you are not writing libraries for general consumption or code that can be accessed fromthe Internet, then you can expend less energy on Code Access Security. The reason that CodeAccess Security is so important is that many very considerable enterprises are writing librariesthat are mission-critical and applications that can be accessed from the Web. For example,in our enterprise application in Oregon, if we allow users to indiscriminately delete records,then inmates—much to their pleasure—will be released early from prison. Almost as bad, ifwe release them too late, then the financial liability could be tremendous. To accommodatethese risky possibilities, we use Code Access Security to tighten up access to operations.

Let’s take a look at how Code Access Security is employed in .NET. I encourage you topick up a copy of the comprehensive book .NET Framework Security, by Brian A. LaMacchia,et al., (Addison-Wesley).

Page 550: Advanced c sharp programming

Implementing Code Access SecurityCode Access Security is employed by adding metadata in the form of attributes or security codedirectly to your application. Whether you use attributes, referred to as “declarative security,”or code, referred to as “imperative security,” you are basically using the same classes definedin the .NET framework.

The security permissions are added to the code, and they stipulate what the code iscapable of doing or would like to be granted permissions to be able to do. Security coversjust about everything imaginable.

Whether the code will actually be granted permission to perform a specific operationdepends on the security policy established by an administrator. (For example, if you are theadministrator on your PC, you can set the security policy.) The security policy can be setwith the command-line tool caspol.exe or the Microsoft .NET Framework Configurationapplet in the Administrative Tools folder. Refer to Figure 16-4 to see where the Microsoft.NET Framework Configuration applet is and Figure 16-5 for the Runtime Security Policyfolder in the .NET Framework Configuration view.

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 2 3

Figure 16-4 The Microsoft .NET Framework Configuration applet provides access to the securitypolicy management capabilities of .NET.

Page 551: Advanced c sharp programming

Remember that the default security permissions are pretty good. It is a good idea to havean outcome-based plan before modifying security permissions. I would treat changing thesecurity policy with the same deference you give to modifying the registry. Change thewrong permissions and your applications, even Visual Studio .NET, may stop functioningcorrectly. Fortunately, the last task in the tasks list (shown in Figure 16-5) enables you toreset all security policy levels.

Each node in the Runtime Security Policy equates to a different level of security. As thenames suggest, the Enterprise node refers to enterprise security policy and is the highestlevel, while User is the lowest. Each node contains code groups, permission sets, and PolicyAssemblies.

Code Groups refer to the code that falls under the specific policy. The default code groupis All_Code. The Permission Sets provide specific levels of access to resources based onwhich set a specific assembly belongs to. The Permission Sets contain the following sets:FullTrust, SkipVerification, Execution, Nothing, LocalIntranet, Internet, and Everything. Ifyou examine the permissions for each set, you will see that permissions are the most restrictedfor the Internet set. That is, code that falls under control of the Internet permission set has themost restrictive permissions. (Click Enterprise | Permission Sets | Internet and select ViewPermissions to see the permissions given to code that originates from the Internet.) The PolicyAssemblies node refers to assemblies that are used to evaluate the security policy. If youcreate custom permissions, you will associate the assembly that implements the custompermission with the list of Policy Assemblies.

5 2 4 A d v a n c e d C # P r o g r a m m i n g

Figure 16-5 The Runtime Security Policy folder permits administrators to set enterprise, machine,and user security permissions.

Page 552: Advanced c sharp programming

As developers, we can use .NET framework security declaratively by adding attributesto our assembly or imperatively by writing code. The next two sections provide a couple ofexamples of each. Because security is implemented as part of the framework, you can extendand customize security in .NET through inheritance. However, you should thoroughly explorethe comprehensive existing security capabilities before customizing security.

Declarative SecurityDeclarative security is implemented in .NET by adding an attribute to a code element. Theattribute will be the name of a specific security attribute class constructor with the necessarypositional or named arguments required to construct the security object.

Suppose your code needs File IO permissions to function correctly. You can requestFile IO permissions with an assembly-level attribute to indicate that the assembly must have FileIO permissions to function correctly. This doesn’t mean that your assembly will be givenFile IO permissions, but when it doesn’t function correctly, an administrator can diagnosethe problem and determine whether File IO should be granted or not (see Listing 16-9).

Listing 16-9 Declaratively requesting File IO permissions at the assembly level

[assembly: System.Security.Permissions.FileIOPermission(

System.Security.Permissions.SecurityAction.RequestMinimum,

Unrestricted=true)]

The enumerated value SecurityAction.RequestMinimum is a position argument to theattribute FileIOPermissionAttribute, and Unrestricted is a named argument. This attributeindicates that the assembly will need unrestricted File IO permissions at a minimum tofunction correctly.

When the attribute is applied declaratively—as is the case in Listing 16-9—the assemblycan be evaluated. If it will not be given the minimum requested permissions, the assemblywill not run. You can also request SecurityAction.RequestOptional and SecurityAction.RequestRefuse. RequestOptional will allow the code to run even if it will not be granted thepermissions, and RequestRefuse indicates that the code should not be granted the permission—in this case, File IO—even if the security policy would have normally granted it.

There are several security classes that can be explored in the System.Security namespace.

Imperative SecurityDeclarative security can be examined by the .NET framework before an assembly is allowedto execute. Imperative security works by creating instances of the security classes in codeand invoking operations on those security objects. The assembly will load and run, but aspecific section of code may not be able to run if it isn’t granted that permission. For example,Listing 16-10 demonstrates how to demand File IO permissions imperatively at run-time.

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 2 5

Page 553: Advanced c sharp programming

Listing 16-10 Imperative security demand for File IO

1: using System;

2: using System.IO;

3: using System.Security.Permissions;

4: using System.Diagnostics;

5:

6: namespace FileServices

7: {

8: public class MyFile

9: {

10: public MyFile(){}

11:

12: public static void Delete(string FileName)

13: {

14: if( !File.Exists(FileName)) return;

15:

16: (new FileIOPermission(

17: FileIOPermissionAccess.NoAccess, @"C:\WINNT\")).Demand();

18: // Simulate deleting the file

19: Debug.WriteLine(string.Format("Deleting file {0}", FileName));

20: //File.Delete(FileName);

21: }

22:

23: }

24: }

The FileServices.MyFile class is contained in a class library that provides a single service:it deletes files. (This class is representative of a part of a system File IO service.) Lines 16and 17 of Listing 16-10 create an instance of the FileIOPermission class (not the attribute)in line with FileIOPermissionAccess.NoAccess and the path to the C:\WINNT directory.The FileIOPermission object is created, and the Demand method is invoked. This code isunderstood to mean that all callers in the call stack must have FileIOPermission to accessfiles in the WINNT directory. If a stack walk reveals that the caller does not have permission,a SecurityException is raised, like the one in Figure 16-6.

The consumer ImperativeFileSecurityDemo.csproj and FileServices.csproj actuallyresided on the same machine, so I had to manipulate the security permissions for the

5 2 6 A d v a n c e d C # P r o g r a m m i n g

Page 554: Advanced c sharp programming

C h a p t e r 1 6 : S e c u r i t y a n d A u t h e n t i c a t i o n 5 2 7

ImperativeFileSecurityDemo.csproj application (the consumer). Listing 16-11 demonstratesthe assembly-level attribute used to disable FileIOPermissions for the C:\WINNT directoryfor the consumer application.

Listing 16-11 Refuse FileIOPermission for the C:\WINNT directory for the assembly thatcontains this attribute

[assembly: FileIOPermission(SecurityAction.RequestRefuse, All=@"C:\WINNT")]

You have a tremendous amount of control in .NET. The security policy manager andCode Access Security permissions, in conjunction with more traditional approaches, willpermit you to maintain tight access control over users and code in .NET.

SummaryThis chapter demonstrated how roles-based security is used in the IBuySpy portal. Youlearned how to use the FormsAuthentication class, create principals to manage users, andstore information in cookies.

Roles-based security is just one possibility in .NET. In the “Secondary Topics” section,you were also introduced to both declarative and imperative Code Access Security. From theperspective of management, this is probably the topic that will cause the most concern.

What you now know is that basic security capabilities are better in .NET than in any otherplatform prior. However, the greatest control over your network requires a holistic approach.

Figure 16-6 A System.Security.Security exception because the consumer application callingMyFile.Delete did not have FileIOPermission to perform the requested operation

Page 555: Advanced c sharp programming

Limiting physical access to computers, updating operating systems with security patches,maintaining proper network administration, employing roles-based and Windows userauthentication, and using Code Access Security are all part of the whole. Ignore one of thesefacets and some unscrupulous person is liable to take advantage of your code or resources.This is a big job, but .NET security now provides you with better control than ever before. Asa programmer, you can use existing good practices and combine Code Access Security whereappropriate and beneficial, working with your network administrator to obtain optimal security.

5 2 8 A d v a n c e d C # P r o g r a m m i n g

Page 556: Advanced c sharp programming

529

Index

AAboutBox control, implementing, 246–248

coding AboutBox, 247drawing AboutBox, 247surfacing constituent properties, 247–248

Access-Modifier, 11ACT (Application Center Test), 473ActiveX Media Player, 69Adapter Fill method, invoking, 146–147Adapter FillSchema method, invoking, 148Adapters, initializing, 144–146Adapters, understanding role of, 143–149

initializing adapters, 144–146invoking adapter Fill methods, 146–147invoking adapter FillSchema method, 148updating changes to data, 148–149

Add method, emitting, 357–359Adding

background images to PlayControl, 71–72components to Toolbox, 280–281controls to tables, 413–414controls to Toolbox, 95custom controls to PlayControl, 72–75data to caches, 493DataList to implement navigation tabs,

414–415DataTable objects to DataSet, 150–151elements from ListBoxes, 244–246files for Wizard Library templates, 177–178other members to custom attributes, 305TODO items to Task List, 96–99Trace listener, 221

ADO.NET, building database applications with,133–166

binding DataSet to DataGrid, 163

connecting to DataSources, 135–143demonstrated topics, 134–135displaying information in DataGrid,

159–160generating SQL with CommandBuilder, 162implementing TraceListener, 164–165quick review of ADO.NET namespaces, 135returning DataSet from Web Service,

163–164secondary topics, 162understanding role of adapters, 143–149using command objects, 161–162using DataReader for read-only data, 159using DataTable, 155–157using DataView, 158–159working with DataSet, 150–154

ADO.NET interfaces, using to declare types,139–143

ADO.NET namespaces, quick review of, 135Aggregation, 28–29Alias, creating with using directive, 40AngleEditor, implementing, 293–296Animals, rules in common for, 118–119Application caches, 500Application Center Test (ACT), 473Applications, implementing classes in console,

5–8Applying AttributeUsageAttribute, 306–307Argument, PaintValueEventArgs, 293Arguments

named, 304–305passing, 223–224passing by reference, 173positional, 303–304

Arrays, using for conditional logic, 77–79

Copyright 2002 by The McGraw-Hill Companies, Inc. Click Here for Terms of Use.

Page 557: Advanced c sharp programming

ASP.NET, IBuySpy and dynamic user interfacesin, 399–434

administrating portals, 432–434creating CSS (cascading style sheets),

401–408debugging IBuySpy portals, 434demonstrated topics, 400–401implementing portal banner, 408–431introducing mobile modules, 434secondary topics, 432

Assembliesdynamically updating, 113–115executing dynamic, 362–363loading dynamic, 362–363loading using Reflection, 331–333saving emitted, 352–353

Assemblies, emitting dynamic, 348–363emitting assemblies in memory, 349–352executing dynamic assemblies, 362–363loading dynamic assemblies, 362–363saving emitted assemblies to file, 352–353using builder objects to create IL, 353–355writing lines of OpCodes, 355–362

Assemblies, emitting regular expression, 366–367Assembly manager, code listing for, 36–49

broadcasting operations, 45–49defining classes, 41–42defining one’s namespace name, 40–41using statements, 39–40walking assembly using Reflection, 43–45

Assembly Viewer, 35–62code listing for Assembly Manager, 36–49demonstrated topics, 36secondary topics, 49–62

Assembly, walking using Reflection, 43–45Assembly.LoadFromPartialName, 332Asynchronous processing defined, 385Attribute class, defining custom, 303Attribute constructor, implementing, 303–305

named arguments, 304–305positional arguments, 303–304

Attributes, 33–34, 403adding other members to custom, 305commenting, 309–311control, 447defining forms, 510loginUrl, 510reading, 307–309setting carnivore, 127

Attributes, creating custom, 301–318adding other members to custom

attributes, 305applying AttributeUsageAttribute, 306–307commenting attributes, 309–311defining custom attribute class, 303demonstrated topics, 302–303implementing attribute constructor,

303–305implementing Extender Provider, 311–315reading attributes, 307–309reviewing

DesignerSerializationVisibilityAttribute,316–318

reviewing EditorBrowsableAttribute,315–316

secondary topics, 309AttributeUsageAttribute, applying, 306–307

attributes applied multiple times, 306–307specifying attribute targets, 306specifying if your attribute is inherited, 306

Authenticating users, 511–516Authentication, modifying Web.config file for

Windows, 507–508Authentication, modifying Web.config files for

forms, 509–511defining forms attributes, 510supplying user credentials, 510–511

Authentication, security and, 505–528administering users, 521–522demonstrated topics, 506–507implementing code access security,

523–527implementing forms authentication with

cookies, 509–520implementing SignIn module, 520–521secondary topics, 522using Windows NTLM authentication,

507–509Authentication, Windows, 513Authentication, Windows NTLM, 507–509

allowing authorization, 508–509denying authorization, 508–509modifying Web.config file for Windows

authentication, 507–508Authorization, allowing, 508–509Authorization, denying, 508–509

5 3 0 A d v a n c e d C # P r o g r a m m i n g

Page 558: Advanced c sharp programming

I n d e x 5 3 1

BBackground images, adding to PlayControl,

71–72Background, transparent UserControl, 254Banner, implementing portal, 408–431Base class constructor, emitting constructor that

calls, 361–362Base control properties, exploring, 447Base controls, reviewing PortalModuleControl,

446–447exploring base control properties, 447using control attributes, 447

BeginUpdate, 246, 256Behaviors

implementing trap, 279–280programming hunting, 128

Binders, defining, 327–330Binding and navigating, 252–253BindingFlags, specifying, 324–327BindingFlags values, combining, 325Block script, 454–455Blocks

coding script, 453–455creating script, 456finally, 139resource protection, 139

BooleanSwitch, switching between data providerswith, 141–143

Bounds property, 293Broadcaster, implementing, 47–48Broadcasting operations, 45–49

defining interface, 48–49implementing Broadcaster, 47–48

Brushes, linear gradient, 85–87Builder objects, using to create IL, 353–355ButtonCluster control, creating, 234–244

designing UserControl visual interface,235–239

surfacing constituent events, 239–244Buttons, determining equal subdivisions for,

237–239

CC#

passing arguments by reference in, 223–224strings in, 138

Cache expiration, specifying, 483–484Cache limitation, five minute, 482Caches

Application, 500caching objects in Session, 497–499managing Session, 500

Caches, Session, 495–500caching data in Session, 496–497caching objects in Session caches, 497–499managing Session caches, 500

Cachingby custom string and by header, 478–481enabling output, 484examples in IBuySpy portal, 482objects in Session caches, 497–499output, 469–504by parameters, 476–478, 484partial pages, 484–486portal, 487–493

Caching data, 486–495adding data to caches, 493creating file dependency, 493–495portal caching, 487–493

Caching data in Session, 496–497Caching, declarative, 471–481

caching by custom string and by header,478–481

caching by parameters, 476–478examining performance heuristics, 473–476

Caching pages, output, 471–484caching examples in IBuySpy portal, 482using declarative caching, 471–481using programmatic caching, 482–484

Caching, programmatic, 482–484caching by parameters, 484specifying cache expiration, 483–484

Calling Graphics.DrawImage method, 90–92Carnivore attributes, setting, 127Carnivore, creating, 127–128

programming hunting behavior, 128setting carnivore attributes, 127

Case-sensitive, Register statement is, 438Class header, specifying for HelpProvider,

311–312Class libraries, creating, 268–277

abstract generic behavior, 269adding finishing touches, 272–277defining new controls, 269–272

Class modifiers, 31

Page 559: Advanced c sharp programming

ClassesControl, 437ControlPaint, 89–90creating instances of, 22–23debug, 225–228defining ComplexConverter, 288–289defining custom attribute, 303disabling Debug, 227–228elements and, 406–408enabling Debug, 227–228implementing ContactInformation, 250–252implementing in console applications, 5–8Process, 99programming with FormsAuthentication,

514–515run-time conversion with

TypeDescriptor, 290System.Convert, 283working properties of PropertyInfo,

157–158Classes, defining, 5–22, 41–42, 406

garbage collection, 18–19implementing classes in console

applications, 5–8implementing Dispose method, 19implementing Events, 19–22implementing fields, 9–12implementing methods, 17–18implementing properties, 9–12indexed properties, 14–17non-deterministic destruction, 18–19property naming conventions, 12read-only properties, 12–14write-only properties, 14

Client, configuring Terrarium, 104–105Client, running Terrarium, 105–106

configuring ecosystem mode, 106configuring screen saver mode, 105configuring Terrarium mode, 105–106

Client updates, smart, 112–115Clipping regions, defining, 84Clock, implementing elapsed time, 93–94CLR Reference application, Assembly Viewer 2,

returning to, 338Code

convergent versus divergent, 146littering your, 154writing with macros, 187–188

Code access security, 115

Code access security, implementing, 523–527declarative security, 525imperative security, 525–527

Code access security, introducing, 129Code-behind module, programming in, 415–431

implementing event handlers in Global.asaxmodule, 416–417

implementing page load Init handler,426–431

reading portal settings from databases,417–426

reviewing portal design, 431Code, coding script blocks in HTML, 453–454,

453–455employing DataBinder, 455using block script, 454–455using NavigateUrl, 454

Code generators, creating, 188–191Code listing for assembly manager, 36–49

broadcasting operations, 45–49defining classes, 41–42defining one’s namespace name, 40–41using statements, 39–40walking assembly using Reflection, 43–45

Code, managing debug, 211–216Code, managing debug automatically

conditional compilation, 212–214using switches, 215–216

CodingAboutBox, 247operators methods, 31–32script blocks in HTML code, 453–455

Collection, garbage, 18–19CollectionBase, inheriting from, 123–126Columns

creating primary key, auto-increment,156–157

Timestamp, 327COM Interop, 99Command objects, 161–162Command prompt, reading from, 7–8Command window, running wizards from,

197–198CommandBuilder, generating SQL with, 162Commenting attributes, 309–311Common Environment Object Model,

introduction to, 186–187Common.js script, modifying, 180–181Compilation, conditional, 212–214

5 3 2 A d v a n c e d C # P r o g r a m m i n g

Page 560: Advanced c sharp programming

Complex data, returning from Web Service,388–392

ComplexConverter class, defining, 288–289Components

adding to Toolbox, 280–281EventLog, 203–207

Components, testing your, 278–280implementing test functions, 278–279implementing trap behavior, 279–280

Composite custom controls, creating, 444–446Computing, peer-to-peer, 115Conditional compilation, 212–214Conditional logic, using arrays for, 77–79Configuring

ecosystem mode, 106screen saver mode, 105session state servers, 501–502SQL Server for session management,

503–504Terrarium, 103–106Terrarium client, 104–105Terrarium mode, 105–106Terrarium server, 106

Connection pooling strategies, 144–146Console applications, implementing classes in,

5–8Constituent controls, promoting events in,

239–241Constituent events, surfacing, 239–244Constituent properties, surfacing, 247–248Constructors, 18, 76–77

control, 76emitting constructor that calls base class,

361–362implementing, 42implementing attribute, 303–305private, 222–223

ContactInformation class, implementing, 250–252Control attributes, 447Control class, 437Control constructors, 76Control Dispose methods, 76Control Library, creating, 234Control properties, exploring base, 447Control states, saving, 440–443

posting form data, 443retaining control value between form posts,

441–443

Control value, retaining between form posts,441–443

ControlPaint class, 89–90Controls

adding custom, 72–75adding to tables, 413–414adding to Toolbox, 95creating ButtonCluster, 234–244creating composite custom, 444–446creating custom server, 437–438creating custom Web, 435–468creating PickList, 244–246defining Tracker, 88designing User, 408–409implementing tracker, 87–92invalidating, 81loading XML documents with XML,

460–462loading XSL documents with XML,

460–462positioning, 258promoting events in constituent, 239–241Repeater, 467SignIn.ascx, 520sizing, 258ToolTip, 94–95Windows Forms, 52–57

Controls, creating user, 231–259creating ButtonCluster control, 234–244creating PickList control, 244–246custom painting in UserControls, 253–254defining data bound UserControl, 248–253demonstrated topics, 232dynamically positioning and sizing

controls, 258extending UserControls through inheritance,

254–255implementing AboutBox control, 246–248loading ListBoxes, 255–258secondary topics, 255transparent UserControl background, 254understanding UserControls, 232–234

Controls, rendering dynamically, 436–446creating composite custom controls,

444–446creating custom server controls, 437–438incorporating custom server controls into

Web pages, 438–440reviewing custom controls in portals, 446

I n d e x 5 3 3

Page 561: Advanced c sharp programming

saving controls states, 440–443using WebControl library project

templates, 446Controls, reviewing PortalModuleControl base,

446–447exploring base control properties, 447using control attributes, 447

Conventions, property naming, 12Convergent versus divergent code, 146Conversion, run-time, 290Converters, creating type, 282–290Cookie path, 519Cookieless session states, converting to, 495Cookies

managing, 518using in portal, 518–520

Cookies, expiration time for, 519Cookies, implementing forms authentication with,

509–520authenticating users, 511–516modifying Web.config files for forms

authentication, 509–511programming with cookies, 516–520

Cookies, programming with, 516–520managing cookies, 518using cookies in portal, 518–520

Copying existing project templates, 176–177Cow, improving, 126–127Create IL, using builder objects to, 353–355CreateChildControls method, 444Creating

alias with using directive, 40ButtonCluster control, 234–244carnivore, 127–128class library, 268–277code generators, 188–191composite custom controls, 444–446Control Library, 234critters, 116, 118–128CSS (cascading style sheets), 401–408custom attributes, 301–318custom portal modules, 462–465custom server controls, 437–438custom Web controls, 435–468custom wizards, 170–174data column mappings, 153–154DataTable objects, 156event source, 219file dependency, 493–495

GradientEditor, 291–293GUIs (graphical user interfaces) for

PlayerControl, 71–87herbivores, 119–126image modules, 451–452inline objects, 154instances of classes, 22–23Links modules, 452–456master-detail relationships, 151–152Merge Module, 281–282PickList control, 244–246plants, 116, 117–118project templates for Visual Studio .NET

Wizards, 175–184script blocks using Template Editor, 456simple Web Services, 380–388stored procedures, 426type converters, 282–290user controls, 231–259VSDir files, 182–184wizard launch files, 181–182wizard projects, 172

Creating dialog boxes, 221–224passing arguments by reference in C#,

223–224private constructors for dialogs, 222–223

Credentials, supplying user, 510–511Critter timing, 111Critters, creating, 116, 118–128

creating carnivore, 127–128creating herbivores, 119–126improving Cow, 126–127rules in common for animals, 118–119

Critters, introducing to Terrarium, 128–129CSS (cascading style sheets), creating, 401–408

defining classes, 406defining elements, 405–406navigating style sheets, 401–404using elements and classes, 406–408

Custom attribute class, defining, 303Custom attributes, adding other members to, 305Custom attributes, creating, 301–318

adding other members to customattributes, 305

applying AttributeUsageAttribute, 306–307commenting attributes, 309–311defining custom attribute class, 303demonstrated topics, 302–303

5 3 4 A d v a n c e d C # P r o g r a m m i n g

Page 562: Advanced c sharp programming

I n d e x 5 3 5

implementing attribute constructor,303–305

implementing Extender Provider, 311–315reading attributes, 307–309reviewing

DesignerSerializationVisibilityAttribute,316–318

reviewing EditorBrowsableAttribute,315–316

secondary topics, 309Custom controls

adding to PlayControl, 72–75creating composite, 444–446reviewing in portals, 446

Custom editor, applying with EditorAttribute, 296Custom log, deleting, 220Custom modules, integrating into IBuySpy

portals, 463–465Custom painting

implementing PaintEventHandler for, 263in UserControls, 253–254

Custom portal modules, creating, 462–465Custom server controls

creating, 437–438incorporating into Web pages, 438–440

Custom string, caching by, 478–481Custom Web controls, creating, 435–468Custom wizards, creating, 170–174Customizing Visual Studio .NET, 169–200

DData

adding to caches, 493binding tabs, 447–449posting form, 443returning complex, 388–392returning simple, 380–384updating changes to, 148–149using DataReader for read-only, 159

Data bound UserControl, defining, 248–253Data, caching, 486–495

adding data to caches, 493creating file dependency, 493–495portal caching, 487–493

Data, caching in Session, 496–497Data column mappings, creating, 153–154

Data providers, switching between, 141–143Data store

connecting to MS SQL Server, 139connecting to OLE DB, 136–139

Database applications with ADO.NET, building,133–166

binding DataSet to DataGrid, 163connecting to DataSources, 135–143demonstrated topics, 134–135displaying information in DataGrid,

159–160generating SQL with CommandBuilder, 162implementing TraceListener, 164–165quick review of ADO.NET namespaces, 135returning DataSet from Web Service,

163–164secondary topics, 162understanding role of adapters, 143–149using command objects, 161–162using DataReader for read-only data, 159using DataTable, 155–157using DataView, 158–159working with DataSet, 150–154

Databases, documenting and managing, 153Databases, reading portal settings from, 417–426

creating stored procedures, 426debugging stored procedures, 426defining input parameters for stored

procedures, 420–425defining output parameters for stored

procedures, 420–425implementing stored procedures, 417–420invoking GetPortalSettings stored

procedures, 425–426modifying stored procedures, 426

Databases, switching between, 140–141DataBinder, employing, 455DataGrid

binding DataSet to, 163displaying information in, 159–160

DataList, adding to implement navigation tabs,414–415

DataReader, using for read-only data, 159DataSet

adding DataTable objects to, 150–151binding to DataGrid, 163returning from Web Services, 163–164,

392–393

Page 563: Advanced c sharp programming

DataSet, working with, 150–154adding DataTable objects to DataSet,

150–151creating data column mappings, 153–154creating master-detail relationships,

151–152DataSources, connecting to, 135–143

connecting to MS SQL Server data store, 139connecting to OLE DB data store, 136–139using ADO.NET interfaces to declare types,

139–143DataTable, 155–157

creating DataTable objects, 156creating primary key, auto-increment

columns, 156–157working properties of PropertyInfo class,

157–158DataTable objects

adding to DataSet, 150–151creating, 156

DataView, 158–159DateTime value, 327DC (Device Context), 57, 65Debug classes, 225–228

assert everything, 225–227disabling, 227–228enabling, 227–228writing debug information to Output

window, 225Debug code, managing automatically, 211–216

conditional compilation, 212–214using switches, 215–216

Debug information, writing to Outputwindow, 225

Debugging stored procedures, 426Debugging strategy, tracing as, 207–209

tracing to event log, 208–209Debugging tools, history of, 226–227Declarative caching, 471–481

caching by custom string and by header,478–481

caching by parameters, 476–478examining performance heuristics, 473–476

Declarative security, 302, 525Default.js script, modifying, 178–180Defining

custom attribute class, 303fields, 9properties, 11

Deletingcustom log, 220event source, 220

Delphi language, 447Dependency, creating file, 493–495Description, providing Namespace and, 384Design, reviewing portal, 431Design view, working with, 409–415

adding controls to tables, 413–414adding DataList to implement navigation

tabs, 414–415using HTML tables to manage layouts,

411–413Designer, defining Windows forms, 297–298DesignerSerializationVisibilityAttribute,

reviewing, 316–318Designing UserControl visual interface, 235–239Destruction, non-deterministic, 18–19Destructors, 18, 76–77

locations of, 77Detail relationships, creating master, 151–152Device Context (DC), 57, 65Diagnostic tools

using StackFrame as, 211using StackTrace as, 209–210

Dialog boxes, creating, 221–224dialogs return DialogResult, 222

Dialog form strategies, 193–194DialogResult, dialogs return, 222Dialogs

private constructors for, 222–223return DialogResult, 222

Directive, creating alias with using, 40Disabling Debug class, 227–228DISCO is back, 374–375Disco.exe, 374Dispose methods, 76–77

control, 76implementing, 19

Divergent code, convergent versus, 146Documents, refueling XML, 457–458Documents, reviewing XSL, 459–460

selecting data conditionally, 460sorting XML, 460

Downloading Terrarium, 103–106Drawing

AboutBox, 247shapes, 81–82

5 3 6 A d v a n c e d C # P r o g r a m m i n g

Page 564: Advanced c sharp programming

I n d e x 5 3 7

Dynamic assembliesexecuting, 362–363loading, 362–363

Dynamic assemblies, emitting, 348–363emitting assemblies in memory, 349–352executing dynamic assemblies, 362–363loading dynamic assemblies, 362–363saving emitted assemblies to file, 352–353using builder objects to create IL, 353–355writing lines of OpCodes, 355–362

EEcosystem mode, configuring, 106Editor

creating script blocks using Template, 456custom, 296implementing type, 291–296

EditorAttribute, applying custom editor with, 296EditorBrowsableAttribute, reviewing, 315–316Elapsed time clock, implementing, 93–94Elements

and classes, 406–408defining, 405–406

Embedded resource file management, 57–58Emitted assemblies, saving to file, 352–353Enabling Debug class, 227–228Encapsulation, 28–29EndUpdate, 246, 256EntryWritten events

frequency of, 206handling, 205–206

Environment Object Model, introduction toCommon, 186–187

Event entries, responding to, 206Event handlers

implementing in Global.asax modules,416–417

implementing Paint, 79–80implementing UserControl Resize, 236–237

Event log, tracing to, 208–209Event loggers, 201–230

creating dialog boxes, 221–224demonstrated topics, 202–203dumping stack, 209–211

exploring System.Diagnosticsnamespace, 203

FileVersion information, 224–225implementing loggers, 216–221managing debug code automatically,

211–216measuring performance, 228–230secondary topics, 221tracing as debugging strategy, 207–209using Debug class, 225–228using EventLog component, 203–207

Event sourcecreating, 219deleting, 220

EventLog component, 203–207handling EntryWritten events, 205–206logging events to remote machines,

206–207writing to EventLog, 204–205

EventLog, writing to, 204–205Events

binding to dynamically, 347–348frequency of EntryWritten, 206handling EntryWritten, 205–206implementing, 19–22logging to remote machine, 206–207promoting in constituent controls, 239–241

Events, surfacing constituent, 239–244promoting events in constituent controls,

239–241viable alternatives to member promotion,

241–244Exceptions

catching specific, 95–96handling specific, 95–96handling Web Service, 384–385

Expression, assemblies, emitting regular,366–367

Extender Provider, implementing, 311–315applying ProviderPropertyAttribute, 312implementing HelpProvider, 313–314implementing IExtenderProvider interface,

312–313specifying class header for HelpProvider,

311–312using HelpProvider, 314–315

Page 565: Advanced c sharp programming

FFields

defining, 9implementing, 9–12

Fields, accessing properties and, 343–347setting and getting fields using FieldInfo,

345–346setting and getting fields using

InvokeMember, 344–345File dependency, creating, 493–495File management, embedded resource, 57–58Files

adding for Wizard Library templates,177–178

completing sample wizard launch, 196creating VSDir, 182–184creating wizard launch, 181–182Machine.config, 509saving emitted assemblies to, 352–353Web.config, 507, 507–508

FileStream, writing to, 131–132FileVersion information, 224–225Fill method, invoking adapter, 146–147Filling shapes, 81–82FillSchema method, invoking adapter, 148Finally block, 139Finally handler, try, 139Form data, posting, 443Form posts, retaining control value between,

441–443Form strategies, dialog, 193–194Formats, reviewing Web Services wire, 378–379Forms attributes, defining, 510Forms authentication, implementing with cookies,

509–520authenticating users, 511–516modifying Web.config files for forms

authentication, 509–511programming with cookies, 516–520

Forms authentication, modifying Web.config filesfor, 509–511

defining forms attributes, 510supplying user credentials, 510–511

Forms Controls, Windows, 52–57using LinkLabel, 56–57using MainMenu, 55using OpenFileDialog, 55

using RichTextBox, 55–56using StatusBar, 54working with Form Control, 52–54

Forms designer, defining Windows, 297–298Forms, using GraphicsPath objects to create

shaped, 83–85FormsAuthentication class, programming with,

514–515Foundations, language, 3–34Framework, reviewing Terrarium, 116Functions, implementing test, 278–279

GGarbage collection, 18–19Garbage in, garbage out (G.I.G.O.), 192GDI+ defined, 64–67

GDI+ namespaces, 65understanding GDI+ programming model,

65–67GDI+ namespaces, 65GDI+ programming model, understanding, 65–67GDIs (graphics device interfaces), 64General programming strategies, 146GetPortalSettings stored procedures, invoking,

425–426G.I.G.O. (garbage in, garbage out), 192Global.asax modules, implementing event

handlers in, 416–417Gradient brushes, linear, 85–87GradientEditor, creating, 291–293Graphical user interfaces (GUIs), 71–87Graphics device interfaces (GDIs), 64Graphics objects, 79–82

applying transforms to, 82–83drawing shapes, 81–82filling shapes, 81–82implementing PaintEventHandler, 79–80invalidating controls, 81overloading OnPaint method, 81

Graphics.DrawImage method, calling, 90–92GraphicsPath objects, using to create shaped

forms, 83–85GUI is king, 233–234Guidelines, overloading operator, 32–33GUIs (graphical user interfaces), 71–87

5 3 8 A d v a n c e d C # P r o g r a m m i n g

Page 566: Advanced c sharp programming

HHandler, implementing page load Init, 426–431Handlers, try finally, 139Headers

caching by, 478–481class, 311–312

Hello World, 6–7HelpProvider, 314–315

implementing, 313–314specifying class header for, 311–312

Herbivores, creating, 119–126inheriting from CollectionBase, 123–126inheriting from ReadOnlyCollectionBase,

123–126iterating collection of objects, 123

Heuristics, examining performance, 473–476HTML code, coding script blocks in, 453–454,

453–455employing DataBinder, 455using block script, 454–455using NavigateUrl, 454

HTML tables, using to manage layouts, 411–413HTTPServerUtility, 466Hunting behavior, programming, 128

IIBuySpy and dynamic user interfaces in

ASP.NET, 399–434administrating portals, 432–434creating CSS (cascading style sheets),

401–408debugging IBuySpy portals, 434demonstrated topics, 400–401implementing portal banner, 408–431introducing mobile modules, 434secondary topics, 432

IBuySpy portalscaching examples in, 482debugging, 434integrating custom modules into, 463–465

IConvertible, implementing, 283–287IDbConnection, switching between databases

using, 140–141Idioms, implementing metaclass, 364–365IDTWizard interface, implementing, 172

IExtenderProvider interface, implementing,312–313

IIS, Web.config file is not served by, 507IL (Intermediate Language), 34, 43, 320IL, using builder objects to create, 353–355Image modules, creating, 451–452Images, adding background, 71–72Imperative security, 302, 525–527Implementing

AboutBox control, 246–248AngleEditor, 293–296attribute constructor, 303–305Broadcaster, 47–48constructors, 42Dispose method, 19elapsed time clock, 93–94events, 19–22Extender Provider, 311–315fields, 9–12HelpProvider, 313–314IConvertible, 283–287IExtenderProvider interface, 312–313interfaces, 24–25loggers, 216–221methods, 17–18PaintEventHandler, 79–80PaintEventHandler for custom painting, 263PlayerControl, 67–71properties, 9–12test functions, 278–279Tracker control, 87–92trap behavior, 279–280type editor, 291–296TypeConverter, 287–290UserControl, 248–250Web Services, 371–397

Indexed properties, 14–17Information

displaying in DataGrid, 159–160requesting type, 338–340writing debug, 225

Information, output caching and persisting state,469–504

caching data, 486–495caching partial pages, 484–486configuring session state servers, 501–502configuring SQL Server for session

management, 503–504demonstrated topics, 470

I n d e x 5 3 9

Page 567: Advanced c sharp programming

output caching pages, 471–484secondary topics, 500–501using Application caches, 500using Session caches, 495–500

Inheritance, 25–28example, 26–28extending UserControls through, 254–255general guidelines for, 26terminology, 25–26

Init handler, implementing page load, 426–431Initializing adapters, 144–146Inline objects, creating, 154Input parameters, defining for stored procedures,

420–425Installing Terrarium, 103–106Instances of classes, creating, 22–23Interface strategies, user, 191–193Interfaces

defining, 23–24, 48–49designing UserControl visual, 235–239generating user, 333–337implementing, 24–25implementing IDTWizard, 172implementing IExtenderProvider, 312–313implementing ISerializable, 394–397implementing wizard user, 191–194programming with IPrincipal, 515–516using ADO.NET, 139–143

Intermediate Language (IL), 34, 43, 320Interop, COM, 99Invocation, dynamic member, 323–331

defining Binder, 327–330defining member to invoke, 324passing parameters to InvokeMember,

330–331specifying BindingFlags, 324–327

Invocation, Windows Forms synchronousmethod, 62

InvokeMemberpassing parameters to, 330–331setting and getting fields using, 344–345setting and getting properties using, 346

IPostBackEventHandler, implementing, 443IPrincipal interface, programming with, 515–516ISerializable interface, implementing, 394–397

JJscript .NET, returning to, 198–199

KKiosk, Video, 63–100

demonstrated topics, 64examining PlayerControl, 67–92GDI+ defined, 64–67secondary topics, 93–99

LLanguage foundations, 3–34

attributes, 33–34object-oriented basics, 4–31operator overloading, 31–33Reflection, 34

LanguagesDelphi, 447.NET, 323spoken, 447

Languages, support for multiple programming, 115Layouts, using HTML tables to manage, 411–413Libraries

creating class, 268–277creating Control, 234

Library project templates, using WebControl, 446Limitations, overloading operator, 32–33Linear gradient brushes, 85–87Lines, writing of OpCodes, 355–362LinkLabel, 56–57Links modules creating, 452–456

creating script blocks using TemplateEditor, 456

Links modules, creating, coding script blocks inHTML code, 453–455

List, adding TODO items to Task, 96–99ListBoxes

adding elements from, 244–246using ThreadPool to load, 256–258

ListBoxes, loading, 255–258using BeginUpdate, 256using EndUpdate, 256using ThreadPool to load ListBox, 256–258

5 4 0 A d v a n c e d C # P r o g r a m m i n g

Page 568: Advanced c sharp programming

ListBoxes, removing elements from, 244–246Listener, adding Trace, 221Listing, code, 36–49Loggers, event, 201–230

creating dialog boxes, 221–224demonstrated topics, 202–203dumping stack, 209–211exploring System.Diagnostics

namespace, 203FileVersion information, 224–225implementing loggers, 216–221managing debug code automatically,

211–216measuring performance, 228–230secondary topics, 221tracing as debugging strategy, 207–209using debug class, 225–228using EventLog component, 203–207

Loggers, implementing, 216–221adding Trace listener, 221creating event source, 219deleting custom log, 220deleting event source, 220determining if source exists, 220

Logging events to remote machines, 206–207Logic, using arrays for conditional, 77–79LoginUrl attribute, 510Logs

deleting custom, 220tracing to event, 208–209

MMachine.config file, 509Machines, logging events to remote, 206–207Macros

testing wizards with, 197writing, 185–186writing code with, 187–188

MainMenu, 55Manage layouts, using HTML tables to, 411–413Management

configuring SQL Server for session,503–504

embedded resource file, 57–58Manager, code listing for assembly, 36–49Mappings, creating data column, 153–154Master-detail relationships, creating, 151–152

Media Player, ActiveX, 69Member invocation, dynamic, 323–331

defining Binder, 327–330defining member to invoke, 324passing parameters to InvokeMember,

330–331specifying BindingFlags, 324–327

Member modifiers, 31Member promotion, viable alternatives to,

241–244Memory, emitting assemblies in, 349–352MemoryStream objects, serializing organisms

using, 130–131Merge Module, creating, 281–282Metaclass idioms, implementing, 364–365Method parameters, 341–343Methods

calling Graphics.DrawImage, 90–92coding operator, 31–32control Dispose, 76CreateChildControls, 444defined, 146Dispose, 76–77emitting Add, 357–359implementing, 17–18implementing Dispose, 19invoking adapter Fill, 146–147invoking adapter FillSchema, 148Object.Finalize, 18overloading OnPaint, 81passing instance of control to, 263reflecting, 341–343SetStyle, 88–89

Microsoft Desktop Engine (MSDE), 139Microsoft Mobile Internet Toolkit (MMIT), 434MMIT (Microsoft Mobile Internet Toolkit), 434Mobile modules, introducing, 434Models

introduction to Common EnvironmentObject, 186–187

understanding GDI+ programming, 65–67Modes

configuring ecosystem, 106configuring screen saver, 105configuring Terrarium, 105–106

Modifiersclass, 31member, 31using, 30–31

I n d e x 5 4 1

Page 569: Advanced c sharp programming

5 4 2 A d v a n c e d C # P r o g r a m m i n g

Modifyingcommon.js script, 180–181default.js script, 178–180stored procedures, 426

Module, creating Merge, 281–282Modules

creating custom portal, 462–465creating image, 451–452creating Links, 452–456implementing event handlers in

Global.asax, 416–417implementing SignIn, 520–521implementing XML/XSL transform,

457–462introducing mobile, 434programming in code-behind, 415–431visually designing SignIn, 449–451

MS SQL Server data store, connecting to, 139MSDE (Microsoft Desktop Engine), 139Multicasting, workings of, 22Multiple programming languages, support for, 115Multithreading, 58–62

queuing work items in ThreadPool, 61ThreadPool threading, 60–61Windows Forms synchronous method

invocation, 62

NName, defining one’s namespace, 40–41Named arguments, 304–305Namespace and Description, providing, 384Namespace name, defining one’s, 40–41Namespaces

exploring System.Diagnostics, 203GDI+, 65quick review of ADO.NET, 135

Naming conventions, property, 12NavigateUrl, 454Navigating, binding and, 252–253Navigation tabs, adding DataList to implement,

414–415.NET framework, exploring with Reflection,

337–348accessing properties and fields dynamically,

343–347binding to events dynamically, 347–348reflecting method parameters, 341–343reflecting methods, 341–343

requesting type information, 338–340returning to CLR Reference application,

Assembly Viewer 2, 338.NET languages, 323Net start servicename, 502Net stop servicename, 502No BSDs! (No Blue Screen of Death), 71Non-deterministic destruction, 18–19NT Lan Manager (NTLM), 507NTLM authentication, Windows, 507–509

allowing authorization, 508–509denying authorization, 508–509modifying Web.config file for Windows

authentication, 507–508NTLM (NT Lan Manager), 507

OObj.Command(0), 17Object Model, introduction to Common

Environment, 186–187Object-oriented basics, 4–31

aggregation, 28–29creating instances of classes, 22–23defining classes, 5–22defining interfaces, 23–24encapsulation, 28–29implementing interfaces, 24–25inheritance, 25–28polymorphism, 29–30using modifiers, 30–31

Object-oriented programming language(OOPL), 4

Object.Finalize method, 18Objects

adding DataTable, 150–151applying transforms to graphics, 82–83caching in Session caches, 497–499command, 161–162creating DataTable, 156creating inline, 154generating user interfaces with reflected,

333–337graphics, 79–82instantiating with Reflection, 322–323iterating collection of, 123type, 321–322using GraphicsPath, 83–85

Page 570: Advanced c sharp programming

Objects, serializing, 129–132, 365–366serializing organisms using MemoryStream

objects, 130–131writing to FileStream, 131–132

Objects, serializing organisms usingMemoryStream, 130–131

OLE DB data store, connecting to, 136–139OnPaint method, overloading, 81OOPL (object-oriented programming language), 4OpCodes, writing lines of, 355–362

emitting Add method, 357–359emitting constructor that calls base class

constructor, 361–362emitting indexer property and property

methods, 359–361OpenFileDialog, 55Operations, broadcasting, 45–49Operator guidelines, overloading, 32–33Operator limitations, overloading, 32–33Operator methods, coding, 31–32Operator overloading, 31–33

coding operators methods, 31–32overloading operator guidelines, 32–33overloading operator limitations, 32–33

Organisms, serializing using MemoryStreamobjects, 130–131

Output caching and persisting state information,469–504

caching data, 486–495caching partial pages, 484–486configuring session state servers, 501–502configuring SQL Server for session

management, 503–504demonstrated topics, 470output caching pages, 471–484secondary topics, 500–501using Application caches, 500using Session caches, 495–500

Output caching, enabling, 484Output caching pages, 471–484

caching examples in IBuySpy portal, 482using declarative caching, 471–481using programmatic caching, 482–484

Output parameters, defining for storedprocedures, 420–425

Output window, writing debug information to, 225Overloading operator, 31–33

guidelines, 32–33limitations, 32–33

PPage load Init handler, implementing, 426–431Pages

caching partial, 484–486incorporating custom server controls into

Web, 438–440Pages, output caching, 471–484

caching examples in IBuySpy portal, 482using declarative caching, 471–481using programmatic caching, 482–484

Paint event handler, implementing, 79–80PaintEventHandler, implementing for custom

painting, 263Painting

custom, 253–254implementing PaintEventHandler for

custom, 263PaintValueEventArgs argument, 293Parameters

caching by, 476–478, 484method, 341–343passing to InvokeMember, 330–331tabid, 417tabindex, 417

Partial pages, caching, 484–486Path, cookie, 519Peer-to-peer computing, 115Pens, 87Performance heuristics, examining, 473–476Performance, measuring, 228–230Persisting state information, output caching and,

469–504PickList control, creating, 244–246

adding elements from ListBoxes, 244–246BeginUpdate, 246EndUpdate, 246removing elements from ListBoxes,

244–246Plants

creating, 116, 117–118introducing to Terrarium, 128–129

PlayControl, 69adding background images to, 71–72adding custom controls to, 72–75

Player, ActiveX Media, 69PlayerControl, creating GUIs (graphical user

interfaces) for, 71–87

I n d e x 5 4 3

Page 571: Advanced c sharp programming

5 4 4 A d v a n c e d C # P r o g r a m m i n g

PlayerControl, examining, 67–92creating GUIs (graphical user interfaces) for

PlayerControl, 71–87implementing PlayerControl, 67–71

PlayerControl, implementing, 67–71Polymorphism, 29–30Portal banner, implementing, 408–431

designing user controls, 408–409programming in code-behind module,

415–431working with design view, 409–415

Portal caching, 487–493Portal, caching examples in IBuySpy, 482Portal design, reviewing, 431Portal settings, reading from databases, 417–426

creating stored procedures, 426debugging stored procedures, 426defining input parameters for stored

procedures, 420–425defining output parameters for stored

procedures, 420–425implementing stored procedures, 417–420invoking GetPortalSettings stored

procedures, 425–426modifying stored procedures, 426

Portal, using cookies in, 518–520PortalModuleControl base controls, reviewing,

446–447exploring base control properties, 447using control attributes, 447

Portalsadministrating, 432–434debugging IBuySpy, 434integrating custom modules into IBuySpy,

463–465reviewing custom controls in, 446

Positional arguments, 303–304Positioning controls, 258Posts, retaining control value between form,

441–443Practical Reflection, 319–367

demonstrated topics, 320discovering and using types dynamically,

320–337emitting dynamic assemblies, 348–363emitting regular expression assemblies,

366–367exploring .NET framework with Reflection,

337–348

implementing metaclass idioms, 364–365Reflection and Web Services, 363secondary topics, 363serializing objects, 365–366

Printing, basic, 57Private constructors for dialogs, 222–223Procedures

creating stored, 426debugging stored, 426defining input parameters for stored,

420–425defining output parameters for stored,

420–425implementing stored, 417–420invoking GetPortalSettings stored, 425–426modifying stored, 426

Process class, 99Processing, asynchronous, 385Programmatic caching, 482–484

caching by parameters, 484specifying cache expiration, 483–484

Programmingin code-behind module, 415–431with cookies, 516–520with FormsAuthentication class, 514–515hunting behavior, 128with IPrincipal interface, 515–516languages, support for multiple, 115model, understanding GDI+, 65–67strategies, general, 146XML Web Service, 107–111

Project templates, copying existing, 176–177Projects, creating wizard, 172Promotion, viable alternatives to member, 241–244Prompt, reading from command, 7–8Properties

Bounds, 293defining, 11exploring base control, 447implementing, 9–12indexed, 14–17read-only, 12–14surfacing constituent, 247–248write-only, 14

Properties and fields, accessing dynamically,343–347

setting and getting properties usingInvokeMember, 346

setting and getting properties usingPropertyInfo, 346–347

Page 572: Advanced c sharp programming

I n d e x 5 4 5

Property naming conventions, 12PropertyInfo class, working properties of,

157–158PropertyInfo, setting and getting properties using,

346–347Protection block, resource, 139Prototyping, rapid control, 262–268

creating brushes, 264–265creating rectangle, 265creating StringFormat object, 265–268drawing text, 264examining problem, 263–268implementing PaintEventHandler for

custom painting, 263passing instance of control to methods, 263

Provider, implementing Extender, 311–315ProviderPropertyAttribute, applying, 312Providers, switching between data, 141–143

Q@-quoted string, 138Quoted string, 138

RRAD (Rapid Application Design), 262RaisePostDataChangedEvent, 443Rapid Application Design (RAD), 262Read-only data, using DataReader for, 159Read-only properties, 12–14Reading

attributes, 307–309from command prompt, 7–8

ReadOnlyCollectionBase, inheriting from,123–126

Refactoring defined, 146Reference, passing arguments by, 173Reference types, value types versus, 49–51Reflected objects, generating user interfaces with,

333–337Reflecting methods, 341–343Reflection, 34

defined, 43instantiating objects with, 322–323

loading assemblies using, 331–333using, 43–45walking assembly using, 43–45and Web Services, 363

Reflection, exploring .NET framework with,337–348

accessing properties and fields dynamically,343–347

binding to events dynamically, 347–348reflecting method parameters, 341–343reflecting methods, 341–343requesting type information, 338–340returning to CLR Reference application,

Assembly Viewer 2, 338Reflection, practical, 319–367

demonstrated topics, 320emitting dynamic assemblies, 348–363emitting regular expression assemblies,

366–367exploring .NET framework with Reflection,

337–348implementing metaclass idioms, 364–365Reflection and Web Services, 363secondary topics, 363serializing objects, 365–366

Regasm utility, 199Register statement is case-sensitive, 438Relationships, creating master-detail, 151–152Remote machines, logging events to, 206–207Removing elements from ListBoxes, 244–246Repeater control, 467Resize event handler, implementing UserControl,

236–237Resource file management, embedded, 57–58Resource protection block, 139Reviewing

DesignerSerializationVisibilityAttribute,316–318

EditorBrowsableAttribute, 315–316RichTextBox, 55–56RTTI (run-time type information), 320Rules in common for animals, 118–119Run-time conversion with TypeDescriptor

class, 290Run-time type information (RTTI), 320Running Terrarium client, 105–106

Page 573: Advanced c sharp programming

SScope, defining using, 40Screen saver mode, configuring, 105Script blocks, coding in HTML code, 453–454,

453–455employing DataBinder, 455using block script, 454–455using NavigateUrl, 454

Scriptsblock, 454–455modifying common.js, 180–181modifying default.js, 178–180

Secondary topicsadding controls to Toolbox, 95adding TODO items to Task List, 96–99catching specific exceptions, 95–96COM Interop, 99handling specific exceptions, 95–96implementing elapsed time clock, 93–94implementing ISerializable interface,

394–397SoapFormatter, 393–394using Process class, 99using ToolTip control, 94–95

Securitycode access, 115declarative, 302, 525imperative, 302, 525–527

Security and authentication, 505–528administering users, 521–522demonstrated topics, 506–507implementing code access security,

523–527implementing forms authentication with

cookie is, 509–520implementing SignIn module, 520–521secondary topics, 522using Windows NTLM authentication,

507–509Security, implementing code access, 523–527

declarative security, 525imperative security, 525–527

Security, introducing code access, 129Sentinel, 513Serializing objects, 129–132Server controls, creating custom, 437–438Server data store, connecting to MS SQL, 139

Serversconfiguring session state, 501–502configuring SQL, 503–504configuring Terrarium, 106

Serviceimplementing Web, 371–397stop and start Windows, 502

Servicename, net start, 502Servicename, net stop, 502Session caches, 495–500

caching objects in, 497–499caching objects in Session caches, 497–499managing, 500managing Session caches, 500

Session, caching data in, 496–497Session management, configuring SQL Server

for, 503–504Session state servers, configuring, 501–502Session states, converting to cookieless, 495SetStyle method, 88–89Shaped forms, using GraphicsPath objects to

create, 83–85Shapes

drawing, 81–82filling, 81–82

SignIn modules, visually designing, 449–451SignIn module, implementing, 520–521SignIn.ascx control, 520Singleton defined, 47Sizing controls, 258SoapFormatter, 393–394Source, deleting event, 220Source, determining if it exists, 220Special effects text, 261–299Specific exceptions

catching, 95–96handling, 95–96

Spoken languages, 447SQL, generating with CommandBuilder, 162SQL Server, configuring for session management,

503–504Stack, dumping, 209–211

using StackFrame as diagnostic tool, 211using StackTrace as diagnostic tool,

209–210StackFrame, using as diagnostic tool, 211StackTrace, using as diagnostic tool, 209–210State information, output caching and persisting,

469–504

5 4 6 A d v a n c e d C # P r o g r a m m i n g

Page 574: Advanced c sharp programming

caching data, 486–495caching partial pages, 484–486configuring session state servers, 501–502configuring SQL Server for session

management, 503–504demonstrated topics, 470output caching pages, 471–484secondary topics, 500–501using Application caches, 500using Session caches, 495–500

Statements, 39–40creating alias with using directive, 40defining using scope, 40using, 139

Statesconverting to cookieless session, 495saving control, 440–443

StatusBar, 54Stored procedures

creating, 426debugging, 426defining input parameters for, 420–425defining output parameters for, 420–425implementing, 417–420invoking GetPortalSettings, 425–426modifying, 426

Strategiesconnection pooling, 144–146dialog form, 193–194general programming, 146user interface, 191–193

Stringsin C#, 138custom, 478–481@-quoted, 138quoted, 138

Style sheets, navigating, 401–404Subroutine defined, 185Switches, 215–216Synchronous method invocation, Windows

Forms, 62System.Convert class, 283System.Diagnostics namespace, exploring, 203

TTabid parameters, 417Tabindex parameters, 417

Tablesadding controls to, 413–414HTML, 411–413

Tabs, adding DataList to implement navigation,414–415

Tabs data, binding, 447–449Task List, adding TODO items to, 96–99Teleporter, 112Template Editor, creating script blocks using, 456Templates

adding files for Wizard Library, 177–178copying existing project, 176–177using WebControl library project, 446

Temporary variables, 154Terrarium, 101–132

configuring, 103–106creating critters, 116creating plants, 116demonstrated topics, 102–103downloading, 103–106installing, 103–106introducing code access security, 129introducing plants and critters to, 128–129introducing plants and critters to Terrarium,

128–129playing Terrarium, 107–115reviewing Terrarium framework, 116secondary topics, 129serializing objects, 129–132understanding XML Web Services in, 107

Terrarium clientconfiguring, 104–105running, 105–106

Terrarium framework, reviewing, 116Terrarium mode, configuring, 105–106Terrarium, playing, 107–115

code access security, 115critter timing, 111environment reporting every six minutes,

107–111peer-to-peer computing, 115smart client updates, 112–115support for multiple programming

languages, 115teleporter, 112

Terrarium server, configuring, 106Test functions, implementing, 278–279Testing, defined, 202Testing, wizards, 173–174

I n d e x 5 4 7

Page 575: Advanced c sharp programming

5 4 8 A d v a n c e d C # P r o g r a m m i n g

Text, special effects, 261–299adding components to Toolbox, 280–281applying custom editor with

EditorAttribute, 296creating class library, 268–277creating Merge Module, 281–282creating type converters, 282–290defining Windows forms designer, 297–298demonstrated topics, 262implementing type editor, 291–296rapid control prototyping, 262–268secondary topics, 282testing your components, 278–280

Threading, ThreadPool, 60–61ThreadPool

queuing work items in, 61threading, 60–61using to load ListBox, 256–258

Time clock, implementing elapsed, 93–94Timestamp column, 327Timing, critter, 111TODO items, adding to Task List, 96–99Toolbox

adding components to, 280–281adding controls to, 95

Toolshistory of debugging, 226–227using StackFrame as diagnostic, 211using StackTrace as diagnostic, 209–210

ToolTip control, 94–95Trace listener, adding, 221TraceListener, implementing, 164–165Tracing

as debugging strategy, 207–209to event log, 208–209

Tracker controldefining, 88implementing, 87–92

Transform modules, implementing XML/XSL,457–462

Transforms, applying to graphics objects, 82–83Trap behavior, implementing, 279–280Try finally handler, 139Type converters, creating, 282–290

implementing IConvertible, 283–287implementing TypeConverter, 287–290

Type editor, implementing, 291–296creating GradientEditor, 291–293implementing AngleEditor, 293–296

Type information, requesting, 338–340

Type objects, 321–322TypeConverter, implementing, 287–290

defining ComplexConverter class, 288–289run-time conversion with TypeDescriptor

class, 290using TypeConverterAttribute, 289–290

TypeConverterAttribute, 289–290TypeDescriptor class, run-time conversion

with, 290Types

reference, 49–51using ADO.NET interfaces to declare,

139–143value, 49–51

Types, discovering and using dynamically,320–337

dynamic member invocation, 323–331generating user interfaces with reflected

objects, 333–337instantiating objects with Reflection,

322–323loading assemblies using Reflection,

331–333using type objects, 321–322

UUnit testing, defined, 202Updates, smart client, 112–115Updating assemblies, dynamically, 113–115User control; See also UserControlUser control, creating, 231–259

creating ButtonCluster control, 234–244creating PickList control, 244–246custom painting in UserControls, 253–254defining data bound UserControl, 248–253demonstrated topics, 232dynamically positioning and sizing

controls, 258extending UserControls through inheritance,

254–255implementing AboutBox control, 246–248loading ListBoxes, 255–258secondary topics, 255transparent UserControl background, 254understanding UserControls, 232–234

User control, designing, 408–409User credentials, supplying, 510–511User interfaces

Page 576: Advanced c sharp programming

generating with reflected objects, 333–337strategies, 191–193

User interfaces, implementing wizard, 191–194User interfaces, implementing Wizard

dialog form strategies, 193–194user interface strategies, 191–193

User interfaces in ASP.NET, IBuySpy anddynamic, 399–434

administrating portals, 432–434creating CSS (cascading style sheets),

401–408debugging IBuySpy portals, 434demonstrated topics, 400–401implementing portal banner, 408–431introducing mobile modules, 434secondary topics, 432

UserControlcustom painting in, 253–254defined, 233extending through inheritance, 254–255implementing, 248–250

UserControl background, transparent, 254UserControl, defining data bound, 248–253

binding and navigating, 252–253implementing ContactInformation class,

250–252implementing UserControl, 248–250

UserControl Resize event handler, implementing,236–237

UserControl, understanding, 232–234creating Control Library, 234GUI is king, 233–234UserControl defined, 233

UserControl visual interface, designing, 235–239determining equal subdivisions for buttons,

237–239implementing UserControl Resize event

handler, 236–237Users, administering, 521–522Users, authenticating, 511–516

programming with FormsAuthenticationclass, 514–515

programming with IPrincipal interface,515–516

Using directive, creating alias with, 40Using scope, defining, 40Using statement, 139Utility, regasm, 199

VValue types versus reference types, 49–51Values

combining BindingFlags, 325DateTime, 327

Variables, temporary, 154Video Kiosk, 63–100

demonstrated topics, 64examining PlayerControl, 67–92GDI+ defined, 64–67secondary topics, 93–99

Video Kiosk (VK), 67VideoKiosk.sln, 69View, working with design, 409–415Viewer, Assembly, 35–62

code listing for Assembly Manager, 36–49demonstrated topics, 36secondary topics, 49–62

Visual interface, designing UserControl, 235–239Visual Studio .NET, customizing, 169–200

creating custom wizards, 170–174creating project templates for Visual Studio

.NET Wizards, 175–184demonstrated topics, 170extending Visual Studio .NET with wizards,

184–198returning to Jscript .NET, 198–199secondary topics, 198using regasm utility, 199

Visual Studio .NET, extending with wizards,184–198

completing sample wizard launch files, 196creating code generators, 188–191implementing wizard user interface,

191–194implementing wizards, 194–196introduction to Common Environment

Object Model, 186–187registering wizards, 196–197running wizards from command window,

197–198testing wizards with macros, 197writing code with macros, 187–188writing macros, 185–186

Visual Studio .NET Wizards, creating projecttemplates for, 175–184

adding files for Wizard Library templates,177–178

I n d e x 5 4 9

Page 577: Advanced c sharp programming

5 5 0 A d v a n c e d C # P r o g r a m m i n g

copying existing project templates, 176–177creating VSDir files, 182–184creating wizard launch files, 181–182modifying common.js script, 180–181modifying default.js script, 178–180

VK (Video Kiosk), 67VSDir files, creating, 182–184

WWeb controls, creating custom, 435–468

binding tabs data, 447–449creating custom portal modules, 462–465creating image modules, 451–452creating Links modules, 452–456demonstrated topics, 436implementing XML/XSL transform

modules, 457–462rendering controls dynamically, 436–446reviewing PortalModuleControl base

controls, 446–447secondary topics, 465–466using HttpServerUtility, 466using Repeater control, 467visually designing SignIn modules, 449–451

Web pages, incorporating custom server controlsinto, 438–440

Web Service exceptions, handling, 384–385Web Services

consuming, 376–378invoking asynchronously, 385–388programming XML, 107–111Reflection and, 363returning complex data from, 388–392returning DataSet from, 163–164, 392–393testing, 379understanding XML, 107

Web Services, creating simple, 380–388handling Web Service exceptions, 384–385invoking Web Services asynchronously,

385–388providing Namespace and Description, 384returning simple data, 380–384

Web Services Description Language (WSDL),375–376

Web Services, discovery and description,372–379

consuming Web Services, 376–378

DISCO is back, 374–375reviewing Web Services wire formats,

378–379WSDL (Web Services Description

Language), 375–376Web Services, implementing, 371–397

creating simple Web Services, 380–388demonstrated topics, 372returning complex data from Web Services,

388–392returning DataSets from Web Services,

392–393secondary topics, 393–397testing Web Services, 379Web Services, discovery and description,

372–379Web Services wire formats, reviewing, 378–379Web.config file for Windows authentication,

modifying, 507–508Web.config files, 507–508

are not served by IIS, 507Web.config files, modifying for forms

authentication, 509–511defining forms attributes, 510supplying user credentials, 510–511

WebControl library project templates, using, 446Windows

running wizards from command, 197–198writing debug information to Output, 225

Windows authentication, 513modifying Web.config file for, 507–508

Windows Forms Controls, 52–57Windows forms designer, defining, 297–298Windows Forms synchronous method

invocation, 62Windows NTLM authentication, 507–509

allowing authorization, 508–509denying authorization, 508–509modifying Web.config file for Windows

authentication, 507–508Windows service, stop and start, 502Wire formats, reviewing Web Services, 378–379Wizard launch files, completing sample, 196Wizard launch files, creating, 181–182Wizard Library templates, adding files for,

177–178Wizard projects, creating, 172Wizard user interface, implementing, 191–194

dialog form strategies, 193–194user interface strategies, 191–193

Page 578: Advanced c sharp programming

Wizardsdefined, 171implementing, 194–196registering, 196–197running from command window, 197–198testing, 173–174testing with macros, 197

Wizards, creating custom, 170–174creating wizard projects, 172implementing IDTWizard interface, 172passing arguments by reference, 173testing wizards, 173–174

Wizards, creating project templates for VisualStudio .NET, 175–184

adding files for Wizard Library templates,177–178

copying existing project templates, 176–177creating VSDir files, 182–184creating wizard launch file, 181–182modifying common.js script, 180–181modifying default.js script, 178–180

Wizards, extending Visual Studio .NET with,184–198

completing sample wizard launch files, 196creating code generators, 188–191implementing wizard user interface,

191–194implementing wizards, 194–196introduction to Common Environment

Object Model, 186–187registering wizards, 196–197running wizards from command window,

197–198

testing wizards with macros, 197writing code with macros, 187–188writing macros, 185–186

Write-only properties, 14Writing

code with macros, 187–188debug information to Output window, 225to EventLog, 204–205to FileStream, 131–132lines of OpCodes, 355–362macros, 185–186

XXML control

loading XML documents with, 460–462loading XSL documents with, 460–462

XML documentsloading with XML control, 460–462reviewing, 457–458

XML Web Servicesprogramming, 107–111in Terrarium, 107

XML/XSL transform modules, implementing,457–462

XSL documents, loading with XML control,460–462

XSL documents, reviewing, 459–460selecting data conditionally, 460sorting XML, 460

I n d e x 5 5 1

Page 579: Advanced c sharp programming

INTERNATIONAL CONTACT INFORMATION

AUSTRALIAMcGraw-Hill Book Company Australia Pty. Ltd.TEL +61-2-9415-9899FAX +61-2-9415-5687http://[email protected]

CANADAMcGraw-Hill Ryerson Ltd.TEL +905-430-5000FAX +905-430-5020http://www.mcgrawhill.ca

GREECE, MIDDLE EAST,NORTHERN AFRICAMcGraw-Hill HellasTEL +30-1-656-0990-3-4FAX +30-1-654-5525

MEXICO (Also serving Latin America)McGraw-Hill Interamericana Editores S.A. de C.V.TEL +525-117-1583FAX +525-117-1589http://[email protected]

SINGAPORE (Serving Asia)McGraw-Hill Book CompanyTEL +65-863-1580FAX +65-862-3354http://[email protected]

SOUTH AFRICAMcGraw-Hill South AfricaTEL +27-11-622-7512FAX [email protected]

UNITED KINGDOM & EUROPE(Excluding Southern Europe)McGraw-Hill Education EuropeTEL +44-1-628-502500FAX +44-1-628-770224http://[email protected]

ALL OTHER INQUIRIES Contact:Osborne/McGraw-HillTEL +1-510-549-6600FAX +1-510-883-7600http://[email protected]