Pro IronPython Alan Harris Pro IronPython Copyright © 2009 by Alan Harris All rights reserved. No part of this work may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage or retrieval system, without the prior written permission of the copyright owner and the publisher. ISBN-13 (pbk): 978-1-4302-1962-0 ISBN-13 (electronic): 978-1-4302-1963-7 Printed and bound in the United States of America 9 8 7 6 5 4 3 2 1 Trademarked names may appear in this book. Rather than use a trademark symbol with every occurrence of a trademarked name, we use the names only in an editorial fashion and to the benefit of the trademark owner, with no intention of infringement of the trademark. Lead Editors: Mark Beckner, Jonathan Hassel Technical Reviewer: Shawna Garver Editorial Board: Clay Andres, Steve Anglin, Mark Beckner, Ewan Buckingham, Tony Campbell, Gary Cor- nell, Jonathan Gennick, Michelle Lowman, Matthew Moodie, Jeffrey Pepper, Frank Pohlmann, Ben Renow-Clarke, Dominic Shakeshaft, Matt Wade, Tom Welsh Project Manager: Beth Christmas Copy Editor: Elliot Simon Associate Production Director: Kari Brooks-Copony Production Editor: April Eddy Compositor: Linda Weidemann, Wolf Creek Publishing Services Proofreaders: Linda Seifert and Kim Burton Indexer: Julie Grady Cover Designer: Kurt Krames Manufacturing Director: Tom Debolski Distributed to the book trade worldwide by Springer-Verlag New York, Inc., 233 Spring Street, 6th Floor, New York, NY 10013. Phone 1-800-SPRINGER, fax 201-348-4505, e-mail orders-ny@springer-sbm.com, or visit http://www.springeronline.com. For information on translations, please contact Apress directly at 2855 Telegraph Avenue, Suite 600, Berkeley, CA 94705. Phone 510-549-5930, fax 510-549-5939, e-mail info@apress.com, or visit http://www. apress.com. Apress and friends of ED books may be purchased in bulk for academic, corporate, or promotional use. eBook versions and licenses are also available for most titles. For more information, reference our Special Bulk Sales–eBook Licensing web page at http://www.apress.com/info/bulksales. The information in this book is distributed on an “as is” basis, without warranty. Although every precau- tion has been taken in the preparation of this work, neither the author(s) nor Apress shall have any liability to any person or entity with respect to any loss or damage caused or alleged to be caused directly or indi- rectly by the information contained in this work. The source code for this book is available to readers at http://www.apress.com. v Contents at a Glance About the Author. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xiii About the Technical Reviewer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv Acknowledgments. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xix Chapter 1 Introduction to IronPython. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Chapter 2 IronPython Syntax. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .15 Chapter 3 Advanced IronPython. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .39 Chapter 4 IronPython Studio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Chapter 5 Mixing and Mingling with the CLR. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .79 Chapter 6 Advanced Development . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 Chapter 7 Data Manipulation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .163 Chapter 8 Caught in a Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 Chapter 9 IronPython Recipes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .239 index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 vii Contents About the Author. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xiii About the Technical Reviewer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv Acknowledgments. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xix Chapter 1 Introduction to IronPython . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 A Humble Beginning. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Jython: A Taste for Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .2 IronPython: “Import .NET”. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Why Is .NET Important? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 What Exactly Is IronPython?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .3 What Can IronPython Do for Me Today?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Yes, But Will It Blend?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5 What Is a Dynamic Language?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 What This Will Book Cover. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8 Who This Book Is For . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 For Consenting Adults Only!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9 Prerequisites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .10 IPY and You. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .12 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .14 Chapter 2 IronPython Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Data Types and Control Structures. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .15 Strings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .15 Integers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Conditional Statements. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .19 Input() or Raw_Input(). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .20 Error Handling and Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Try-Catch-Finally. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .23 ■CONTENTSviii Built-In Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .26 abs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 chr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 dict. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 dir. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Files via open. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 for (iterations). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .29 help. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .30 hex. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 int. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 len. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32 list. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32 max and min. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32 ord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 pow. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .33 random. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34 randrange. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 round. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 uniform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 But Wait, There’s More!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .38 Chapter 3 Advanced IronPython. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 String Operations Revisited. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 A Quick Software Development Detour. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Back on Track . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Floating-Point Numbers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Booleans. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Classes and OOP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 .NET Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Value and Reference Types. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 Mixing and Matching. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .61 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .62 ■CONTENTS ix Chapter 4 IronPython Studio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .63 Hopping Onto the Steamroller. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .63 So Much Typing . . .Is There a Better Way?. . . . . . . . . . . . . . . . . . . . . . . . . . .66 Forms, from the Ground Up. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .70 It’s All This Substandard Wiring!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .72 Clean Code Is Happy Code. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .74 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .78 Chapter 5 Mixing and Mingling with the CLR. . . . . . . . . . . . . . . . . . . . . . . . . .79 “CLR-ance, Clarence .”. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .79 The Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 The Design. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 The Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Bad Medicine. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 I’d Like to See a Menu. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Reading, Writing, Arithmetic. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Open Sesame. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .101 I Can’t Even Save Myself. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 Print, Please. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 A Touch of OOP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 Exit Strategy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .116 Beautification. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 Project Postmortem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .117 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .118 Chapter 6 Advanced Development. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .119 Base Classes for Fun and Profit (aka “The LEGOs on the Bottom Don’t Really Exist”). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .119 Plug and Play. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 Architecting Flexibility. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .131 Calling IronPython Code. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Creating a Plug-in Base. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 Choices, Choices. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 Supporting Healthy Arguments. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 ■CONTENTSx “Somebody’s Watching Me”. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 The Plan. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .151 The Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 Writing the Basic IronPython Classes. . . . . . . . . . . . . . . . . . . . . . . . . .152 Creating the Parent Application. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .153 Wiring Things Together. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .155 Project Postmortem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .160 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161 Chapter 7 Data Manipulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 SQL. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .163 A Sample Database. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .165 Create. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .169 Retrieve. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 Update. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 Delete. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .173 Preventing SQL Injection Attacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 Parameterized Queries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 Stored Procedures. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .176 Connection Pooling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 XML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 Comma-Separated Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 Creating an Effective Data Layer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .186 Using the dataManager. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .187 Business As Usual. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 Exceptional Handling!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 Inserting a New Employee. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 Deleting an Employee. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .201 Chapter 8 Caught in a Web. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .203 .NET, IIS, and the Road to Today. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .203 .ASPX and You. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 The State of the View. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 ■CONTENTS xi POST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216 Creating a Simple Form. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 Know Your Limitations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 Cross-Page PostBacks. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 Accessing ­Cross-­Page Data. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .225 Validation (for a Reasonable Fee). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 Using the RequiredFieldValidator. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227 Handling Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 Subtle Security Flaws. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .234 Arbitrary Code Execution. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .238 Chapter 9 IronPython Recipes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 How to Use This Chapter. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 Displaying the String Representation of an Object . . . . . . . . . . . . . . . . . . . 240 Converting Between Two Base Data Types . . . . . . . . . . . . . . . . . . . . . . . . . 241 Implementing Your Own .ToString() Method. . . . . . . . . . . . . . . . . . . . . . . . .242 Inheriting from a Base Class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 Getting User Input from the Console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 Concatenating Strings Efficiently with the StringBuilder . . . . . . . . . . . . . . 244 Creating a Set of Enumerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245 Retrieving ­Command-­Line Arguments. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .246 Listing All the Files in a Folder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 Conveniently Check the State of a String . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 Implementing the Singleton Design Pattern. . . . . . . . . . . . . . . . . . . . . . . . . 249 Opening a Connection to a Database. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .251 Performing a Bubble Sort on a Set of Elements. . . . . . . . . . . . . . . . . . . . . .252 Using the StopWatch Class to Time Operations. . . . . . . . . . . . . . . . . . . . . 253 Baking Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 Reading Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 Deleting Cookies. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 Storing Data in Session State. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 Adding a Web Control Programmatically. . . . . . . . . . . . . . . . . . . . . . . . . . . .258 Telling .NET to Render ­XHTML-­Compliant Markup Using Web.Config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260 ■contentsxii Custom HTML via the HtmlGenericControl. . . . . . . . . . . . . . . . . . . . . . . . . . 261 Passing Information via the QueryString. . . . . . . . . . . . . . . . . . . . . . . . . . . .263 Caching In. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .264 Setting HTML Attributes at Runtime. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .265 Using JavaScript to Determine ­Server-­Side Operations. . . . . . . . . . . . . . .268 Screen Scraping. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .269 Setting the Default Button on a Form. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .271 Viewing Tracing Information About Pages. . . . . . . . . . . . . . . . . . . . . . . . . . .272 Performing ­SEO-­Friendly 301 Redirects. . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 Looping Through the Server Variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .274 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .275 Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 xiii About the Author nAlan Harris is a developer at the Council of Better Business Bureaus in Arlington, Virginia, where he works feverishly on the content man- agement systems and search engine optimization initiatives. He has been working with the .NET framework since 2002 and has an admit- ted preference for C#, though he wishes those VB .NET folks could get a little more respect sent their way. In a previous life he worked for a naval subcontractor, writing firmware in C to allow custom safety hardware to communicate via the ORBCOMM satellite network; at a nonprofit, migrating legacy code to .NET; and on cost-analysis tools for industry use. He keeps his F# experience tucked away as a secret weapon. When not parked in front of a computer of some kind, he is an avid practitioner of Krav Maga, has been a drummer and percussionist for more than 20 years, can’t seem to stay out of Gold’s Gym, and has a demonstrated capability to watch Akira Kurosawa’s classic Seven Samurai more than once in a single day. He also has a longstanding bet on the true nature of the Smoke Monster in Lost. xv About the Technical Reviewer nShawna Garver started working as a professional FORTRAN programmer for SAIC at the age of 16 while attending the University of Maryland. On her first day, she tried to use her computer, only to discover it would not start. Using her advanced problem-solving skills, she began to push the power button on and off like mad. It was then she discovered the latest technological advance in 1980s-era computing—the power strip! Voila, problem solved! Shawna went on to win an all-tuition-and-fees scholarship to one of the nation’s top engineering programs and to graduate with dual degrees in engineering and architecture. Since then she has worked as an engineer and a project manager in the maritime and aerospace industries, with specialty in programming, data analysis, database design, and web applications. Shawna lives near Washington, D.C., with her husband, Chris, and her two children. xvii Acknowledgments My family and friends are my lifeline to the outside world, which apparently con- tains a mysterious glowing orb that occasionally seeks to be my undoing. I thank them from the bottom of my heart for their support and encouragement as well as for count- less hours of sheer entertainment. I’ll win that war against the giant glowing pain orb eventually. To Mark Beckner, Beth Christmas, Jonathan Hassell, Elliot Simon, and Shawna and Chris Garver, thank you so much for putting up with my ability to turn something in precisely one day later than you asked. More specifically, thank you for the time and effort you put in to helping me write something that fills a void on my shelf and hope- fully the shelves of many other developers wondering what exactly this IronPython business is all about. I couldn’t think of a better way to relieve a little stress than spending some time with the folks at Krav Works in Falls Church, Virginia. Vince, you’re an excellent instructor; I now keep my hands in front of my face at inappropriate times, just in case a punch comes out of nowhere. Finally, George the Cat: get off my countertops. Seriously: you’re the best cat ever, but kitties are not for countertops. I looked it up. xix Introduction I come from a background of static typing and rigid languages: C, C++, C#. I’m seeing more than one trend at work here. For the longest time I felt something of a warm, fuzzy sensation when it came to my programming. The data type on the left matched the data type on the right. All was well. Then Python walked through the door. Python’s not the only game in town to use dynamic typing (also known as duck typ- ing, which you will learn about in due time), but it did catch my eye and challenged my perspective as a programmer. “What is this? How does one accurately program anything in this fashion? Five hundred lines of code and not one duck! Python’s a liar.” A little unsettling, you can imagine. It’s taken me some time to get really comfortable with the notions behind Python and, by extension, IronPython. The effort was not wasted. IronPython is powerful, fast, and a first-rate language supported fully by Microsoft, enabling developers to get their work done better, faster, and cleaner. In the end, a good measurement of a programming language is how elegantly you can express your intentions in code while still achieving the functionality you desired. I think you’ll be pleasantly surprised with IronPython. I’m not asserting that you’re going to be an instant convert to the ways of the Pythonistas. I’m simply asking you to try. If you’re coming from a programming back- ground, particularly one with more rigid rules, jump into the deep end of the pool for a bit. Try something scary. I think you’ll find that type errors really don’t crop up too often, I might argue that it requires a somewhat more careful developer. But the freedom of flex- ibility is perhaps not even as significant a benefit as is some added attention on your part. The net result should be better code all around; happier developers, happier users. If you have no programming background whatsoever, come on in anyway. I love a blank slate. A moment ago I mentioned duck typing, a concept that comes up a lot in IronPython: if it looks like a duck and quacks like a duck, it must be a duck. This fundamental idea, when applied to data types and objects, allows a significant amount of polymorphism to be baked right into the language itself from the outset. Many developers take issue with this and find the approach too loose, too error-prone. I was speaking to Pythonista and author Michael Foord about the matter. I mentioned the argument “What if it looks like a duck, quacks like a duck, but is really a dragon impersonating a duck? You don’t want a dragon in your pond.” He replied, “If you code dragons, you’ve got no one to blame but yourself if they get in your pond.” I informed him that “the only difference between my IronPython dragons and my C# ones is that my C# dragons have ‘Hello, my name is’ nametags.” So it goes. ■INTRODUCTIONxx Is This for Me? IronPython and the .NET framework are very approachable to new developers. The tools are free, and there is an overabundance of both documentation and skilled developers who are happily sharing their knowledge with the world. The barrier to entry is supremely low these days. If this is your first programming book, so be it! Come along for the ride. You’ll see both sides of the programming fence, for you’ll find examples here in IronPython and C# as well as an entire chapter devoted to getting the two to play happily and nicely with one another. I also cover many basic programming fundamentals as well as the advanced stuff. You’ll get exposed to multiple languages and the .NET framework by the time we’re through. If you’re already versed in Python but not in .NET, you might just find that you can get your programming tasks done a lot more easily with the tested and powerful .NET framework behind you. If you already know both IronPython and .NET, this book should make for a good ref- erence of various tricks and techniques, particularly in the realms of language integration and web development. An Overview of This Book Being an IronPython developer can mean a lot of things. You could write software to be run via the command line, as a Windows Forms application, or as a web application. That means we have a lot of ground to cover. We need both to address IronPython syntax as well as to look at how it fits into the larger .NET framework. Chapter 1: Introduction to IronPython The introductory chapter provides you with a little background on Python and IronPython as well as on the .NET framework itself. We’ll look at what constitutes a dynamic language and contrast it with a static one. Then we’ll get ourselves a copy of IronPython and immediately try our hand at a sample and see how the language works. Chapter 2: IronPython Syntax IronPython has a rich but straightforward syntax and many built-in functions that make your life easier as a developer and ensure you don’t have to reinvent the wheel. This chapter looks at that syntax but does not yet cover interaction with the larger .NET framework. In fact, it will become apparent that you can actually write entire IronPython applications that don’t really make use of the framework at all, allowing Python develop- ers to ease into the .NET world quite easily and gradually. ■INTRODUCTION xxi Chapter 3: Advanced IronPython As with most programming languages, you can use the simplest syntax to express the most complicated ideas. In this chapter we’ll expand what we know and look at more complex data constructs, base classes, and object-oriented design principles. Chapter 4: IronPython Studio This chapter focuses on IronPython Studio and how you can use it to speed your devel- opment process. Up until this chapter the code has been entered entirely using the command-line IronPython interpreter. It’s time to kick things up a notch and begin working with the Integrated Development Environment and also to begin working with Windows Forms applications. Chapter 5: Mixing and Mingling with the CLR It’s difficult to really know and understand a language until you’ve built something with it, hit some walls, and learned how to take an application from design to implementation. In this chapter we’ll begin making heavy use of the .NET framework and build the distant cousin of a very familiar application from the ground up. We’ll pay special attention to points where the .NET framework can save us time and energy, especially when coupled with the IronPython Studio IDE. Chapter 6: Advanced Development One of the coolest things about IronPython is how easily it can be used with other .NET languages. This chapter is all about how to employ IronPython as a scripted plug-in man- ager in a C# application. The plug-in system is designed to be straightforward and simple, and it should prove to be a good starting point for your own improvements and customi- zations. It can really save you endless hours of work if the need arises for extensibility in an existing application (or if you just want to add something neat like that at the very beginning). Chapter 7: Data Manipulation This chapter covers communicating with SQL Server and how to use Structure Query Language (SQL) to work with the database via IronPython code. I’ve also provided advice on how to protect yourself and your users against malicious entities who might try to use specially crafted SQL to circumvent your security. ■INTRODUCTIONxxii Chapter 8: Caught in a Web If you’re interested in web development, search engine optimization, and standards com- pliance, this chapter will be of special interest because it provides insight into all these areas and how IronPython helps you achieve the results you want. You’ll find useful tips like how to do cross-page PostBacks, how to prevent arbitrary code injection, and more. Chapter 9: IronPython Recipes This final chapter provides a lot of varied snippets for many aspects of console, desktop, and web development, ranging from design patterns to search engine optimization tips, along with a final message for readers who kindly explored IronPython with me. Obtaining This Book’s Source Code While I’m a believer in hands-on learning and dutifully type every line of code in the books that I read, I know there’s at least one person out there thinking, “I do not type nearly fast enough even to consider that a possibility.” No worries: the code examples in this book are available as a free download from the Source Code/Download area of the Apress website at http://www.apress.com. Look up this book by its name, Pro IronPython, to find the appropriate downloads. Obtaining Updates for This Book My being blessed with terrible vision results in a simple truth: despite having four eyes, I’ve likely missed something along the way. The wonderful editors do their best to keep up with my erratic keyboard pounding, but even they are only human, and the burden of guilt lies squarely with me. The Apress website maintains a list of errata and provides a way to notify me of errors that might pop up after you have this book in your hands. Contacting Me I’m an outgoing type of guy, and I love to talk to other developers. I currently maintain status updates of the minutiae of my life at Twitter; feel free to follow me at http:// twitter.com/Anachronistic. Alternatively, if 140 characters just isn’t enough to get those thoughts out, you are completely welcome to contact me at dotnetalan@gmail.com. I’m a web developer by day, Kravist by night; if I don’t get back to you immediately, I promise I’m not deliberately blowing you off. You can always drop me a quick reminder that you need a little attention, too. 1 Chapter 1 Introduction to IronPython “Snakes. Why’d it have to be snakes?” — Indiana Jones This is a great time to be a .NET developer. Software architects and engineers have a fantastic toolkit at their disposal that allows them to produce quality code quickly. This book is about IronPython and how you can fit it into your toolkit to solve the issues you face as a developer. IronPython represents a very new offering from Microsoft that works alongside the other .NET family of languages, adding the power and flexibility that comes with a dynamic language such as Python. To understand where IronPython fits into the scheme of things, let’s go back to the origins of Python and see how we got to the present day. If you’re coming from a software development background from another language, such as C# or VB.NET, this history should help clarify some of the design decisions about the Python language, which, although very different from many other languages in use today, results in a powerful, flexible, and rapid development tool. A Humble Beginning Python’s origins date back to the 1980s. A developer named Guido van Rossum created Python to be the successor to a language called ABC. The idea was that the Python lan- guage would be extremely readable and not cluttered with confusing syntax and markup. Blocks of code are denoted by whitespace indentation, variables are strongly typed, and it would not try to force developers to learn and implement any one particular program- ming style. For example, Python developers had at their disposal the language features necessary to move between functional, object-oriented, and structured programming, and more. The language is quite capable of adapting to the individual developer’s needs with an expanding array of add-ins and an active user community. nNote  Guido has remained very active in the Python community over the years and has since had bestowed on him the lofty mantle of “Benevolent Dictator for Life,” a title that is indeed well earned. ;and what developers can accomplish with it. Python is by no means a new language ment. Releases are frequent and stable and further the capabilities of the language very experienced) Python community and fueled by their continual input and refine- Microsoft and a growing user community, built on the backs of the hardworking (and Let’s not mince words here: IronPython is a first-class language, supported by IronPython full time, with the support and resources of the software giant behind him. a .NET equivalent of Jython. In 2004, he joined the Microsoft CLR team to work on turned out to perform very well, and his focus shifted. He decided to create IronPython, .NET Is a Terrible Platform for Dynamic Languages.” To his surprise, the framework eye. He began working with the CLR with the intention of creating an article titled “Why After leaving the Jython project, the .NET framework and CLR caught Jim Hugunin’s was created. any language they like. It is in this .NET-and-CLR platform environment that IronPython Intermediate Language (CIL) bytecode. Developers can write for the .NET framework in choice, so long as there exists a compiler that can translate the source code into Common .NET framework and CLR present developers with a way to write code in their language of time (CLR) were really starting to make waves in the software development world. The In 2004, the Microsoft .NET framework and, in particular, the Common Language Run- IronPython: “Import .NET” ine this point again shortly. story, and I’ll emphasize it again: Jython can call and use Java classes natively. We’ll exam- thereby expanding the Python language. This is a very important point in the IronPython ing ahead here!). Jython’s strength lies in its ability to call and use Java classes natively, 1997 by Jim Hugunin (who would eventually go on to create IronPython, but we’re jump- the best of all of these. The most notable predecessor to IronPython is Jython, created in it in the past that aim to make use of other languages within Python, thereby blending Although Python by itself is a powerful language, there have been implementations of Jython: A Taste for Java the benefits of Python. ence quickly or for those looking to improve their existing back-end infrastructure with represents an attractive language choice for companies looking to create an online pres- without requiring a large team of developers to create and maintain them. As such it tation for being easy to work with and for allowing applications to be highly available a wide variety of high-visibility web sites, including YouTube and Google. It has a repu- Over the years, Python has proven itself to be quite a capable language, powering Chapter 1  ■ INTRODUCTION TO IRONPYTHON2 IronPython way of creating a Windows form programmatically happens to be very similar taking advantage of and learning to use this underlying set of technologies as you go. The Common Language Infrastructure. Since IronPython is implemented in .NET 2.0, you are lying technology of .NET, combined with the Common Intermediate Language and the than you might realize at a first glance. The Common Language Runtime is the under­ ing how to write code in IronPython, you’re expanding your skill set by a greater amount cover dynamic versus static languages in a bit. By investing time and energy into learn- written in C# and built to run on the Microsoft .NET 2.0 (or greater) framework. We’ll IronPython is a dynamic language, an implementation of the Python language that is What Exactly Is IronPython? own separate language. when we encounter them. As we progress, just keep in the back of your mind the notion that IronPython is its a few underlying language differences, which we will cover throughout the book, and I will flag known issues version compatibility, but not everything written in IronPython will work in CPython, and vice versa. There are nCaution  It’s worth mentioning at this point that IronPython is designed to implement CPython 2.5.2 for sional C# sample for comparison. IronPython over other .NET languages throughout the book, and I will provide the occa- or in a more straightforward manner. We will examine some of the particular strengths of guage has strengths and weaknesses, and certain languages do perform some tasks easier architecture is what allows something like IronPython to exist in the first place. Each lan- structure, language choice is down to preference and comfort, not necessity. This design IronPython; because the .NET framework unifies these languages with a common infra- or solve a task. A studied Visual Basic .NET developer can easily pick up C#, F#, or finds that he or she needs to learn a multitude of language nuances to be productive ther than language independence as a selling point. Gone are the days when a developer developers when it comes to building desktop and web software. One need look no fur- framework is a significant offering from Microsoft; it is the platform of choice for many The mixing and mingling of Python and the .NET framework is powerful. The .NET Why Is .NET Important? cessful projects, which puts it quite far ahead in any programming language race. IronPython gains the benefit of those many years of development experience and suc- Chapter 1  ■ INTRODUCTION TO IRONPYTHON 3 .in the book as well. We will be using IronPython to customize other .NET applications later by learning IronPython, you can build and customize aspects of those programs myself” variety. Many commercial products use Python as a scripting language; appli­cations, both of the “commercial, off-the-shelf” variety and the “I made it Easy integration: It’s extremely easy to integrate IronPython code in other • are required! either in scripts or using the interactive interpreter. No massive compilation times Rapid prototyping: IronPython allows developers to design and test ideas quickly, • markup that many languages have that it looks like pseudocode, but it is in fact executable code. able pseudocode, it’s really a statement about how readable the Python language is. It lacks so much of the simply needs to convey the intended design to someone else. When people refer to Python code as execut- analogy for pseudocode is scribbling out a drawing on a napkin: it doesn’t have to look perfect; rather, it something like the language in which the final code will be written, but without the messy details. A good nNote  Pseudocode is a fancy term for code intended for human, not machine, use, and it generally looks “executable pseudocode.” In fact, the Python language is considered so readable that many people refer to it as but there really is a lot under the hood in terms of programming power and elegant code. your programming time more productive? The short answer is “a lot.” It sounds cliché, to wager that one of the biggest questions you have is what IronPython can do to make Unless you’re looking at IronPython from a strict hobbyist perspective, I would be willing What Can IronPython Do for Me Today? poor decision! languages if you choose to do so, and it’s hard to argue that learning more in less time is a What you learn by teaching yourself IronPython gives you a leg up in learning other .NET underlying .NET framework these languages use; you’re getting more bang for your buck! to the way it would look and be done in C# or Visual Basic .NET. The reason for this is the Chapter 1  ■ INTRODUCTION TO IRONPYTHON4 .powerful way to program, it’s also arguably the most difficult understand what’s happening in the program. Although working with machine code is considered the most if you were reading the machine code that the computer actually executes, it would be much harder to closer to machine instructions. IronPython code is so easy to read because of its high level. By contrast, nNote  High-level languages are considered easier for humans to read, whereas low-level languages are development efforts for the programmer. under simpler constructs; this does not degrade performance, but can simplify As a result, some of the more complex aspects of programming are abstracted on the interpreter’s part to get it into a form the computer can use as instruction. Semantics: IronPython is a high-level language and therefore requires a bit of work • this chapter for some details on this point. considered public in terms of visibility. See “For Consenting Adults Only!” later in Data visibility: Everything in the Python language (and therefore IronPython) is • mance that a compiled language will have. Performance: IronPython is an interpreted language and loses a bit of the perfor- • ing potential pitfalls. though the pros outweigh the cons, it’s not fair to list only the good bits without address- development problems perfectly. No language is perfect, including IronPython. Even Be wary of anyone trying to sell you a language as a silver bullet to solve every one of your Yes, But Will It Blend? versus object-oriented manner. able with F# than C#, IronPython will happily allow you to program in a functional IronPython allows a variety of programming constructs. If you’re more comfort- programmer or a functional programmer or any other type you can think of. Style convenience: IronPython does not require you to be an object-oriented • life easier! driven code. Flexibility is key; IronPython is designed to make your programming easy to add new functionality to your IronPython applications with community- Extensible: The Python community is vibrant, thriving, and large. It’s extremely • Chapter 1  ■ INTRODUCTION TO IRONPYTHON 5 yourName = 5 infers the variable type from the value # this does NOT throw an error; the compiler➥ author = True name = "Alan Harris" number = 5 # the value type on the right dictates the variable type on the left def DynamicTyping(): Listing 1-2. An IronPython Method That Demonstrates Dynamic Typing ing 1-2), albeit with a slightly different result, which we will discuss afterward. Now compare this to an IronPython snippet that performs the same tasks (List- } bool yourName = 5; 5 is not a valid boolean value // the next line will throw a compile-time error;➥ bool author = true; string name = "Alan Harris"; int number = 5; assignment on the right // the value type on the left enforces a valid value➥ { public void StaticTyping() Listing 1-1. A C# Method That Demonstrates Static Typing dynamically typed language. compare it to IronPython’s dynamic typing, we’ll examine exactly what makes up a Consider the C# snippet in Listing 1-1. After we look at a statically typed language and sour taste in the mouth of programmers with a background in statically typed languages. a variable before you use it. The nature of IronPython variables and values may leave a typed language. In dynamically typed languages, you do not have to define the type of I need to make an important point here before continuing: IronPython is a dynamically What Is a Dynamic Language? Chapter 1  ■ INTRODUCTION TO IRONPYTHON6 IronPython) dialect. Sacrificed are the brackets and the semicolons and embraced are have used in the past. A lot of value is placed on readability in the Python (and therefore give you an idea of how this style of programming differs from other languages you may We’ll cover the nitty-gritty details of writing IronPython code shortly, but this should DynamicTyping() # calls the method defined above and executes the assignments error = 5 infers the variable type from the value # this does NOT throw an error; the compiler➥ author = True name = "Alan Harris" number = 5 variable type on the left # the value type on the right dictates the➥ def DynamicTyping(): Indentation Listing 1-3. An IronPython Method That Demonstrates Program Flow via Whitespace and IronPython relies on whitespace and indentation to control program flow (Listing 1-3). are the line-ending semicolons, the method opening and closing brackets, and so on. may notice that there is very little extraneous markup to indicate program flow. Missing There’s something else going on here that was mentioned earlier in this chapter: you this by examining the data type of the value returned when exiting the function. type requested properly. This is not the case in IronPython; it will take care of handling “return name;” to the end of the method to avoid a compiler error and return the data return a value of data type string when we exit the method, and we would have to add would need to change the word “void” to “string” to tell the C# compiler that we want to at the end of the method. If we wanted to return the value of the “name” variable, we in C#, we included the word “void” to indicate that we would not be returning any value The eagle-eyed among you may have also noticed that when we started the method ful if used correctly. assignment because it is in stark contrast to many other languages, but it is quite power- from the type of the assigned variable. It is very important to be aware of this manner of a value of 5 because the compiler knows the desired type of the variable only by inferring pile. In the IronPython code in Listing 1-2, the variable called “yourName” is assigned type that does not match the variable type generates an error and the code won’t com- know at compilation time what the type for a variable is. Attempting to assign a value See the difference? In a statically typed language such as C#, the compiler needs to Chapter 1  ■ INTRODUCTION TO IRONPYTHON 7 .(built from the ground up IronPython in large-scale projects (regardless of whether they are legacy systems or being ping things up with enterprise solution recipes, which will cover ways you can use code. Next we’ll move onto database access and web applications, before finally wrap- Windows forms applications and be making effective use of the CLR within IronPython won’t stand on ceremony for long, though, because shortly afterward we’ll delve into to focus on syntax without the complications of user interface design and the like. We tions make a fantastic starting point for IronPython development because they allow us We’ll then apply that knowledge to the creation of console applications. Console applica- the different programming paradigms that an IronPython programmer can make use of. in one situation applies to the other. We’ll begin by looking at IronPython syntax and is a lot of framework overlap between desktop and web applications, and what we learn ground. Luckily we are .NET developers making use of a .NET dynamic language, so there To encompass IronPython software development fully, we need to cover a lot of of this book. good a time as any to take the 10,000-foot tour of where we are going through the course Now that you’ve have taken the 10,000-foot tour of IronPython and its roots, this is as What This Will Book Cover execution speed at runtime during unit testing for runtime errors, reduced code arbitrary code, simpler mocking Reduced type safety, increased potential Easier to write, allows execution of Dynamic value types which hurts performance optimized machine code output generally requires casting to change clarity of code, easier to debug, Rigid enforcement of type assignment, Enforced type safety at compile time, Static Cons Pros Typing Style Table 1-1. Benefits of Static vs. Dynamic Typing into play. shown. We will see throughout this book examples where these considerations come As an IronPython developer you’ll need to make informed choices about the trade-offs nature. There are pros and cons to dynamic typing, which are summarized in Table 1-1. and as you learn how to work with IronPython these things will soon become second the benefits will become apparent quickly as you learn to develop applications rapidly, even VB .NET; there are no landmarks, no street signs by which to navigate. Rest assured, indentation and whitespace. This is a scary world for developers coming from C#, C++, or Chapter 1  ■ INTRODUCTION TO IRONPYTHON8 .is brought up in conversation, for some very good (if not tongue-in-cheek) reasons “For consenting adults only” is a phrase that gets trotted out sometimes when Python That sounds a lot scarier than it actually is, but I want to grab your attention on this one. For Consenting Adults Only! guru to make it through those sections. should keep everything clear and understandable. You don’t need to be a web design with XHTML and CSS, great! If not, no worries. I will give you a crash course as we go that the book does cover web development in several ways. If you happen to have experience (meaning boring old DOS prompts) and then Windows Forms applications, the ending of Although the beginning of this book is heavily slanted toward console applications fully have a little fun doing it. build from small components to larger applications throughout each chapter, and hope- we go so that your code is of high quality at the end. We will cover the language syntax, would best serve you by integrating some software design principles in the examples as degree, nor do I assume this is your first time using a computer; rather I assume that I that target the Microsoft .NET framework. I don’t assume you’ve got a computer science IronPython that can either stand by itself or work effectively with other applications to cover every exhaustive language detail; instead I want to get you writing code in speed quickly and start learning the language with a minimum of fuss. I’m not going This book is designed for people new to Python and IronPython who want to get up to Who This Book Is For ronment) and language choices by the wayside. with which they choose to work. We’ll leave the arguments about using an IDE (interactive development envi- would be accurate to say that “real programmers” have an in-depth knowledge of the language and systems the “hardcore” category, although the scale for measuring a real programmer seems to be a sliding one. It nNote  In developer jargon, “real programmers” are just developers whom you might describe as fitting in quite a few folders of IronPython code at your fingertips. library of code for you to reference later. By the end of the book, you (ideally) will have quick activities to get you used to thinking in IronPython terms as well as to create a nice grammers” love to get their hands dirty. The projects and code are designed to be fairly would argue that it is more effective to learn by doing than by reading, and “real pro- that you take the time to work through and code each of the examples in this book. I Although it is completely feasible that you learn solely by reading, I highly suggest Chapter 1  ■ INTRODUCTION TO IRONPYTHON 9 ./express/vcsharp Express Edition throughout this book. It is available at http://www.microsoft.com/ free editions of their Visual Studio suite, and we will be using Visual C# 2008 ing a copy of Visual Studio available. Microsoft has generously made available interacting with other .NET applications, you will get the most benefit by hav- Microsoft Visual Studio 2008: When we reach the chapters in the book that involve • displaylang=en. downloads/details.aspx?FamilyID=ab99342f-5d1a-413d-8319-81da479ab0d7& this book, you will need to download .NET 3.5 here: http://www.microsoft.com/ in the .NET 3.5 framework. Since I will be using Visual Studio 2008 throughout Microsoft .NET 3.5: The IronPython build relies heavily on new features available • choice. ers and running programs, so it should be simple enough to follow along in your OS of terms of the operating system is really any more complex than moving between fold- Vista on my machine, and my instructions and examples reflect that. But nothing in of charge, and I’ve noted where and why that is the case. I am using Microsoft Windows book, you don’t have to spend a cent. Every tool and component described next is free system is set up properly. The great news is that to take advantage of everything in this There are a few requirements for installing and using IronPython, so let’s make sure your Prerequisites wiser for it. cover your rear end, and a developer that is hip to that notion at the beginning is the everything in due time. It’s just very important to remember that Python won’t always If all this talk about public and private objects is alien to you, no problem! We’ll cover really, really boneheaded decisions “because we’re all adults here.” tions. Just know that the Python language on which IronPython is based will let you make world, and we will cover ways to enforce good programming decisions and implementa- you can always touch Python’s private parts!). This is actually not the worst thing in the protect any objects or data you want to be private (the tongue-in-cheek humor being that therein. In Python, everything is considered public, and the language won’t do a thing to encountered the notion that objects and data can be public, private, or some variety If you’ve already got some programming experience under your belt, you have likely Chapter 1  ■ INTRODUCTION TO IRONPYTHON10 . Click the Releases tab 2. In the web browser of your choosing, go to http://www.codeplex.com/IronPython. 1. samples, and forum discussions. a new home at CodePlex. The IronPython CodePlex site is your one-stop shop for downloads, tutorials, At the time of this book’s writing, IronPython was at version 2.0 (December 10, 2008, release) and had DOWNLOADING AND INSTALLING IronPython modifying IronPython itself. will deal with the compiled binaries and not concern ourselves with building or you are allowed to download the source binaries, for the purposes of this book we release and install it. I have provided instructions here for how to do so. Although IronPython 2.0: This is available as a free download; you’ll need to get the latest • well as an application reboot. This installation is a bit more complex and requires a few configuration steps as can download this version at http://www.microsoft.com/express/sql/default.aspx. has released a free edition of SQL Server 2008 that will serve us well later on. You using SQL Server 2008 as our storage system. As with Visual Studio 2008, Microsoft Microsoft SQL Server 2008: The sections on database access assume that we are • Chapter 1  ■ INTRODUCTION TO IRONPYTHON 11 ?try it, shall we preter. You can execute IronPython code within this interpreter and see the results. Let’s What’s this ipy.exe business all about? Quite simply, it’s an interactive IronPython inter- IPY and You cates that installation was successful. installed IronPython. Type ipy and press Enter. You should see the following screen, which indi- After the installation is complete, open a command prompt and go to the directory to which you 5. the onscreen directions to install IronPython 2.0. Once the file has downloaded, open the folder containing the Installer Package and run it. Follow 4. choose Save File when the file download window appears. Click the IronPython.msi link, read and agree (if you do agree) to the License Agreement, and 3. Chapter 1  ■ INTRODUCTION TO IRONPYTHON12 . Type exit() and press Enter to exit the interpreter 5. Type print “The ultimate answer to everything is”, answer and press Enter. 4. prompt. Type answer = 42 and press Enter. The interpreter should immediately respond with a new 3. The interpreter should return 15, followed by a new prompt. 2. At the prompt (>>>), type 1 + 2 + 3 + 4 + 5 and press Enter. 1. press Enter. The IronPython interpreter should start. If you have not yet done so, open a command prompt and go to your IronPython directory. Type ipy and USING THE IronPython INTERPRETER Chapter 1  ■ INTRODUCTION TO IRONPYTHON 13 .up the IronPython interpreter and solving the mysteries of the universe at the same time through installation and verification of IronPython from the CodePlex site before firing tory of Python and IronPython, discussed the benefits of using IronPython, and walked and to create applications that are easy to create, read, and maintain. We covered the his- language that allows developers to harness the power of the CLR within the Python syntax IronPython is an exciting addition to the .NET language family. It is a powerful, dynamic Summary check out the full range of interpreter command-line options by typing ipy -? at the command prompt. prompt and press Enter. You should see color-coding instead of the plain monochrome format. You can nNote  If you’re looking for a little splash of color in your life, type ipy –X:ColorfulConsole at the command applications tend to get very large. way to test certain things without having to run an entire application, for production prompt open with the interpreter running while you are working; it’s a quick and easy the interpreter that way for a while. However, it’s always a good idea to keep a command do what you want. We’ll cover that usage immediately in Chapter 2 and continue to use scripts, which are basically text files that contain specific instructions to make IronPython The interpreter is not only for typing code in real time. It can also run IronPython Chapter 1  ■ INTRODUCTION TO IRONPYTHON14 15 Chapter 2 IronPython Syntax “High thoughts must have high language.” — Aristophanes Now that we’ve seen where IronPython comes from, it’s time to start the fun stuff. Specifically we’re going to start writing some IronPython code, learn how IronPython handles various programming constructs, and build the knowledge foundation for the rest of the book. Ready? Data Types and Control Structures At a very basic level, programming is all about manipulating data via instructions to the computer, with the goal of performing whatever tasks need to be completed, usually in a specific sequence. Since the core of this practice revolves around data, let’s start by exam- ining how IronPython handles different data types. After we’ve looked at some basic data types, we’ll look at various control structures, which alter the way a program executes depending on various criteria (including input, raw data, and errors.) A lot of truly fantastic programming books start off with examples of how to display “Hello World!” to the user, and I’m not about to break tradition. nNote  Totally random trivia: the archetype of the Hello World! programs is actually “The C Programming Language” by Brian Kernighan and Dennis Ritchie. Since the book in your hands is about IronPython, which is based on CPython, which was written in and (in part) modeled after the C language syntax, it seems only fitting to keep the tradition alive. .HelloWorld method and executes our instructions variable named greeting and proceeded to display it to the screen. The final line calls our the following code is structured a particular way. Then we assigned a string value to a and future maintenance programmers (which might be you too!) that describes why loWorld that accepts no parameters. We added a comment for the benefit of ourselves Let’s take a closer look at exactly what happens. We defined a method called Hel- Figure 2-1. Hello World, IronPython style see results like those displayed in Figure 2-1. installed. Type ipy c:\python\HelloWorld.py and press Enter. If all goes well, you should Now, open a console window and go to the directory where you have IronPython HelloWorld() print greeting greeting = "Hello World!" # create a string variable that holds Hello World! as content def HelloWorld(): Listing 2-1. Hello World! I will be keeping my source code in C:\Python. as HelloWorld.py in a convenient directory. For the sake of our examples in this book, Fire up your favorite text editor, type the block of code in Listing 2-1 exactly, and save it Strings Chapter 2  ■ IRONPYTHON SYNTAX16 ()HelloDynamic print greeting + spam + eggs #print our greeting, followed by the sum of spam and eggs print spam + eggs eggs = 2 spam = 1 #create a handful of integer variables print greeting greeting = "Hello World!" # create a string variable that holds Hello World! as content def HelloDynamic(): Listing 2-3. Dynamic Typing at Work take my word for this. Let’s prove it. Listing 2-3 does just that. on what appears to the left side of the assignment operator (the equals sign.) Don’t just Nowhere in this code did we indicate variable types; the compiler figures that out based Let me reiterate at this point that IronPython is a dynamically typed language. syntax so that your program matches Listing 2-2. Run this program. Did you get 3 as output? If so, great! If not, correct any errors in HelloMath() print spam + eggs eggs = 2 spam = 1 #create a handful of integer variables def HelloMath(): Listing 2-2. Hello Math! quite well as a calculator, so let’s put it to work (Listing 2-2). components. For example, 1 is an integer, 1.1 is not. IronPython happens to function class, integers are whole numbers that can be expressed without fractional or decimal Having tried strings, let’s take a stab at integer values. As you probably recall from math Integers Chapter 2  ■ IRONPYTHON SYNTAX 17 print greeting greeting = "Hello World!" # create a string variable that holds Hello World! as content def HelloDynamic(): Listing 2-4. Dynamic Typing at Work, Fixed! Alan Harris!” It does this by concatenating, or joining those strings together to create one string. and your last name separately, but when you log in the application says something like, “Welcome back, to think of this would be your own name. Frequently registration forms offer fields for both your first name nNote  In programming terms, concatenation refers to joining two or more strings together. One easy way what we want (Listing 2-4). in Listing 2-3 and see if we can give IronPython the instructions it needs to understand these things and to display everything to the screen. We’ll go ahead and modify the code what IronPython thinks too! We need to tell IronPython that we want to concatenate World!, the number 1, and the number 2 together. It doesn’t make sense, does it? That’s This is dynamic typing in action. We have just instructed the compiler to add Hello Figure 2-2. IronPython complains quite loudly when it encounters a problem. rudely telling you that something went wrong. The error looks something like Figure 2-2. didn’t? If you’re playing along at home, what you probably just saw was the compiler Run this program. Did it print out Hello World!, then 3, and then Hello World! 3? It Chapter 2  ■ IRONPYTHON SYNTAX18 ((" : age = int(raw_input("Please enter your age lastName = raw_input("Please enter your last name: ") firstName = raw_input("Please enter your first name: ") def HelloConditional(): Listing 2-5. Getting User Input executes (Listing 2-5). add a dash of user input to this program so that the user has a say in how the program set a criterion (or condition) that alters the way a program executes. We’re also going to situations? The answer is a conditional statement. A conditional statement allows us to a given condition and other code is not. How does IronPython allow us to handle those any change in application flow. More often than not, certain code is executed based on the works. It’s a very rare program “in the wild” that operates from top to bottom without Now that we have a resounding victory under our belts, we need to throw a wrench in Conditional Statements is correct. I described, check your syntax against the code in Listing 2-4 and make sure everything the integer result of adding spam and eggs together. Again, if you do not see the output we want to display the string value of the greeting variable and then we want to display Success! The use of the comma instead of the plus sign tells IronPython that first Hello World! 3 3 Hello World! HelloDynamic() print greeting, spam + eggs #print our greeting, followed by the sum of spam and eggs print spam + eggs eggs = 2 spam = 1 #create a handful of integer variables Chapter 2  ■ IRONPYTHON SYNTAX 19 .can’t convert “My name is Fred” to an integer conversion works; for example, you can convert the string “1234” to an integer with a value of 1234, but you and convert a string back to an integer. There are a wide variety of types to convert between, but not every receiving input in the form of a string, but we converted that to an integer. You could also do the reverse nNote  Casting is a method of converting between two data types. In the preceding example, we were integer value 25. comparisons, and in the eyes of the computer the string value “25” is not equal to the going to be very important to have the correct data type because we need to make some variable would contain a value like “25” instead of 25. As we’ll see in just a moment, it’s raw_input() function takes a string value for input. So without the integer cast, the age the entire input line in int() is an operation known as casting. As I just discussed, the that when I asked for the user’s age, I wrapped the entire input line in int(). Wrapping there without telling you. You’ll see that I made use of the raw_input() function and this conditional statement. You may have noticed that I slipped two new concepts in output of the program, indeed the entire program operation itself, changed based on or older than I or perhaps that you are the same age. But the key here is that the Depending on what you entered for your age, you may see that you are younger You, Alan Harris , are the same age as the author. Please enter your age: 25 Please enter your last name: Harris Please enter your first name: Alan HelloConditional() print "You, ", firstName, lastName, ", are older than the author." else: print "You, ", firstName, lastName, ", are the same age as the author." elif age == 25: print "You, ", firstName, lastName, ", are younger than the author." if age < 25: # change the program output based on the user's age Chapter 2  ■ IRONPYTHON SYNTAX20 continuing execution but that results in flawed or inaccurate data or output. Logical This is an example of a logical error, that is, an error that does not stop the program from than I am. But when is the last time you met someone who was younger than 0 years? Now technically, that is correct; someone who is –1 years old is definitely younger You, Alan Harris , are younger than the author. Please enter your age: -1 Please enter your last name: Harris Please enter your first name: Alan the following. expects. But run it again; this time enter -1 as your age. It should output something like This program works great if you provide it exactly what it needs in exactly the format it glaring omission from this program. I’ll spoil it for the rest of you: error handling. From the heading of this section, some of you may have already guessed about one Error Handling and Exceptions security hazard. We’ll cover SQL and protection against injection attacks in Chapter 7. executing arbitrary IronPython commands is a security risk, not protecting your SQL commands is a terrible most overlooked security flaws is neglecting to secure communication to and from the database. Much like guage, and it’s a very common way to communicate with a database. One of the simplest and certainly nNote  A very close relative to this concept is that of SQL injection. SQL stands for Structured Query Lan- consider it a best practice to stick to raw_input() for getting input from the user. cannot be executed directly. We’ll cover use of the input function later, but for right now mind. The raw_input() function is more appropriate because it accepts a string input that users to execute any IronPython code they see fit should always raise a red flag in your This can be a tremendous security risk and should be used with caution. Allowing your very dangerous; it allows the execution of IronPython commands based on user input. command in IronPython called input. The input command, while useful, can also be does not allow arbitrary execution or evaluation of user input. There is another, similar The raw_input() function is a safe method for getting input from the user because it Input() or Raw_Input() Chapter 2  ■ IRONPYTHON SYNTAX 21 ((" : age = int(raw_input("Please enter your age lastName = raw_input("Please enter your last name: ") firstName = raw_input("Please enter your first name: ") # get a few data values from the user def HelloConditional(): Listing 2-6. Getting User Input with Error Handling error handling to the application. start small and then identify trouble spots and how to fix them. Listing 2-6 adds a touch of Note that this is not a comprehensive rule set that covers every possible error. We need to That said, let’s modify our program to use the two rules we on which we just decided . old, but 151 years old and not happy that he can’t use your program. mum age for a user to input, only to find that somebody out there is not only 151 years very careful when designing rules like these; guaranteed you’ll choose 150 as the maxi- cannot have a negative age” or “a user cannot be more than 150 years old.” Always be makes sense to compare the user’s input to some known sanity checks, such as “a user How can we best ensure that we have handled this particular error gracefully? It and to get you thinking about ways to improve our growing application design. from your database. We’ll discuss these concepts in depth later; I just wanted them on your radar for now presentation layer with content to display), and the data layer takes care of speaking to and retrieving data handles all rules related to the business or application domain (as well as input validation and providing the interchangeable in conversation. The presentation layer is generally the user interface, the business layer physical separation of components, whereas layer is more of an abstract concept, but the terms tend to be their programs. Typically this refers to the traditional three-tier application design. Tier ordinarily denotes a nNote  You may hear developers talking in feverish terms about presentation, business, and data layers in we’ll place it alongside the input code. business layer of an application. But we’re not quite that far along yet, so for right now proper program operation. Rules like the ones we’re about to create typically exist in the ensure that the input we’re getting from the user meets the criteria we need to guarantee in the source code instructions themselves. We need to write some validation rules that errors are generally far harder to identify and correct than syntax errors, which are errors Chapter 2  ■ IRONPYTHON SYNTAX22 tion strays far off course. A pretty common example would be dividing by 0. Many burned! This is an example of an exception. An exception occurs when program execu- Yikes! We didn’t even get asked for our name and age again. It just crashed and ValueError: invalid integer number literal File 'c:\python\HelloConditional.py', line 4, in HelloConditional File 'c:\python\HelloConditional.py', line 20, in c:\python\HelloConditional.py Trackback (most recent call last): Please enter your age: 25.1 Please enter your last name: Harris Please enter your first name: Alan decimal value (such as 25.1) for your age. You will see something like the following. range of inputs. But it can still be tripped up. Run the program again and provide some That’s great! Now our program has some knowledge about what constitutes a valid Please enter your first name: You, Alan Harris , need to input a valid age before continuing! Please enter your age: -1 Please enter your last name: Harris Please enter your first name: Alan HelloConditional() print "You, ", firstName, lastName, ", are older than the author." else: print "You, ", firstName, lastName, ", are the same age as the author." elif age == 25: print "You, ", firstName, lastName, ", are younger than the author." elif age < 25: HelloConditional() before continuing!" print "You, ", firstName, lastName, ", need to input a valid age➥ elif age > 150: HelloConditional() before continuing!" print "You, ", firstName, lastName, ", need to input a valid age➥ if age < 0: Chapter 2  ■ IRONPYTHON SYNTAX 23 ()HelloConditional print "You, ", firstName, lastName, ", are older than the author." else: print "You, ", firstName, lastName, ", are the same age as the author." elif age == 25: print "You, ", firstName, lastName, ", are younger than the author." elif age < 25: HelloConditional() before continuing!" print "You, ", firstName, lastName, ", need to input a valid age➥ elif age > 150: HelloConditional() before continuing!" print "You, ", firstName, lastName, ", need to input a valid age➥ if age < 0: HelloConditional() before continuing!" print "You, ", firstName, lastName, " need to input a valid age➥ except ValueError: age = int(raw_input("Please enter your age: ")) try: lastName = raw_input("Please enter your last name: ") firstName = raw_input("Please enter your first name: ") # get a few data values from the user def HelloConditional(): Listing 2-7. Getting User Input with Error Handling and Exception Handling looking at an example (Listing 2-7). that will suit this purpose perfectly, but with a few caveats, which we will address after code block that could notify us if things go terribly awry? IronPython provides a construct Wouldn’t it be nice if we could just wrap potentially unsafe operations in some sort of Try-Catch-Finally tions and handle them accordingly. encountered the error: a ValueError exception. We can check for those types of excep- types of cases. In fact, IronPython just told you what type of exception it threw when it languages (C#, for example) even have built-in DivideByZero exceptions to handle those Chapter 2  ■ IRONPYTHON SYNTAX24 "!before continuing print "You, ", firstName, lastName, ", need to input a valid age➥ elif age > 150: HelloConditional() before continuing!" print "You, ", firstName, lastName, ", need to input a valid age➥ if age < 0: print "This code is executed in the finally block." finally: HelloConditional() before continuing!" print "You, ", firstName, lastName, ", need to input a valid age➥ except ValueError: age = int(raw_input("Please enter your age: ")) try: lastName = raw_input("Please enter your last name: ") firstName = raw_input("Please enter your first name: ") # get a few data values from the user def HelloConditional(): Listing 2-8. Exception Handling with a Finally Block leaks and other nastiness. Listing 2-8 adds a finally block to the exception handlers. otherwise may become bogged down with unhandled exceptions, leading to memory that the try-catch-finally block is critical to releasing valuable system resources that exiting the try block. Later, when we delve into more advanced concepts, we’ll learn you add a finally section, the code within is guaranteed to execute immediately upon ations, there is an additional step, making what is termed a try-catch-finally block. If the end of the block, regardless of whether or not an exception is raised. For those situ- some action. There may be situations in which you want a block of code to execute at the code within the try section. If an exception is “thrown,” we catch it and perform code in what is known as a try-catch block. In a try-catch block, we first “try” to execute Now that’s a lot more user-friendly. We have wrapped the potentially offending Please enter your first name: You, Alan Harris , need to input a valid age before continuing! Please enter your age: 25.1 Please enter your last name: Harris Please enter your first name: Alan Chapter 2  ■ IRONPYTHON SYNTAX 25 5 >>> print abs(-5) unnecessary! IronPython gives you the abs() function for convenience. something like sign checking and then multiplying by –1 to flip that sign, but that’s For various reasons, you may need the absolute value of a number. You could do abs use the built-in interpreter for now. have an IronPython interpreter open. But you don’t need to make any .py files; we’ll just built-in-funcs.html. For the remaining portions of this chapter, I will assume that you You can learn more about the other functions at http://www.python.org/doc/2.5.2/lib/ we will walk through some of the more frequently used ones and see some examples. to version 2.5.2 of CPython as of the time of this writing), which I will list next. Then are or how to find them. IronPython includes quite a few built-in functions (current functions, such as raw_input(), but I haven’t really broken down what these functions Occasionally throughout this chapter I’ve made references to built-in IronPython Built-In Functions program flow. Bear that in mind as you develop your code. on performance. By definition, they should be exceptional cases, not the way you control Exceptions are “heavier” than conditional statements and have a slightly larger impact is not a normal circumstance by our definition, so we have an exception to handle it. under normal circumstances. A user’s entering the string “twenty-five” for his or her age entry. We have conditional statements in Listing 2-8 to handle direction of the program around exceptions. There is a difference between handling exceptions and validating data I should stress the importance of not structuring your program execution solely HelloConditional() print "You, ", firstName, lastName, ", are older than the author." else: print "You, ", firstName, lastName, ", are the same age as the author." elif age == 25: print "You, ", firstName, lastName, ", are younger than the author." elif age < 25: HelloConditional() Chapter 2  ■ IRONPYTHON SYNTAX26 SyntaxError: duplicate keyword argument ^ dict(red=1, blue=2, green=3, red=2) File "", line 1 >>> dict(red=1, blue=2, green=3, red=2) dictionary. Now let’s see what happens if we try to duplicate key names in the same configuration. In this code output we can clearly see IronPython showing us the key-value pair {'red' : 1, 'blue' : 2, 'green' : 3, 'yellow' : 1} >>> dict(red=1, blue=2, green=3, yellow=1) bers assigned to them. You will notice that red and yellow are assigned the same value. the following example, the keys are the names of the colors and the values are the num- ated in key-value pairs. Note that each key must be unique but that values can repeat. In function allows you to specify a dictionary, which is a collection of data that is enumer- We’re going to look at sets and dictionaries in more depth in the next chapter. The dict() dict conversions (hexadecimal, octal, etc.). I use http://www.asciitable.com frequently for that purpose. all. There are quite a few handy tables out there that list the ASCII values from 0 to 255 as well as various ety of characters, including letters, numbers, symbols, and so on. Luckily, you don’t have to remember them nNote  ASCII values refer to a range of integer values, from 0 to 255, that can be used to represent a vari- A l a n >>> print chr(65), chr(108), chr(97), chr(110) tion and example and to return here. which we will take a look at shortly. Feel free to flip forward to the ord() function descrip- function is meant to do that. It is something of a sister function to the ord() function, have been converted to their ASCII values, you may need to convert them back. The chr() If you are working with a bit of code where the individual letters in a string of characters chr Chapter 2  ■ IRONPYTHON SYNTAX 27 .tions a bit more in Chapter 6 when we build a plug-in system using C# and IronPython number is a type of enumeration; it establishes a unique identity for a specific entity. We’ll cover enumera- nNote  Enumeration is the act of assigning a unique numerical value to an object in a list. A Social Security dir() function and providing the module name as a parameter. can drill down further to see what functions the sys module provides by running the functionality. We imported the sys module and it became enumerated in our list. We __doc__, and __name__ modules were available to us. These provide basic IronPython So we can see that before we imported an additional module, the __builtins__, ['__builtins__', '__doc__', '__name__', 'sys'] >>> dir() >>> import sys ['__builtins__', '__doc__', '__name__'] >>> dir() programming standpoint, the effect is the same. tional Python, with the notable exception of needing the clr module to access the .NET namespaces. From a nNote  Importing .NET code modules in IronPython is handled very similarly to how it’s handled in tradi- after running an import. import is the sys module, so let’s compare the output of the dir() function before and ate the names of modules available in the symbols table. One of the first modules we When we cover importing modules, in the next chapter, you may find it useful to enumer- dir comparison. the dict() function. When you learn more about lists, refer back to this for the sake of chapter we discuss the list() function, which has some very important differences from short, we messed up! Remember to keep keys unique in your dictionaries. Later in this cate key in line 1 of stdin, which means “standard input,” in this case the keyboard. In IronPython didn’t care for that at all. The error tells us there was a problem with a dupli- Chapter 2  ■ IRONPYTHON SYNTAX28 .on a system performance, a critical failure that leaves disk resources open is one of the fastest ways to leak resources are those. Beside the fact that any sort of communication to and from the drive is very costly in terms of nNote  If ever a family of operations deserved to be wrapped in try-catch-finally blocks, file operations under load it’s a very heavy type of operation to complete. ence when opening one or two files on your own machine, but trust me when I say that from disk to get those integers in the first place. You may not perceive much of a differ- example, adding two integers is considerably slower when you have to open two text files File operations are very, very expensive compared to operations in memory. For >>> open("c:\python\HelloWorld.py") has been opened in “read” mode. example, the response from the interpreter tells you that the file you asked for exists and command, with the location of the file on disk provided as a parameter. In the following of all the URLs on a website to a file on your drive. Files can be opened using the open() puter. Perhaps you are running a web crawler that you created and you want to save a list Files I/O operations are used when you want to interact with physical files on the com- Files via open tion is very useful for listing classes in a module These are the various functions that sys exposes to you as a developer. The dir() func- derr', 'stdin', 'stdout', 'version', 'version_info', 'warnoptions', 'winver'] 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setrecursionlimit', 'settrace', 'st meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', ilesystemencoding', 'getrecursionlimit', 'hexversion', 'maxint', 'maxunicode', ' c_prefix', 'executable', 'exit', 'getcheckinterval', 'getdefaultencoding', 'getf clear', 'exc_info', 'exc_traceback', 'exc_type', 'exc_value', 'excepthook', 'exe , 'argv', 'builtin_module_names', 'byteorder', 'copyright', 'displayhook', 'exc_ ['__name__', '__stderr__', '__stdin__', '__stdout__', '_getframe', 'api_version' >>> dir(sys) Chapter 2  ■ IRONPYTHON SYNTAX 29 we told it to create an object called “student” for each item in the list. Our instruction individual item to live in so that we can do some work on it. When we created the for loop The for loop is constructed in such a way that we need to provide an object for each Curly Moe Larry Sally Harry James Tommy Bobbie Susie … [Press 'Enter' key] >>> for student in students: print student 'Moe', 'Curly'] >>> students = ['Susie', 'Bobbie', 'Tommy', 'James', 'Harry', 'Sally', 'Larry',➥ [‘value1’, ‘value2’, ‘value3’] and so on. nNote  A list of items in IronPython is expressed as an array. Arrays are in the format variableName = over the list and display each student on a separate line of output for the user. lowing example, we’re going to create a list of students, and then we are going to iterate through a list of items and allow you to perform some task on the item or data. In the fol- When you iterate over a list, what you’re telling IronPython to do is go step-by-step and so on. nNote  There are many types of looping structures in programming, such as while, do while, do until, for, called a for loop. would you do that? In IronPython, you can iterate over a list of items using what is that you wanted to produce a list of names for the teacher to take attendance. How Suppose for a moment that you had a list of students in an elementary school class and for (iterations) Chapter 2  ■ IRONPYTHON SYNTAX30 ''0x7b >>> hex(123) '0xd' >>> hex(13) IronPython makes converting from decimal to hex very simple. Take a look. chose letters, so those next fingers would be A, B, C, D, E and F. odd, you’d have to have some way of referring to them numerically. Mathematicians What if you were to add six fingers to the end of your right hand? Besides looking a bit other pinky and end up at 9 (0 through 9 being a total of 10 numbers.) That’s base-10. fingers, you could start at the far left, with your pinky as 0, and count all the way to your It seems silly, but think of the fingers on your hands. Assuming you have all 10 of your mal representation of thirteen is 13, in hexadecimal it would be D. How is that possible? misnomer, because they actually involve letters as well. For example, whereas the deci- numbers, which are in base-16. Calling these hexadecimal numbers seems a bit of a 2, 3, . . . , 10, 11, 12, 13, . . . , etc.). Computer and software systems often use hexadecimal are quite used to dealing with decimal numbers, which are base-10 numbers (i.e., 0, 1, As developers, we may have to interact with a variety of numerical systems. Most of us hex >>> help('print') >>> help() the command itself. nNote  For the sake of brevity I have not included the entire output of the help() function, only examples of can ask for help either with or without a function in mind. Sometimes you get stuck! IronPython is there to help. Similar to the dir() function, you help in the list, if there are any. If not, the loop exits. name) is printed to the screen. Control flow returns to the loop to iterate to the next item in the students list, the value of the student object (in this case it will be the student’s student object, then execute any code following the colon symbol.” So for each element could be read, “for each element in the list students, take that element and place it in a Chapter 2  ■ IRONPYTHON SYNTAX 31 the key must be unique. Lists are similar to dictionaries, except they are not key-value Earlier in this chapter we looked at dictionary objects, which are key-value pairs where list 3 >>> len(colors) >>> colors = dict(red=1, blue=2, green=3) 13 not counted as part of the string length >>> len('spam_and_eggs') # note that the quote marks are➥ an array, the number of characters in a string, and so on. tion is your best bet. With it you can count the number of objects in a dictionary, a list, or If you need to count the number of items in an object or sequence quickly, the len() func- len 9018724 >>> int(9018724.8971230987) 123 >>> int(123.456) pass a number to the int() function, and the integer portion of the value will be returned. Similar to hex, we may need to extract the integer value from a decimal number. You can int see if you can work out how 255 equals FF. conversions of each of those numbers, ranging from 0 to FF. Without cheating (I’m looking at you, Google!), nNote  Remember the ASCII values we described earlier that ranged from 0 to 255? There are hexadecimal Since IronPython is built with CPython at its core, the convention continues. that has been carried over from C, which is the language in which CPython was created. What’s with the 0x before the hex values? It’s just a way of representing hex values Chapter 2  ■ IRONPYTHON SYNTAX32 65 108 97 110 >>> print ord('A'), ord('l'), ord('a'), ord('n') would use ord()to get the numeric ASCII value from a character. you would use chr()to get the character equivalent from a numeric ASCII value, you The ord() function is the counterpart to the chr() function we discussed earlier. Where ord 'a' >>> min('abcdefghijklmnopqrstuvwxyz') 's' >>> max('spam_and_eggs') other types of objects. max() and min() functions. These functions work on strings, numbers, dictionaries, and tasks for performance or other reasons, but in most cases you can rely on the built-in quickly. In some cases, you may choose to create your own functions to perform these In any set or list, you may have an interest in finding the largest or smallest element max and min appropriate solution. needs to be unique and items can be repeated without any problems, a list would be an guaranteed unique, choose a dictionary. If you have a collection of items where nothing be a better choice. In general, if you need something in the collection of items to be There are situations where a dictionary is the way to go and situations where a list would that one will work better for you in all or most cases; neither is truly superior to the other. choosing the correct one for the type of operation you’re doing is critical. It’s hard to say It’s important to understand the difference between dictionaries and lists, and 't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g'] 'f', 'o', 'x', ' ', 'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r', ' ', ['T', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ', 'b', 'r', 'o', 'w', 'n', ' ',➥ >>> list('The quick brown fox jumped over the lazy dog') ['red', 'blue', 'green', 'red', 'blue', 'green'] >>> list(colors) >>> colors = ["red", "blue", "green", "red", "blue", "green"] expressed without a function keyword. based and are therefore free of the restriction that keys must be unique. Lists can also be Chapter 2  ■ IRONPYTHON SYNTAX 33 78.5398163397 >>> print area >>> area = math.pi * pow(radius, 2) >>> radius = 5 >>> import math circle with code such as the following. value of pi times the radius of the circle squared (A = πr2). You can compute the area of a This function is great for stuff like finding the area of a circle, which is equal to the 9.869587728099999 >>> pow(3.14159, 2) 27 >>> pow(3, 3) 4 >>> pow(2, 2) 1 >>> pow(1, 2) properly. number 10.12345 is a floating-point number and needs the decimal and additional values to be expressed and does not need the decimal or any additional values after the decimal to express its complete value; the that they are numbers that need to be expressed with a decimal component. The number 10 is an integer nNote  We haven’t yet covered working with floating-point values; for right now the short explanation is as integers or floats. itself p times. It is used in a format of pow(n, p), where n and p are numeric values, such in here. The pow() function is used to raise a number n by power p, or n multiplied by able a multiplied by x raised to the second power.” It’s that word power we’re interested for those who need the refresher!) The first term of the polynomial, ax2, is read as “vari- Remember the quadratic formula from elementary and high school? (It’s ax2 + bx + c = 0, pow that effect (“expected a character, but a string of length n was found.”) parameter; you can only pass one character at a time or you will receive an error telling you something to nNote  The ord() function will work only on single characters, not strings. You cannot pass (‘Alan’) as the Chapter 2  ■ IRONPYTHON SYNTAX34 .uses, called Mersenne Twister, is very effective at quickly generating pseudo-random numbers solutions. If you’re interested in learning more about how IronPython generates random numbers, the algorithm it those values alone. So if cryptography or security is in your development future, you will need to find alternative more than 600 random numbers generated in IronPython, you would be able to determine all future outputs from covers areas such as encryption. The random number generator is deterministic; if you were to observe a little that I mention at this point that the random number generator in IronPython is NOT suitable for cryptography, which both beyond the scope of this book and a rabbit hole I have no desire to travel down at the moment! It is important nNote  Discussing random numbers and the many ways one can derive random numbers for a given system is 0.113435200468 >>> print random.random() 0.501539671096 >>> print random.random() 0.872601615671 >>> print random.random() 0.004263442943 >>> print random.random() 0.686993518233 >>> print random.random() >>> import random to import the aptly named random module first, as in the following example. pseudo-random generator, but it will suffice for many applications. To use it, you’ll need are termed pseudo-random. The random number generator in IronPython is indeed a is difficult. Technically, most random number generators are not truly random; they data for testing code or algorithms, and so on. Generating totally random numbers numbers. You may need them for logic decisions in a computer game, to create dummy As a programmer you’ll find plenty of situations where you want to generate random technically built in but that requires you to import a module to use it. I’m going to take a little liberty as the author at this point and employ a function that is random approximation to get the same results. in IronPython is equal to 3.141592653589793. If you don’t want to use the math module, you can use that yourself. For this example we used the math.pi function to get an approximation of the value for pi, which wide variety of mathematical operations included so that you don’t have to take the time to implement them nNote  We made use of the built-in math module, which we haven’t covered yet. The math module has a Chapter 2  ■ IRONPYTHON SYNTAX 35 .the following example for an illustration of this point decimal points specified, the resulting answers may not be expressible as integers. See is zero, but it can be whatever you like. Note that if you perform rounding with additional many digits after the decimal point you want to consider when rounding. By default, this You can also provide a second parameter to the round() function that dictates how 75198.0 >>> round(75198.098123750) 46.0 >>> round(45.9) 1.0 >>> round(1.2) (1.0 is equivalent to 1). value, the number itself can be expressed without any digits following the decimal point Note that although the answer returned from the round() function is a floating-point ing to zero decimal places, 1.2 would round down to 1.0, and 45.9 would round up to 46.0. Certain mathematical operations require you to round numbers. For example, in round- round -15 >>> print random.randrange(-57, 3) 5 >>> print random.randrange(1, 10) 21 >>> print random.randrange(1, 52) >>> import random module before using any of these functions. and randrange() are defined in the random module, so you need to import the random uniform(), which is discussed later in this chapter. Please note that random(), uniform(), values n and m. There is a comparable function that returns floating-point values called 52 (for a card game, perhaps.) The randrange() function will return an integer between You may find yourself in a situation where you need a random integer between 1 and except it lets you be a bit more specific about the range and type of values you need. This function is almost identical in purpose to the random() function in IronPython, randrange Chapter 2  ■ IRONPYTHON SYNTAX36 -137.981091038 >>> print random.uniform(-200, 80) 0.907017286358 >>> print random.uniform(-10, 10) 81.2660990526 >>> print random.uniform(1, 100) >>> import random random module before using it. m. Please note that, like random() and randrange(), uniform() requires you to import the n and m, the uniform() function will return a floating-point value between values n and in the chapter, except whereas randrange() will return an integer value between values This function is almost identical in purpose to the randrange() function discussed earlier uniform 592874.0 >>> round (592873.5) -11029835019244.0 >>> round (-11029835019243.5) 1.0 >>> round(0.5) of where the fault lies (this is particularly true in e-commerce and banking software). different systems you may encounter unusual behavior or results and not have any idea ing methods are in use today, and if you are not aware of how numbers are rounded in toward the next-largest number. This is very important to understand. A variety of round- answer for that. In IronPython, the rounding answer will always move away from 0 pens if you’re exactly halfway between one number and another?” Well, there’s a simple I can hear someone out there now saying, “Aha! But how is .5 rounded? What hap- 75198.09812 after the decimal point (.09812) >>> round(75198.098123750, 5) # we have specified 5 digits➥ 75198.1 integer would lose the decimal values >>> round(75198.098123750, 1) # this cannot; converting to an➥ 75198.0 as an integer with no loss of precision (75198) >>> round(75198.098123750) # the answer can be expressed➥ Chapter 2  ■ IRONPYTHON SYNTAX 37 .should be comfortable with basic programming operations now you should have a pretty good grasp of how IronPython code is structured, and you your development life simpler and to prevent you from having to reinvent the wheel. By then took a closer look at the various built-in functions that IronPython provides to make input to produce a desired output, and handle any errors encountered gracefully. We to it. This is really the basic context in which a developer works: get input , work with that Hello World application and added user input, string concatenation, and error checking and integer manipulation, error handling, and exception handling. We took a very simple language. In this chapter we explored the basics of IronPython syntax, including string IronPython (and Python in general) is designed to be a very easy-to-read, easy-to-write Summary to maintain—for anyone. are, your memory may play tricks on you. A poorly implemented program is a nightmare that you may not be the person who maintains your code in the future. And even if you thousands of lines long you’ll be able to work efficiently with them. Always keep in mind look at ways to keep applications easy to code and maintain so that even when they are As we add more and more aspects of the IronPython language to your toolkit, we’ll resource. The underlying Python language is always evolving, and IronPython with it. done and solve most programming problems, but it’s always nice to have a complete, consistently updated lib/built-in-funcs.html. In general, what’s been listed in this chapter will allow you to get the job nNote  You can find the complete list of built-in commands at http://www.python.org/doc/2.5.2/ very complicated solutions. far. Unfortunately the world is far from ideal, and business problems sometimes require simple and we’d be working with straightforward code such as what we’ve worked with so tions of working in a dynamically typed language. In an ideal world, coding would be code is structured and to beat you about the head and shoulders with the coding implica- IronPython, just a general overview to get you comfortable with the general way IronPython This is not an exhaustive description of every command and control structure in But Wait, There’s More! Chapter 2  ■ IRONPYTHON SYNTAX38 39 Chapter 3 Advanced IronPython “Mathematics may be defined as the subject in which we never know what we are talking about, nor whether what we are saying is true.” — Bertrand Russell At this point we’ve already written some basic IronPython code. We’ve gotten input from the user, done some processing of that input, handled errors and exceptions gracefully, and provided output back to the user in a structured fashion. But we have only scratched the surface of working with IronPython. In this chapter we will expand our knowledge of strings, integers, floats, and other data types. We will also be intro- duced to object-oriented programming, and in the end we will build an object-oriented IronPython business solution. Welcome to Advanced IronPython! String Operations Revisited The output we have displayed to the user so far has been fairly simplistic. It has consisted of a few words put together into a line or two with no real concern for formatting or special characters. But certain very common situations require more complex output formats. For instance, suppose we want to create an application that takes the user’s name as input and prints it back to the screen. Sample code to do this is presented in Listing 3-1. Listing 3-1. Returning User Input to the Screen def MyName(): firstName = raw_input("Please enter your first name: ") lastName = raw_input("Please enter your last name: ") print "Hello, ", firstName, lastName, "." MyName() This is an acceptable solution. But suppose we decide to get fancy with our output. For example, if we want to put the user’s name in quotation marks, we need a way to tell IronPython that we do not want to end the current string. This requires us to escape the ()MyName print "Hello,", "\"", firstName, lastName, "\".\nIt is nice to see you again!" lastName = raw_input("Please enter your last name: ") firstName = raw_input("Please enter your first name: ") def MyName(): Character Listing 3-3. Returning the User’s Name with Quotation Marks Included and a Newline move output down to a different line (Listing 3-3). marks where we need them. It can also function to introduce newlines if you need to The escape method has a variety of uses that are not limited to getting quotation Hello, " Alan Harris ". Please enter your last name: Harris Please enter your first name: Alan MyName() print "Hello,", "\"", firstName, lastName, "\"." lastName = raw_input("Please enter your last name: ") firstName = raw_input("Please enter your first name: ") def MyName(): Listing 3-2. Returning the User’s Name with Quotation Marks Included data is escaped for security purposes. escaped to be interpreted properly, and it comes up as well in database development, where user-provided as being special. The term also comes up in web development when certain HTML characters need to be doing is informing IronPython that we want to insert a specific character that the compiler normally interprets nNote  In programming lingo, escaping a string can have multiple meanings. In this case, what we’re cate that it is a literal character (Listing 3-2). quotation mark. In IronPython we “escape” the quotation mark with a backslash, to indi- Chapter 3  ■ aDVANCED IRONPYTHON40 .able to fill that field correctly to enforce. Extracting only the appropriate number of characters ensures that you are ple, database fields you plan to fill with user input have a given length that you may need from a given string. This can be of particular use when handling form inputs. For exam- You can also modify the previous code quite easily to extract ranges of characters The first character in your last name is H The first character in your first name is A Please enter your last name: Harris Please enter your first name: Alan MyName() print "The first character in your last name is", lastName[0] print "The first character in your first name is", firstName[0] lastName = raw_input("Please enter your last name: ") firstName = raw_input("Please enter your first name: ") def MyName(): Listing 3-4. Demonstrating IronPython’s Zero-Based Arrays element in an array of n elements is the number 0. This is demonstrated in Listing 3-4. arrays (be they character arrays or any other type) are zero-based, meaning that the first which is itself based on the C programming language. Like C and CPython, IronPython very little code. Remember that IronPython is meant to be compatible with CPython, IronPython is also quite adept at pulling one or more characters out of a string using It is nice to see you again! Hello, " Alan Harris ". Please enter your last name: Harris Please enter your first name: Alan Chapter 3  ■ aDVANCED IRONPYTHON 41 .Your name is 10 characters long Please enter your last name: Harris Please enter your first name: Alan MyName() print "Your name is", len(firstName) + len(lastName), "characters long." lastName = raw_input("Please enter your last name: ") firstName = raw_input("Please enter your first name: ") def MyName(): Listing 3-6. Determining the Length of a String equals the total number of characters in a string (Listing 3-6). string operation: the built-in len function. The len function returns an integer value that Before we move on to numerical operations, let’s look at one more particularly useful The first 3 characters in your first name are Ala Please enter your last name: Harris Please enter your first name: Alan MyName() print "The first 3 characters in your first name are", firstName[0:3] lastName = raw_input("Please enter your last name: ") firstName = raw_input("Please enter your first name: ") def MyName(): Listing 3-5. Extracting a Range of Characters from a String Input Chapter 3  ■ aDVANCED IRONPYTHON42 ,this is important to understand. If we call a method and need to extract a value from it We have begun using the return keyword to send values back to the calling method, and last name from the user, and a method that displays the desired results back to the user. Now we have a method that gets the first name from the user, a method that gets the Your name is 10 characters long. Please enter your last name: Harris Please enter your first name: Alan DisplayResult(GetFirstName(), GetLastName()) print "Your name is", len(firstName) + len(lastName), "characters long." def DisplayResult(firstName, lastName): return lastName lastName = raw_input("Please enter your last name: ") def GetLastName(): return firstName firstName = raw_input("Please enter your first name: ") def GetFirstName(): Listing 3-7. Separation of Code into Methods ing as the book continues. time right now to modify that program (Listing 3-7), and we’ll continue this line of think- sure that each method in our code is responsible for only one task at a time. Let’s take the ity to write clean code that is easy to maintain. One way to accomplish this is to make gets user input in addition to displaying the output. As developers we have a responsibil- cifically we have a catch-all method called MyName that performs multiple operations: it principles there. The one I want to draw attention to first is separation of concerns. Spe- If you look at Listing 3-6 again, you’ll see that we are violating a few software design methods. This will make development and maintenance much easier down the road. some comfort with IronPython. We’re going to separate our code further into granular ples, but we’re going to change things a bit moving forward now that we have established Up to this point, all of our code has involved a single method. That is fine for small exam- A Quick Software Development Detour Chapter 3  ■ aDVANCED IRONPYTHON 43 return text text = raw_input("Please enter the text block: ") def GetText(): return word word = raw_input("Please enter the word you want to find: ") def GetWord(): Listing 3-8. Finding a Word in a Block of Text that text, the program will tell us at what numerical position it was found (Listing 3-8). we want to flag; then we’ll provide a block of text that we want to search. If the word is in tion, we’re going to make a word-finding tool. We will provide a word to the program that our new skills and exploring the other data types available to us. For our complex applica- more complex IronPython applications. We will create a more complex application using Now that we have a little experience with separation of concerns, we can begin to create Back on Track DisplayResult method. methods and return any values within them.” Those values get passed along to the DisplayResult line, it reads the two parameters and goes, “I need to execute these two GetFirstName and GetLastName methods. When the IronPython interpreter hits the The DisplayResult method now has two parameters, which happen to be the Loosely coupled, highly cohesive! class as your user login code. This makes it hard to understand what a unit of code is trying to accomplish. terms of purpose and functionality. You don’t want a lot of code related to, say, file operations in the same Cohesion refers to the degree that the methods in a particular area of code are related to one another in putes the length of the first name and last name variables decoupled and placed in their own methods. with the task of displaying content. In fact, the code in Listing 3-7 could stand to have the code that com- My code to display content to the screen should not rely on the input method; it should be concerned only degree a method in your code relies on other methods to do its job. You want methods to be loosely coupled. the same sentence, usually phrased something like “loose coupling, high cohesion.” Coupling refers to the nNote  Coupling and cohesion are two software development terms you will frequently hear together in out necessarily coupling it to other methods in the program. method. This is how we allow each method to have its own distinct task to complete with- method calls a second method, the second method can return a value back to the first we use the return keyword to tell the method what values to return. Thereby, when one Chapter 3  ■ aDVANCED IRONPYTHON44 .and so on. Different languages support them in different ways, including generics and hash tables they are used in configuration files, the querystring values in a web page URI (Uniform Resource Identifier), nNote  Key-value pairs are very important concepts in software development. You can find them all over; Keep your keys unique; keep your values however you like! the set, a dictionary is indexed by its keys and can use any immutable data type for a key. array like the types we’ve seen before use integers (starting with 0) to index elements in value for a given piece of data, much like a real-world dictionary (Listing 3-9). Where an ming sense, are composed of key-value pairs that allow a developer to look up quickly a IronPython fully supports the concept of dictionaries. Dictionaries, in the program- position 0. lying characteristics. Arrays are always zero-based, meaning that the first value in an array is always at nNote  Bearing in mind that IronPython is built around CPython, we recognize that it shares those under- the preceding example, you won’t get any results. data or group of data points. The find method is case-sensitive; if you try to find “D” in very useful method for determining the presence of a piece of data in another piece of We accomplished the word-finding task by using the built-in method find. This is a The word dog was found at position 40 . Please enter the text block: The quick brown fox jumps over the lazy dog. Please enter the word you want to find: dog DisplayResult(GetWord(), GetText()) print "The word", word, "was not found in this string." else: print "The word", word, "was found at position", position, "." if position > -1: position = text.find(word) def DisplayResult(word, text): Chapter 3  ■ aDVANCED IRONPYTHON 45 .{look like {8000.1, 4302.2, 5115.3, 1098.3 the most significant digits. So a set of fixed-point numbers at five-digit precision might numbers. Fixed-point numbers can have only a specific number of digits to the right of can have a variable number of digits. Fixed-point numbers are a subset of floating-point not fixed. A set of floating-point numbers might look like {131.0005, 9.154, 2.511, 3.14} and be {1.0, 2.0, 3.0, 4.0}. The word floating is applied because the decimal point position is integers looks like {1, 2, 3, 4}, the same numbers expressed in floating-point form would point numbers are expressed using fractional or decimal notation. Where a group of whole number expressed without fractional or decimal notations. In contrast, floating- We addressed integer operations in Chapter 2. You may recall that the integer value is a Floating-Point Numbers tion. Look out, Google! web crawler, and dictionaries will quickly become the backbone for the entire applica- given application. Later, when we look at web development, we will build a rudimentary or items in a list. Being able quickly to index, sort, and parse data is often critical in any Now we have the ability to find items in collections, be they single words in a string Tom is a VP in this company. Please enter an employee's name: Susie DisplayResult(CreateDictionary(), GetName()) print name, "does not work for this company." else: print name, "is a", dict[name], "in this company." if dict.has_key(name): def DisplayResult(dict, name): return name name = raw_input("Please enter an employee\'s name: ") def GetName(): return dict dict = {"Jane":"CEO", "Tom":"CIO", "Susie":"VP", "Bob":"VP"} def CreateDictionary(): Listing 3-9. Using a Dictionary to Store Employees Chapter 3  ■ aDVANCED IRONPYTHON46 145570078.085 147481815117794456794 DisplayResult(AddBigIntegers(), AddBigFloats()) print bigFloat print bigInteger def DisplayResult(bigInteger, bigFloat): return bigFloat + 1987345.987247 + 43793873.9871234 bigFloat = 90871234.12341324 + 8917623.987124➥ def AddBigFloats(): return bigInteger + 345678912091873 + 567891230987230987 bigInteger = 123456789098712390847 + 23456789109182743087➥ def AddBigIntegers(): Listing 3-10. Big numbers! overflow problems that can plague statically typed languages. make your variable a floating point as needed, thereby preventing many of the arithmetic number that’s pretty large! This is where dynamic typing comes in handy. IronPython will be critical; your application isn’t of much use if it goes up in flames every time it gets a systems every bit of performance counts. Stability in dealing with large numbers can also ing quickly with these numbers, and that’s important because in computationally heavy IronPython handles big numbers—very big (Listing 3-10). It’s quite adept at work- plication, and division, and IronPython is great at doing all those operations. strong suit, don’t worry. We’re not going to be doing anything more difficult than addition, subtraction, multi- fuzzy logic and neural network programming and in all manner of mathematical software. If math isn’t your nNote  Floating-point numbers are an absolutely critical aspect of software development. They show up in Chapter 3  ■ aDVANCED IRONPYTHON 47 of design is on creating objects, which are essentially the virtual representations of either Object-oriented programming is a method of writing software where the emphasis knee deep into object-oriented programming (OOP). that concept a step further and separate our code into classes, because we’re about to get and to make development and future development easier to manage. We’re going to take methods and modules so that there would be a proper division of concerns in our code immediately! At our last detour we introduced the notion of separating code into different construction on the highway. The difference here is that this will make your life easier If what we took earlier was a software development detour, this next part is the major Classes and OOP The flag is set to true. DisplayFlag(SetFlag()) print "The flag is set to false." else: print "The flag is set to true." if flagEnabled == True: def DisplayFlag(flagEnabled): return flagEnabled flagEnabled = True def SetFlag(): Listing 3-11. Using a Boolean Value for a Flag car or not?). See Listing 3-11. is checking whether or not a specific flag is enabled (for example, is there gasoline in your zeroes (false). There’s a lot of power in the Boolean values. A typical use of Boolean values software running on a computer system is nothing more than a series of ones (true) and sound very simple, but it’s an extraordinarily critical notion. At the most basic level, the can you get than true or false? That’s exactly what a Boolean value is, true or false. It does Is the following statement true or false? Boolean values are pure logic. How much simpler Booleans Chapter 3  ■ aDVANCED IRONPYTHON48 . Type the code in Listing 3-12 exactly as it appears into humanBeing.py and save the file 2. Open up the text browser of your choosing and create a file called humanBeing.py. 1. going on and there’s much to discuss as we go, so please excuse any hand-holding! earlier, in the OOP discussion. This is going to be screenshot-heavy and highly detailed because a lot is For this exercise, we’re going to implement the human being, employee, and CEO classes described CLASSES AHOY! oriented fashion and is essential for you to learn as a developer. will actually be a bit simplistic, but this is a play-by-play of writing classes in an object- exercise, our first big leap into developing real software with IronPython. The end result see how and why we are applying object-oriented principles. This is going to be a big code. Time is money! For this example we will work through the code step-by-step to We are not going to spend a lot of time on theory; we’re going to move right into these principles to interacting with a statically typed language. on. See Chapter 6 for specific examples of polymorphism as well as how to apply This can be achieved by overriding methods, inheriting from other classes, and so those lists, without needing a separate method for every type of list under the sun. or any other type of data. Your business object could use one method to process lists of data. For a fictional business it may be lists of employees, salaries, days off, on different data types. For example, you may need to create code that examines Polymorphism: An object can have a method that performs the same operation • be encapsulated in the CEO object because that is where they are most relevant. class doesn’t need to know that information. The specifics of being a CEO would to have code specific to the particular job of being a CEO, but the human being hiding the implementation details from other objects. For example, a CEO needs Encapsulation: Objects can wrap up behaviors and data specific to that object, • are increasingly specific and customized. object is the generic parent object, and employees and CEOs are subobjects that traits of an employee, who inherits the traits of a human being. The human being a parent–child relationship between objects. For example, a CEO can inherit the Inheritance: Objects can inherit traits from other objects, which essentially creates • ing OOP code, so let’s break these principles down. combination of these. Certain design principles must be adhered to for a developer writ- abstract or tangible items. These objects may contain attributes, behaviors, data, or any Chapter 3  ■ aDVANCED IRONPYTHON 49 .errors at this initial step Be aware that this does not ensure that your code works properly! This only helps catch syntax finds errors in your code, verify that everything matches Listing 3-12 and perform this step again. screenshot, which indicates that the interpreter has completed successfully. If the interpreter Type ipy c:\python\humanBeing.py and press Enter. You should see a result like the following 3. a few steps from now, you’ll see what I mean more clearly. instance of the object you’re using can refer to its own data and methods. When we cover instantiation nNote  What’s this “self” business we see scattered about Listing 3-12? Simply put, self is how an and weighs", self.weight, "pounds." print "Human being", self.name, "is", self.age, "years old➥ def getInfo(self): return self.weight def getWeight(self): return self.name def getName(self): return self.age def getAge(self): # methods for getting attributes about our human self.weight = weight def setWeight(self, weight): self.name = name def setName(self, name): self.age = age def setAge(self, age): # methods for setting attributes about our human weight = 190 name = 'Alan Harris' age = 25 # some data about our human, with default values for convenience class Human: Listing 3-12. humanBeing.py Implementation Chapter 3  ■ aDVANCED IRONPYTHON50 .the correct location. You should immediately see another prompt in the book for storing your IronPython code. If you have placed your code elsewhere, substitute Now type sys.path.append(‘c:\python’) and press Enter. This is the path I recommended earlier 6. the Human class. them all. In the next steps, you’ll see how to use it to provide access to the code you created for and functionality available for use. IronPython has a lot of features, and we don’t always need another prompt (as in the next screenshot). The import command serves to make additional code At the >>> prompt, type import sys and press Enter. You should immediately be presented with 5. Type ipy and press Enter. This should open the IronPython interpreter. 4. Chapter 3  ■ aDVANCED IRONPYTHON 51 ”.ingful name that allows the interpreter to say, “Oh, I get it, you want this particular thing to happen realize what you’re trying to accomplish. Providing a namespace lets you prefix your classes with a mean- knowing which one you wanted! Computers are powerful but dumb; they need your explicit instruction to names. If you were to try to reference one of yours in your code, the interpreter would have no way of to a minimum. Say you created a handful of classes that have names identical to someone else’s class nNote  A namespace is nothing more than a grouping that allows you to keep naming collisions down working with classes we’ve created. namespace for our code to live in, so we will need to employ that namespace as a prefix when and see what it can do for us. The file name humanBeing is used to set up automatically a Having successfully imported the humanBeing.py file to the environment, let’s take it for a spin 8. press Enter, your screen should look like the following screenshot. can use it. The .py extension is assumed, so you don’t need to add it. After you type this and want to add all the code in a file called humanBeing.py to your current environment so that you Type import humanBeing and press Enter. What this tells the IronPython interpreter is that you 7. next step. didn’t do that, the interpreter wouldn’t know we had code in that location and we’d get an error in the would like to include the location C:\Python as a valid place from which to import modules. If we nNote  What have we just done? The step just performed tells the IronPython interpreter that we Chapter 3  ■ aDVANCED IRONPYTHON52 .Human being Tom Smith is 55 years old and weighs 160 pounds >>> friend.getInfo() Human being Alan Harris is 25 years old and weighs 190 pounds. >>> neighbor.getInfo() but they are specific instances of Humans with unique data in them. Let’s prove that point. We’ve created two Humans now, one called neighbor and one called friend. They are both Humans, 13. >>> friend.setWeight(160) >>> friend.setName('Tom Smith') >>> friend.setAge(55) >>> friend = humanBeing.Human() Now let’s use our Human class to make another instance of a Human, one with different traits. 12. ues we provided, so it returns those defaults. Methods are called in the format object.methodName. of Human that we have. In this case, we have not provided any details that override the default val- The Human class has a method called getInfo that displays information about the particular instance 11. Human being Alan Harris is 25 years old and weighs 190 pounds. >>> neighbor.getInfo() and authors use it, so I would like to maintain some convention as you read other people’s code. ever you like. I use self because it’s convenient and because I am aware that many other developers which Human we’re dealing with. You don’t actually have to use the word self; you could call it what- neighbor, and neighbor is passed into the methods to fulfill the self parameter and to tell the interpreter you will see what the use of self just allowed. We instantiated a class of Human as an object called just did and at the underlying Human() method of the humanBeing class in your humanBeing.py file, nNote  Remember how I said you’d see the self parameter used more clearly? If you look at what we should try to use one of the methods that a Human class provides. class. We can instantiate as many Humans as we like, and we’ll do just that shortly, but first we and instantiated it as a Human, meaning the neighbor object is now one instance of the Human Human, but which human are we talking about? So for this example we made a neighbor object When we have a class, it does not yet refer to any particular object. We have a class called 10. >>> neighbor = humanBeing.Human() it out. Now that we have told the interpreter where to find our class and successfully imported it, let’s try 9. Chapter 3  ■ aDVANCED IRONPYTHON 53 self.hours = hours def setHours(self, hours): self.payrate = payrate def setPayRate(self, payrate): # methods to set that data hours = 40 payrate = 10 # some basic data about our employee class Employee(Human): Listing 3-13. humanBeing.py Continued ing 3-13; then save it. pressing Enter. Open your humanBeing.py file and add to the bottom of the file the code in List- instance that is by default also an instance of a Human. Quit the interpreter by typing exit() and this clearly doesn’t work in reverse!). By creating an Employee, we will be creating an Employee Employee that will be more specific than a Human. Employee will inherit traits from Human (since Let’s make things more specific. We are going to modify our code to make a class called 15. something like the following. Now, that’s pretty cool. If you’ve been following along on your computer, your screen should look 14. Chapter 3  ■ aDVANCED IRONPYTHON54 .which totals $ 400 weekly Current pay rate is 10 dollars per hour at 40 hours per week,➥ >>> tom.getEmployeeInfo() Human being Tom Smith is 55 years old and weighs 160 pounds. >>> tom.getInfo() >>> tom.setHours(40) >>> tom.setPayRate(10) >>> tom.setWeight(160) >>> tom.setAge(55) >>> tom.setName('Tom Smith') >>> tom = humanBeing.Employee() properties that a class must completely implement to meet the criteria of the interface. can generally implement multiple interfaces. Interfaces define a contract, specifying the methods and Technically speaking, although other .NET languages tend to support only single inheritance, you criticized feature of certain languages, and you won’t see me making use of it in this book. they support only single inheritance. Multiple inheritances can be a maintenance nightmare! It’s an oft- than one base class. You will find this is not the case in other .NET languages, such as VB .NET and C#; nNote  IronPython, like CPython, supports multiple inheritance. That is, a class can inherit from more import the humanBeing file. employees and try this out. Open the interpreter again, import sys, add your Python directory, and to the Human class, we’ve got a few data values and ways to display them. So let’s create some the interpreter that Employee inherits from Human. In a moment we’ll prove that point. Similar tion for Employee we have (Human) at the end. This is how inheritance is specified; we’re telling Now we have an Employee class that inherits traits from the Human class. Note that in the defini- 16. self.hours * self.payrate, " weekly." hour at", self.hours," hours per week, which totals $",➥ print "Current pay rate is", self.payrate, "dollars per➥ def getEmployeeInfo(self): return self.hours def getHours(self): return self.payrate def getPayRate(self): # methods to retrieve that data Chapter 3  ■ aDVANCED IRONPYTHON 55 .the file the code in Listing 3-14, and then save it then to make use of it. Close the interpreter and open humanBeing.py again, add to the bottom of We’re almost done! All we need to complete our exercise is to add the CEO class to this code and 18. of 10 dollars an hour. It’s really nothing more complex than that. progresses. In our example here, Tom “is an” Employee, which “is a” Human, and he “has a” pay rate object possesses, and “is a” relationships describe what an object is. We’ll see more of this as the book eties. For example, a dog “is an” animal and “has a” heart. “Has a” relationships describe traits that an nNote  Relationships in object-oriented terms are generally broken down into “is a” and “has a” vari- Employee. ship, in object-oriented terms. An Employee “is a” Human, but a Human is not necessarily an Human, an Employee needs to know how to be a Human. This is an example of an is a relation- derived from our Human class. Whereas the behavior specific to an Employee is hidden from a If all went well, you should see something like the following screenshot. Our Employee class is 17. Chapter 3  ■ aDVANCED IRONPYTHON56 .humanBeing.py is provided on the following pages Type exit() and press Enter to exit the interpreter. You’re done! The entire listing of 20. $11500.0 . The CEO's end-of-year bonus is $ 5000 * 2.3 % annual growth, totaling >>> tom.getCEOInfo() which totals $ 2800 weekly. Current pay rate is 70 dollars per hour at 40 hours per week,➥ >>> tom.getEmployeeInfo() Human being Tom Smith is 55 years old and weighs 160 pounds. >>> tom.getInfo() >>> tom.setAnnualGrowth(2.3) >>> tom.setBonus(5000) >>> tom.setHours(40) >>> tom.setPayRate(70) >>> tom.setWeight(160) >>> tom.setAge(55) >>> tom.setName('Tom Smith') >>> tom = humanBeing.CEO() code. Open the interpreter again, import everything you need from the preceding, and try the following 19. self.bonus * self.annualGrowth, "." " * ", self.annualGrowth, "% annual growth, totaling $",➥ print "The CEO's end-of-year bonus is $", self.bonus,➥ def getCEOInfo(self): # methods to get that data self.annualGrowth = annualGrowth def setAnnualGrowth(self, annualGrowth): self.bonus = bonus def setBonus(self, bonus): # methods to set that data annualGrowth = 2.3 bonus = 5000 # some data about the CEO class CEO(Employee): Listing 3-14. humanBeing.py Continued Chapter 3  ■ aDVANCED IRONPYTHON 57 self.hours = hours def setHours(self, hours): self.payrate = payrate def setPayRate(self, payrate): # methods to set that data hours = 40 payrate = 10 # some basic data about our employee class Employee(Human): "years old and weighs", self.weight, "pounds." print "Human being", self.name, "is", self.age,➥ def getInfo(self): return self.weight def getWeight(self): return self.name def getName(self): return self.age def getAge(self): # methods for getting attributes about our human self.weight = weight def setWeight(self, weight): self.name = name def setName(self, name): self.age = age def setAge(self, age): # methods for setting attributes about our human weight = 190 name = 'Alan Harris' age = 25 # some data about our human, with default values for convenience class Human: Listing 3-15. Complete Listing of humanBeing.py Chapter 3  ■ aDVANCED IRONPYTHON58 .things easy at the beginning of code you haven’t touched in six months or a year, you’ll thank yourself for making will make you a better developer. And when you return to do maintenance on a piece simpler pieces and separating concerns into small, easy-to-maintain components an OOP developer thinks when approaching a problem. Breaking problems down into to the modeling of enterprise solutions, but it is very effective at demonstrating how Granted, the human-to-employee-to-CEO example is rather simple when compared understanding of object-oriented principles and how they apply to business solutions. a pretty good grasp of the fundamentals of classes in IronPython, as well as some an employee, to being a wealthy CEO in a few short pages. By now you should have That’s not a bad raise for Tom! Tom has gone from being a mere human, to being self.bonus * self.annualGrowth, "." " * ", self.annualGrowth, "% annual growth, totaling $",➥ print "The CEO's end-of-year bonus is $", self.bonus,➥ def getCEOInfo(self): # methods to get that data self.annualGrowth = annualGrowth def setAnnualGrowth(self, annualGrowth): self.bonus = bonus def setBonus(self, bonus): # methods to set that data annualGrowth = 2.3 bonus = 5000 # some data about the CEO class CEO(Employee): self.hours * self.payrate, " weekly." self.hours," hours per week, which totals $",➥ "dollars per hour at",➥ print "Current pay rate is", self.payrate,➥ def getEmployeeInfo(self): return self.hours def getHours(self): return self.payrate def getPayRate(self): # methods to retrieve that data Chapter 3  ■ aDVANCED IRONPYTHON 59 ()road = roadmaps.GetHighway vehicle = vehicleTypes.GetCar() # these are reference types; they are instances of objects driver = "Speedy" driving = true speed = 55 # these are value types; they are not instances of objects Listing 3-16. Value Types vs. Reference Types Listing 3-16 shows the implementation differences between the two types. cated in memory for an object, and then instances of that object are created. The code in value types. Classes are reference types; when a reference type is created, space is allo- assigned to that particular variable. Integers, Booleans, and strings are all examples of In .NET, a value type is a variable whose data is stored directly within the memory Value and Reference Types understand this concept and its implications. We can also take advantage of its benefits. this information. When working with other .NET classes and code, it is important to construction of a statically typed language; IronPython by itself does not care for or need unified type system, allowing any class or data type to be treated as an object. This is a What does it mean to have every type derive from object? Essentially it creates a structed is object. with a primitive integer or some fancy GetTerrificProgramFactory class—the foundation on which it’s con- nNote  Object is the superclass that all types inherit from in .NET. It doesn’t matter if you’re working everything in .NET is an object. destination, however; regardless of whether you’re dealing with a value or reference type, with strings and numbers and so on. The .NET framework takes a different route to the In the .NET world, most of what we’ve covered still rings true. Developers are still working .NET Data Types Chapter 3  ■ aDVANCED IRONPYTHON60 The only thing we have to fear >>> print sentence.Substring(0, 30) >>> sentence = "The only thing we have to fear is fear itself." Array[str](('the/', 'quick/brown/fox')) >>> print dummy.Split('q') Array[str](('the', 'quick', 'brown', 'fox')) >>> print dummy.Split('/') >>> dummy = "the/quick/brown/fox" 7 >>> print number.ToString() >>> number = 7 12345 >>> Int32.Parse("12345") Foo has a value. … (press Enter at this prompt.) >>> if not (String.IsNullOrEmpty(foo)): print "Foo has a value." … (press Enter at this prompt. Nothing should be displayed.) >>> if (String.IsNullOrEmpty(foo)): print "Foo has no value." >>> foo = "bar" >>> from System import * >>> clr.AddReference('System') >>> import clr Python syntax and perform various operations on them using classes from .NET. Open up the IronPython interpreter. For this exercise, we’ll create a few types using the traditional MIXING .NET WITH PYTHON constructs to use in your code. another .NET language, you can freely take advantage of traditional Python code and advantage of .NET framework classes when you like. If you’re a seasoned developer in developer, you can use all the same syntax you’re comfortable with and simply take The wonderful thing about IronPython development is that if you’re a seasoned Python Mixing and Matching Chapter 3  ■ aDVANCED IRONPYTHON 61 .mix and match IronPython types with the .NET framework IronPython supports development of advanced systems, and we discussed how we can ated a variety of classes to demonstrate object-oriented software development and how large numerical data types in IronPython and dealt with Boolean values. Finally we cre- we can retrieve that information in a straightforward fashion. We have covered very them. We have learned how to store data in a few different types of structures and how We’ve covered the basic Python data types and how to perform common operations on Summary Chapter 3  ■ aDVANCED IRONPYTHON62 63 Chapter 4 IronPython Studio “Once a new technology starts rolling, if you’re not part of the steamroller, you’re part of the road.” — Stewart Brand One of the best resources that programmers have is an integrated development environment, or IDE. Up until now, we’ve been doing all our programming using the IronPython console interpreter, which is a viable way to program but not the easiest or fastest. Luckily for us, Microsoft has an IDE solution integrated with Visual Studio 2008 that makes developing and managing IronPython applications downright enjoyable. This IDE, called IronPython Studio, is a free download from Microsoft that facilitates develop- ment of IronPython console and Forms applications. Before we get started installing and working with the IDE, let’s take a look at where we’re going regarding application devel- opment and why an IDE is such a useful tool in a programmer’s arsenal. Hopping Onto the Steamroller So far, you should be pretty well versed in building a console application; that’s all we’ve really done so far. Such applications are a terrific way to begin learning a programming language because you don’t have to concern yourself with the shiny veneer of a typi- cal Windows application and the programming tasks that come along with developing software in that type of environment. Console applications are great for trying out ideas or algorithms quickly, and they make wonderful test beds for code libraries and snippets you will write throughout your programming career. nNote  A test bed is a platform or environment where you can test software in a repeatable way. An exam- ple from my real-life development is an ASP .NET project I created at work called Sandbox. The Sandbox project is set up in such a way that I can reference code I’ve written elsewhere and measure performance, perform automated testing to make sure I’m getting the results I expect, and so on. For smaller situations, I’ll frequently make a console application and run my code in it to see how things are working. We’ll cover these types of development situations in detail a bit later and work through some examples firsthand. (Application.Run(form form = IronPythonForm() self.Name = 'FormApp' self.Text = 'IronPython Forms Application' def __init__(self): class IronPythonForm(Form): from System.Windows.Forms import Application, Form clr.AddReference("System.Windows.Forms") import clr import sys Listing 4-1. An Implementation of a Forms Application Listing 4-1 exactly as it appears and save it as form.py. of your choice (for me, it’s Notepad—I feel so low-tech sometimes), enter the code from we’ll be using IronPython Studio to handle some of the heavy lifting. With the text editor set up and why our lives will be so much easier throughout the rest of the book, because builds a basic Forms application by hand. We’ll see what happens when an event loop is We’re going to put all of these things in perspective with some IronPython code that roadblock. Our poor programs are waiting at a perpetual red light with their blinkers on. state indefinitely, unable to perform any other tasks until it has gotten past that one particular power and never crashes, if a user never provides that input, the system will be stuck in that quietly twiddles its thumbs until that input is provided. Assuming that the system always has generally operating in a very linear, one-way style: the program asks the user for input and is that in those examples we did not implement a true event-driven system. We have been ing. In a very limited sense, we have done that ourselves in previous examples. The problem available, and then updating the user interface so that the end user knows things are happen- requires that the program operate in a continual loop, checking for inputs, processing them if we’ve done so far in this book, although there are a few similarities. Responding to events An event-driven programming model is a bit different than the type of programming operating system, the user, or another program entirely. capable of responding to and acting on a variety of events that could be initiated by the use what is called an event-driven programming model, meaning that the application is creative with the interface (sometimes too creative!). In the context of Windows, they controls to maintain a consistent look and feel when possible, although you can get very with in a Windows environment. They generally use the standard Windows menus and Forms applications are the typical style of software you’re probably used to working Chapter 4  ■ IRONPYTHON STUDIO64 .some specifics about the names we want to use in our code that can be found in System from System.Windows.Forms import Application, Form, which indicates to the compiler the System.Windows.Forms namespace. Immediately afterward we told IronPython imported a moment ago, I want to be able to access the methods available to me in the road, as it were. What we’re telling IronPython is: In that clr assembly we The line clr.AddReference("System.Windows.Forms") is where the rubber meets development. file and can be made up of one or more code files. Basically, it’s the building block of .NET software nNote  In .NET lingo, an assembly is the smallest unit of code available for deployment. It is a versioned functionality from scratch by yourself. .NET framework as well as saving you the pain (or pleasure) of implementing a lot of this exposes a large number of namespaces and methods that let you write code to target the framework and is really the starting point for the rest of this book. The CLR assembly assembly. Importing the CLR assembly lets us tap into the vast functionality of the .NET modules or assemblies. We’ve used this command before, but not in terms of the CLR Import sys and import clr allow us to use functionality that is contained in other tions will be built. really examine what’s happening here, for this is the foundation on which our applica- is going to look familiar, and some of it looks just plain cryptic. Let’s go line by line and You have to admit, that’s pretty cool for so little effort. Now, some of the code syntax Figure 4-1. It’s not terribly interesting, but it does run! lines of what’s displayed in Figure 4-1. Adams, “Don’t panic.” Go ahead and run this script. You should see something along the Whoa! This whole IronPython thing just got real. In the immortal words of Douglas Chapter 4  ■ IRONPYTHON STUDIO 65 .Now you’ll need to proceed with the installation of IronPython Studio ter 1. If you haven’t, flip back and do so. that you have installed Visual Studio; it’s one of the prerequisites I mentioned in Chap- install IronPython Studio and see what benefits we can make use of. I’m going to assume boilerplate, and we should be able to automate the whole routine. Let’s take a moment to it. However, I don’t imagine you want to type that code every time you create a form; it’s you understand the system with which you’re working, the better use you can make of Knowing what’s happening at a low level is very, very useful in programming. The more So Much Typing...Is There a Better Way? to teach you a shortcut. input. We’ll provide that sort of useful information to it in a bit. Right now we’re going Underneath the hood, the event loop is running, waiting for incoming messages and it, close it, or leave it sitting quietly while you go about your business. It doesn’t care. around waiting for us to do something either. You can minimize or maximize it, resize You’ll notice that although the program isn’t doing much, it’s also not really sitting for us to use. application, it was created by hand in a dynamic language, and it has a basic event loop useful at the moment, it does exhibit some very important traits: it is a true Windows loop for us. That’s not too bad for 10 lines of code! Although the program isn’t terribly finally we got everything moving with Application.Run(form), which set up the event Next we created an instance of the form by writing form = IronPythonForm(), and the application as well as the name of the form. the properties self.Text and self.Name, we modified the text that appears in the title bar of A form exposes a variety of properties and methods to the outside world. By changing Form object as its parameter. Windows.Forms. Then we created a class, class IronPythonForm(Form), which takes a Chapter 4  ■ IRONPYTHON STUDIO66 .Project. You should see something similar to the image that follows Once the installation is complete, open your copy of Visual Studio and select New and then 4. Download the zip file, open it, and run the IronPythonStudioIntegrated file to install IronPython Studio. 3. IronPythonStudioIntegratedSetup link. Click the IronPythonStudio 1.0 release link; then on the next screen click the 2. In the web browser of your choosing, go to http://www.codeplex.com/IronPythonStudio. 1. price when it comes to a piece of software you want. As I write this chapter, the current version is 1.0. IronPython Studio is available as a free download from the CodePlex web site, and free is always the right DOWNLOADING AND INSTALLING IRONPYTHON STUDIO Chapter 4  ■ IRONPYTHON STUDIO 67 .(right-click on Form1.py and select View Code (Figure 4-3 earlier, however. On the right-hand side of the screen, in the Solution Explorer window, That doesn’t mean that IronPython Studio generated the exact same code we used some of the setup tasks means less code for you to write by hand. it’s not a good use of your time continually to reinvent the wheel. Letting the IDE handle is always a good idea to experiment and try things the manual way where possible. But ment does some of the work for you. Again, for the sake of learning or understanding, it Here we see the first major benefit of using the IronPython Studio IDE: the environ- significantly. Figure 4-2. Looks pretty familiar, no? The IDE can reduce your development times something like Figure 4-2. form2 and click OK. After the program chugs and works for a few moments, you’ll see Studio provides. Click the Windows Application template; then, for a Name type, select The second illustration in the sidebar shows the default project types that IronPython Chapter 4  ■ IRONPYTHON STUDIO68 .program in Figure 4-1 the Debug drop-down menu. Compare the way the program looks in Figure 4-4 to the Go ahead and press F5 to build and run this program, or select Start Debugging from ping the IDE from complaining to you every time you run the application. nNote  Always save your work. It’s just a good practice to get into and has the additional benefit of stop- modify the line self.Text = 'Form1' to self.Text = 'IronPython Forms Application'. ments are recognizable. Modify the line self.Name = 'Form1' to self.Name='FormApp'. Next, Although the code doesn’t look quite the same as what we wrote, some of its ele- Figure 4-3. On second thought, maybe it’s not entirely familiar. Chapter 4  ■ IRONPYTHON STUDIO 69 .precise, just larger than what we started with form. See Figure 4-5 for a rough idea of the size we’re looking for. It doesn’t have to be square halfway down the right-hand side of the form, and drag it to the right to resize the across the bottom of the form. Click and hold down the left mouse button on the white right corner of the form, halfway down the right-hand side of the form, and halfway Designer. With the designer on screen, you will see small white squares on the bottom- [design] tab or right-clicking on Form1.py in the Solution Explorer and selecting View First, select the design view of the application by either clicking the Form1.py programming, let’s try it for ourselves. needs to do something. Having touched on the event-driven model in Forms application demand action. It’s not good enough to sit there and stare back at me; the application It’s all well and good to have a basic form on the screen, but we’re developers. We Forms, from the Ground Up the bare minimum. nNote  Anyone out there who’s a fan of “Office Space” should know what happens to people who just do on the situation and the requirements of the application. implement a lot of features or just the bare minimum to achieve functionality. It depends of truth to that. You can take a variety of ways to get to the same destination. You can They say all roads lead to Rome, and in IronPython development there is an element Figure 4-4. The end result looks the same. Chapter 4  ■ IRONPYTHON STUDIO70 .message if you haven’t saved before trying to run your code nNote  One last reminder to save! The compiler will pop up a well-intentioned, if not slightly annoying, Figure 4-6. We’ve added two controls to the form. and drag a Label object and a Button object to the form (Figure 4-6). on the Common Controls tab in the toolbox so that it is opened up, and then left-click components can easily be added to an application via the toolbox. For right now, left-click this application in C# or VB.NET. This is a second benefit of the IDE: common objects and are standard to .NET, meaning that you can access identical objects if you were building tabs that contain objects you can drag and drop onto your form. Many of these elements On the left-hand side of the screen, you will see the toolbox, which has expandable Figure 4-5. Our form expanded! Chapter 4  ■ IRONPYTHON STUDIO 71 .Button1 to btnUpdate on your form and scroll the properties list to the (Name) property and change it from list. Change the name from Label1 to lblUpdateText. Next, left-click on the Button control Scroll the list of properties until you see one called (Name), which should be third on the the bottom right of the IDE change to reflect the properties of the object you’ve clicked. Left-click on the Label control on your form. You will see the Properties window on figure out the functional difference between Label39 and Label17. Get into good naming habits early! too bad. However, once you’ve got 50 or 100 controls on a form, you’ll want to tear your hair out trying to form, the next one would be called Label5. When you’re dealing with one or two controls on a form, this isn’t than the previous number of that control on the form. For instance, if you had added five Label controls to the you add a control, the IDE will generally use the name of the control followed by a number that is 1 greater nTip  I can’t stress how awful a habit it is to leave controls with the default names the IDE provides. When meaningful names. Before we start working heavily with these controls, we really need to give them precisely zilch. wire our button to the application by providing some instructions on what to do when it’s clicked, it does wouldn’t do anything. You could flip it all day and get no result. The same is true in programming. Until we ing world. A button by itself is useless. If the power switch on your computer weren’t wired to anything, it nNote  When a developer wires a control, he or she is borrowing a term from the electrical engineer- don’t need to do anything about it.” was received loud and clear. Unfortunately, that message was “I was clicked, but you not doing anything for the application’s not knowing we clicked the button. The message ton. Specifically, we didn’t wire the button to anything. Don’t mistake the application’s Nothing happened because we didn’t tell the application what to do if we click the but- It’s All This Substandard Wiring! But why? application again. Click the button. What happened? Well, a big fat nothing happened. Once you’ve got both the label and the button on the form, press F5 to run the Chapter 4  ■ IRONPYTHON STUDIO72 .(see the text change from its initial state to the text you just entered (Figure 4-7 Let’s try running the application again; once it’s loaded, click the button. You should updated accordingly." self._lblUpdateText.Text = "The button was clicked, so this text has been➥ Listing 4-3. Telling the Button to Update the Label Control pass line, add the code shown in Listing 4-3. pass can be removed; it’s a placeholder in IronPython that does nothing. In place of the that accepts self, a sender object, and a list of event arguments as parameters. The line This method signature says that you’ve defined a method called _btnUpdate_Click pass def _btnUpdate_Click(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) Listing 4-2. A Snippet of the Button-Handling Code the IDE Produced code window (Listing 4-2). Note that the IDE has inserted a bit of code on your behalf. a little wiring. Double-click on the Button control on your form; this should bring up the Now that you’ve given some more meaningful names to your controls, it’s time to do make total sense now, but I bet a few months from now things might not be so clear. maintenance in the back of your mind. That button you made called PDmc2 to update the sales reports might alternative naming convention, by all means do what works best for you. With that said, always keep future ing just fine. This a personal preference. If you find you don’t want or need those prefixes or you prefer some occupying a lot of space. To me, LabelUpdateText is a bit verbose, whereas lblUpdateText conveys the mean- at a quick glance in my code I can easily identify the buttons, labels, text boxes, or any other control without three-letter abbreviation, to let me know what type of control I’m dealing with. This comes in really handy; nTip  For controls on either a web page or a Forms application, I tend to prefix the control name with a Chapter 4  ■ IRONPYTHON STUDIO 73 .templates Figure 4-8. IronPython Studio provides some ready-made code templates as well as project then click New Item (Figure 4-8). text to it. Right-click on the form2 project title in the Solution Explorer, select Add, and should do is create an IronPython class file and move the code that actually changes the the form has intimate knowledge of the behavior of the button when it’s clicked. What we Now that we’ve gotten our button wired up, we should clean up our code a bit. Currently Clean Code Is Happy Code Figure 4-7. The button was successfully wired and the label updated. Chapter 4  ■ IRONPYTHON STUDIO74 .run the application _btnUpdate_Click method so that it matches what appears in Listing 4-5, and press F5 to the UpdateText method when the button has been clicked. Change the code in the the button on our form. We need to create an instance of our utility class and call With our UpdateText method complete, we should turn our attention back to accordingly from utility.py." return "The button was clicked, so this text has been updated ➥ def UpdateText(self): "Some basic IronPython utility methods" class utility: Listing 4-4. Filling in the Functionality of the UpdateText Method the code exactly as it appears in Listing 4-4, and save the class file. tially. Let’s go ahead and make a method called UpdateText to fulfill this purpose. Enter The desired result is that we will be updating a label with different text than it had ini- Figure 4-9. Generated class templates are very sparse initially. It should look like Figure 9-10. IronPython Studio will add the file to your current project and automatically open it. Select Class, and name the new class file utility.py. When ready, click Add. Chapter 4  ■ IRONPYTHON STUDIO 75 !get information on the condition of the program, make edits, and so on. Wahoo Studio, when we run the application and an error occurs, operation pauses and we can examine the current state and see precisely what had caused the error. In IronPython us what happened and where, but we would be unable to pause program operation to this code via the IPY interpreter, then once an error occurred the interpreter would tell of the three major benefits of an IDE over the console interpreter. If we were running Before we address what this error means and why it’s there, we should highlight one Figure 4-10. The IDE has pointed us to an error in our code. in my code; see Figure 4-10. happened? We were taken back to IronPython Studio where the IDE had flagged an error form you’ve gotten so used to. Click the button. Did the label control update? No? What The application should start up fairly quickly and you’ll be presented with the same self._lblUpdateText.Text = util.UpdateText util = utility.utility() def _btnUpdate_Click(self, sender, e): Listing 4-5. Updating the Button to Call Our Utility Class Chapter 4  ■ IRONPYTHON STUDIO76 : class form2 import utility from clr import * from System.Drawing import * from System.ComponentModel import * from System.Windows.Forms import * import System Listing 4-6. Importing the Utility Class with the Relevant Line Boldfaced that it matches what appears in Listing 4-6; then press F5. ment importing our utility class for everything to function properly. Modify the code so import statements above the beginning of the class declaration. We need to add a state- Stop button on the toolbar. At the top of the code in Form1.py you will see numerous ging session either by closing the application, by pressing Shift+F5, or by pressing the Let’s correct the error in our application and try this again. You can stop the debug- would say Imports. They both do essentially the same job that import does in IronPython. nNote  In C#, you can access code in another module or assembly with the using keyword. In VB.NET, you that you’ve done everything correctly before you hit F5 and try it out for yourself. something to be aware of; in IronPython the IDE is less of a crutch in terms of ensuring matching method available to call, and it lets us know about it at runtime instead. It’s interpreter won’t know there’s a problem until it hits that line in the script and finds no us immediately and prevent us from even running the code. In IronPython Studio, the we have not made accessible via the using or Imports statement, then the IDE will tell inaccessible from our current location. In C# or VB.NET, if we attempt to call code that import our utility module into our form, so the code we wrote to update the text is a static one like C# or VB.NET. The reason the code has failed to run is that we did not This is also a terrific demonstration of the nature of a dynamic language versus more in the next chapter. a variety of other debugging tasks to nip problems in the bud. We’ll cover these debugging techniques and pauses and control is returned to IronPython Studio. You can examine variable values using watches and do set things called breakpoints in your code. When a breakpoint is set on a line of code, program execution nNote  You don’t only get access to this sort of information when something goes terribly wrong. You can Chapter 4  ■ IRONPYTHON STUDIO 77 .tions and expand our Forms applications significantly forward we’ll cover more advanced debugging techniques and multiple language solu- We’ve only touched the surface of what IronPython Studio can do for you. As we move rifice some of the benefits the IDE provides, such as templates and efficient debugging. fine if you choose to do your work via the command line, but do so knowing that you sac- benefits in working with an IDE over working with the console interpreter. It’s perfectly Now that you’ve had the introductory tour to IronPython Studio, I’m sure you see the Summary Figure 4-11. Now that we have fixed the error, our utility class is used properly. form pop up on screen. Click the button and see if your result matches Figure 4-11. As before, the application should build and execute fairly quickly, and you’ll see the call it accordingly. nTip  These namespaces are case sensitive; if you named your class Utility instead of utility, you’ll need to Chapter 4  ■ IRONPYTHON STUDIO78 79 Chapter 5 Mixing and Mingling with the CLR “Let us change our traditional attitude to the construction of programs. Instead of imagining that our main task is to instruct a computer what to do, let us concen- trate rather on explaining to human beings what we want a computer to do.” — Donald Knuth So far, we’ve skipped along the surface of the Common Language Runtime and the .NET framework, making use of features and functions without really examining the tools at hand to understand what’s happening behind the scenes. This is the point where Iron- Python really begins to separate from the crowd and show off a bit, mixing the best of the Python language with the power and stability of the .NET framework. “CLR-ance, Clarence.” What exactly is the Common Language Runtime? The CLR is a core component of the .NET framework, responsible for taking intermediate bytecode from the CIL, or Common Intermediate Language, and translating it to native code that can be run on the target platform. In plain English, you write your program source code in IronPython. Then this code is compiled to a standardized intermediate language (CIL). But this code isn’t yet native code, that is, at a point where it can be executed by the operating system. The CLR takes care of that remaining step, converting the intermediate code to a final product that the operating system can interpret and execute. In the previous chapter we saw how IronPython connects in code to the CLR, and we have already gotten some experience with standard .NET controls and events. What we need to do now is to take that knowledge a step further and build a completely functional, real-world application from the ground up. This lets us cover most of the phases of the software development life cycle and provide some concrete examples of how to work with the .NET framework. . The application should operate on plain text documents 3. The application should be capable of CRUD operations to the file system. 2. The application should consist of a single form. 1. itself, but we haven’t addressed the functional requirements. mulate a plan. We already described some basic requirements for the application design Like any great bank heist (or piece of software, if crime isn’t your thing), we have to for- The Plan much! Oh, and if the *nix “vi” users could stop snickering we’d all appreciate it. but I’d rather build Notepad from scratch in IronPython than build Windows Media Player, thank you very I’m marginalizing their work. If Notepad were poorly written, nobody would use it. You guys did great work, nNote  I hope the developer or developers at Microsoft who created Notepad don’t get the impression that us do it. For our small application, we’re going to build Notepad, and the CLR is going to help lem domain to build it. I don’t have to implement a massive number of features or understand a complex prob- time-consuming. And finally, it has to have a small enough operational footprint that of the language. It has to be small enough that the project is manageable and not overly operations so that I can examine common (and occasionally not-so-common) aspects a small application from start to finish. It has to be an application that covers a variety of here or a pattern there doesn’t really tie things together the way I’d like. Normally I build learn a language when I have something in mind to build; aimlessly toying with a method to share with you here, because it has served me well in the past. I find that it’s easier to Cliché though it may be, I have a process for learning a new language that I’m going purpose of this discussion, these five are sufficient. and some design methodologies break down individual steps into smaller, more discrete steps. But for the implementing that design, and, finally, maintaining that design. By definition this process tends to repeat, involved in development: planning a project, gathering application requirements, designing the application, working on software. There is no precise definition of these steps. Rather, you can consider five key areas nNote  The software development lifecycle, or SDLC, is a series of steps that developers go through while Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 80 ”.makes maintenance a nightmare and induces a form of “code rigor-mortis never coupled in the first place) is making sure you don’t throw everything into the code of the form itself. It code to do its job. We don’t want that. Step 1 in the process of decoupling code (or, rather, ensuring it is nNote  As you probably remember from Chapter 3, code that is tightly coupled is highly dependent on other consider appropriate, based on the needs of the application you’re developing. gram operation in there. As your applications grow, they can have as many layers as you startup code. We will create a business logic folder and put all our code relating to pro- On the coding side, we’ll have our main project that contains the form and necessary top of the form to facilitate the file and print operations the user will need. tially covers that entire form, except for the menu bar. The menu bar will be at the very have the primary form that houses all our controls, and we’ll have a text box that essen- In terms of the user interface, our application will be pretty straightforward. We’ll multimillion-dollar application and hedge our bets now. should be fairly straightforward, but let’s assume that it might evolve one day into a that change is a constant in software engineering. That said, our text editing application made significantly easier by anticipating, not the changes themselves, but the very fact We’ll see examples throughout the construction of our applications where changes are maintenance comes up; an inflexible architecture is a difficult-to-maintain architecture. teams find that a little design flexibility goes a long way, particularly when software standing that virtually nothing is ever carved in stone. Indeed, many development If at all possible, it helps to pin down the design of an application early, with the under- The Design menting any here. Within reason we want our application to look and act as close to Notepad as possible. delete.” Notepad does not support any functions that delete files from the file system, so we won’t be imple- nNote  CRUD, an abbreviation stolen from the database world, stands for “create, read, update, and sible, although it need not implement every single aspect of the original program. The application should behave as much like Notepad as possible, wherever pos- 6. The application should be capable of printing documents. 5. allowed! The application should be written in pure IronPython; no other languages are 4. Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 81 .Enter. Once you’ve done these steps, your screen should look similar to Figure 5-2 Solution Explorer and select Rename. Change the name to NotQuitePad.py and press exactly a terrific description of our form either. Right-click on the Form1.py listing in the In addition to the control names’ not being particularly meaningful, Form1 isn’t to NotQuitePad. Do the same to the Text property. properties related to our form. Scroll to the top and change the property called (Name) on the right; you should see the Properties window below it update with a variety of scheme for a developer to interact with. Double-click on Form1 in the Solution Explorer be referencing in code because the default naming convention isn’t the most meaningful I mentioned that we should almost always change the names of controls we’re going to automatically a form called Form1, which saves us from having to do it. Recall that earlier When we created out project, IronPython Studio was gracious enough to generate Figure 5-1. Creating the NotQuitePad project instead of in a convenient subfolder. before you click the OK button; if it is not selected, then all the code will live at C:\Python create for us (Figure 5-1). Make sure the “Create directory for solution” box is checked NotQuitePad in the C:\Python\NotQuitePad directory, which we’ll let IronPython Studio that. Open up IronPython Studio; we’re going to create a new Forms application called Chapter 2 we set up C:\Python as the home for our IronPython code, so we’ll stick with The first thing we should do is set up a proper location for our development efforts. In The Implementation Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 82 rename a form. Right now that’s not a huge issue because we’ve only got one form in the light a little “gotcha”: the IDE won’t update those code references automatically if you I purposely didn’t correct this error before having you run the application, to high- Figure 5-3. Who were we calling again? (Figure 5-3). the way you intended; the IDE should throw a fit about some code in Program.py Press F5 to build and debug the application. You should find that it doesn’t execute Figure 5-2. NotQuitePad with a few settings tweaked Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 83 txtUserText. Scroll the Properties window until you see the property called Multiline; set box control from the toolbox (Figure 5-5). Having done so, we rename the control to controls needed to operate the program. First up, we should drag to the form a text Now that our application is starting to take shape, we have to add to the form the Figure 5-4. It’s alive! NotQuitePad0.RealEntryPoint(); if __name__ == "Program": Application.Run(NotQuitePad.NotQuitePad()) Application.EnableVisualStyles() def RealEntryPoint(): @staticmethod class NotQuitePad0: # namespace from NotQuitePad import * from System.Windows.Forms import * from System import * Listing 5-1. Fixing the Program.py Code (Figure 5-4). (I have put the relevant changes in boldface, for convenience). Then try running it again forms are hit infrequently. Correct the code in Program.py so that it looks like Listing 5-1 mind, because those types of errors can be insidious in large applications where certain application and it’s referenced in only one place. But you should definitely keep that in Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 84 self.Load += self._NotQuitePad_Load … Listing 5-3. The IDE Also Wired an Event to Fire When the Form Is Loaded _NotQuitePad_Load method when the form is loaded (Listing 5-3). The IDE also wired an event for you that executes (or, in programmer lingo, fires) the pass def _NotQuitePad_Load(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) Listing 5-2. The IDE Created a _NotQuitePad_Load Method for You Automatically created a bit of code for you (Listing 5-2). should be taken to the source code for NotQuitePad.py. What you’ll notice is that the IDE the form at any location that is outside the boundaries of the text box. Immediately you can do that through the properties, but let’s do it through code instead. Double-click on One of our application requirements is that the text box take up the entire form. We Figure 5-5. Not quite Notepad size, but a start the control’s width, and text cannot move down to a new line. it to True. If you do not set the Multiline property to True, you will be able to resize only Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 85 .(after the size definition and rerun the application (Figure 5-7 that right now. Modify your code to add the location definition shown in Listing 5-5 right the top and left as you can get it!” This should put things in the right location. Let’s do method that fires when the application is loaded to, say, “Hey, put this TextBox as far to A good first attempt at fixing this problem might come in the form of modifying the Figure 5-6. Well, it did exactly what we told it to. quite perfect yet. is now the same size as the NotQuitePad form itself (Figure 5-6). However, things are not If you run the application again by pressing F5, you’ll see that the txtUserText control self._txtUserText.Size = self.Size Listing 5-4. Forcing the txtUserText Control to Be a Given Height and Width line of code and replace it with the code in Listing 5-4. txtUserText control to be equal to the height and width of the window. Remove the pass When the form loads, we can programmatically set the height and width of the fired. This is incredibly powerful and will soon become very important to our application. equals sign, our _NotQuitePad_Load method is part of a list of methods that can be run when the event is to execute would be the method to the right of the equals sign. But since the IDE used a plus sign and an _NotQuitePad_Load event when the form is loaded. If the IDE has just written an equals sign, the only thing nNote  There’s something really interesting at work here. Notice the usage of += to fire the Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 86 .(form down and to the right (Figure 5-8 when we resize the form? With the program running, drag the bottom right corner of the It looks like things are starting to take shape. But there’s a problem. What happens Figure 5-7. Better! But looks can be deceiving. increases going down the screen. see how the coordinate space works; the bottom line is that X increases to the right on the screen and Y top left of the form. If that’s a little confusing, feel free to play with the numbers and come back once you the text box to be Point(10, 20), the text box would start 10 pixels to the right and 20 pixels down from the Y-value increases as it goes down the screen, not up as you might be used to. If we had set the location of is identical to the system used for plotting data, with one important distinction: in programming terms, the pixels and starting from the top left of the containing object. As you probably recall from math classes, this nNote  A Point in .NET refers to an X-and-Y point in two-dimensional space, in that order, measured in self._txtUserText.Location = Point(0, 0) Listing 5-5. Forcing the txtUserText Control to Move to the Top Left of the Form Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 87 .implement a piece of code only once and to call it when needed resizing method. It’s a cardinal sin to duplicate code; it’s always a best practice to remove that resizing code from _NotQuitePad_Load and replace it with a call to our and positioning of our text box when the user changes the size of the form. We should stinks. So instead, we’ll make a method called _ResizeInputBox to handle the resizing cally, it would work, but from a design and implementation standpoint it absolutely We don’t want to call the _NotQuitePad_Load method in our resize event; techni- when the form is resized, so we will add an event to self.Resize. method to the list of events that fires when the form loads. We need to fire some code into. We’ve already seen this in action when the IDE added our _NotQuitePad_Load launched. Luckily the .NET programming model exposes a variety of events we can tap resized while the text box sits quietly at whatever size it was when the program was What exactly can we do about that? Clearly it’s a deal-breaker to have the form be Figure 5-8. Notepad would SO not do that. Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 88 ( super(type(self), self).Dispose(disposing def Dispose(self, disposing): @returns(None) @accepts(Self(), bool) self.InitializeComponent() def __init__(self): __slots__ = ['_txtUserText'] """type(_txtUserText) == System.Windows.Forms.TextBox""" class NotQuitePad(System.Windows.Forms.Form): class NotQuitePad: # namespace from clr import * from System.Drawing import * from System.ComponentModel import * from System.Windows.Forms import * import System Listing 5-6. The Complete Listing of NotQuitePad.py at This Time cation again (Figure 5-9). Make sure your code matches what I’ve provided in Listing 5-6 before running the appli- so I’m going to provide the entire listing because it’s easier when viewed in this fashion. There are quite a few changes to the NotQuitePad.py file that can make this happen, down code is no fun at all, and it is a particularly painful task when you’re working under a deadline. in the greeting,” you’ll be kicking yourself for not making a simple method and using that instead. Hunting months from now and tells you, “Hey, we have to change the program so that it also prints the user’s name adding a line of code that prints “Hello!” at each and every point we need it? If your boss comes to you six that prints “Hello!” (let’s refer to it as SayHello) and to call that method anywhere we need it, instead of can save yourself a terrible headache down the road. Consider this: Why is it better to write a single method nTip  Here’s one of these little “please don’t shoot yourself in the foot” architecture moments where you Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 89 .tell you that you’ve provided one parameter too many To prove this, add self before sender in the call to _ResizeInputBox and run the application. IronPython will ial matter. In terms of the method signature (point 2), remember that self is implied and added automatically. this element because we don’t yet know how large it will be, and adjusting our starting Point values is a triv- the signature seems to say we should be. On the first point, it’s not important that we haven’t left space for menu, and (2) when we call _ResizeInputBox in the _NotQuitePad_Load method, we aren’t passing self like nCaution  If you’ve got eagle eyes, you may have noticed two things: (1) we haven’t left room for the self._txtUserText.Location = Point(0,0) self._txtUserText.Size = self.Size def _ResizeInputBox(self, sender, e): self._ResizeInputBox(sender, e) def _NotQuitePad_Load(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) self.PerformLayout() self.ResumeLayout(False) self.Resize += self._ResizeInputBox self.Load += self._NotQuitePad_Load self.Text = 'NotQuitePad' self.Name = 'NotQuitePad' self.Controls.Add(self._txtUserText) self.ClientSize = System.Drawing.Size(284, 264) # # NotQuitePad # self._txtUserText.TabIndex = 0 self._txtUserText.Size = System.Drawing.Size(100, 20) self._txtUserText.Name = 'txtUserText' self._txtUserText.Multiline = True self._txtUserText.Location = System.Drawing.Point(65, 115) # # txtUserText # self.SuspendLayout() self._txtUserText = System.Windows.Forms.TextBox() def InitializeComponent(self): @returns(None) Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 90 .the docstring if you choose; I would recommend doing so Modify the Interface.py file so that your code looks like Listing 5-8. Feel free to rewrite "Description of class" class interface: Listing 5-7. Our Interface.py Class, Brand-New to the World Press Enter, and IronPython will create the file containing the code in Listing 5-7. class Interface.py to indicate that code relating to the user interface is contained within. don’t yet know what sort of future development the application will see, so we’ll call this We want to add a class to our project that contains the code that resizes the form; we ness and press Enter. Right-click on the business folder and click Add and then New Item. in the NotQuitePad solution, and click Add and then New folder. Name this folder busi- In the IDE, right-click on the NotQuitePad project, which appears as the first element straighten things out! The rest of us will clean up the mess before moving on. the overall organization of the code. If you did notice it, I hope you took the initiative to when you’re moving quickly or working on something unfamiliar and your focus is not on that, then you’ll have plenty of company; it’s very easy for these bad habits to creep in ourselves: we’re dumping code into the form itself. If you hadn’t noticed that we’re doing Although the application is behaving properly, we’ve broken one of the rules we set for Bad Medicine Figure 5-9. This is the behavior we specified for the application.    Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 91 ,purpose of this method is to set the Location parameter of the object. In IronPython The MoveInputBox method takes an object called box as a parameter. The only specific and when to keep them more generalized. APIs. As you become more experienced you’ll develop a sense of when to make methods until you’re used to building and working with application programming interfaces, or such as MoveBox or ResizeBox. It’s worth noting that decisions like this can be very subtle specific type of object.) Optionally, we could have named them something more general, by their naming convention, we’re tying them to a specific purpose (that is, resizing a are good, descriptive names that make it pretty clear what function each serves. But We have created two methods: MoveInputBox and ResizeInputBox. Note that these Bookmark it and consider it your lifeline to the deeper parts of the framework. provides in precise detail every method, namespace, and parameter in the framework. is the Microsoft Developer Network website at http://msdn.microsoft.com/en-us/, which modules. How do we know which modules we’ll need? The best source for information of the features provided by the .NET framework, we’ve begun by importing the relevant about our code, and we need to walk through them. First off, because we’ll be using some It’s worth pointing out that, by definition, we’ve made some implicit design decisions help information for it. block of code that provides a docstring will have that string presented to the user when he or she pulls up the function in IronPython, which you can employ to retrieve information about how to use a piece of code; any nNote  Docstrings are essentially a type of code documentation. Earlier in the book we covered the help box.Size = windowSize "Resizes an object to equal the size of another object" def ResizeInputBox(self, box, windowSize): box.Location = Point(0, 0) "Changes the location of an object to the origin (point 0, 0)" def MoveInputBox(self, box): "Code related to the user interface logic of NotQuitePad" class interface: from System.Drawing import * from System.Windows.Forms import * from System import * Listing 5-8. Interface.py Updated Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 92 .tical. However, we haven’t done much to this particular file, so it’s a pretty easy revision to make nNote  It’s not always necessary to “clean sweep” your code like this; in fact, many times it will be imprac- achieving the same result. moment it’s going to be obsolete anyway because we’ve decided on a better method for want to eliminate the existing resizing code to reduce the potential for errors, plus in a delete any existing resizing code, restoring the application to the state of Listing 5-9. We can be called correctly during the program’s event loop. The first thing we should do is Now that we have designed the code, it’s time to wire the interface so that our code responsible for resizing objects. refactor this one class into two classes, one with the responsibility for moving objects on a form and one technically has two reasons to change: (1) setting the location of the text box and (2) resizing it. We could responsibility principle says that a class should have one and only one reason to change. Our interface class Technically, we could refactor our code and perform a little under-the-hood cleanup; the single cleaner the room, the easier it is to add that swanky sofa you’ve been looking at for so long. whiz-bang new feature. It’s a bit like cleaning up a room, with the intention of buying new furniture. The functionality or appearance of the program; if you’re refactoring an application, you shouldn’t be adding a ing. Refactoring is the practice of cleaning up the underlying structure of a program without changing the nTip  Now is a good time to mention a very important concept in application development: refactor- should be obvious that this change in size will immediately be reflected back on our form. box to that Size value. If you remember that IronPython passes objects by reference, it parameter of the form itself; the single purpose of this method is to set the size of the text ally it takes a parameter called windowSize. This parameter will hold the value of the Size Likewise the ResizeInputBox takes an object called box as a parameter, but addition- an object by value, the original object is left completely unchanged. erence, when program execution returns to me, the initial object will potentially have been modified. If I pass nNote  In programming terms, objects can be passed by reference or by value. So if I pass an object by ref- in the form. calling method, and the text box is immediately placed at the origin, which is point 0,0 every object is passed by reference, not by value, so this change is reflected back in the Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 93 () self.PerformLayout self.ResumeLayout(False) self.Text = 'NotQuitePad' self.Name = 'NotQuitePad' self.Controls.Add(self._txtUserText) self.ClientSize = System.Drawing.Size(284, 264) # # NotQuitePad # self._txtUserText.TabIndex = 0 self._txtUserText.Size = System.Drawing.Size(100, 20) self._txtUserText.Name = 'txtUserText' self._txtUserText.Multiline = True self._txtUserText.Location = System.Drawing.Point(65, 115) # # txtUserText # self.SuspendLayout() self._txtUserText = System.Windows.Forms.TextBox() def InitializeComponent(self): @returns(None) super(type(self), self).Dispose(disposing) def Dispose(self, disposing): @returns(None) @accepts(Self(), bool) self.InitializeComponent() def __init__(self): __slots__ = ['_txtUserText'] """type(_txtUserText) == System.Windows.Forms.TextBox""" class NotQuitePad(System.Windows.Forms.Form): class NotQuitePad: # namespace from clr import * from System.Drawing import * from System.ComponentModel import * from System.Windows.Forms import * import System Listing 5-9. NotQuitePad.py Restored to the Initial State Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 94 ( super(type(self), self).Dispose(disposing def Dispose(self, disposing): @returns(None) @accepts(Self(), bool) self.InitializeComponent() def __init__(self): __slots__ = ['_txtUserText'] """type(_txtUserText) == System.Windows.Forms.TextBox""" class NotQuitePad(System.Windows.Forms.Form): class NotQuitePad: # namespace from Interface import * from clr import * from System.Drawing import * from System.ComponentModel import * from System.Windows.Forms import * import System Listing 5-10. NotQuitePad.py Revised to Minimize Future Maintenance We’ll revise our application to Listing 5-10. you’ve seen it applied it will be clearer why we should always strive for this sort of design. changes are limited to one area. That idea might sound a little confusing, but I think once the responsibility for calling your interface class over to an intermediate method so that to handle a situation like this is not to copy and paste code all over the place, but to hand the form is maximized, minimized, and generally shuffled about the screen. The best way gin and resized to meet the window, and then an unknown number of times afterward as once but likely n times: once when the application loads so that the text box is at the ori- and furthermore we know that the code in our interface class needs to execute at least We know that duplicating code encourages code rot (and believe me, code does rot), pass def _NotQuitePad_Load(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 95 .it’ll complain nCaution  IronPython cares about the little space between parameters. If they’re not separated by a space, newDimensions.ResizeInputBox(self._txtUserText, self.Size) newDimensions.MoveInputBox(self._txtUserText) newDimensions = interface() def _HandleResizing(self): self._HandleResizing() def _ResizeFormEvent(self, sender, e): self._HandleResizing() def _NotQuitePad_Load(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) self.PerformLayout() self.ResumeLayout(False) self.Resize += self._ResizeFormEvent self.Load += self._NotQuitePad_Load self.Text = 'NotQuitePad' self.Name = 'NotQuitePad' self.Controls.Add(self._txtUserText) self.ClientSize = System.Drawing.Size(284, 264) # # NotQuitePad # self._txtUserText.TabIndex = 0 self._txtUserText.Size = System.Drawing.Size(100, 20) self._txtUserText.Name = 'txtUserText' self._txtUserText.Multiline = True self._txtUserText.Location = System.Drawing.Point(65, 115) # # txtUserText # self.SuspendLayout() self._txtUserText = System.Windows.Forms.TextBox() def InitializeComponent(self): @returns(None) Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 96 .Figure 5-11 “Open…”, “Save As…”, and “Exit”, which should leave you with a menu that looks like Below the File heading, go ahead and make a few more menu options; add “New”, schema with which the average user is comfortable. to opening the File menu with Alt+F and to opening the Print dialog box with Ctrl+P. Don’t fight the mental common and familiar to users of a given system. For instance, Windows users are typically quite accustomed so that you can avoid conflicts between hotkey settings. It’s also a best practice to use shortcuts that are nTip  It’s important when designing menus to bear in mind which keys you have set as keyboard shortcuts Alt+F to open the File menu. here.” Click that box and type &File. The ampersand (&) prefix allows the user to press normally appears in Windows. The menu will be blank except for a box that says, “Type will automatically appear on the top of the form in the typical location in which a menu hand pane and expand Menus and Toolbars; then double-click MenuStrip. The menu With the main form open in Visual Studio, scroll down the toolbar options in the left- the functionality at one time; we can start simply by getting the menu bar on the screen. The next step in our implementation is creating the menu bar. We don’t need to have all I’d Like to See a Menu behavior. Figure 5-10. Our refactoring effort was successful; we cleaned the code without changing    If not, ensure your code matches what we’ve created so far and run the application again. anchored to the top left of the form, but also resizes to become the entire size of the form. ure 5-10. If you resize the window, you should see that the text box not only remains If you run the NotQuitePad application now, you should see something like Fig- Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 97 .so that the description matches the implementation be Point(0, 25). If you’ve been including docstrings, make sure to update the content of it Open Interface.py and change the MoveInputBox method to set the initial location to a lot of people actually miss. pixels down the form, we’ll actually cut off one horizontal row of pixels with our text box. It’s a tiny issue that nNote  Why 25 pixels instead of 24? The reason is that the menu strip itself is 24 pixels tall; if we move 24 of the menu from menuStrip1 to mainMenu so that we can identify it easily. text box 25 pixels down from the origin. Before you do so, let’s change the Name property 284, 24 on my screen, which means we need to adjust our positioning code to start the ties window in the bottom right until you see the Size property. The size of the menu is Click the menu in the Visual Studio design window; then scroll through the Proper- adjust our interface method. has just the one text box control on it. We need to find out how tall the menu is and then see that although we’ve created a menu, it’s not visible; the application still looks like it because we didn’t know how large it would be. If you run NotQuitePad now, you will text box control at the origin (point 0,0 on the form), we didn’t leave room for the menu Earlier I noted that when we wrote our code in the interface class to position the Figure 5-11. Placing some basic menu options Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 98 dows cousin. While there are more interface bells and whistles we could be adding right meets our requirements and bears more than a passing resemblance to its built-in Win- We’ve got a pretty good interface going. We’ve succeeded in building something that Reading, Writing, Arithmetic Figure 5-12. The menu is in place and the text box is leaving room for it. working as intended. ber to resize it a few times, and do a quick quality assurance test that everything is Run the program again and check out your snazzy new menu (Figure 5-12). Remem- box.Size = windowSize "Resizes an object to equal the size of another object" def ResizeInputBox(self, box, windowSize): box.Location = Point(0,25) menu(point 0, 25)" "Changes the location of an object to the origin, leaving room for a ➥ def MoveInputBox(self, box): "Code related to the user interface logic of NotQuitePad" class interface: from System.Drawing import * from System.Windows.Forms import * from System import * Listing 5-11. Interface.py with the MoveInputBox Method Updated Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 99 pass desired location." "After executing the Save method, write the file and contents to the➥ def _WriteFileToDisk(self, fileName, fileContents): pass "Handles the Save dialog window." def Save(self): Pass "Opens a connection to the file system for opening a file." def _OpenFileFromDisk(self, fileName): pass "Handles the Open dialog window." def Open(self): pass "Creates a new file within NotQuitePad." def New(self): "Contains file system operations for NotQuitePad." class fileOperations: from System.IO import * from System import * Listing 5-12. The Initial Design of fileOperations.py convention: New, Open, Save. Take a look at Listing 5-12 for an example of what I mean. to file operations. For right now, I’m going to opt for the standard file operation naming triad of methods or whether we should opt for method names that relate more closely naming convention will be. We need to decide whether to stick with the names of this Before we lay out the methods our class will need, it would be prudent to decide what our The most critical one clearly is the Create, Read, and Update operations in the file system. At the beginning of this chapter, we laid out our requirements for the application. called fileOperations.py. with the aesthetics. We’ll begin this task by creating a new class file in our business folder end; as with any structure, the foundation needs to be strong before we concern ourselves now, we should take some time to wire back-end functionality together with the front Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 100 pass "Creates a new file within NotQuitePad." def New(self): "Contains file system operations for NotQuitePad." class fileOperations: from System.IO import * from System.Windows import * from System.Windows.Forms import * from System import * Listing 5-13. FileOperations.py with a Basic Open File Dialog Window fileOperations.py and modify it to look like Listing 5-13. particular file the user has selected. This is best demonstrated with an example. Open The dialog windows facilitate a common user experience and allow us easy access to a Let’s start to use these code snippets, by learning how to open a file from disk. operations. appearance so that users have little if any learning curve with our application’s basic is it simple to display Open and Close dialog windows, but they’re also standardized in huge library of boilerplate code snippets to do all of the basic Windows tasks. So not only is essentially boilerplate code. One of the great things about .NET is that it provides a The task of displaying a functional dialog box that is suitable for opening or saving files Open Sesame scenario applies to the Open and _OpenFileFromDisk methods as well. We’re instructing ourselves and others not to call WriteFileToDisk directly. The same abstraction to the entire process and provide flexibility for unforeseen design changes. at a later date. If we allow Save to call a deeper, private method, then we add a layer of method in an incorrect order. Furthermore we may want additional steps in this process to call an internal method called _WriteFileToDisk; we don’t want code accessing this ing a file. Once the file name and location have been chosen, we want that Save method execute the Save method, which should open that familiar Windows dialog box for sav- What we want to have happen is that when the user clicks “Save As” in the menu, it will an underscore character. This is to indicate that we want this method to be “private.” There’s an interesting point to make here: we have method signatures that start with Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 101 () openDialog.Open openDialog = fileOperations() def _openToolStripMenuItem_Click(self, sender, e): Listing 5-14. Calling Our Dialog Method from the Interface has a reference to the code you created. Now run the application (Figure 5-13). Listing 5-14. Next, import the fileOperations class at the top of the file so that IronPython Click that fires when the Open menu option is clicked. Add to that method the code in IronPython Studio will automatically create a method called _openToolStripMenuItem_ to NotQuitePad.py, click File in your menu, and then double-click the Open menu option. Before we go mucking about in the stream, let’s try out this dialog window. Go back arbitrary number of bytes, in order. If we are reading a stream of data from the disk, we start at the beginning and read an write to or read from the disk. The word stream means there is one-way communication. a method of interacting with the disk; developers need to make use of streams to actually and file save dialogs are a bit separated from the physical files themselves. They provide and read its contents from disk. It may seem counterintuitive, but in .NET the file open pressed. If the user pressed “OK,” we’ll eventually have code here to actually open the file OpenFileDialog object, setting the title to be Load File, and checking what button the user What’s happening here is pretty straightforward. We are creating an instance of an pass desired location." "After executing the Save method, write the file and contents to the➥ def _WriteFileToDisk(self, fileName, fileContents): pass "Handles the Save dialog window." def Save(self): pass "Opens a connection to the file system." def _OpenFileFromDisk(self, fileName): pass if dialog.ShowDialog() == DialogResult.OK: dialog.Title = "Load File" dialog = OpenFileDialog() "Handles the Open dialog window." def Open(self): Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 102 .Listing 5-16 that the user selects from the dialog box. Open fileOperations.py and modify it to look like file read from disk; the next step is to open a stream and read the contents of a given file Now we have provided a way to update the user interface with the contents of the self._txtUserText.Text = openDialog.Open() openDialog = fileOperations() def _openToolStripMenuItem_Click(self, sender, e): Listing 5-15. Populating the Text Box with Data, When Available _openToolStripMenuItem_Click method. Change the method to look like Listing 5-15. yet actually read the file contents from the disk. The first modification to make is in the code to the interface code so that the file contents are displayed, and (2) we have not nothing happens. The reason for this is twofold: (1) we have not wired our file-opening Although the dialog window does display and we can select a file, when we open it selected file. Figure 5-13. Our dialog menu displays, but it does not automatically do anything with a Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 103 pass desired location." "After executing the Save method, write the file and contents to the➥ def _WriteFileToDisk(self, fileName, fileContents): pass "Handles the Save dialog window." def Save(self): return data file.Close() data = file.ReadToEnd().ToString() file = File.OpenText(fileName) "Opens a connection to the file system." def _OpenFileFromDisk(self, fileName): return contents contents = self._OpenFileFromDisk(dialog.FileName) if dialog.ShowDialog() == DialogResult.OK: dialog.Title = "Open" dialog = OpenFileDialog() "Handles the Open dialog window." def Open(self): pass "Creates a new file within NotQuitePad." def New(self): "Contains file system operations for NotQuitePad." class fileOperations: from System.IO import * from System.Windows import * from System.Windows.Forms import * from System import * Listing 5-16. Handling the Physical Read from the Disk Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 104 ”.used to tell me: “Close the door! We’re not heating the whole neighborhood here We’ll cover proper close and dispose patterns throughout the book, but the best advice is what my parents (and application performance as a result) faster than not closing connections when you’re through with them. them! The same applies to resources such as database connections. Nothing kills database performance nNote  Connections to the file system are expensive resources. If you open them, make sure you close properly display. .ToString() method to convert the Stream contents to a data type that the text box can methods that are used for reading individual lines or bytes from a file. We are calling the until the EOF (end of file) marker, in a one-way fashion. The File class also exposes other connection to the file on the disk, and .ReadToEnd() reads the entire contents of that file particular. The File.OpenText() method in this namespace is responsible for opening a .NET. This namespace exposes a lot of functionality for dealing with the file system in This section of code is our first low-level exposure to the System.IO namespace in things in terms of ease of maintenance. introduced to a program designed at the Notepad level. Hopefully you’re already seeing imagine how quickly things would get bloated, even with the limited functionality we’ve face code. If everything had been smashed together in the interface form code, you can the heavy lifting and send the contents of the desired file back for return to the user inter- method handles the dialog window itself and makes a call to _OpenFileFromDisk to do code from the dialog code. Now the workload is divided properly and cleanly; the Open This is the proof in the pudding from earlier about separating the file-opening (think of creating a form on the screen, making a functional menu bar, and so on). task. But you may find that in doing so you have to write more code to perform basic boilerplate operations If desired you can employ traditional Python methods anywhere you like in IronPython to do just about any return data file.close() data = file.read() file = open(fileName) ing a file’s contents is the same for a comparable amount of code. you based on any nonprintable characters in the contents (such as line breaks), but the net result of access- _OpenFileFromDisk method and get the exact same result. Technically .NET will apply some formatting for similar the .NET method of opening a file is to Python’s. You could substitute the following Python code in the nTip  If you have any prior Python programming experience, you have probably noticed how strikingly Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 105 .this type of operation, so we’ll move forward with our two primary choices only wouldn’t gel with the way Notepad operates and would be contrary to what the typical end user expects of append to the file, meaning you could add additional text to the end of the existing file. But this behavior cels and (2) to overwrite the entire file with the new contents if the user chooses to save. You can always nNote  The only two choices we really have based on user input here are (1) to do nothing if the user can- remain at the dialog window. content. If the user chooses not to overwrite, we will not append any content; we’ll simply file (if one does in fact exist); if so, we’ll wipe out what’s there and replace it with new nation file?” What we will do is ask the user if she or he wants to overwrite the existing the question “If the destination file already exists, do we cancel or overwrite the desti- some additional considerations to take into account; a good example would be answering streams, we now know enough to write code to save text back to the disk. But we have Having written IronPython code to open a text file from disk and to do a little work with I Can’t Even Save Myself Figure 5-14. With the file stream code in place, the Open function works. code to this particular file, and my screen looks like Figure 5-14. your drive (an IronPython source code file works great in this case). I opened the source If you run the application now, click File, then Open, and then select a text file from Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 106 .saved. We’ll tackle that now and to try to save it to disk; you’ll find that there are no errors but that the file doesn’t get dialog menu will display, it won’t yet save the file (Figure 5-15). Feel free to type some text option, at this point in the process we haven’t wired everything together, so, although the Run the application and give the Save As menu option a spin. As with the Open menu saveDialog.Save(self._txtUserText.Text) saveDialog = fileOperations() def _saveAsToolStripMenuItem_Click(self, sender, e): Listing 5-18. Calling the Save File Dialog from the User Interface a parameter (Listing 5-18). fileOperations class and call the Save method, passing the contents of the text box as signature to handle the option being clicked. We need to make an instance of our then double-click Save As. As with the Open option, IronPython Studio creates a method Now return to NotQuitePad.py’s Design View, click on the File menu option, and pass if dialog.ShowDialog() == DialogResult.OK: dialog.Title = "Save As" dialog = SaveFileDialog() "Handles the Save dialog window." def Save(self, fileContents): Listing 5-17. Setting Up a Save File Dialog in fileOperations.py Open method, just modified a bit to save a file instead of open one. method. It should look pretty familiar; it’s essentially identical to what we did in the First, let’s open fileOperations.py and add the code in Listing 5-17 to the Save design at the beginning pays off big dividends down the line. nNote  I think you’ll love how terribly simple this next bit is. This is one of those moments when a decent to disk from the interaction task of finding that location. On our end, we will stick to our own convention and separate the physical task of writing a file, now we will use a SaveFileDialog to do the opposite and save a file back to the disk. naming convention; where we used OpenFileDialog to browse the file system and select Thankfully the .NET framework developers were nice enough to stick with a common Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 107 pass "Creates a new file within NotQuitePad." def New(self): "Contains file system operations for NotQuitePad." class fileOperations: from System.IO import * from System.Windows import * from System.Windows.Forms import * from System import * Listing 5-19. fileOperations.py with Save As Implemented again almost identical to what we did for opening the file. Open fileOperations.py and modify it to look like Listing 5-19. The code for saving is Figure 5-15. History repeats; until we wire the UI to the business layer, this is for show only. Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 108 .(ment, and then verify that the Save As and Open features work as expected (Figure 5-16 the end to clean up our resources. Press F5 to run the application again, create a docu- contents of our text box back to the file system, making sure to call the .Close() method at So what’s happening is that we’re using a StreamWriter object named file to write the file.Close() file.Write(fileContents) file = File.CreateText(fileName) desired location." "After executing the Save method, write the file and contents to the➥ def _WriteFileToDisk(self, fileName, fileContents): self._WriteFileToDisk(dialog.FileName, fileContents) if dialog.ShowDialog() == DialogResult.OK: dialog.Title = "Save As" dialog = SaveFileDialog() "Handles the Save dialog window." def Save(self, fileContents): return data file.Close() data = file.ReadToEnd().ToString() file = File.OpenText(fileName) "Opens a connection to the file system." def _OpenFileFromDisk(self, fileName): return contents contents = self._OpenFileFromDisk(dialog.FileName) if dialog.ShowDialog() == DialogResult.OK: dialog.Title = "Open" dialog = OpenFileDialog() "Handles the Open dialog window." def Open(self): Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 109 .main NotQuitePad form and add a Print option to the drop-down menu can indulge them this one time. First things first. Let’s head to the Design View for the Our users are spoiled; they actually like to print the documents they create. I suppose we Print, Please NotQuitePad. Figure 5-16. The Save As functionality works; I have opened a file I created with Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 110 .(sible for any tasks related to the actual printing of the document (Figure 5-17 as doc. The document is passed to our private _PrintDocument handler, which is respon- The PrintDialog expects a PrintDocument object to be passed to it, which we define printDocument.Print() "Sends a document to the selected printing device." def _PrintDocument(self, printDocument, fileContents): self._PrintDocument(doc, fileContents) dialog.Document = doc doc = PrintDocument() if dialog.ShowDialog() == DialogResult.OK: dialog = PrintDialog() "Handles the Print dialog window." def Print(self, fileContents): Listing 5-21. Calling the Print Dialog tion and break the code down (Listing 5-21). very similar fashion to the dialogs we’ve used so far. Let’s look at a complete implementa- going to use another type of dialog that .NET provides called the PrintDialog. It works in a Open up the fileOperations class. For the purposes of printing documents, we’re printDialog.Print(self._txtUserText.Text) printDialog = fileOperations() def _printToolStripMenuItem_Click(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) Listing 5-20. The Code for the Print Option Menu Click will create shortly that accepts our text box text as a parameter. modified in Listing 5-20 to call our fileOperations class and to use a .Print() method we The IDE will dutifully create a snippet of code for you to start with, which we have that Print option so that we can wire some functionality to it. expects to find such. So left-click and drag it between Save As and Exit. Next, double-click option will be added to the menu, but it’s at the bottom, which is not the location a user “Type Here” below the Exit option. In that box, type Print, then press Enter. The Print Click on the File option to open the menu, and you will see a blank box with the text Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 111 _contents = String.Empty _isDirty = False # properties of the document "Represents a document in NotQuitePad." class document: from System import * Listing 5-22. The Document Class Implementation display to the user (Listing 5-22). quickly to check the state of a document when needed to make decisions about what to we’re going to create two methods: IsDirty() and SetDirty(). These methods will allow us whether or not a document has been modified since saving (if it has been saved at all), In the business folder of your project, create a new class called document.py. To track exposed, that lets the calling code know the status of the document’s workflow. flexible approach would be to create a class that represents our document, with methods the main text box, which is really not the most elegant way to do things. A better, more document. We can do this in variety of ways, including just blindly deleting any text in There’s one menu option we haven’t touched yet, the New option, which creates a new A Touch of OOP Figure 5-17. The Print Dialog in Action Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 112 .method fileOperations class, add the line shown in Listing 5-24 immediately below the .New() maintain the document workflow state throughout the application. At the top of the We need to establish a document object in the fileOperations class so that we can to display the .Save() dialog and give the user the option to save her work. cates that the file was not marked dirty and did not need to be saved. Otherwise, we need that if the .New() method returns True, we should clear the text box completely. This indi- handle content based on abstract decision trees further in. What we’ve done here is say will it) whether a document’s _isDirty flag is set to True or not. It only knows how to By calling down into the fileOperations class, the UI doesn’t ever need to know (nor self._txtUserText.Text = String.Empty newDocument.Save(self._txtUserText.Text) else: self._txtUserText.Text = String.Empty if newDocument.New() == True: newDocument = fileOperations() def _newToolStripMenuItem_Click(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) Listing 5-23. Handling the New Menu Option with excessive decisions. Modify the click method as shown in Listing 5-23. much code out of the interface as possible so that the user interface (UI) is not burdened cation design, we should keep to the design patterns we’ve followed so far and leave as New menu option to create the click stub as we have done before. In the interest of appli- Open the main NotQuitePad form again in Design View, and double-click on the nNote  For right now, we’re going to wire the document state-checking code to the New menu option only. self._isDirty = value "Sets whether or not a document has been saved since modification." def SetDirty(self, value): return self._isDirty "Gets whether or not a document has been saved since modification." def IsDirty(self): Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 113 .see http://msdn.microsoft.com/en-us/library/system.windows.forms.messagebox.show.aspx case, we’re using the overload with parameters (String, String, MessageBoxButtons). For more information, nNote  The MessageBox.Show() method has several different overloads that expose different options. In this method returns True to indicate that the text box contents can be cleared. to be saved and the user wishes to save it. If the file wasn’t dirty in the first place, the returns False back to the UI to indicate that the method determined the file was not ready MessageBox object that asks the user if he wants to save changes to the file. If so, the code It immediately checks whether a document has been marked dirty, and if so it creates a changes in the future. The .New() method is called when the New menu option is clicked. functionality related to the document workflow checking be kept separate, in case it cally want to keep that sort of functionality out of the .New() method; it’s best that any calls down into the document class to find out if a document is marked dirty. We specifi- The first thing worth noticing is the private method _CheckIfFileIsDirty(), which return self.doc.IsDirty() and needs to be saved." "Call the document class to find out if a document has been marked dirty➥ def _CheckIfFileIsDirty(self): return True else: return True else: return False if msg == DialogResult.Yes: to your file?", "NotQuitePad", MessageBoxButtons.YesNo) msg = MessageBox.Show("Do you want to save the changes ➥ if self._CheckIfFileIsDirty() == True: "Creates a new file within NotQuitePad." def New(self): Listing 5-25. Fleshing Out the New Command in fileOperations.py py. The implementation of it is shown in Listing 5-25. The next step is to fill out the .New() method we created so long ago in fileOperations. doc = document() Listing 5-24. Creating a Document Object for the Class to Use Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 114 (newDocument.SetDirty(false self._txtUserText.Text = String.Empty newDocument.Save(self._txtUserText.Text) else: self._txtUserText.Text = String.Empty if newDocument.New() == True: newDocument = fileOperations() def _newToolStripMenuItem_Click(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) Listing 5-28. Letting the UI Know to Pass Along That a Document Is No Longer Dirty (Listing 5-28). class knows to set the Dirty flag to False after a successful creation of a new document The last step in this process is modifying the UI code so that the fileOperations self.doc.SetDirty(value) "Call the document class to set the dirty property of a document." def SetDirty(self, value): Listing 5-27. SetDirty Implementation in fileOperations.py Next, we need to implement the .SetDirty() method in fileOperations.py (Listing 5-27). file.SetDirty(True) def _txtUserText_TextChanged(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) Listing 5-26. The TextChanged Event Makes a Call to the fileOperations Class the UI remains ignorant of document code (Listing 5-26). contents of the text box change; we will make a call to our fileOperations.py class so that should create a stub called _txtUserText_TextChanged(). This event is fired every time the dirty. To do so, open the main form in Design View and double-click on the text box. This Next, we need a way to tell the document class that our document should be marked Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 115 .(Figure 5-18 make some adjustments to the resizing code to accommodate the scrollbar (Listing 5-31, (feel free to run the application yourself real quick). Let’s open the Interface.py file and same as those of the form. The problem with that now is that we can’t see the scrollbar Recall that in our interface code, we had set the text box width and height to be the display window, so scroll to the Scrollbars property to Vertical. Next, we need to set the vertical scrollbars to appear when the text gets too large for the sole size 10, if you have that font on your machine; if not, pick one of your preferences. the main form in Design View and click on the text box. Set the Font to be Lucinda Con- One of the last steps in our NotQuitePad wrap-up is to provide a prettier typeface. Open Beautification nNote  This application exit code is a prime candidate for adding an .IsDirty() check as a user convenience. ui.AppExit() ui = interface() def AppExit(self): Listing 5-30. Calling the Custom Exit Code in the interface Class main form (Listing 5-30). Now we can call this method from the Exit command in the drop-down menu in the Application.Exit() "Exits the current application" def AppExit(self): Listing 5-29. Exiting an Application shown in Listing 5-29 to that file. Interface.py file with the other UI code we created earlier in the chapter. Add the method patterns and move it to an abstracted location. The best home for this code is in the ing the application into the UI because it is terribly short, we should stick to our design getting it to shut down is much simpler. Although it’s tempting to drop the code for exit- You’ve done all the hard work of getting a basic version of Notepad running; luckily, Exit Strategy Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 116 .DONE sible, although it need not implement every single aspect of the original program. The application should behave as much like Notepad as possible, wherever pos- 6. The application should be capable of printing documents. DONE. 5. DONE. The application should be written in pure IronPython; no other languages allowed! 4. The application should operate on plain text documents. DONE. 3. The application should be capable of CRUD operations to the file system. DONE. 2. The application should consist of a single form. DONE. 1. review the plan requirements from earlier in the chapter and see how we did. straightforward and logical. Now that we’ve got a functional application, we should Well, hopefully that wasn’t too painful! Developing IronPython applications is actually Project Postmortem Figure 5-18. Side-by-side comparison box.Size = windowSize windowSize.Width = windowSize.Width – 15 windowSize.Height = windowSize.Height - 60 "Resizes an object to equal the size of another object" def ResizeInputBox(self, box, windowSize): Listing 5-31. Leaving Room for the Scrollbars Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 117 ..NET framework to extend the functionality of both Python and .NET itself code that we could easily extend in the future, and saw how IronPython hooks into the project to basic text editor. We looked at object-oriented programming concepts, wrote From design patterns to concrete implementations, in one chapter we went from empty Summary application is stable enough for some initial testing. fulfilling the requirements we set for ourselves at the beginning of the chapter, and the Although we did not implement every single feature of Notepad, we did succeed in Chapter 5  ■ MIXING AND MINGLING WITH THE CLR 118 119 Chapter 6 Advanced Development “The cost of adding a feature isn’t just the time it takes to code it. The cost also includes the addition of an obstacle to future expansion. The trick is to pick the features that don’t fight each other.” — John Carmack Up to this point, we’ve built fairly linear applications that are not truly component- based. Although we designed with future maintenance in mind, we haven’t built with the idea that we may want to reuse parts of our application later in related (or totally differ- ent) applications. We would be doing ourselves a disservice if we didn’t take a look at how to do this sort of development in IronPython. nNote  The primary focus of this chapter is going to be interacting with C#, another language in the .NET family. We’ll be looking at how to extend an application using IronPython as a plug-in architecture, and some of the C# syntax is fairly advanced. Don’t get too hung up about the C#; follow the directions and enter the code exactly as it appears. The important thing here is to examine how a statically typed language like C# can interact with a dynamically typed one like IronPython. On the other hand, if you’re familiar with C# (or you want to learn about it), then hopefully this will introduce you to some concepts that (I think) are pretty darn cool. Base Classes for Fun and Profit (aka “The LEGOs on the Bottom Don’t Really Exist”) One of the conveniences of object-oriented software development is polymorphism, which we briefly covered much earlier in the book. Polymorphism, in simplest terms, is the ability of an object of one type to be substituted seamlessly for an object of another type. In IronPython this sort of functionality is easily achieved; indeed, most modern lan- guages actually support this. However, the difference between a dynamic language and .Syntactically there are differences, but the underlying tasks are the same references to .NET namespaces and defines a class and a method with some parameters. of programs we’ve written in IronPython. The initial C# file that’s created adds a few the application file structure and the code itself bear a close similarity to the structure in Figure 6-2. Although I don’t expect you to know C#, what should stand out is that both Once you’ve created the project, you’ll be presented with a screen similar to the one location, if you prefer; it won’t hurt anything to do so. Save the project before continuing. Python directory for the sake of keeping our projects together. You could put it in another Project, select the console application. Call this application. I’m placing this in my C:\ We’re going to create a console application, but using C# (Figure 6-1). Under File ➤ New Let’s open up Visual Studio or the free Microsoft Visual C# 2008 Express Edition. need to implement some common methods. inherit from, hence the term abstract. It generally serves as a starting point for a variety of subclasses that nNote  An abstract base class is a class that cannot be instantiated but is only used for other classes to members of the base class. base class. Any subclasses, to compile properly, have to inherit and implement fully the our purposes). In C#, we have a few choices on how to do this. One way is via an abstract a human being (we could go deeper and start with “living thing,” but this is sufficient for current application we’re going to need to implement some basic object that represents we’ll get back to pure IronPython in the next chapter). Assume for a moment that for our language handles polymorphism (I promise the detour to C# is temporary but useful; help clarify matters a bit. Let’s start with C# so that we can see how a statically typed Whew! That hits you like reading stereo instructions, doesn’t it? A little code might implementation or interface implementation. a static language requires explicit typing information and either an abstract-base-class morphic operations implicitly based on which methods are present in a class, whereas a static one is in how this is actually implemented. A dynamic language performs poly- Chapter 6  ■ aDVANCED DEVELOPMENT120 .Figure 6-2. Visual Studio created a basic structure for us Figure 6-1. Creating a C# console application Chapter 6  ■ aDVANCED DEVELOPMENT 121 .does not care about is indentation, however colons in the right place relative to the brackets, that the correct things are capitalized, and so on. What it your language be very explicit. If you find errors after entering this code, make sure you’ve got all the semi- nTip  C# is picky, picky, picky when it comes to syntax. This is not a bad thing; it’s just a requirement that file. You can clear that code entirely and replace it with Listing 6-1. As always, Visual Studio does a little legwork and provides some skeleton code in the Figure 6-3. Adding the humanBeing.cs file to the project class file to the project. Call it humanBeing.cs and click Add (Figure 6-3). Explorer, click Add, and then click Class. This will bring up a window for adding a new to start by creating a new file. Right-click on the AbstractBase project in the Solution Even though Visual Studio was kind enough to create some code for us, we’re going Chapter 6  ■ aDVANCED DEVELOPMENT122 .nature all its explicit typing, manages to interact pretty deftly with IronPython’s freewheeling it, some absolutely hate it. What will be interesting to see in a few pages is how C#, with guage C# does require quite a bit of exposition to run properly. Some people swear by That is a lot more typing than we’re used to seeing. Indeed, as a statically typed lan- } } public new abstract string ToString(); public abstract void CleanUp(); public abstract void Initialize(); } dateOfBirth = _dateOfBirth; cannot be null."); } if (!_dateOfBirth.HasValue) { throw new Exception("Date of birth➥ { public void SetDateOfBirth(DateTime? _dateOfBirth) } name = _name; be null."); } if (String.IsNullOrEmpty(_name)) { throw new Exception("Name cannot➥ { public void SetName(string _name) public DateTime? dateOfBirth { get; private set; } public string name { get; private set; } { abstract class humanBeing { namespace AbstractBase using System; Listing 6-1. A Sample humanBeing.cs C# Abstract Base Class for a Human Being Chapter 6  ■ aDVANCED DEVELOPMENT 123 a very similar fashion to what we did earlier in the book with IronPython. Add a new class With a base class under our belt, we can create a class to implement that base class in http://msdn.microsoft.com/en-us/library/ms173154(VS.80).aspx for more details on this. should override the ToString method in order to provide information about your type to client code.” See documentation has the following to say on the subject: “When you create a custom class or struct, you of useful output. Plus it can be very helpful for debugging purposes, as we’ll see in a bit. The MSDN one else’s component, calling .ToString() on it, only to get something like “MyClass.MethodName” instead Always include a .ToString() override method in your custom classes. Nothing is worse than using some- nTip  If you take only one piece of development advice from this entire book, please take this one: that we want to override the basic version that .NET provides. required to implement their own version; we had to use the “new” keyword to identify class. The last thing we did was to override the .ToString() method so that subclasses are We defined a few abstract methods that handle the initialization and cleanup of the defined, allowing us a bit of control. those variables easily, but the mutators (“set”) are private, so calling code has to go through channels we’ve you to retrieve those values. In our case, our accessors (“get”) are public because we want code to access admittedly too cool for them. Mutators allow you to change the value of a property, and accessors allow nNote  Mutator is one-half of the geeky programming terms mutator and accessor, whose names are prevents us from doing validity checks and other useful tasks. through our public methods. If we allowed calling code to set the parameters directly, it We marked the mutator as private so that the properties can be modified only by going so that we can check whether a valid value has been provided, a new feature in .NET. properties for the entity: name and date of birth. We’ve set the dateOfBirth to be nullable As far as putting things in context, the humanBeing base class we wrote defines a few I capitalize the first letter. An example would be calculateSalesTax. I think project names that use this style look kind of funny, so named because the first letter is not capitalized, creating a word that looks like it has one or more humps. really just what I’m comfortable with. The variables and classes use what’s called camel casing, so nNote  Why capitalize the project names when the variables and classes have a different style? It’s Chapter 6  ■ aDVANCED DEVELOPMENT124 { { { ... // perform any cleanup tasks here { public override void CleanUp() } return sb.ToString(); } sb.Append("\r\n"); sb.Append(pi.GetValue(this, null)); sb.Append(": "); sb.Append(pi.Name); { foreach (PropertyInfo pi in p) StringBuilder sb = new StringBuilder(); PropertyInfo[] p = GetType().GetProperties(); { public override string ToString() } SetDateOfBirth(Convert.ToDateTime("1/1/2009")); SetName("Alan Harris"); { public override void Initialize() { class person : humanBeing { namespace AbstractBase using System.Text; using System.Reflection; using System; Listing 6-2. A Sample person.cs C# Class That Implements the Human Being Base Class the code Visual Studio provided for you initially. file called person.cs to the project and enter the code in Listing 6-2. Make sure to delete Chapter 6  ■ aDVANCED DEVELOPMENT 125 .Figure 6-4. Our calling class implements the base class IronPython import keyword, this tells C# to look for code in a specific place. add using CSharpTestbed; to the top of the Program.cs file and leave the namespace alone. Much like the being able to find the person class. If for whatever reason you don’t want to change the namespace, you can class files we’d created earlier. If you don’t change the namespace, you’ll find that C# complains about not nTip  Note in Figure 6-4 that we changed the namespace of this file to CSharpTestbed, which matches the } } } p.CleanUp(); Console.ReadLine(); Console.WriteLine(p.ToString()); p.Initialize(); person p = new person(); { static void Main(string[] args) { class Program { namespace AbstractBase using System; Listing 6-3. Using the Base Class in Program.cs have done so, press F5 to run the application (Figure 6-4). we get. We can modify the Program.cs file in the project to look like Listing 6-3. Once you The final step is actually to call an instance of the person class and see what output Chapter 6  ■ aDVANCED DEVELOPMENT126 .Listing 6-5 presents the output in Figure 6-5 to substitute one of person type, which is a more specific subclass. Executing the code in Although the accepts class is looking for an object of type humanBeing, we were able Console.ReadLine(); Console.WriteLine(a.AcceptsTypes(p)); accepts a = new accepts(); p.CleanUp(); Console.WriteLine(p.ToString()); p.Initialize(); person p = new person(); Listing 6-5. Passing a Person to the accepts Class ing 6-5, and press F5 to try it out. if we provide a person instead? Modify the Program.cs file to look like the code in List- This code expects an object of type humanBeing to be passed to it. What happens } } return h.name; { public string AcceptsTypes(humanBeing h) { class accepts using System; Listing 6-4. A Demonstration of the Polymorphic Nature of the Base Class in accepts.cs add to it the code in Listing 6-4. of code in Listing 6-4 is completely valid in C#. Make a new class file called accepts.cs, and idea that the person object can be substituted for the base class it implements. The bit Don’t worry about the syntax or implementation in C#. What’s important here is the Chapter 6  ■ aDVANCED DEVELOPMENT 127 pass def CleanUp(self): pass def Initialize(self): return self.__dateOfBirth def GetDateOfBirth(self): self.__dateOfBirth = _dateOfBirth def SetDateOfBirth(self, _dateOfBirth): return self.__name def GetName(self): self.__name = _name def SetName(self, _name): "Human being base class." class humanBeing(object): from System import * Listing 6-6. A Bare-Bones IronPython Base Class in C#. It is sufficient just to follow along and compare to the C# way of doing things. are simply illustrational with an end result that will be very similar to what you just did You don’t need to create a new project or the IronPython class files in Listing 6-6; they approach. Let’s look at a similar skeleton structure and see how IronPython handles it. information is regulated and checked. It’s all very explicit. IronPython takes a different Notice how much information we had to pass back and forth and how tightly that Figure 6-5. The name is printed via the “a” object, which accepts humanBeing. Chapter 6  ■ aDVANCED DEVELOPMENT128 (('p.SetDateOfBirth(Convert.ToDateTime('1/1/2009 p.SetName('Alan Harris') p = person() Listing 6-8. The Polymorphic Nature of the Classes Allows Us to Substitute One for Another. interchangeable. methods called are implemented in both classes, the parent and child classes are totally Listing 6-8 proves that we can substitute one class for another; so long as any pass # perform any cleanup tasks here def CleanUp(self): pass # perform any display tasks here def ToString(self, p): SetDateOfBirth(Convert.ToDateTime("1/1/2009")) SetName("Alan Harris") def Initialize(self): "A person that inherits from humanBeing" class person(humanBeing): from humanBeing import * from System import * Listing 6-7. An Implementation of a Person of a person (Listing 6-7). humanBeing in any code that operates on it. First, let’s look at a simple implementation Now, you can substitute any class that inherits from humanBeing in place of dateOfBirth = property(GetDateOfBirth, SetDateOfBirth) name = property(GetName, SetName) pass def ToString(self): Chapter 6  ■ aDVANCED DEVELOPMENT 129 .are critical in this respect, as we’ll quickly come to see quite a bit. The inheritance and polymorphism aspects of object-oriented programming As such, I’ve come to rely on some simple design patterns that shorten even those tasks by whatever project I’m currently working on—or allows rapid prototyping of new features. IronPython libraries in my applications helps to lower the maintenance requirements of for applications written in other languages. As a C# developer by day, I find that using the In my opinion, one of the cooler uses for IronPython is as a plug-in, or scripting engine Plug and Play complex interfaces. make some truly useful, reusable components without requiring our users to implement can encounter the occasional hard-to-squash bug. The flip side is that we’re permitted to a little tricky to keep track of what’s happening structurally behind the scenes, and you sure things don’t get out of control. Until you’re used to that style of object handling, it’s ties and really opens the development doors, although a careful eye is required to make intents and purposes they are interchangeable. This presents some unique opportuni- In IronPython, if two classes implement the same methods being used, then for all operate on differing object types and methods as though they were the same. a dynamic language IronPython will loosely allow one class to be substituted for another, enabling code to nNote  Duck typing: If it looks like a duck and quacks like a duck, it must be a duck. This means that as although mildly terrifying to developers used to statically typed languages. point. IronPython relies extensively on what is called duck typing. It’s a powerful feature, implemented a .GetName() method would work in Listing 6-8, and therein lies the entire You could argue that our example is a bit contrived. Technically, any class that for o in objects: print o.GetName() objects.append(h) objects.append(p) objects = [] h.SetDateOfBirth(Convert.ToDateTime('2/2/2009')) h.SetName('Tom Smith') h = humanBeing() Chapter 6  ■ aDVANCED DEVELOPMENT130 .promotes future code reuse UI, Data, and Business projects underneath. This helps to enforce good separation of concerns and ideally I were building an application for the accounting department, I could create an empty parent solution that has nTip  For most projects, I find it best to create a parent solution that houses all subprojects. For example, if solution (Figure 6-6). empty solution file for us. Call the project Plugin. Click OK to create the empty project Create a new Visual Studio project; select the Empty Project type, which will create an Architecting Flexibility libraries and look at how to call our classes from C#. Before we dig into a full plug-in architecture, we will build a simple application using those as well as provide ways to modify and retrieve information from IronPython class files. Scripting.Core.dll. These expose a variety of functions that facilitate the use of IronPython are IronPython.dll, IronPython.Modules.dll, Microsoft.Scripting.dll, and Microsoft. eral libraries are included for use in your .NET applications. The four we’ll be using So where can you find this ScriptEngine class? When you install IronPython, sev- where. Reusability is going to be a running theme. all over the place. More to the point, we may want to reuse this plug-in architecture else- to set up the ScriptEngine in a reusable way; we don’t want to have to create ScriptEngines IronPython code and hosting code. Over the remainder of this chapter, we’ll look at a way to hook the IronPython engine in and communicate back and forth between your from other .NET code? Microsoft has provided a ScriptEngine class that allows you The most notable hoop is the first one you’ll hit: How exactly do you call IronPython cantly reduced. a small plug-in framework for reuse in other projects, even that workload can be signifi- isn’t 1-to-1. Consequently, you’ll find one or two hoops to jump through. But if you create a few caveats. The relationship between IronPython classes and traditional .NET classes Luckily, calling IronPython code from C# or VB is a straightforward task, but this has switch between IE and Firefox tabs, which can save you a lot of time in web development. I’m a big fan of the Web Developer Toolbar, Firebug, and ShowIP, among others. There are also plug-ins to ably familiar with the various user-created add-ins that add or modify the functionality of the browser itself. nNote  A plug-in is an extension to a parent application. For example, if you’re a Firefox user, you’re prob- Chapter 6  ■ aDVANCED DEVELOPMENT 131 Figure 6-7. Adding the ConsoleUI project to the Plugin solution (Figure 6-7). click Add ➤ New project. Add a new Console Application called ConsoleUI and click OK Next we’ll add a C# project to our solution. Right-click on the empty solution and Figure 6-6. Creating an empty parent solution called Plugin Chapter 6  ■ aDVANCED DEVELOPMENT132 !If your Visual Studio solution looks like Figure 6-9, then we’re ready to get started be used by a calling executable. executable like a typical application; their file extension is .dll (dynamically linked library), and they can only extremely effective development tools for creating reusable code. The catch is that they are not directly tions. They contain one or more classes and can expose methods and properties to calling code. They’re nNote  Class libraries are individual code components that can be added as references to other applica- Figure 6-8. Adding the IPEngine class library to the Plugin solution ing OK (Figure 6-8). click Add ➤ New project. Add a new class library called IPEngine to the solution by click- Finally, we should add a class library to our project. Right-click on the solution and Chapter 6  ■ aDVANCED DEVELOPMENT 133 .(want to use in our file (Listing 6-9 Manager.cs. At the top of the file, we need to import the code from our libraries that we The IPEngine project will contain a class file called Class1.cs. Rename that to Engine- explanation to put it in context first. compiling but you haven’t finished the section yet, it’s likely that something’s about to be added but needs fine. The application has a lot of wiring in it and I’m covering things in a specific order. If things aren’t more coding on our part. As a result, compiling it at various early stages will produce errors. This is totally nCaution  Unlike some of our earlier applications, this one is of an increased difficulty and requires a bit IPEngine project (Figure 6-10). Microsoft.Scripting.dll, and Microsoft.Scripting.Core.dll. Click OK to add them to the folder. Hold down the Ctrl key and left-click on IronPython.dll, IronPython.Modules.dll, 2.0. Once you’ve browsed to the correct folder, you’ll see a variety of files listed in the IronPython is installed on your hard drive. For me, that’s C:\Program Files\IronPython click Add Reference. Click the Browse tab; you’ll need to locate the directory on which the libraries I mentioned earlier in the chapter. Right-click on the IPEngine project and Before we can use IronPython code in our application, we need to add references to Calling IronPython Code Figure 6-9. The solution is set up. Chapter 6  ■ aDVANCED DEVELOPMENT134 .(set up each of these (Listing 6-10 data between IronPython and calling classes. Let’s add private variables to the class that ScriptEngine. It relies on ScriptSource, ScriptScope, and ObjectOperations objects to pass Our communication with IronPython code will be handled by an object called the } } { public class EngineManager { namespace IPEngine using Microsoft.Scripting.Hosting; using IronPython.Hosting; Listing 6-9. The Beginning of the EngineManager Class Figure 6-10. We need to add the IronPython libraries to our project. Chapter 6  ■ aDVANCED DEVELOPMENT 135 The name of the IronPython source file to➥ /// /// Sets up the IPEngine for use by calling code. /// private ObjectOperations operations; private ScriptScope scope; private ScriptSource source; private ScriptEngine engine; { public class EngineManager { namespace IPEngine using Microsoft.Scripting.Hosting; using IronPython.Hosting; Listing 6-11. Creating an Initialize Method to Handle Setup Within EngineManager.cs us so that the details are invisible to calling code (Listing 6-11). abstractions. Therefore, let’s create an Initialize method to handle the setup workload for Remember that we always want to hide as much information as possible behind } } private ObjectOperations operations; private ScriptScope scope; private ScriptSource source; private ScriptEngine engine; { public class EngineManager { namespace IPEngine using Microsoft.Scripting.Hosting; using IronPython.Hosting; EngineManager.cs Listing 6-10. Setting Up the Necessary Objects for IronPython Communication in Chapter 6  ■ aDVANCED DEVELOPMENT136 The name of the IronPython source file to➥ /// /// Sets up the IPEngine for use by calling code. /// private ObjectOperations operations; private ScriptScope scope; private ScriptSource source; private ScriptEngine engine; { public class EngineManager { namespace IPEngine using Microsoft.Scripting.Hosting; using IronPython.Hosting; Listing 6-12. The First Version of the EngineManager Class Completed method called. We’ll create an Execute method to handle this (Listing 6-12). Next we need to provide a mechanism by which a class can be referenced and a you accomplished something; I’ll read your comments if I need to figure out why. is best. In general, “what” and “why” make for better comments than “how”; I’ll read your code to see how you’ll see this information appear in the IntelliSense that shows up as you type, so a mix of clarity and brevity optional, but I find them to be quite useful. When you call these methods in the ConsoleUI application later, docstring in Python, in that it describes the method for use in helpful instructions elsewhere. They are totally nNote  The /// comments at the top of the method are XML documentation. This is similar to the use of } } } operations = engine.Operations; scope = engine.CreateScope(); source = engine.CreateScriptSourceFromFile(file); engine = Python.CreateEngine(); { public void Initialize(string file) Chapter 6  ■ aDVANCED DEVELOPMENT 137 folder in it called Scripts. Now open your text editor of choice and create in that folder a navigate to the folder on your drive where the Plugin solution lives and create a new chance for things to get messy. You’ll have to go outside Visual Studio for this next step: we want. But we should start small; the more complexity we introduce, the greater the That’s it—seriously. We can use this simple class to execute any IronPython code to be a string, an int, or some type of object. It will be up to the calling code to sort out those details. returning some generic information back to the calling class. We’re telling C# that we don’t know if it’s going nTip  The Execute method has some unique syntax to it. The bit implies that we are } } } return results; var results = (EngineResults) operations.Call(classMethod); var classMethod = operations.GetMember(classInstance, methodName); var classInstance = operations.Call(classObj); var classObj = scope.GetVariable(className); source.Execute(scope); { string methodName) public EngineResults Execute(string className,➥ /// The results of IronPython execution. /// The name of the method to execute. /// The name of the class to reference. class. /// Generic result from IronPython➥ /// /// Gets the results of an IronPython file after execution. /// } operations = engine.Operations; scope = engine.CreateScope(); source = engine.CreateScriptSourceFromFile(file); engine = Python.CreateEngine(); { public void Initialize(string file) Chapter 6  ■ aDVANCED DEVELOPMENT138 { { ;() Console.Read Console.WriteLine(e.Execute("pluginTest", "HelloPlugin")); e.Initialize(@"C:\Python\Plugin\Scripts\pluginTest.py"); var e = new EngineManager(); { static void Main(string[] args) { class Program { namespace ConsoleUI using IPEngine; using System; Listing 6-14. Calling the Plug-in Library to Run Our pluginTest Code where you’ve created the project. code in Listing 6-14. You’ll need to modify the path for the IronPython script file to match Having added it, let’s try it out. Open the Program.cs file; modify it to look like the see several dlls. Select IPEngine.dll and click OK to add it to the ConsoleUI project. eral folders. You’ll want to navigate to bin and then to Debug. In the Debug folder you’ll reference. Navigate to the folder that contains the IPEngine project; inside you’ll see sev- none!). Once the build is successful, right-click on the ConsoleUI project and select Add Now select Build ➤ Build Solution and correct any errors that appear (hopefully, dio, where you can add “Solution Folders” and so on directly in the IDE. nNote  This whole file and folder creation is expedited significantly in the commercial version of Visual Stu- return message message = 'Hello via the plugin!' def HelloPlugin(self): class pluginTest: Listing 6-13. A Very Basic IronPython Class for Testing the Plug-in Library In the pluginTest file, enter the code shown in Listing 6-13 and save it. nice and neat. file called pluginTest.py. We’ll house our IronPython scripts in this folder to keep things Chapter 6  ■ aDVANCED DEVELOPMENT 139 :A useful base class for a plug-in would have the following properties features. to inherit so that any plug-in object we want to use is guaranteed to have some basic would benefit us as developers to standardize our plug-ins with a base class from which Although what we have is quite successful in terms of executing our IronPython code, it Creating a Plug-in Base on data-tier libraries. thing to get compiled and will depend directly on any business-tier libraries, which themselves will depend depends on IPEngine. In general, if you’ve built with an n-tier architecture in mind, the UI will be the last sure IPEngine is listed before ConsoleUI. You can also set dependencies in the aptly named tab; ConsoleUI can adjust the order in which projects are compiled when you build or run the solution; in our case, make get errors and it may not be clear why. Right-click on the solution and select Project Build Order. Here you library may be dependent on another one in your solution. If the build order is not correct, you’ll continually nTip  Frequently in solutions where you have class libraries (particularly multiple class libraries), one Figure 6-11. IronPython via C# script is executed (Figure 6-11). Now, if you run this application by pressing F5, you should find that your IronPython explicit, nor would we make accommodations specifically with the intention of allowing generic access. differences between static and dynamically typed languages: in pure Python we wouldn’t need to be so very that we expect the return type of the Execute method to be of type string. This is a classic example of the nTip  Here’s the follow-up to the generic method defined in the library. Our calling code tells the library Chapter 6  ■ aDVANCED DEVELOPMENT140 { ; public pluginStatus status { get; private set public string fileLocation { get; private set; } public string methodName { get; private set; } public string className { get; private set; } public string displayName { get; private set; } public string id { get; private set; } } Failed Loaded, Unavailable, { public enum pluginStatus { public abstract class BasePlugin { namespace IPEngine using System; Listing 6-15. The Abstract Base Class for Our Plug-ins in BasePlugin.cs called BasePlugin.cs. Modify the code in BasePlugin to look like the code in Listing 6-15. Right click on the IPEngine project and click Add New Item. Add to the project a class Failed 3. Loaded 2. Unavailable 1. In terms of our enumeration for plug-in status, we can start with the following: The name of the method to execute • The name of the class to use • An enumeration that describes the current status of the plug-in • The location of the plug-in on disk • A useful display name • A unique ID for the plug-in instance • Chapter 6  ■ aDVANCED DEVELOPMENT 141 { ; methodName = _methodName new Exception("Method name cannot be null."); } if (String.IsNullOrEmpty(_methodName)) { throw➥ { public void SetMethodName(string _methodName) /// The name of the method. /// /// Sets the name of the IronPython method to execute. /// } className = _className; new Exception("Class name cannot be null."); } if (String.IsNullOrEmpty(_className)) { throw➥ { public void SetClassName(string _className) /// The name of the class. /// /// Sets the name of the IronPython class to use. /// } displayName = _displayName; new Exception("Display name cannot be null."); } if (String.IsNullOrEmpty(_displayName)) { throw➥ { public void SetDisplayName(string _displayName) /// The string to display. /// /// Sets the friendly display name for a plugin. /// } id = Guid.NewGuid().ToString().ToLower(); has already been defined."); } if (!String.IsNullOrEmpty(id)) { throw new Exception("Plugin id➥ { public void SetPluginID() /// /// Initializes a unique ID for a plugin. /// Chapter 6  ■ aDVANCED DEVELOPMENT142 .(ate in the IPEngine project a new class file called TestPlugin.cs (Listing 6-16 ConfigurePlugin and ExecutePlugin, that inheriting classes have to implement. Let’s cre- to all these properties and the enumeration defined within. It also defines two methods, setting the properties of the plug-in. Classes that inherit from this base class have access purposes. Its primary functions are to define the status types for a plug-in and to handle Although Listing 6-15 looks a bit long, it actually serves a few very straightforward } } public abstract new string ToString(); public abstract ExecuteResults ExecutePlugin(); string _className, string _methodName, string _fileLocation); public abstract void ConfigurePlugin(string _displayName,➥ // classes that inherit from this base must implement the following methods } status = _status; if (status == _status) return; { public void SetStatus(pluginStatus _status) enumeration. /// The status as defined by the pluginStatus➥ /// /// Sets the current status of the plugin. /// } fileLocation = _fileLocation; new Exception("File location cannot be null."); } if (String.IsNullOrEmpty(_fileLocation)) { throw➥ { public void SetFileLocation(string _fileLocation) /// The path of the file on disk. /// /// Sets the location of the plugin on disk. /// Chapter 6  ■ aDVANCED DEVELOPMENT 143 { ;("… throw new Exception("I have to do something to compile // to do: implement this method { public override ExecuteResults ExecutePlugin() } } SetStatus(pluginStatus.Failed); { catch } em.Initialize(fileLocation); em = new EngineManager(); // set up the instance of the plugin engine SetStatus(pluginStatus.Loaded); SetFileLocation(_fileLocation); SetMethodName(_methodName); SetClassName(_className); SetDisplayName(_displayName); SetPluginID(); // set up the plugin properties { try { string _className, string _methodName, string _fileLocation) public override void ConfigurePlugin(string _displayName,➥ private EngineManager em; { public class TestPlugin : BasePlugin { namespace IPEngine using System.Text; using System; Listing 6-16. A Test Plug-in That Needs an ExecutePlugin Method Implemented Chapter 6  ■ aDVANCED DEVELOPMENT144 (public EngineResults Execute(BasePlugin plugin /// The results of IronPython execution. /// A plugin that implements BasePlugin. IronPython class. /// Generic result from➥ /// /// Gets the results of an IronPython plugin after execution. /// Listing 6-17. Overriding the Execute Method in EngineManager to Accommodate Plug-ins Listing 6-17. Open the EngineManager class and add below the existing Execute method shown in don’t have anything in our EngineManager to handle our plug-in! Let’s remedy that. The reason we haven’t implemented the ExecutePlugin method yet is simple: we pluginName.ToString() and get a quick view at the object’s contents. it does make debugging easier. Now if I need to do a quick sanity check on the state of the object, I can call plays a string in the form “display name : class calling method method.” Again, it’s not a requirement, but nNote  Please note that I practice what I preach! We have overridden a ToString method here that dis- } } } return s.ToString(); s.Append(methodName); s.Append(" calling method "); s.Append(className); s.Append(": "); s.Append(displayName); var s = new StringBuilder(); { public override string ToString() Chapter 6  ■ aDVANCED DEVELOPMENT 145 .(terrain looks in a new light (Figure 6-12 compare the direct EngineManager to the more abstract plug-in class and see how the robust plug-in system, let’s modify the main UI program to use it (Listing 6-19), and we’ll the calling class to indicate some type of failure has occurred. Now that we have a more erty to find out whether to execute the IronPython method or throw an exception back to This ExecutePlugin method now evaluates the current value of the pluginStatus prop- } } initialize properly."); throw new Exception("Unable to verify plugin status; please➥ default: properly."); throw new Exception("Plugin is unavailable; please initialize➥ case pluginStatus.Unavailable: throw new Exception("EngineManager failed to initialize plugin."); case pluginStatus.Failed: return em.Execute(this); case pluginStatus.Loaded: { switch (status) { public override ExecuteResults ExecutePlugin() Listing 6-18. Overriding the Execute Method in EngineManager to Accommodate Plug-ins TestPlugin class and implement that ExecutePlugin method we left hanging (Listing 6-18). plug-in (such as plugin.className) instead of specific strings. Now let’s jump back to our it accepts any object that inherits from BasePlugin and that it uses properties of that It’s pretty similar to the existing Execute method. The only real differences are that } return results; var results = (EngineResults)operations.Call(classMethod); var classMethod = operations.GetMember(classInstance, plugin.methodName); var classInstance = operations.Call(classObj); var classObj = scope.GetVariable(plugin.className); source.Execute(scope); { Chapter 6  ■ aDVANCED DEVELOPMENT146 provide a standardized way to access IronPython code, you can easily extend the architecture? I would recommend using the plug-in architecture; although both classes Is it better to use a direct call to the EngineManager or better to go through the plug-in Choices, Choices Figure 6-12. The basic plug-in architecture is working. } } } Console.Read(); Console.WriteLine(e.Execute("pluginTest", "HelloPlugin")); e.Initialize(@"C:\Python\Plugin\Scripts\pluginTest.py"); var e = new EngineManager(); Console.WriteLine(p.ExecutePlugin()); @"C:\Python\Plugin\Scripts\pluginTest.py"); p.ConfigurePlugin("test", "pluginTest", "HelloPlugin",➥ var p = new TestPlugin(); { static void Main(string[] args) { class Program { namespace ConsoleUI using IPEngine; using System; Listing 6-19. A Modified Program.cs in the ConsoleUI Application Chapter 6  ■ aDVANCED DEVELOPMENT 147 ;( return em.Execute(this, parameters case pluginStatus.Loaded: { switch (status) { public override ExecuteResults ExecutePlugin(string[] parameters) Listing 6-21. Overloading ExecutePlugin in the TestPlugin to Accept Parameters ExecutePlugin method in TestPlugin.cs the code shown in Listing 6-21. our parameters can be passed to the EngineManager properly. Add after the existing Next we need to overload the ExecutePlugin method in the TestPlugin class so that public abstract ExecuteResults ExecutePlugin(string[] parameters); Listing 6-20. Overloading ExecutePlugin to Accept Parameters shown in Listing 6-20. file and add immediately after the existing ExecutePlugin method the overload method passing of one or more arguments to an IronPython class. First, open the BasePlugin.cs The last expansion to the plug-in system will be an overloaded method that supports the Supporting Healthy Arguments you need to. on the plug-in architecture. You can always make calls to the EngineManager directly if somewhere, used when needed, and otherwise not seen. For that reason alone, my vote’s level” code. Code with that aroma seems like it should be tucked in the background hides those specific implementation details too, but it still has the slight scent of “low- or any of that business. That’s the way things should be. Granted, the EngineManager with a handful of methods; it doesn’t know anything about the IronPython ScriptEngine clue what’s happening behind the scenes. All it knows is that there’s a TestPlugin class There’s another, more subtle benefit happening here. The user interface code has no expand on it greatly. It’s all relative. far is, frankly, overkill. For other applications, we would need to take this system and rules. For some applications, developing a plug-in system like the one we’ve created so trying to tack something on later. Don’t take my word though; there are no hard-and-fast you’re doing yourself a service if you introduce a little flexibility as you go rather than generally fixes everything. As I’ve mentioned on a few occasions throughout this book, BasePlugin class to other types, and my personal experience is that a layer of abstraction Chapter 6  ■ aDVANCED DEVELOPMENT148 { ; return results var results = (EngineResults)operations.Call(classMethod, parameters); var classMethod = operations.GetMember(classInstance, plugin.methodName); var classInstance = operations.Call(classObj); var classObj = scope.GetVariable(plugin.className); source.Execute(scope); { public EngineResults Execute(BasePlugin plugin, string[] parameters) /// The results of IronPython execution. /// An array of string parameters. /// A plugin that implements BasePlugin. IronPython class. /// Generic result from➥ /// /// Gets the results of an IronPython plugin after execution. /// Listing 6-22. Overloading Execute to Accept Parameters in EngineManager.css Execute method. to accept parameters as well. Place the code in Listing 6-22 immediately after the existing of parameters. To fix this, we’ll overload the Execute method in the EngineManager class down to the EngineManager.Execute method, which as of now does not accept an array overridden to accept an array of strings. Note that we’re also passing these parameters code in Listing 6-21 to the code in Listing 6-18 and you can see how the method has been call that accepts an array of strings as parameters to use in the method. Compare the The method signature now indicates that the method is willing to accept an alternate } } initialize properly."); throw new Exception("Unable to verify plugin status; please➥ default: properly."); throw new Exception("Plugin is unavailable; please initialize➥ case pluginStatus.Unavailable: plugin."); throw new Exception("EngineManager failed to initialize➥ case pluginStatus.Failed: Chapter 6  ■ aDVANCED DEVELOPMENT 149 ;(( Console.WriteLine(p.ExecutePlugin(parameters string[] parameters = {"Alan", "24"}; @"C:\Python\Plugin\Scripts\pluginParameters.py"); p.ConfigurePlugin("params", "pluginParameters", "tryParams",➥ var p = new TestPlugin(); Console.WriteLine(t.ExecutePlugin()); @"C:\Python\Plugin\Scripts\pluginTest.py"); t.ConfigurePlugin("test", "pluginTest", "HelloPlugin",➥ var t = new TestPlugin(); { static void Main(string[] args) { class Program { namespace ConsoleUI using IPEngine; using System; Listing 6-24. A Test of the New Plug-in Overloads in Program.cs look like Listing 6-24; then run the solution (Figure 6-13). works as intended. In the ConsoleUI project, open the Program.cs file and modify it to The last step is to try calling our overloaded plug-in methods to see if everything return "Hello, " + name + ", you are " + age + " years old." def tryParams(self, name, age): class pluginParameters: Methods Listing 6-23. pluginParameters.py, A Quick IronPython Script to Test Our Parameter Scripts folder called pluginParameters.py (Listing 6-23). Now let’s write a little IronPython code that tests this out. Add a new script to the Chapter 6  ■ aDVANCED DEVELOPMENT150 .IronPython plug-ins The application should handle all actions based on file system changes via 3. The application should monitor the file system for various changes. 2. The application should consist of a single form. 1. problems or changes along the way. heading. We’ll leave a little wiggle room for future development as well as for unforeseen As always, it’s a good idea to start off with a basic plan so that we know where we’re The Plan tion’s desired file system activities. the C# side will be the form itself, and the IronPython side will handle all of the applica- depending on what sort of activities have occurred. We’ll use a mix of C# and IronPython: Forms application that uses plug-ins to watch the file system and take various actions and implement it step by step. For this application, what we’re going to build is a small NotQuitePad in Chapter 5, we’ll lay out a set of design requirements, plan the application, We should put our plug-in system to the test in a real-life example. Much like “Somebody’s Watching Me” Figure 6-13. The plug-in overloads now accept an array of parameters. } } } Console.Read(); Console.WriteLine(e.Execute("pluginTest", "HelloPlugin")); e.Initialize(@"C:\Python\Plugin\Scripts\pluginTest.py"); var e = new EngineManager(); Chapter 6  ■ aDVANCED DEVELOPMENT 151 .(Scripts folder is called p1Handler.py (Listing 6-25 when I start with a simple, easily tested foundation. The first class we’ll create in the For now, we’ll keep the IronPython classes simple. I find development tasks are easier Writing the Basic IronPython Classes http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.aspx. nNote  You can find more information on the FileSystemWatcher at .p2—inform the calling application that the file has been added to the folder 2. .p1—inform the calling application that the file has been deleted from the folder 1. C:\Python for the presence of .p1 and .p2 files. The tasks will be as follows: specific types of files. It’s actually quite a powerful tool. Our program will monitor of actions in the file system, such as adding or deleting a file and watching for only .NET provides called the FileSystemWatcher. This object allows us to monitor a variety To accomplish the task of monitoring the file system, we’re going to use an object warnings. Build ➤ Rebuild Solution from the menu bar; everything should rebuild with no errors or ect a reference to IPEngine.dll and click OK. Now rebuild the entire solution by clicking Next, right-click on the FSWatcher project and click Add Reference. Add to the proj- Click OK to add it to the solution. Select Visual C# ➤ Windows ➤ Windows Forms Application, and name it FSWatcher. code in the IPEngine library. Right-click on the solution and click Add ➤ New Project. it in this solution; it’s just convenient for the moment in case we want to debug or modify First we’ll create a new project in the Plugin solution. We don’t necessarily have to do check box is checked, we should see the task performed; otherwise it should be ignored. of the way by performing the task a given plug-in should be watching for. If the given The check boxes will activate or deactivate individual plug-ins. We’ll test this at each step or feedback based on file system changes; IronPython code will provide that feedback. as two check box controls. The purpose of the text box is to display to the user any output What I envision for this application is a single form that has a text box control on it as well The Design Chapter 6  ■ aDVANCED DEVELOPMENT152 .Figure 6-14. The GroupBox is a nice container for our controls the form, and change the Text property to File System Watcher (Figure 6-14). View and double-click on GroupBox in the toolbox. Resize it until it’s nicely centered in and make sure everything is named something useful. First, open the form in Design In the FSWatcher application, we have to set up the main form with the controls we need Creating the Parent Application return fileName + " was added." def NotifyCaller(self, fileName): "Informs the calling application that a file has been added." class p2Handler: Listing 6-26. p2Handler Will Also Return Information to the Calling Application that a file has been added to the folder we’re watching (Listing 6-26). folder we’re watching. The next handler, p2Handler.py, will inform the calling application This first handler informs the calling application that a file has been deleted from the return fileName + " was deleted." def NotifyCaller(self, fileName): "Informs the calling application that a file is deleted." class p1Handler: Listing 6-25. p1Handler Will Return Information to the Calling Application Chapter 6  ■ aDVANCED DEVELOPMENT 153 .Figure 6-16. The FSWatcher UI is complete and Watch .p2 files (Figure 6-16). erEnabled and p2WatcherEnabled, respectively. Set the Text properties to Watch .p1 files Now we need to add two check box controls off to the right. Name them p1Watch- Figure 6-15. The updates text box once all the properties have been set the updates box and disrupting the results (Figure 6-15). You should also set the ReadOnly property to True; this will prevent users from typing in the name to txtUpdates. Resize it to approximately two-thirds the size of the GroupBox. control in the toolbox and set the Multiline property to True, Scrollbars to Vertical, and Next we’ll add the text box control to the GroupBox. Double-click on the TextBox Chapter 6  ■ aDVANCED DEVELOPMENT154 .is just a test at this point nNote  I created a dummy file called test.p1 in my C:\Python folder. It doesn’t matter what’s in there—this } } } txtUpdates.Text = p.ExecutePlugin(parameters); string[] parameters = {@"C:\Python\test.p1"}; @"C:\Python\Plugin\Scripts\p1Handler.py"); p.ConfigurePlugin("test", "p1Handler", "NotifyCaller",➥ var p = new TestPlugin(); { private void Form1_Load(object sender, EventArgs e) } InitializeComponent(); { public Form1() { public partial class Form1 : Form { namespace FSWatcher using IPEngine; using System.Windows.Forms; using System; Listing 6-27. The Main Form with a Basic Plug-in Test Added (Figure 6-17). Open Form1.cs and modify the code to look like Listing 6-27. Then we’ll run it ally the way we expect them to be. quick test to make sure that our IPEngine reference is correct and that things are gener- With the UI set up, we can start wiring our code together. The first thing we’ll do is run a Wiring Things Together Chapter 6  ■ aDVANCED DEVELOPMENT 155 ; private FileSystemWatcher fp2 private FileSystemWatcher fp1; private static string updateText = String.Empty; { public partial class Form1 : Form { namespace FSWatcher using IPEngine; using System.Windows.Forms; using System.IO; using System; Listing 6-28. The Main Form with FileSystemWatcher Added We’ll discuss it afterwards. With the Timer in place, open the Form1 code and modify it to look like Listing 6-28. Enabled is set to True. the txtUpdates control with anything that has happened; make sure that Timer property (which is expressed in milliseconds.) We’ll use the Timer to fire every 250ms and update First, on the main form, add a new Timer control and set the Interval property to 250 FileSystemWatcher object and see how it can help us out. With the knowledge that our plug-in architecture is working, let’s take a look at the Figure 6-17. A first run of the UI shows that our IronPython class is called. Chapter 6  ■ aDVANCED DEVELOPMENT156 { ;" updateText += "\r\n updateText += p.ExecutePlugin(parameters); string[] parameters = { e.FullPath }; @"C:\Python\Plugin\Scripts\p1Handler.py"); p.ConfigurePlugin("test", "p1Handler", "NotifyCaller",➥ var p = new TestPlugin(); { private static void OnP1Deleted(object sender, FileSystemEventArgs e) } return f; f.Created += OnP2Created; EnableRaisingEvents = true }; var f = new FileSystemWatcher { Path = path, Filter = filter,➥ { public FileSystemWatcher InitializeP2Watcher(string path, string filter) } return f; f.Deleted += OnP1Deleted; EnableRaisingEvents = true }; var f = new FileSystemWatcher { Path = path, Filter = filter,➥ { public FileSystemWatcher InitializeP1Watcher(string path, string filter) } fp2 = InitializeP2Watcher(@"C:\Python", "*.p2"); fp1 = InitializeP1Watcher(@"C:\Python", "*.p1"); { private void Form1_Load(object sender, EventArgs e) } InitializeComponent(); { public Form1() Chapter 6  ■ aDVANCED DEVELOPMENT 157 .appropriate types, I get the results shown in Figure 6-18 ation of .p2 files. After running the application and adding and deleting a few files of the monitors the file system for the deletion of .p1 files, and the other monitors for the cre- Once we’ve created the form, we set up two FileSystemWatcher objects. One of them EventHandlers together. it here to keep things simple. The point is the use of IronPython code via C#, not how to wire a variety of nNote  I really, really do not like the Timer control. It does have its uses, but I try to avoid it. I’m using } } } txtUpdates.Text = updateText; { private void timer1_Tick(object sender, EventArgs e) } updateText += "\r\n"; updateText += p.ExecutePlugin(parameters); string[] parameters = { e.FullPath }; @"C:\Python\Plugin\Scripts\p2Handler.py"); p.ConfigurePlugin("test", "p1Handler", "NotifyCaller",➥ var p = new TestPlugin(); { private static void OnP2Created(object sender, FileSystemEventArgs e) Chapter 6  ■ aDVANCED DEVELOPMENT158 ; break fp2.Created -= OnP2Created; case false: { switch (p2WatcherEnabled.Checked) } break; fp1.Deleted += OnP1Deleted; default: break; fp1.Deleted -= OnP1Deleted; case false: { switch (p1WatcherEnabled.Checked) txtUpdates.Text = updateText; { private void timer1_Tick(object sender, EventArgs e) Listing 6-29. The Main Form with FileSystemWatcher Added make the correct processing decision based on their value (Listing 6-29). So far so good. Now all we need to do is to check the state of the check boxes and file system. Figure 6-18. The FSWatcher is using the IronPython plug-in code and monitoring the Chapter 6  ■ aDVANCED DEVELOPMENT 159 .your IronPython scripts can grow from there a common format for plug-ins that permits some very consistent implementations, and can adapt to a variety of situations. We can pass and retrieve data from it, we’ve created information. What we have accomplished is to create a functional plug-in system that tial for growth here. I purposefully kept the plug-ins simple, just returning some basic our IronPython plug-in system through its paces. Hopefully you see there’s a lot of poten- Given that this application is technically simple, we did a pretty good job of putting plug-ins. DONE. The application should handle all actions on file system changes via IronPython 3. The application should monitor the file system for various changes. DONE. 2. The application should consist of a single Form. DONE. 1. Looking back, how did we do based on our design requirements? Project Postmortem Figure 6-19. The FSWatcher with adjusted settings SystemWatcher and added back in when the boxes are checked (Figure 6-19). the txtUpdates box with relevant file system data. The events are removed from the File- Now, if one or both of the check boxes are unchecked, the FSWatcher will not update } } } break; fp2.Created += OnP2Created; default: Chapter 6  ■ aDVANCED DEVELOPMENT160 .pure IronPython and we’ll look at accessing various data sources Although we took a brief detour into a land heavy with C# code, we’re moving back to few applications that use IronPython plug-ins, including console and Forms applications. constructions IronPython provides for integration (such as the ScriptEngine) and built a into any .NET application you write that makes use of IPEngine. We’ve looked at the of the language of your choice. That means you can drop your own IronPython code in any .NET language. Just add it as a reference, instantiate, and use it given the syntax ment IronPython code in another .NET language. In fact, you can use the IPEngine library With the plug-in architecture in place, you’ve seen firsthand how quickly you can imple- Summary Chapter 6  ■ aDVANCED DEVELOPMENT 161 163 Chapter 7 Data Manipulation “Complexity kills. It sucks the life out of developers, it makes products difficult to plan, build and test, it introduces security challenges and it causes end-user and administrator frustration.” — Ray Ozzie You will not get far in development before you realize the need to store information that you are generating or collecting. Modern software applications typically store, or persist, their data somewhere for modification or retrieval later. There are a variety of ways to handle this task; databases, XML files, comma-separated value files, and flat text files are just some of the viable options, depending on the requirements of the application or organization. In this chapter we’ll look at some of the various ways to store and retrieve data, beginning with one of the most common methods, the database. After covering a bit about each underlying data storage method, we’ll look at how IronPython allows us to communicate with it to get our jobs done more easily. SQL When it comes to the task of data storage and retrieval, a relational database man- agement system (RDBMS) such as Microsoft SQL Server is excellent at efficient data management. A database comprises one or more tables, with each table defining how to store information using columns of various data types. Data is stored as records, or rows in the tables, following the column specifications. Relating data in one table to that in another gives developers a powerful and versatile way to express data. nNote  It’s a common misconception that the word relational refers to the relationship between two or more tables. Actually, each individual table in the database is called a relation. The reason for this is that every row of data in the table conforms to the same constraints and data types; you can’t have one row of n columns and another row with a different number of columns or data types. Knowing this won’t make or break your database success, but it might serve to clarify certain aspects. .comes down to testing to find out what works best for the current problem making retrieval incredibly fast. Like many aspects of development, this is one of those trade-offs that often entity in an application, denormalized data could go so far as to have all the data you need stored in one row, Where normalized data is very granular and generally requires several steps to assemble as a finished and there are situations in which you’d actually like your data to be denormalized for performance reasons. always desirable, however. Most situations don’t require normalization past what is called 3rd Normal Form, Employee and Department has been normalized to a point, but it could be normalized further. This is not the integrity of the data in your system. And normalized data can take multiple forms. The table design for By separating data in this manner, we have normalized it. Normalized data is designed to strengthen What happens when your boss asks for a list of everyone in the IT department? enter “IT,” another might enter “Information Technology,” and, worse, yet another might enter “Developers.” would it duplicate data unnecessarily, but it would make maintenance a nightmare. Where one user might nTip  Why didn’t we just store the name of the department in the employee record? If we had, not only because it references another table. key is a stored reference to the primary key in another table. It is termed a foreign key ment, and a relationship is established between the two tables. Basically, a foreign table is a foreign key. The Department table defines the unique IDs for each depart- identify uniquely each row in the table. The departmentID column in the Department table, the employeeID column is what’s called the primary key, which is a value used to of the columns in boldface? Those columns indicate keys. In the case of the Employee ment. The Employee table has five columns, the Department table has two. Why are two Figure 7-1 presents two tables: one called Employee and the other called Depart- Figure 7-1. A sample database design for the employees might look something like Figure 7-1. date of birth, and the date of hiring. A relational database design for this type of system a given department, along with some basic information, such as each employee’s name, employees in the company. Your boss wants an easily maintainable list of employees in Consider that your employer has asked you to build an application to manage the Chapter 7  ■ DATA MANIPULATION164 .(log in to the default instance (Figure 7-3 far. Open up the Microsoft SQL Server Management Studio you installed in Chapter 1 and Employee and Department tables from earlier and fill them in with the data we’ve seen so First, we need to set up a sample database with some dummy data. We’ll create the A Sample Database SqlClient. that are specific to particular databases. For our purposes, we’ll focus on System.Data and System.Data. Oracle, and other databases. Most of this functionality lives under System.Data and related namespaces nectivity simpler and to relieve you, as a developer, of the burden of creating data access code for MS SQL, nNote  The .NET framework exposes a variety of providers and boilerplate code to make database con- tions and how to execute them via the IronPython console. already covered. After setting up a basic design, we’ll break down these four core opera- because the language itself is built on very basic concepts, including some we have Although complete coverage of SQL is beyond the scope of this book, we’re in luck Query Language, or SQL (pronounced “sequel”). custom language for communicating with the system and working with data: Structured manually and figure out who works where. Luckily, modern database systems provide a open these tables, it’s not terribly efficient, because we have to look through the records department in which an employee works. Now, while it’s all well and good that we can Note that the departmentID column has an integer value in it representing the Figure 7-2. Some sample data for our small company demonstrates the contents and relationships in our sample company. Assuming that we have a few data points already filled in for both tables, Figure 7-2 Chapter 7  ■ DATA MANIPULATION 165 .(set to their defaults. Click OK to add the database (Figure 7-4 New Database. Name this new database IronPython, and leave the remaining options machine it’s the SQLEXPRESS instance. Right-click on the Databases folder and click On the left side you’ll see the server to which you’re currently connected; on my Figure 7-3. Logging in to SQL Server Chapter 7  ■ DATA MANIPULATION166 GO SET QUOTED_IDENTIFIER ON GO SET ANSI_NULLS ON GO USE [IronPython] Listing 7-1. SQL Code to Create the Department Table right; press F5 to run it. click New Query. Enter the query shown in Listing 7-1 in the Query Editor window on the Next, we’ll create the Department table. Right-click on the IronPython database and Figure 7-4. Creating the IronPython database Chapter 7  ■ DATA MANIPULATION 167 , [dateHired] [datetime] NOT NULL [dateOfBirth] [datetime] NOT NULL, [name] [varchar](150) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [departmentID] [int] NOT NULL, [employeeID] [int] IDENTITY(1,1) NOT NULL, CREATE TABLE [dbo].[Employee]( GO SET ANSI_PADDING ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_NULLS ON GO USE [IronPython] Listing 7-2. SQL Code to Create the Employee Table F5 key. data. Replace the previous script with the one in Listing 7-2 and execute it by pressing the Now we’ll create the Employee table; then we can start populating everything with well, but we won’t dive into the deep end. be spent with data modification language, called DML. The DML statements can get very, very complex as fall into a category of SQL statements called data definition language, or DDL, whereas most of our time will can be complex, so don’t spend too much time trying to understand these statements at the moment. They nNote  The SQL statements for creating and modifying a table’s structure, keys, indexes, and relationships SET ANSI_PADDING OFF GO WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]) ON [PRIMARY] ([departmentID] ASC) CONSTRAINT [PK_Department] PRIMARY KEY CLUSTERED [title] [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [departmentID] [int] IDENTITY(1,1) NOT NULL, CREATE TABLE [dbo].[Department]( GO SET ANSI_PADDING ON Chapter 7  ■ DATA MANIPULATION168 * >>> from System.Data.SqlClient import >>> from System.Data import * >>> clr.AddReference("System.Data") >>> import clr Listing 7-4. Adding a Department to the Department Table the ALAN-DEVPC\SQLEXPRESS name in the sqlConnection with the name of your SQL instance. IronPython. Open the IronPython interpreter and enter the code in Listing 7-4—replacing with the INSERT INTO command. Let’s try adding a record to the Department table from Adding new records (or rows) to a SQL table from your program can be accomplished Create INSERT INTO Employee VALUES (2, 'Jane Doe', '1/1/2009', '2/2/2009') INSERT INTO Employee VALUES (1, 'Ted Smith', '1/1/2009', '2/2/2009') INSERT INTO Employee VALUES (2, 'Alan Harris', '1/1/2009', '2/2/2009') INSERT INTO Department (title) VALUES ('Accounting') INSERT INTO Department (title) VALUES ('Marketing') INSERT INTO Department (title) VALUES ('IT') INSERT INTO Department (title) VALUES ('Human Resources') Listing 7-3. SQL Code to Fill the Tables with Default Values the tables with a little bit of data. Finally, replace that SQL script with the one in Listing 7-3 and execute it to populate ALTER TABLE [dbo].[Employee] CHECK CONSTRAINT [FK_Employee_Employee] GO REFERENCES [dbo].[Department] ([departmentID]) FOREIGN KEY([departmentID]) ALTER TABLE [dbo].[Employee] WITH CHECK ADD CONSTRAINT [FK_Employee_Employee]➥ GO SET ANSI_PADDING OFF GO WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]) ON [PRIMARY] ([employeeID] ASC) CONSTRAINT [PK_Employee] PRIMARY KEY CLUSTERED Chapter 7  ■ DATA MANIPULATION 169 ()>>> conn.Close 1 >>> comm.ExecuteNonQuery() >>> conn.Open() '1/1/2009', '2/2/2009')", conn) >>> comm = SqlCommand("INSERT INTO Employee VALUES (1, 'Sally''s Employee',➥ Integrated Security=True;Initial Catalog=IronPython;User Instance=false") >>> conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ >>> from System.Data.SqlClient import * >>> from System.Data import * >>> clr.AddReference("System.Data") >>> import clr Listing 7-5. Adding a Department to the Employee Table Remember to change the SqlConnection to specify your database. Let’s add an entry to the Employee table that demonstrates this (Listing 7-5). auto-incremented primary key. is left off in our case because SQL Server will fill it in automatically, since it’s set to be an the INSERT statement are in the order in which they appear in the table; the first column Department), you would provide a value of Joe’’s Department. Also note that the fields in so if you want to insert a single quote into a field (for example, a department called Joe’s what data to insert. Note that in SQL commands, strings are denoted by single quotes, The INSERT INTO command needs to know which table to insert data to, as well as row in that statement. by the last statement. In this case, you inserted one record, so the return value says that you modified one returned from the IronPython interpreter. This is SQL Server informing you of how many rows were modified nNote  Immediately after typing the ExecuteNonQuery() command, you should see the number 1 >>> conn.Close() 1 >>> comm.ExecuteNonQuery() >>> conn.Open() >>> comm = SqlCommand("INSERT INTO Department VALUES ('IPY Department')", conn) Integrated Security=True;Initial Catalog=IronPython;User Instance=false") >>> conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ Chapter 7  ■ DATA MANIPULATION170 .may not matter. For the sake of performance, this is the manner of data retrieval we’ll use an intimate knowledge of the underlying data types of the table. Depending on your situation, this may or ean. These are the fastest methods of retrieving data from the database, but they require your code to have nTip  There are a variety of typed methods for the SqlDataReader, such as GetString, GetChar, and GetBool- >>> conn.Close() >>> reader.Close() Alan Harris (press Enter here) print (reader.GetString(0)) >>> if (reader.Read()): >>> reader = comm.ExecuteReader() >>> conn.Open() >>> comm = SqlCommand("SELECT name FROM Employee", conn) Integrated Security=True;Initial Catalog=IronPython;User Instance=false") >>> conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ >>> from System.Data.SqlClient import * >>> from System.Data import * >>> clr.AddReference("System.Data") >>> import clr Listing 7-6. Selecting Specific Columns from the Employee Table Listing 7-7. want to retrieve, as in Listing 7-6, or you can select all the columns in the schema, as in are multiple ways to use the SELECT command. You can specify the columns you If we want to retrieve data from a SQL table, we can use the SELECT command. There Retrieve law somewhere). She was born on January 1, 2009, and hired on February 2, 2009 (which has to violate a Human Resources Department (department ID 1) and who is named Sally’s Employee. Here we’ve added an employee to the Employee table who is a member of the Chapter 7  ■ DATA MANIPULATION 171 .Listing 7-8 or the command will fail. Let’s update the record for “Sally’s Employee,” as shown in UPDATE command, one or more rows in the table must already exist to be updated, Modifying existing data in a SQL table is done via the UPDATE command. To use the Update method of data retrieval, though, and it is used very commonly. of data; it’s not possible to move backward in the stream. The SqlDataReader is a very fast The SqlDataReader we used to read the data from the table is a forward-only stream second time because all of the columns had been returned to us. Tacky! the column names in your query. Also note that we had to move to a different column in the result set the pain if something breaks in the schema. Generally speaking you are safest if you are explicit about each of the schema of the database to map columns in place of the wildcard, and it makes maintenance a potential wasteful because it can potentially return unnecessary columns in the result set, it requires the retrieval of nTip  Although it can be tempting, don’t rely on SELECT * in any real-world code you plan to use. It is >>> conn.Close() >>> reader.Close() Alan Harris (press Enter here) print (reader.GetString(2)) >>> if (reader.Read()): >>> reader = comm.ExecuteReader() >>> conn.Open() >>> comm = SqlCommand("SELECT * FROM Employee", conn) Integrated Security=True;Initial Catalog=IronPython;User Instance=false") >>> conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ >>> from System.Data.SqlClient import * >>> from System.Data import * >>> clr.AddReference("System.Data") >>> import clr Listing 7-7. Selecting All Records from the Employee Table Chapter 7  ■ DATA MANIPULATION172 * >>> from System.Data.SqlClient import >>> from System.Data import * >>> clr.AddReference("System.Data") >>> import clr Listing 7-9. Updating the Record for “Sally’s Employee” in the Employee Table name-changing “Sally Employee” (Listing 7-9). ing had both a short and tumultuous career in our small company, it’s time to delete the Deleting data from a SQL table is reserved for the aptly named DELETE command. Hav- Delete returned. Technically, the command has failed, but it’s not a catastrophic failure, just a logical one. nNote  If there is no data to update that matches the WHERE clause filter (if any), you’ll just get 0 rows statements. many. You can also use the WHERE clause to limit the results you get back in SELECT row in the table! I can think of a few cases where this is the intended behavior, but not to make sure you’ve applied the WHERE clause properly, otherwise you’ll modify every When executing statements that modify or delete data, it’s particularly important >>> conn.Close() 1 >>> comm.ExecuteNonQuery() >>> conn.Open() WHERE name='Sally''s Employee'", conn) >>> comm = SqlCommand("UPDATE Employee SET name='Sally Employee'➥ Integrated Security=True;Initial Catalog=IronPython;User Instance=false") >>> conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ >>> from System.Data.SqlClient import * >>> from System.Data import * >>> clr.AddReference("System.Data") >>> import clr Listing 7-8. Updating the Record for “Sally’s Employee” in the Employee Table Chapter 7  ■ DATA MANIPULATION 173 .easily be recreated, but I want you to know what you’re in for before you execute dangerous code able with losing the data in the Employee table. Everything we currently have is testing data only and can nCaution  Listing 7-10 is for example purposes ONLY! Don’t run this code directly unless you’re comfort- dangerous input bold for clarity. Listing 7-10 is an example of code that’s very susceptible to injection; I’ve made the not relieve you of the threat. some would have you think; this is absolutely untrue. Burying your head in the sand does believe that SQL injection attempts are not common or are not as easy to perform as as dangerous than to get caught with your security lacking. There are developers who This sounds like a terribly pessimistic view of the world, but it’s better to treat all input “All input should be considered dangerous and never trusted. No exceptions.” chapter. carefully and deliberately. It’s important. Consider it the most important quote of this live in as developers. I want you to pause for a moment and digest this next sentence no user-supplied input to sanitize. This is generally not representative of the world we So far we’ve operated in a very safe, sterile data environment with SQL. There has been Preventing SQL Injection Attacks your table! use TRUNCATE TABLE Employee. Don’t execute that command unless you want to obliterate all the data in rows; it will eradicate all the data in the table. If you wanted to clear out the Employee table, you could you can use the broader command TRUNCATE TABLE. However, this does not allow you to delete individual nNote  If you want to delete everything in a table and reset any incremental keys back to 0 in one step, >>> conn.Close() 1 >>> comm.ExecuteNonQuery() >>> conn.Open() >>> comm = SqlCommand("DELETE FROM Employee WHERE name='Sally Employee'", conn) Integrated Security=True;Initial Catalog=IronPython;User Instance=false") >>> conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ Chapter 7  ■ DATA MANIPULATION174 .((Listing 7-12 way, watch how easily you gain some security over your statements, with minimal work because you’ve got no bad habits to break. If you’re used to building SQL statements this string concatenation. If you’re new to development, then you’ve nothing to worry about Using parameterized queries means letting go of assembling SQL statements involving Parameterized Queries provides. success), or you can use the tried-and-tested parameterized SQL statements that .NET self by hand (which many developers have done over the years, to varying degrees of What’s a .NET developer to do? You can opt to try to clean all your SQL inputs your- nooks and crannies of legacy code. name!). SQL injection attacks can be particularly insidious, and they tend to hide in the the name was set to foo or the name was not null (thus basically every employee with a You can see that we told SQL Server to delete everything in the Employee table where DELETE FROM Employee WHERE name='foo' OR NAME IS NOT NULL; Listing 7-11. What We Really Told the Database to Do The final statement we issued to SQL Server looks like Listing 7-11. the execution of arbitrary SQL commands and left our database exposed. We didn’t perform any steps to sanitize the user input. By not doing so, we allowed >>> conn.Close() 3 >>> comm.ExecuteNonQuery() >>> conn.Open() >>> comm = SqlCommand("DELETE FROM Employee WHERE name='" + empName + "'", conn) Enter the employee name to delete: foo' OR name IS NOT NULL; >>> empName = raw_input('Enter the employee name to delete: ') Integrated Security=True;Initial Catalog=IronPython;User Instance=false") >>> conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ >>> from System.Data.SqlClient import * >>> from System.Data import * >>> clr.AddReference("System.Data") >>> import clr Listing 7-10. A 5cc Injection of SQL Insecurity Chapter 7  ■ DATA MANIPULATION 175 .weigh the pros and cons before looking at how it’s actually done developers swear by this, others swear against it. It’s a holy war in and of itself. Let’s sets of SQL instruction contained entirely within the IronPython source code itself. Some SQL commands we’ve run so far have been inline SQL; that is, they have been complete In terms of performance and security, it’s hard to argue against stored procedures. The Stored Procedures be exploited with disastrous results. rity should be one of your primary concerns at all times during application development. Any minor flaw can that you’re immune to all attacks. You’re definitely better off for using the resources .NET provides, but secu- nCaution  Don’t fall into the trap of assuming that because you’ve followed basic guidelines for security wouldn’t you agree? all the rows in the table, none of them were deleted. It’s a small price to pay for security, treated as string literals and not as interpreted commands. As a result, instead of deleting to escape various characters and patterns in the statement so that everything would be Then we supplied a value for the parameter. Behind the scenes, .NET did some work Instead of using concatenation, we added to the statement a parameter called @name. >>> conn.Close() 0 >>> comm.ExecuteNonQuery() >>> comm.Parameters.AddWithValue("@name", empName) >>> conn.Open() >>> comm = SqlCommand("DELETE FROM Employee WHERE name=@name", conn) Enter the employee name to delete: foo' OR name IS NOT NULL; >>> empName = raw_input('Enter the employee name to delete: ') Integrated Security=True;Initial Catalog=IronPython;User Instance=false") >>> conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ >>> from System.Data.SqlClient import * >>> from System.Data import * >>> clr.AddReference("System.Data") >>> import clr Listing 7-12. With Parameterized Queries, We Add a Nice Layer of Security to Our Code Chapter 7  ■ DATA MANIPULATION176 .that point securityLoginUser and authenticationLogoutUser if you can help it. It’s just a recipe for confusion at call it securityLoginUser or authenticationLoginUser. The trick really is to be consistent. Don’t have they’re related or on the function they provide. For example, for a login stored procedure, I might I would recommend prefixing your stored procedures based on the area of your program to which that’s a silly one to waste any resources on. blessed with millions of happy, active users connecting to your data storage, every little bit counts and on that prefix and you’ll incur a negligible performance penalty for it. It’s nothing major, but if you’re spLoginUser. This is a bad practice to get into; SQL Server will check the system procedures first based with sp. So if you were to make a stored procedure that logged in a user, you would likely name it nTip  For a long time, the recommended naming convention for stored procedures was that they begin not a concern. you get those stored procedures to your production database first! With inline SQL, this is choice. Be aware, however, that when rolling code out to production, you have to ensure answer isn’t crystal clear, place your SQL in a stored procedure, because this is the safer these distinctions is mainly a judgment call based on experience. If you’re not sure or the in the near future, it won’t kill you to have it in your source code. I will say that making as it sees fit. If you’ve got a small bit of infrequently hit code that isn’t likely to change executed very frequently, place it in a stored procedure and let SQL Server manage it in limited other cases. In general, if you’ve got complex code or code that is going to be My personal preference is for stored procedures in most cases and for inline SQL and you don’t want to waste that time moving between applications. SQL Server and your code IDE. When a deadline looms, sometimes every second counts in your source code because it means you don’t have to switch back and forth between business layer and not out at the database itself. Frankly, it can be easier to keep your SQL the hundreds. From a design standpoint, it keeps potential business domain code in the working on an enterprise application where the number of stored procedures can be in can help to keep the database from getting cluttered, which becomes important if you’re On the other side of the coin, keeping smaller bits of SQL code in your source code the database to do its job. server itself, reducing the traffic to and from your program and the server and allowing updated SQL code on the next connection. Finally, you can perform more steps on the code modules; any client code that uses the stored procedures will automatically use the tions can be made server-side without requiring recompilation or deployment of updated the database. Maintenance of your code is generally easier because SQL code modifica- procedure, you help SQL Server perform those optimization steps and reduce the load on optimize the result set and to cache the execution plan. By placing your code in a stored When SQL Server executes queries, a lot of work is going on behind the scenes to Chapter 7  ■ DATA MANIPULATION 177 >>> comm.CommandType = CommandType.StoredProcedure >>> comm = SqlCommand("GetEmployeeNameByID", conn) Integrated Security=True;Initial Catalog=IronPython;User Instance=false") >>> conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ >>> from System.Data.SqlClient import * >>> from System.Data import * >>> clr.AddReference("System.Data") >>> import clr Listing 7-15. Selecting Records from the Employee Table with a Stored Procedure Now let’s call this stored procedure from IronPython (Listing 7-15). You should get a single result; for me it was my name, Alan Harris. EXEC GetEmployeeNameByID 1 Listing 7-14. Calling the Stored Procedure with a Sample ID line of code in Listing 7-14. We can try it out in SQL Server before calling it from our IronPython code with the GO END WHERE employeeID = @employeeID SELECT [name] FROM Employee SET NOCOUNT ON; BEGIN AS @employeeID INT CREATE PROCEDURE GetEmployeeNameByID GO SET QUOTED_IDENTIFIER ON GO SET ANSI_NULLS ON Listing 7-13. A Simple Stored Procedure to Retrieve an Employee’s Name shown in Listing 7-13. see how to use it from IronPython. Create a new query in SQL Server and enter the code Let’s create a simple stored procedure to retrieve an employee’s name; then we’ll Chapter 7  ■ DATA MANIPULATION178 is the same every time you communicate with the database. If even a single character is The trick to connection pooling is that the pooling works only if the connection string one spent troubleshooting a poor SQL connection that is flaking out because of connection pooling issues. find that fewer errors occur if you take the time to specify what you want outright. A wasted day of work is nTip  Connection pooling is on by default if you do not specify otherwise, but it never hurts to be explicit. I head of creating a connection object is not incurred. a very quick procedure. SQL Server maintains a cache of these connections, so the over- if there are any existing connections in the pool, one will be retrieved and reused. This is When you create a connection to SQL Server with connection pooling enabled, then created. keep up with resource demands. To counteract this problem, connection pooling was frequently, can completely disable a database or application as SQL Server struggles to performance perspective. It takes a heavy toll on response times and, if performed too The act of creating and destroying connections to the database is very costly, from a Connection Pooling lems and dropped connections. to a halt very quickly. It’s easy to overlook and can cost you plenty in hard-to-diagnose performance prob- coding flaws. Always make sure to close your SqlDataReaders and SqlConnections, or you’ll see things grind erly. Database connections and resources can be very expensive and are prone to leaks due to easily missed nTip  Earlier in the book I mentioned that certain resources were expensive and needed to be freed prop- >>> conn.Close() >>> reader.Close() Alan Harris (press Enter here) print (reader.GetString(0)) >>> if (reader.Read()): >>> reader = comm.ExecuteReader() >>> comm.Parameters.AddWithValue("@employeeID", 1) >>> conn.Open() Chapter 7  ■ DATA MANIPULATION 179 < 2009 Pro IronPython Alan Harris Listing 7-17. A Sample XML Document XML markup looks like (Listing 7-17). markup languages. Before we dive into working with XML, let’s take a look at what some esting thing about XML is that its technical purpose is to facilitate the creation of other use XML, short for Extensible Markup Language, as their weapon of choice. The inter- As an alternative to databases for the storage and retrieval of data, developers can opt to XML fectly reasonable values to use in production. nTip  A maximum pool size of 100 and a minimum pool size of 5 are also default values in .NET and per- >>> conn = SqlConnection(connString) Max Pool Size=100;Min Pool Size=5;Pooling=true" Integrated Security=True;Initial Catalog=IronPython;User Instance=false;➥ >>> connString = "Data Source=ALAN-DEVPC\SQLEXPRESS;➥ Listing 7-16. A Sample Connection String Variable connections run smoothly. you need it. That will help prevent typographical errors and ensure that your database variable to hold the connection string (Listing 7-16) and that you reuse that variable when ated. For that reason, I highly recommend that, in your data access code, you create a different, then the connection string is not identical and a new connection will be cre- Chapter 7  ■ DATA MANIPULATION180 .(code to retrieve this feed (Listing 7-18 usatoday.com/usatoday-NewsTopStories. The first step is to write a little IronPython USA Today. At the time of this writing, the feed was available at http://rssfeeds. To see a real-world example of working with XML, let’s use the RSS feed from documents is a good example of metadata; the XML describes data points about other objects. nNote  Metadata is, quite literally, data that describes other data. In Listing 7-17, the XML that describes in a way that is very similar to XML. XML for its configuration data, but the basic construction of a web page can be expressed development, we’ll see that not only does the .NET framework whole-heartedly embrace of communication and storage. In fact, in the next chapter, in which we work with web during transmission. Regardless of which side you’re on, it remains an oft-used method consider it a tremendously verbose way to describe data and feel it is prone to errors the other side of the fence, there are those who view XML as a failed experiment. They lends itself to standards and can easily be validated because of its fairly strict rule set. On Proponents of XML say that it’s a terrific, well-structured way to convey metadata. It As with stored procedures, there is some debate over the pros and cons of XML. opened within other tags must be closed within that same tag. is valid, but is not. Tags Tags must not overlap, meaning that the tree hierarchy must be preserved. • Tags are case sensitive; cannot be properly closed by a tag of . • matching to close that element. well as opening and closing tags. If you open an element , there must be a Tags must be well formed. That means opening and closing brackets, < and >, as • that are important to follow. It’s actually rather simple. However, there are some rules to properly formatted XML 1933 How to Climb a Building King Kong Chapter 7  ■ DATA MANIPULATION 181 .((Listing 7-20 step. We should treat it as proper XML and use the navigation methods .NET provides methods to try and extract the data we want, but that would be a clunky, unnecessary navigating; it’s a rather flat, meaningless structure. We could use some string parsing Now we have the XML, but it is in string format, which isn’t terribly useful for USATODAY.com News - Top Stories Mon, 09 Mar 2009 22:07:00 GMT Copyright 2009, USATODAY.com, USA TODAY en-us USATODAY.com News - Top Stories (USA TODAY) http://www.usatoday.com/news/default.htm USATODAY.com News - Top Stories list xmlns:cf="http://www.microsoft.com/schemas/rss/core/2005" version="2.0"> >>> print result.ToString() Listing 7-19. Retrieving an RSS Feed (cont.) (Listing 7-19). sake of brevity; you will see significantly more output from printing the result variable can verify this for yourself. Note that I did not display the entire contents here, for the The variable called result now contains the entire XML markup of the RSS feed. You >>> result = reader.ReadToEnd() >>> reader = StreamReader(response.GetResponseStream()) >>> response = request.GetResponse() WebRequest.Create("http://rssfeeds.usatoday.com/usatoday-NewsTopStories") >>> request =➥ >>> from System.IO import * >>> from System.Net import * >>> from System import * >>> clr.AddReference("System.Net") >>> clr.AddReference("System") >>> import clr Listing 7-18. Retrieving an RSS Feed Chapter 7  ■ DATA MANIPULATION182 .successful in storing data to the drive Opening this file in Notepad shows the result in Figure 7-5, indicating that we were >>> xmlWriter.Close() >>> xmlWriter.WriteEndElement() >>> xmlWriter.WriteEndElement() >>> xmlWriter.WriteStartElement("cat") >>> xmlWriter.WriteStartElement("dog") >>> xmlWriter.WriteProcessingInstruction("xml", "version='1.0' encoding='UTF-8'") >>> xmlWriter = XmlTextWriter("C:\Python\IPXml.xml", Text.Encoding.UTF8) >>> from System.IO import * >>> from System.Xml import * >>> from System import * >>> clr.AddReference("System.Xml") >>> clr.AddReference("System") >>> import clr Listing 7-21. Writing an XML File to Disk (Listing 7-21)? data. It is only for efficient retrieval. What if we want to write an XML document to disk only reader that is very fast but that does not allow modification of the underlying The XmlTextReader is the XML equivalent of the SqlDataReader. It is a forward- (press Enter here) print xmlReader.NodeType.ToString() + " " + xmlReader.Name >>> while (xmlReader.Read()): XmlTextReader("http://rssfeeds.usatoday.com/usatoday-NewsTopStories") >>> xmlReader = ➥ >>> from System.Xml import * >>> clr.AddReference("System.Xml") Listing 7-20. Retrieving an RSS Feed (cont.) Chapter 7  ■ DATA MANIPULATION 183 .((Listing 7-22 (Figure 7-6). You’ll see that consuming this information in IronPython is extremely trivial I created a simple text file that contains some CSV data and saved it to my desktop person is going to expect the C to stand for comma. ter you want to parse out. But obviously if someone is expecting you to work with traditional CSV data, that nNote  Technically speaking, it doesn’t have to be a comma that separates values. It could be any charac- Each row of text is expressed as one line; the columns are separated by a comma. very similar to databases; that is, they can be thought of in terms of rows and columns. Comma-separated values (CSV) are a very simple way of storing data. In a way they are Comma-Separated Values Figure 7-5. The XML file was created successfully. Chapter 7  ■ DATA MANIPULATION184 dog lazy the over jumped fox brown quick the (press Enter here) print field for field in fields: fields = line.Split(',') >>> for line in lines: >>> lines = File.ReadAllLines("C:\Python\data.csv") >>> from System.IO import * >>> clr.AddReference("System") >>> import clr Listing 7-22. Reading and Displaying the Contents of a CSV File Figure 7-6. A simple CSV file Chapter 7  ■ DATA MANIPULATION 185 .your particular application the maintainability of the application, but never be afraid to bend the rules a bit to find what works best for for being moved elsewhere. You may find that moving it to another layer is a bad idea or does not improve down to maintainability. If code is obviously difficult to maintain in a particular location, it’s a likely candidate any place in stored procedures? These are not easy questions to answer. In my mind, the decision comes means retrieving and returning unnecessary data? What exactly constitutes business logic, and does it have do a minimalistic, straightforward data retrieval and leave all processing to the business layer, even if it nNote  This is where the data layer holy war grumbling begins. Should the application-stored procedures make an informed choice. some methods that use stored procedures and some that use inline SQL so that you can work to the business layer for appropriate processing. For comparison, we will examine of speaking to the data storage application and just quietly pass the end result of that their own individual tasks. The data layer should handle all the implementation details neighbors; you should be instructing them to mind their own business and to perform SQL Server. That information is none of its business. Think of your layers like noisy What this means is simply that the business logic shouldn’t know that we’re using revealing an unnecessary amount of implementation details to any calling layers. job when it facilitates easy, straightforward access to the underlying data storage without So what makes an effective data layer? In my mind, an effective data layer does its ing down if you leave them in your code. possible. But if you have infrequently hit small bits of code, the world won’t come crash- cedures or to go with inline SQL. Again, I would recommend stored procedures where we weighed the pros and cons about whether or not it was better to create stored pro- You also need to make the design decision of where to store your SQL code. Earlier exactly what platform someone is going to be working with. need to, particularly if you plan to build public components where you don’t know so I’m not building a data layer to accommodate that. However, you may find that you storage will change. I myself have no plans to shift from SQL Server on my local machine, ourselves to a particular one (MS SQL Server.) That’s okay if it’s not likely that our data will change. .NET has a variety of providers for different data sources, but so far we’ve tied The first concern is weighing the likelihood that the underlying data storage platform carefully. data layer doesn’t have to be a nightmare; in fact it can be downright simple, if you plan to the three-tier architecture is the data layer. Building an effective, easily maintained limited exclusively to the presentation and business layers, but the third component concerns and keep code divided into easily maintainable layers. Before now this has been As I’ve mentioned all along, it’s important when building applications that you separate Creating an Effective Data Layer Chapter 7  ■ DATA MANIPULATION186 .(Manager class to handle the connectivity aspects of data access (Listing 7-24 Now we can create an employeeData class in the Data folder that makes use of the data- Using the dataManager maintenance simpler). ever change in the future, we only have to change them in this one place, which will make we should have no problems with connection pooling (and if the settings here should By defining a GetConnection method that returns the same connection string each time, Note that this also fulfills the connection pooling requirements we had from earlier. return comm comm = SqlCommand(commandString, connection) "Gets a new SqlCommand object" def GetCommand(self, commandString, connection): return conn Max Pool Size=100;Min Pool Size=5;Pooling=True") Integrated Security=True;Initial Catalog=IronPython;User Instance=False;➥ conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ "Gets a new SqlConnection object" def GetConnection(self): "The data layer manager for our IronPython test application" class dataManager: from System.Data.SqlClient import * from System.Data import * from System import * clr.AddReference("System.Data") clr.AddReference("System") import clr Listing 7-23. The dataManager.py Class this class is given in Listing 7-23. add a new class file to this Data folder. We’ll call this file dataManager.py. The code for Once the project is created, right-click on the project and add a folder called Data. Next, project called IPData; make sure the Create directory for solution box is checked, as usual. Let’s try designing a data layer from scratch. We’ll create a new Windows Forms Chapter 7  ■ DATA MANIPULATION 187 .Server that the command text is the name of a stored procedure do you need to take that extra step don’t need to specify anything in particular for the CommandType parameter. Only if you’re instructing SQL nNote  The reverse is not true: if you supply a SQL statement (such as SELECT * FROM Employee), you will not be executed. procedure. If you have a stored procedure but do not perform this step, the procedure we explicitly told the command object that what we provided should be found as a stored name of a stored procedure) and the connection itself to the GetCommand method. Next, mand. Notice that we’re passing a SQL statement (either a series of commands or the we instantiated a dataManager class. Next we created a connection, followed by a com- created a variable called result that will hold the output of the method, if any. Next, So let’s look at what’s happening in the GetEmployeeNameByID method. First, we return result conn.Close() reader.Close() result = reader.GetString(0) if (reader.Read()): reader = comm.ExecuteReader() comm.Parameters.AddWithValue("@employeeID", employeeID) conn.Open() comm.CommandType = CommandType.StoredProcedure comm = dm.GetCommand("GetEmployeeNameByID", conn) conn = dm.GetConnection() dm = dataManager() result = String.Empty def GetEmployeeNameByID(self, employeeID): "The data class for the Employee table." class employeeData: from dataManager import * from System.Data.SqlClient import * from System.Data import * from System import * clr.AddReference("System.Data") clr.AddReference("System") import clr Listing 7-24. The employeeData.py Class Chapter 7  ■ DATA MANIPULATION188 .Double-click on the button and add the code in Listing 7-26 text box txtEmployeeID and the button btnSubmit. Set the button’s text to be Get Name. retrieve these fields dynamically. Add a text box and a button to the form. Name the Now we can modify the main form to accept some user input so that we can employeeID value here to ensure it’s an integer and not a string). ture multiple locations to make even basic changes (maybe you need to validate the hang additional code. If you choose a simpler, more direct route, you’ll have to restruc- requirement changes down the line, you already have hooks in your code on which to it’s not functioning as anything other than a pass-through at this point. But if that against this, but what I find is that having this type of setup gives you options. Sure, much other than act as a pass-through. That’s not a bad thing. I’ve heard people argue Admittedly, the GetEmployeeNameByID method in the business class doesn’t do return emData.GetEmployeeNameByID(employeeID) emData = employeeData() def GetEmployeeNameByID(self, employeeID): "The business class for the Employee table." class employeeBusiness: from employeeData import * Listing 7-25. The employeeBusiness.py Class class (Listing 7-25). Add a folder to the application called Business, and inside create an employeeBusiness Business As Usual could call it is the user interface directly, and we’ve established that as a no-no. oper to developer, no, we can’t. We can’t call it because the only place from which we string. Now we can call our employeeData class—can’t we? Technically, yes. But devel- appropriate value. We retrieved the result from the table and returned it via the result Next we added the necessary parameters to the command object and provided the Chapter 7  ■ DATA MANIPULATION 189 .(button again without entering a number instead of the employee name (Figure 7-8 All is not entirely well, however. Watch what happens when you click the Get Name Figure 7-7. The data layer is accurately returning our fields based on user input.    the button, the text will be swapped with the employee name (Figure 7-7). you run the application, you can enter an employee ID in the text box, and when you click You’ll also need to import the employeeBusiness class at the top of the form. Now if employee.GetEmployeeNameByID(self._txtEmployeeID.Text) self._txtEmployeeID.Text =➥ employee = employeeBusiness() def _btnSubmit_Click(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) Listing 7-26. The Submit Button with Code Wired In Chapter 7  ■ DATA MANIPULATION190 * from dataManager import from System.Data.SqlClient import * from System.Data import * from System import * clr.AddReference("System.Data") clr.AddReference("System") import clr Listing 7-27. Modifying the employeeData Class with Exception Handling exception-handling blocks (Listing 7-27, Figure 7-9). released in the event of an exception. So let’s modify that employeeData class with some less of what we do, one step has to be ensuring that connections get closed and resources We have several options for places to step in and do a little data validation. But, regard- Exceptional Handling! methods. Figure 7-8. Whoops! This is how connections leak. Notice that we’re not hitting the Close Chapter 7  ■ DATA MANIPULATION 191 .that doesn’t match that criterion will result in a blank value in the text box database will return a valid result only for employee IDs that exist in the table. Anything If you run the application again, feel free to try submitting anything you like. The of the connection. more than twice in this code anyway, because the final Close is wrapped in a check against the current state the connection has been closed; it just quietly passes along. Technically speaking it should never be called no penalty or risk in calling it multiple times. The Close method won’t throw an exception if it is called after nTip  What’s the deal with calling conn.Close() so many times? Well, the simple answer is that there’s return result conn.Close() if (conn.State == ConnectionState.Open): conn.Close() except: return result conn.Close() reader.Close() result = reader.GetString(0) if (reader.Read()): reader = comm.ExecuteReader() comm.Parameters.AddWithValue("@employeeID", employeeID) conn.Open() try: comm.CommandType = CommandType.StoredProcedure comm = dm.GetCommand("GetEmployeeNameByID", conn) conn = dm.GetConnection() dm = dataManager() result = String.Empty def GetEmployeeNameByID(self, employeeID): "The data class for the Employee table." class employeeData: Chapter 7  ■ DATA MANIPULATION192 (() comm.Parameters.AddWithValue("@birthDate", DateTime.Now.ToString comm.Parameters.AddWithValue("@name", employeeName) conn.Open() try: (1, @name, @birthDate, @hireDate)", conn) comm = dm.GetCommand("INSERT INTO Employee VALUES➥ conn = dm.GetConnection() dm = dataManager() def InsertNewEmployee(self, employeeName): Listing 7-28. Modifying the employeeData Class with Insertion Code fairly straightforward. In the employeeData class, add the method shown in Listing 7-28. ment for simplicity’s sake, but at this point adding one additional parameter should be Now let’s try adding a new employee to the table. We’re going to hard-code the depart- Inserting a New Employee parameters, handling the exception, cleaning up, and so on. in .NET. A simple check in an earlier layer would prevent the overhead of setting up the SQL connection and of the program up to the exceptions you’ve created. They’re fairly costly operations, despite being very fast nTip  Exceptions, by definition, are meant to handle exceptional, fringe cases. You shouldn’t leave operation Figure 7-9. With exception handling in place, the program no longer fails catastrophically.    Chapter 7  ■ DATA MANIPULATION 193 .up in always trying to make a whiz-bang UI, unless you plan to demo it to customers or your boss sparse UIs for the purpose of quickly testing (sometimes known as mocking up) an idea, so don’t get too caught test UI that you know will not see the light of day. There are times in development when you’ll be creating rather nTip  The UI is admittedly quite bland. The key here is the underlying structure, not spending a lot of time on a Figure 7-10. A set of UI controls to add an employee should be able to add an employee to the table. btnAdd, with the text label of Add Name (Figure 7-10). When you run the application, you adding an employee to the table. Name the text box txtNewEmployee and the button Now we can add an additional text box and button to the form. This set will be for emData.InsertNewEmployee(employeeName) emData = employeeData() def InsertNewEmployee(self, employeeName): Listing 7-29. Modifying the employeeBusiness Class with Insertion Code employeeBusiness class and add to it the method shown in Listing 7-29. Again, it’s best practice not to call this directly from the user interface, so open the conn.Close() if (conn.State == ConnectionState.Open): conn.Close() except: conn.Close() comm.ExecuteNonQuery() comm.Parameters.AddWithValue("@hireDate", DateTime.Now.ToString()) Chapter 7  ■ DATA MANIPULATION194 .and Listing 7-35 shows the entire employee data code file the entire business layer code file, Listing 7-34 shows the entire data manager code file, out. For your convenience, Listing 7-32 shows the entire UI code file, Listing 7-33 shows The final step is to create some UI controls and then to run the application to try it emData.DeleteEmployee(employeeID) emData = employeeData() def DeleteEmployee(self, employeeID): Listing 7-31. Calling the Deletion Code from the Business Layer Now we can call that code from the business layer (Listing 7-31). conn.Close() if (conn.State == ConnectionState.Open): conn.Close() except: conn.Close() comm.ExecuteNonQuery() comm.Parameters.AddWithValue("@employeeID", employeeName) conn.Open() try: employeeID = @employeeID", conn) comm = dm.GetCommand("DELETE FROM Employee WHERE➥ conn = dm.GetConnection() dm = dataManager() def DeleteEmployee(self, employeeID): Listing 7-30. Adding Deletion Code to the employeeBusiness Class Data class the code in Listing 7-30. Lastly, let’s wire some code to delete an employee from the table. Add to the employee- Deleting an Employee Chapter 7  ■ DATA MANIPULATION 195 self._txtEmployeeID.TabIndex = 0 self._txtEmployeeID.Size = System.Drawing.Size(260, 20) self._txtEmployeeID.Name = 'txtEmployeeID' self._txtEmployeeID.Location = System.Drawing.Point(12, 12) # # txtEmployeeID # self.SuspendLayout() self._btnDelete = System.Windows.Forms.Button() self._txtDelEmployee = System.Windows.Forms.TextBox() self._btnAdd = System.Windows.Forms.Button() self._txtNewEmployee = System.Windows.Forms.TextBox() self._btnSubmit = System.Windows.Forms.Button() self._txtEmployeeID = System.Windows.Forms.TextBox() def InitializeComponent(self): @returns(None) super(type(self), self).Dispose(disposing) def Dispose(self, disposing): @returns(None) @accepts(Self(), bool) self.InitializeComponent() def __init__(self): '_txtDelEmployee', '_btnDelete', '_btnSubmit'] __slots__ = ['_txtEmployeeID', '_txtNewEmployee', '_btnAdd',➥ System.Windows.Forms.Button""" type(_btnDelete) == System.Windows.Forms.Button, type(_btnSubmit) ==➥ System.Windows.Forms.Button, type(_txtDelEmployee) == System.Windows.Forms.TextBox,➥ (txtNewEmployee) == System.Windows.Forms.TextBox, type(_btnAdd) ==➥ """type(_txtEmployeeID) == System.Windows.Forms.TextBox, type➥ class Form1(System.Windows.Forms.Form): class IPData: # namespace from employeeBusiness import * from clr import * from System.Drawing import * from System.ComponentModel import * from System.Windows.Forms import * import System Listing 7-32. The Entire UI Code File Chapter 7  ■ DATA MANIPULATION196 ' self._btnDelete.Text = 'Delete Name self._btnDelete.TabIndex = 5 self._btnDelete.Size = System.Drawing.Size(93, 23) self._btnDelete.Name = 'btnDelete' self._btnDelete.Location = System.Drawing.Point(99, 148) # # btnDelete # self._txtDelEmployee.TabIndex = 4 self._txtDelEmployee.Size = System.Drawing.Size(260, 20) self._txtDelEmployee.Name = 'txtDelEmployee' self._txtDelEmployee.Location = System.Drawing.Point(12, 122) # # txtDelEmployee # self._btnAdd.Click += self._btnAdd_Click self._btnAdd.UseVisualStyleBackColor = True self._btnAdd.Text = 'Add Name' self._btnAdd.TabIndex = 3 self._btnAdd.Size = System.Drawing.Size(75, 23) self._btnAdd.Name = 'btnAdd' self._btnAdd.Location = System.Drawing.Point(108, 93) # # btnAdd # self._txtNewEmployee.TabIndex = 2 self._txtNewEmployee.Size = System.Drawing.Size(260, 20) self._txtNewEmployee.Name = 'txtNewEmployee' self._txtNewEmployee.Location = System.Drawing.Point(12, 67) # # txtNewEmployee # self._btnSubmit.Click += self._btnSubmit_Click self._btnSubmit.UseVisualStyleBackColor = True self._btnSubmit.Text = 'Get Name' self._btnSubmit.TabIndex = 1 self._btnSubmit.Size = System.Drawing.Size(75, 23) self._btnSubmit.Name = 'btnSubmit' self._btnSubmit.Location = System.Drawing.Point(108, 38) # # btnSubmit # Chapter 7  ■ DATA MANIPULATION 197 ( employee.DeleteEmployee(self._txtDelEmployee.Text employee = employeeBusiness() def _btnDelete_Click(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) employee.InsertNewEmployee(self._txtNewEmployee.Text) employee = employeeBusiness() def _btnAdd_Click(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) employee.GetEmployeeNameByID(self._txtEmployeeID.Text) self._txtEmployeeID.Text =➥ employee = employeeBusiness() def _btnSubmit_Click(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) pass def _Form1_Load(self, sender, e): @returns(None) @accepts(Self(), System.Object, System.EventArgs) self.PerformLayout() self.ResumeLayout(False) self.Load += self._Form1_Load self.Text = 'Form1' self.Name = 'Form1' self.Controls.Add(self._txtEmployeeID) self.Controls.Add(self._btnSubmit) self.Controls.Add(self._txtNewEmployee) self.Controls.Add(self._btnAdd) self.Controls.Add(self._txtDelEmployee) self.Controls.Add(self._btnDelete) self.ClientSize = System.Drawing.Size(284, 264) # # Form1 # self._btnDelete.Click += self._btnDelete_Click self._btnDelete.UseVisualStyleBackColor = True Chapter 7  ■ DATA MANIPULATION198 return comm comm = SqlCommand(commandString, connection) "Gets a new SqlCommand object" def GetCommand(self, commandString, connection): return conn Max Pool Size=100;Min Pool Size=5;Pooling=True") Integrated Security=True;Initial Catalog=IronPython;User Instance=False;➥ conn = SqlConnection("Data Source=ALAN-DEVPC\SQLEXPRESS;➥ "Gets a new SqlConnection object" def GetConnection(self): "The data layer manager for our IronPython test application" class dataManager: from System.Data.SqlClient import * from System.Data import * from System import * clr.AddReference("System.Data") clr.AddReference("System") import clr Listing 7-34. The Entire Data Manager Code File emData.DeleteEmployee(employeeID) emData = employeeData() def DeleteEmployee(self, employeeID): emData.InsertNewEmployee(employeeName) emData = employeeData() def InsertNewEmployee(self, employeeName): return emData.GetEmployeeNameByID(employeeID) emData = employeeData() def GetEmployeeNameByID(self, employeeID): "The business class for the Employee table." class employeeBusiness: from employeeData import * Listing 7-33. The Entire Business Layer Code File Chapter 7  ■ DATA MANIPULATION 199 () conn.Open try: @hireDate)", conn) comm = dm.GetCommand("INSERT INTO Employee VALUES (1, @name, @birthDate,➥ conn = dm.GetConnection() dm = dataManager() def InsertNewEmployee(self, employeeName): return result conn.Close() if (conn.State == ConnectionState.Open): conn.Close() except: return result conn.Close() reader.Close() result = reader.GetString(0) if (reader.Read()): reader = comm.ExecuteReader() comm.Parameters.AddWithValue("@employeeID", employeeID) conn.Open() try: comm.CommandType = CommandType.StoredProcedure comm = dm.GetCommand("GetEmployeeNameByID", conn) conn = dm.GetConnection() dm = dataManager() result = String.Empty def GetEmployeeNameByID(self, employeeID): "The data class for the Employee table." class employeeData: from dataManager import * from System.Data.SqlClient import * from System.Data import * from System import * clr.AddReference("System.Data") clr.AddReference("System") import clr Listing 7-35. The Entire Employee Data Code File Chapter 7  ■ DATA MANIPULATION200 .data code cleanly divided effective and built a simple IronPython data layer to keep our presentation, business, and we consumed a comma-separated value file. We also looked at what makes a data layer code, we’ve worked with XML via RSS feeds and created a document from scratch, and We’ve covered basic SQL operations and how to connect to SQL Server with IronPython Summary conn.Close() if (conn.State == ConnectionState.Open): conn.Close() except: conn.Close() comm.ExecuteNonQuery() comm.Parameters.AddWithValue("@employeeID", employeeName) conn.Open() try: WHERE employeeID = @employeeID", conn) comm = dm.GetCommand("DELETE FROM Employee➥ conn = dm.GetConnection() dm = dataManager() def DeleteEmployee(self, employeeID): conn.Close() if (conn.State == ConnectionState.Open): conn.Close() except: conn.Close() comm.ExecuteNonQuery() comm.Parameters.AddWithValue("@hireDate", DateTime.Now.ToString()) comm.Parameters.AddWithValue("@birthDate", DateTime.Now.ToString()) comm.Parameters.AddWithValue("@name", employeeName) Chapter 7  ■ DATA MANIPULATION 201 203 Chapter 8 Caught in a Web “I calculated the total time that humans have waited for web pages to load. It can‑ cels out all the productivity gains of the information age. Sometimes I think the web is a big plot to keep people like me away from normal society.” — Scott Adams It’s hard to argue that the invention and spread of the Internet has been anything other than monumental and certainly one of the most important technology developments ever, one that has allowed people to communicate, perform business transactions, and share information faster than anyone could have predicted at its inception. Entire economic cultures have been created and destroyed in the life cycle of the Internet. We’ll look at how IronPython lets us build web sites quickly and easily, and along the way you will see how everything we’ve learned so far applies as effectively to web development as to desktop development. .NET, IIS, and the Road to Today In the good old days (if you prefer to wear ­rose-­colored glasses) web pages used to be very static entities. They didn’t change much, they offered a terribly limited amount of interaction for the end user, and were essentially, well, ugly. Web pages then and now are created primarily using HTML, which stands for HyperText Markup Language. It’s visually very similar to the XML we explored in the last chapter, in that tags are used to describe elements on the page; the difference is that your browser will handle just about any garbage you throw at it, whereas strict XML is much less forgiving. HTML, without any styling applied to it, is also terribly plain (see ­Figure 8‑1, ­Listing 8‑1). .(across all pages that use that style sheet (see ­Figure 8‑2, Listings ­8-­2 and ­8-­3 allowing them to make ­site-­wide updates to single style sheet files and see the results development story. CSS allows web developers to separate their markup from their style, (Cascading Style Sheets), despite the problems associated with it, is significant in the web mentioned many times the importance of keeping code separate; the advent of CSS you to modify the style on each and every page that used it. Like a broken record, I’ve styles, but even a simple change to the size of a single font on your page would require ates a maintenance nightmare. Certainly you could create some unique and innovating same file. Although this does allow for some control over the appearance of a page, it cre- In those same good old days, styling had been applied to the markup directly, in the ­

This is the language that is used to construct web pages.

It's not terribly interesting.

This is the title of my page!

A sample HTML page ­Listing 8‑1. HTML That Creates the Page in ­Figure 8‑1 ­Figure 8‑1. A sample web page, created in HTML and viewed in Firefox Chapter 8  ■ CAUGHT IN A WEB204 < ­

This is the language that is used to construct web pages.

It's not terribly interesting.

This is the title of my page!

A sample HTML page ­Listing 8‑2. HTML Modified with a Link to the Style Sheet ­Figure 8‑2. The same web page with a small amount of CSS applied you try to browse via the filesystem. tions. It will allow you to work with web pages outside of .NET; otherwise you won’t see certain behaviors if I recommend visiting Microsoft’s IIS site at http://www.iis.net for downloads and installation instruc- nTip  If for some reason you do not have Internet Information Services (IIS) installed on your computer, Chapter 8  ■ CAUGHT IN A WEB 205 .that make maintenance a breeze to perform. They also have some unique benefits over traditional desktop applications some cases accomplish tasks that would be much more difficult for a desktop application create powerful and advanced web applications that behave like desktop ones, and in software. The .NET framework, in combination with IronPython and IIS, can be used to ­server-­side and ­client-­side code and effectively blur the line between desktop and web ity that is leaps and bounds above the Internet of just a few years ago. They are a mix of Facebook are just a few examples of advanced web applications that provide functional- feel more like desktop applications. Google Mail, MapQuest, YouTube, MySpace, and In recent years, there has been a significant push to make web applications look and ­not-so-­distant cousins. your desktop knowledge out the window; desktop and web development have become Microsoft has applied their .NET technology to their IIS server, you don’t have to throw work with IIS, which has resulted in quite the powerful entity for developers. Because IIS. Consequently, Microsoft has made a significant effort to integrate the .NET frame- soft’s flagship web server technology is the Internet Information Services platform, or and the server renders that content and sends the response back to the user. Micro- browser makes a specially formed request to the server for a specific piece of content, Web pages are delivered to a user after being served up by web servers; the user’s represent itself as another. It’s indeed a tangled web out there. unique to your system configuration. This is a direct result of the browser wars, when one browser would you’ll see that the ­user-­agent is reported as “Mozilla/4.0 (compatible; MSIE 7.0;)” along with some other data browser is sending to web servers. A perfect example is Internet Explorer 7; if you browse this page with IE7, linger, then head to ­http://www.htaccesstools.com/browser-­check/ and see what information your trenches” problems about which people have written entire books. If you need proof that the effects still These are not fringe problems you’ll never encounter as a developer. These are everyday, “in the CSS that displays in IE but not in Firefox or Opera, and so on. precisely, Internet Explorer (IE) has ­Microsoft-­supported conditional markup statements so that you can write the casualties of this war. As of this writing, no browser on the market currently supports the CSS standard code were produced that worked in one but not in the other. To boil down 10+ years of history, CSS is one of a browser war. Each provider tried to offer features the other didn’t, and as a result proprietary methods and nNote  What problems exist with CSS? For the longest time, Microsoft and Netscape were engaged in p { ­margin-­left: 35px; border: 1px solid #000; width: 380px; } h2 { float: right; ­font-­family: verdana; } h1 { ­text-­decoration: underline; ­font-­style: italic; } body { ­font-­family: calibri; } /* styles.css */ ­Listing 8‑3. The CSS for the Web Page We Created Chapter 8  ■ CAUGHT IN A WEB206 into web development, but with some additional tidbits added on. Probably the best way have .py source code files, and so on. Most of your knowledge transfers smoothly right applications. You will still have a solution that can contain multiple projects, you will still When creating web sites in Visual Studio, the process is very similar to creating Forms style of Microsoft web page called ASP, or Active Server Pages. running behind them generally have the extension of .aspx, which is a nod to the older .php, .asp, .aspx, or a variety of other extensions. In the .NET world, pages with .NET code Web pages have generally had the file extension of .htm or .html; you may also have seen .ASPX and You this problem was a big motivator for the creation of .NET assemblies. can browse to is what you’ve provided them, so everyone should be on the same page at all times. In fact, things get complicated. Web development can skirt around that issue entirely, because the only thing users functionality for your end users once things are out of sync. It can be a truly awful situation to diagnose when incompatible with one another, resulting in error messages, unpredictable behavior, or a complete lack of dreaded DLL Hell. To the lucky ones who aren’t familiar with this concept, it happens when libraries are nNote  If you count yourself among the “grizzled ancient” developers, no doubt you’ve encountered the should stand out as a big deal. immediately visible to the end user and don’t require additional work on their part. This if I make a change to the code or markup of files in my web application, those changes are taken for end users to benefit from them. This is where web development can truly shine: at Microsoft want to fix an issue or provide some new feature, additional steps must be happens in the background for you. The issue still remains that when the developers the files you already have. Nowadays things are pretty advanced and this all generally you to benefit from those patches is to get the code on your machine and to apply it to developers are patching code for the Windows operating system, but the only way for Automatic Updates that pop up from time to time. What’s happening is that Microsoft If you’ve spent any time in the Windows environment, no doubt you’ve seen the the JavaScript language. the browser war effects that plague CSS, namely, different browsers supporting custom methods or pieces of ­well-­known ­client-­side code. JavaScript is executed by the user’s browser and actually suffers from some of ­Client-­side code is the opposite; it is code that is executed entirely by the client. JavaScript is the most write will be executed on the server by IIS; the results of that execution are what is sent back to the user. nNote  Server-side code is code that is executed entirely on the server itself. The IronPython code you Chapter 8  ■ CAUGHT IN A WEB 207 ­Figure 8‑3. Opening a web site in Visual Studio ­Figure 8‑3). you unzipped the project files and open the ­“ironpython-webform-­sample” site (see Visual Studio, and then click File, Open, and Web Site. Navigate to the folder where .NET WebForms IronPython Sample” to your desktop or any convenient location. Open ProjectReleases.aspx?ReleaseId=17613. You’ll want to download and unzip the “ASP You can download the sample project from http://aspnet.codeplex.com/Release/ IronPython. sample IronPython for ASP .NET project Microsoft provides; it’s a fine starting point for web development in But for the time being we’ll have to take a few steps manually. For the sake of simplicity, we’ll use the in Visual Studio 2008. The CodePlex site indicates that these templates are forthcoming with future releases. nNote  At the time of my writing this, there was no set of project templates for IronPython that can be used from Microsoft and build from there. to jump into this is at the deep end, so the first thing we’ll do is acquire the sample project Chapter 8  ■ CAUGHT IN A WEB208 .aspx.py .NET languages) and (2) that the IronPython code for this file can be found in Default. code in this page should be processed as IronPython (as opposed to C#, VB .NET, or other server to know about and operate on. It tells the server two things: (1) that any ­server-­side will you see it physically on the page. This is a bit of “inline” code that is solely for the You won’t see this line of text in the source code of the page once it’s rendered, nor <%@ Page Language="IronPython" CodeFile="Default.aspx.py" %> ­Listing 8‑4. The Page Directive from Default.aspx scenes. The exact text of the Page directive at the top of the file is as shown in ­Listing 8‑4. These lines are specific to .NET and reveal a bit about what’s happening behind the beginning of the file and the line that appears in between the
tags. The two things I’d like to point out in particular are the <%@ Page … %> line at the ­Figure 8‑4. The project as it initially appears, with Default.aspx opened things do you notice that are different? in this file to the markup in the HTML file we created earlier in the chapter. What sorts of are already present. ­Double-­click on Default.aspx to open the file. Compare the markup ­Figure 8‑4. In the Solution Explorer on the right, notice that a variety of folders and files After opening the project, you’ll be presented with a screen similar to the one in Chapter 8  ■ CAUGHT IN A WEB 209 .(debugging in the web.config file. The output of the page should look like ­Figure 8‑5 debugging is not enabled, feel free to select the option that allows Visual Studio to enable trip people up at the outset. Press F5 to run the web site. If you see a notification that Now is a fine time to introduce you to an aspect of .NET web development that can page with an ID of foo. a control with its ID property set to foo, you cannot have another control on the same tifies the control on the page, to avoid conflict with any other control. Thus, if you have We’ve also defined an ID property and set it to "messageLiteral". This uniquely iden- will not even run. must include runat="server" in the tag. If this property is not set correctly, then the code hooks for style information and other features. Note that for this to work properly, you from an HTML tag; it provides additional functionality behind the scenes, in addition to First, it’s prefixed with asp. This denotes an ASP .NET server control, which is distinct The tag looks something like a regular HTML tag, but with some extra information. ­Listing 8‑5. The Markup That Declares the Literal Control parameters based on the type of object that it is, which we’ll cover next. The line with the Literal control is shown in ­Listing 8‑5. This has some special syntax but I would highly recommend the ­code-­behind model wherever possible. rate .py file on the server. You can mix and match, and in some cases this is an appropriate methodology, versus inline, I am referring to the act of writing your IronPython code directly in the page versus in a sepa- the language, and so on. Without that, you can’t have a functional .aspx page. When I refer to ­code-­behind Every .aspx page will have at least one line in <% %> brackets, and that line defines the Page element, ­code-­behind style. It’s also worth noting that Microsoft chose to structure their example this way as well. separate. Be aware that there is another way, but I wouldn’t consider it the better way and I’ll be using the Personally, I hate this method of development for most situations. I prefer to keep my markup and my code are stored in the same file, with the IronPython code stored in between specially structured "); \x6F\x20\x69\x6E\x73\x65\x63\x75\x72\x65\x20\x77\x6F\x72\x6C\x64\x21\"];➥ [\"\x48\x65\x6C\x6C➥ Response.Write("")); \"\x48\x65\x6C\x6C\x6F\x20\x69\x6E\x73\x65\x63\x75\x72\x65\x20\x77\x6F\x72\x6C➥ \"text/javascript\">var _0x3774=[➥ Response.Write(Server.HtmlEncode("