PUBLISHED BY Microsoft Press A Division of Microsoft Corporation One Microsoft Way Redmond, Washington 98052-6399 Copyright © 2007 by GrandMasters, LLC and Mark Blomsma All rights reserved. No part of the contents of this book may be reproduced or transmitted in any form or by any means without the written permission of the publisher. Library of Congress Control Number 2006932074 ISBN-13: 978-0-7356-2332-3 ISBN-10: 0-7356-2332-5 Printed and bound in the United States of America. 1 2 3 4 5 6 7 8 9 QWT 1 0 9 8 7 6 Distributed in Canada by H.B. Fenn and Company Ltd. A CIP catalogue record for this book is available from the British Library. Microsoft Press books are available through booksellers and distributors worldwide. For further information about international editions, contact your local Microsoft Corporation office or contact Microsoft Press International directly at fax (425) 936-7329. Visit our Web site at www.microsoft.com/mspress. Send comments to
[email protected]. Microsoft, Microsoft Press, Active Directory, BackOffice, BizTalk, Excel, IntelliSense, Internet Explorer, MSDN, MSN, Visual Basic, Visual C#, Visual J#, Visual SourceSafe, Visual Studio, Windows, Windows Mobile, Windows NT, Windows Server, and WinFX are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries. Other product and company names mentioned herein may be the trademarks of their respective owners. The example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious. No association with any real company, organization, product, domain name, e-mail address, logo, person, place, or event is intended or should be inferred. This book expresses the author’s views and opinions. The information contained in this book is provided without any express, statutory, or implied warranties. Neither the authors, Microsoft Corporation, nor its resellers, or distributors will be held liable for any damages caused or alleged to be caused either directly or indirectly by this book. Acquisitions Editor: Ken Jones Project Editor: Maureen Williams Zimmerman Editorial Production: nSight, Inc. Copy Editor: Teresa Horton Technical Reviewer: Jim Fuchs Indexer: Lucie Haskins Body Part No. X12-48725
Dedication
For my best friend —Sara Morgan
To my mom and dad, Carole and Walter. And to the other two most important people in my life, Kim and Sara. —Bill Ryan
For Patricia, Nashly, Abbi, and London —Shannon Horn
For my angel, Belina; and my family, Greta, Tim, and Jerri, who have always been there for me. I love you all so much. —Murray Gordon
About the Authors Sara Morgan Sara Morgan, MCSD, MCDBA, is an independent author and consultant located in Baton Rouge, Louisi ana. She specializes in developing leading-edge Webbased applications utilizing Microsoft technologies. Since graduating from Louisiana State University in 1993 with a degree in quantitative business analysis, she has been developing software for a variety of indus tries, including a not-for-profit hospital, a financial com pany offering mortgages, a major retailer, a software company that writes legislative software, and an Application Service Provider. In 1998, she wrote an article on stress-testing Web sites, which was featured on the front cover of MSDN Magazine, formerly named Microsoft Interactive Developer (MIND). Since then, she has written articles featured in Enterprise Development, .NET Development, Visual Studio Magazine and DevX.com. To reference the articles Sara Morgan has written, refer to http://www.custsolutions.net. This Web site also fea tures her latest research efforts and is a good resource for readers who want to learn more about enhanced computing.
Bill Ryan Bill Ryan is a consultant with Magenic Technologies in its Atlanta office. Bill is a Microsoft MVP in the Windows Embedded category. Bill is also the coauthor of Pro fessional ADO.NET 2 and Professional WinFX Beta, both by Wrox Press. Bill is a fre quent speaker at events such as Microsoft Code Camps, Speech Server Days, and .NET User’s Groups. He also runs two .NET-focused Web sites, www.devbuzz.com and www.knowdotnet.com.
v
vi
About the Authors
Mark Blomsma Mark Blomsma is a software architect and owner of his own business, Develop-One (www.develop-one.com). A frequent speaker and writer of articles for a variety of magazines, he specializes in Microsoft .NET technology, enterprise application development, application integra tion, and software renovation. Visit his blog at http:// blog.develop-one.com.
Shannon Horn Shannon speaks and trains for companies such as Microsoft, AppDev, Carrig Learn ing, WestLake Internet Training, LearnIt, Accelebrate, Webucator, and Hands on Technology Transfer and has been a featured speaker on training videos with LearnKey. He has also worked with large corporate clients including Microsoft, Universal Studios, MGM Studios, Monster.com/FlipDog.com, Intel, Polygram Pictures, Pruden tial, Micro Accounting Systems, Sky Harbor International Airport, and Evans Newton, Inc., on projects utilizing Microsoft technologies such as Visual FoxPro, Visual Stu dio.NET, C#, ADO.NET, and ASP.NET. Shannon is currently pursuing his seconddegree black belt in tae kwon do (Kukkiwon / WTF 2nd Dan) through Grand Master Won Ki Hong and Master Choi (http://www.masterhong.com). Shannon has been playing electric bass guitar since he was young and plays weekly in multiple bands in the large church he attends in Peoria, Arizona (http://www.lighthouseaz.com). Physical fitness is a major part of Shannon’s life, and he lives for his kids! You can find out more about Shannon by visiting http://shannonhorn.spaces.live.com/.
Olof Nyström Olof is a senior software developer and Microsoft trainer at RBAM AB, a professional software company located in Sweden. Olof is a Microsoft Certified Trainer and spe cializes in distributed .NET development. He is a frequent speaker at many local sem inars in Sweden and currently does most of his teaching for Sweden’s largest CPLS.
About the Authors
vii
Murray Gordon Murray Gordon is the Director of Technology at Cambar
Solutions (www.cambarsolutions.com), a consultation ser
vices and custom supply chain software company to many
of the world’s largest distribution companies. Murray
actively consults on legacy modernization to the world’s
leading distribution companies. His vision is to transform
rigid, heavily customized IT environments into flexible,
adaptive environments that are competitive with contem
porary enterprise solutions. This vision embraces a
respect for and the preservation of his client’s differentiat
ing competitive practices, using phased transformation
roadmaps that are governed by their business priorities.
Murray has led consulting engagements for development, implementation, operation, and support of mission-critical systems in food, healthcare, petroleum, finance, man ufacturing, and retail sales. He has been an active contributor to the evolution of dis tributed computing architectures since 1994. His most recent efforts have been in Legacy Host Integration, Service Oriented Architecture, User Interface Modernization to Smart Clients and Thin Clients, E-Commerce (B2B and B2C), Internet/Intranet Portals and Reporting Systems, Warehouse Management Systems, Order Manage ment Systems and Data Warehousing, and Transaction Processing Solutions. Murray is a frequent speaker at events such as Microsoft Code Camps and .NET User’s Groups. He is the President of the Greater Charleston .NET User’s Group (www.gcnug.org) in Charleston, South Carolina.
Scott Allen Scott Allen has 14 years of experience in delivering commercial software on platforms as diverse as 8-bit embedded firmware to highly scalable Web applications. Scott is a Microsoft MVP and currently focuses on development with the .NET Framework. He has published books with Wrox Press and Packt Publishing, and he has published articles with MSDN Magazine, Dr. Dobb’s Journal, and Windows Developer Maga zine. Scott speaks at national conferences and regional user groups. Scott is a founder of OdeToCode.com and writes regularly for his blog, which can be found at http:// www.OdeToCode.com/blogs/scott.
Contents at a Glance
1
Creating an XML Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2
Extending XML Web Services with SOAP Formatting,
Custom Headers, and Extensions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3
Configuring and Publishing XML Web Services . . . . . . . . . . . . . . . . . . . . 91
4
Creating a Remoting Server Application . . . . . . . . . . . . . . . . . . . . . . . . . 125
5
Creating a Remoting Client Application. . . . . . . . . . . . . . . . . . . . . . . . . . 167
6
Debugging and Deploying Remote Applications . . . . . . . . . . . . . . . . . . 219
7
Method Invocations and Event Management
with .NET Web Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
8
Method Invocations and Event Management
with .NET Remoting. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
9
Web Services Enhancements 3.0 in Client and
Server Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
10
WSE Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
11
Messaging and Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
12
Creating Serviced Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
13
Serviced Component Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 567
ix
Table of Contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxvii Hardware Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxvii Software Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxviii Installing SQL Server 2005 Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxx Installing the Adventure Works Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxi Using the CD and DVD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxi How to Install the Practice Tests. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxii How to Use the Practice Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxii Microsoft Certified Professional Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxiv Technical Support. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxiv Foundation Edition Software Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxv
1
Creating an XML Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1
Lesson 1: How to Create a Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .3
What Is a Web Service? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .3
How to Create a New Web Service Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .4
The WebMethods Framework. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .7
Lab 1: Creating a Web Service Project Using Visual Studio . . . . . . . . . . . . . . . . .11
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .13
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .13
Lesson 2: How to Create a Web Service Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .15
Applying the WebMethod Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .15
Configuring a Web Service Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .16
Creating a One-Way Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .17
Lab 2: Creating a Public Web Service Method . . . . . . . . . . . . . . . . . . . . . . . . . . .20
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .22
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .23
What do you think of this book? We want to hear from you!
Microsoft is interested in hearing your feedback about this publication so we can continually improve our books and learning resources for you. To participate in a brief online survey, please visit: www.microsoft.com/learning/booksurvey/
xi
xii
Contents
Lesson 3: How to Test and Consume a Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Testing Your Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Consuming a Web Service. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Lab 3: Testing and Consuming a Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Chapter Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Chapter Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Case Scenarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Case Scenario 1: Deciding to Use Web Services . . . . . . . . . . . . . . . . . . . . . . . . . 34
Case Scenario 2: Testing your Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2
Extending XML Web Services with SOAP Formatting,
Custom Headers, and Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Lesson 1: How to Configure SOAP Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Configuring SOAP Messages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Customizing SOAP Messages with XML Serialization . . . . . . . . . . . . . . . . . . . . . 49
Lab 1: How to Configure SOAP Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Lesson 2: How to Implement SOAP Headers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
What Is a SOAP Header? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Adding a Custom SOAP Header Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Building a Client that Processes the SOAP Header . . . . . . . . . . . . . . . . . . . . . . . 60
How to Handle Unknown SOAP Headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Lab 2: How to Implement a Custom SOAP Header. . . . . . . . . . . . . . . . . . . . . . . 63
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Lesson 3: How to Implement SOAP Extensions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
What Is a SOAP Extension? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Contents
xiii
Creating a Custom SOAP Extension Class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .74
Configuring the SOAP Extension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .78
Lab 3: How to Implement a Custom SOAP Extension . . . . . . . . . . . . . . . . . . . . .80
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85
Chapter Review. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .87
Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .87
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .88
Case Scenarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .88
Case Scenario 1: Creating a Custom SOAP Header . . . . . . . . . . . . . . . . . . . . . . .88
Case Scenario 2: Creating a SOAP Extension . . . . . . . . . . . . . . . . . . . . . . . . . . . .88
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .89
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .89
3
Configuring and Publishing XML Web Services . . . . . . . . . . . . . . . . . . . . 91
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .92
Lesson 1: How to Manage State in a Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93
Using the Application Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93
Using the Session Object. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .95
Handling Session State on the Client with Cookies . . . . . . . . . . . . . . . . . . . . . . .96
Performance Implications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .99
Lab 1: Using the Session Object to Maintain State in a Web Service . . . . . . . . .99
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Lesson 2: How to Configure a Web Service Application. . . . . . . . . . . . . . . . . . . . . . 104
Using a Configuration File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Using the Machine.config File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Configuring the Session State Mode. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Lab 2: Configuring a Web Service Application . . . . . . . . . . . . . . . . . . . . . . . . . 109
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Lesson 3: How to Deploy and Publish a Web Service . . . . . . . . . . . . . . . . . . . . . . . . 112
Publishing Your Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Using a Static Discovery File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
xiv
Contents
Using a Dynamic Discovery File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Deploying a Web Service Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Lab 3: Deploying a Web Service Using a Web Setup Project . . . . . . . . . . . . . 117
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Chapter Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Chapter Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Case Scenarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Case Scenario 1: Creating a Session Variable to Store the
Selected Vendor ID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Case Scenario 2: Creating a Custom Service Help Page for
All Your Web Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Case Scenario 3: Creating Setup Projects to Deploy Your
Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
4
Creating a Remoting Server Application. . . . . . . . . . . . . . . . . . . . . . . . . 125
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Lesson 1: Remoting Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
What Is Remoting? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Types of Remote Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Hosting a Remote Server Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Activating Your Remote Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
Selecting a Communication Channel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Marshal-by-Value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Creating a Remote Server Object. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Lab 1: Creating a Remote Server Application. . . . . . . . . . . . . . . . . . . . . . . . . . . 136
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
Lesson 2: How to Configure a Server Application Programmatically . . . . . . . . . . . 141
Specifying the Name of the Server Application. . . . . . . . . . . . . . . . . . . . . . . . . 142
Registering the Remote Object. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Contents
xv
Configuring Versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
Registering a Communication Channel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Lab 2: How to Configure a Remote Server Application
Programmatically . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Lesson 3: How to Configure a Server Application with a Configuration File. . . . . 152
Creating a Configuration File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
The Application Element . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Configuring Versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Configuring Formatting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Configuration Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Lab 3: How to Configure a Server Application Using a
Configuration File. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Chapter Review. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
Case Scenarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Case Scenario 1: Suggesting a Distributed Technology . . . . . . . . . . . . . . . . . 165
Case Scenario 2: Using a Configuration File to Configure
Your Remoting Application. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
5
Creating a Remoting Client Application. . . . . . . . . . . . . . . . . . . . . . . . . . 167
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
Lesson 1: Creating a Client Application to Access a Remote Object. . . . . . . . . . . . 169
How to Create a Client Instance of a Remote Object . . . . . . . . . . . . . . . . . . . . 169
How to Configure a Client Application Programmatically . . . . . . . . . . . . . . . 171
Lab 1: Creating a Client Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
xvi
Contents
Lesson 2: How to Configure a Client Application Using a Configuration File . . . . 193
Configuring the Communication Channel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Configuring the Activation Mode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Lab 2: Creating a Client Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
Lesson 3: How to Access a Remote Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Lab 3: How to Access a Remote Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Chapter Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Chapter Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
Case Scenarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
Case Scenario 1: Creating a Secure Globally Distributed Application . . . . . . 215
Case Scenario 2: Improving the Remoting Client Using Interfaces . . . . . . . . 216
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
6
Debugging and Deploying Remote Applications . . . . . . . . . . . . . . . . . 219
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Lesson 1: How to Deploy a Remoting Application . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
.NET Remoting Deployment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
Hosting Remote Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
Deploying Remote Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
Creating a Web Setup Project. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
Lab 1: Creating and Installing a Web Setup Project . . . . . . . . . . . . . . . . . . . . . 235
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Lesson 2: How to Debug a Remoting Application . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
Visual Studio 2005 Debugger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
The RemotingException Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
Using Performance Counters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
.NET Remoting Tracking Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
Contents
xvii
Lab 2: Debugging a Remoting Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
Lesson 3: How to Manage the Lifetime of Remote Objects . . . . . . . . . . . . . . . . . . . 255
Lab 3: Managing the Lifetime of Remote Objects. . . . . . . . . . . . . . . . . . . . . . . 261
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Chapter Review. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Case Scenarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Case Scenario 1: Tracking and Improving the Performance
of a Distributed Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Case Scenario 2: Simplifying Remote Object Deployment . . . . . . . . . . . . . . . 265
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
7
Method Invocations and Event Management
with .NET Web Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
Lesson 1: Calling Web Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Calling a Web Method Synchronously . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Calling a Web Method Asynchronously . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Lab 1: Calling a Web Method. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
Lesson 2: Polling Web Methods for Completion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
Polling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
Begin and IAsyncResult . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
Lab 2: Calling a Web Method Asynchronously . . . . . . . . . . . . . . . . . . . . . . . . . 290
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
Chapter Review. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
xviii
Contents
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
Case Scenarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
Case Scenario 1: Exposing Functionality Through .NET Web Services . . . . . 296
Case Scenario 2: Refining the Use of Web Services . . . . . . . . . . . . . . . . . . . . . 298
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
8
Method Invocations and Event Management
with .NET Remoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
Lesson 1: Calling Remoting Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
Calling a Remoting Method Synchronously . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
Stateless Invocation Using One-Way Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
Calling a Remoting Method Asynchronously . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
Lab 1: Invoking a Remoting Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
Lesson 2: Callbacks and Remoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
Polling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
Determining Whether an Asynchronous Call Has Finished . . . . . . . . . . . . . . . 320
Polling for Completion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
Polling Using a Callback Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
Lab 2: Calling a Remoting Method Asynchronously . . . . . . . . . . . . . . . . . . . . . 327
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334
Lesson 3: Implementing and Responding to Events . . . . . . . . . . . . . . . . . . . . . . . . . 336
Handled Remote Events. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
Lab 3: Handle Events from a Remote Object . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
Chapter Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
Chapter Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
Contents
xix
Case Scenarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348
Case Scenario 1: Building Robust, Scalable Enterprise Applications . . . . . . . 348
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
9
Web Services Enhancements 3.0 in Client and
Server Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352
Lesson 1: Enabling and Referencing WSE 3.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354
Adding References to WSE Assemblies in Client Applications . . . . . . . . . . . . 356
Inheriting from the WebServicesClientProtocol Class . . . . . . . . . . . . . . . . . . . . 358
Editing the Proxy Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
Lab 1: Adding WSE 3.0 to a Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
Lesson 2: Using soapExtensionTypes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
Configuration File Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366
Adding a Child Element with the Element . . . . . . . . . . . . . . . . . . . . . . 369
Specifying a Priority . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
Specifying a Group. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
Lab 2: Creating a Class That Inherits from WebServicesClientProtocol. . . . . . 370
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
Lesson 3: Using Digital Signatures to Ensure Message Integrity . . . . . . . . . . . . . . . 379
Digital Signature Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
X.509 Certificates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
Kerberos Token . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
UserNameToken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
Lab 3: Creating a Policy for Digital Signatures. . . . . . . . . . . . . . . . . . . . . . . . . . 383
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
Chapter Review. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
xx
Contents
Case Scenarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391
Case Scenario 1: Where Does WSE Fit into My Application
Development Strategy? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391
Case Scenario 2: Refining the Process?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
10
WSE Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
Lesson 1: Web Service Policy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
Creating a Policy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
Using a Policy File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
Lab 1: Configuring Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
Lesson 2: Custom Policies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
Basic Encryption . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
Creating a Custom Policy Assertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
Encryption with a Security Token . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
Decryption . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424
Using a Security Token Issuing Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424
Lab 2: Signing and Encrypting a Message Exchange
Using Custom Policy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 439
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440
Lesson 3: Message Filters. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441
Message Filters Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 442
Implementation and Migration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443
Lab 3: Creating a Custom Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448
Chapter Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 450
Chapter Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 450
Contents
xxi
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452
Case Scenario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452
Case Scenario 1: How Secure Is Secure Enough? . . . . . . . . . . . . . . . . . . . . . . . 452
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454
11
Messaging and Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
Lesson 1: How to Configure WSE Messaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457
WSE Soap Messaging. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457
Selecting a Communication Protocol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 458
Implementing One-Way Messaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 459
Implementing Two-Way (Bidirectional) Messaging . . . . . . . . . . . . . . . . . . . . . 463
Sending Attachments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Lab 1: Data Transfer with MTOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479
Lesson 2: How to Create a WSE Router . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
What Is a WSE Router? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
Creating a WSE Router Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482
Configuring the WSE Router Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 484
Configuring a Referral Cache for Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 486
Lab 2: Creating a WSE Router. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 492
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 492
Lesson 3: How to Add and Verify Security Credentials . . . . . . . . . . . . . . . . . . . . . . . 494
Why Add Security Credentials? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 494
Adding Security Credentials Using an X.509 Certificate . . . . . . . . . . . . . . . . . 494
Other Security Tokens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 498
Verifying the Security Credential . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499
Lab 3: Adding Security to Your WSE Router. . . . . . . . . . . . . . . . . . . . . . . . . . . . 500
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503
Chapter Review. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
xxii
Contents
Chapter Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506
Case Scenarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506
Case Scenario 1: Performing a Server Upgrade . . . . . . . . . . . . . . . . . . . . . . . . 506
Case Scenario 2: Selecting a Security Method for Your WSE Router . . . . . . . 506
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507
Implement WSE SOAP Messaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507
Route SOAP Messages by using a WSE Router . . . . . . . . . . . . . . . . . . . . . . . . . 507
Add and Verify Security Credentials. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508
12
Creating Serviced Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
Lesson 1: Serviced Component Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511
COM+ Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
Creating a Serviced Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517
Register a Serviced Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 521
Adding Attributes to a Serviced Component . . . . . . . . . . . . . . . . . . . . . . . . . . . 525
Implementing Security on a Serviced Component . . . . . . . . . . . . . . . . . . . . . . 537
Services Without Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 542
System.Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 544
Lab 1: Building a Simple Serviced Component . . . . . . . . . . . . . . . . . . . . . . . . . 546
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 552
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553
Lesson 2: Consuming the Serviced Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555
Referencing the Assembly that Contains the Component . . . . . . . . . . . . . . . . 555
Declaring and Instantiating the Component . . . . . . . . . . . . . . . . . . . . . . . . . . . 556
Access Properties, Methods, and Events of the Component . . . . . . . . . . . . . . 557
Lab 2: Using the Serviced Component in a Separate Application . . . . . . . . . 558
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559
Chapter Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561
Chapter Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561
Contents
xxiii
Case Scenario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 562
Case Scenario 1: Reliability Through Transactions . . . . . . . . . . . . . . . . . . . . . . 562
Case Scenario 2: Optimizing Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
Create, Configure, and Access a Serviced Component . . . . . . . . . . . . . . . . . . 563
Implement Security in a Serviced Component . . . . . . . . . . . . . . . . . . . . . . . . . 564
Using Automatic Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 564
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 565
13
Serviced Component Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 567
Before You Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 568
Lesson 1: Message Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 570
Message Queuing Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 570
Setting Up a Message Queue. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 572
Sending a Message to a Message Queue. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 577
Reading a Message from a Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586
Deleting a Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588
Sending and Receiving a Message Asynchronously . . . . . . . . . . . . . . . . . . . . . 588
Sending and Receiving a Message Synchronously . . . . . . . . . . . . . . . . . . . . . . 588
Listening to a Queue Asynchronously. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589
Sending Objects Using Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589
Sending a Message Using Queued Components . . . . . . . . . . . . . . . . . . . . . . . 595
Setting Permissions on a Message Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597
Peeking and Enumerating Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 602
Correlating Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606
Rules and Triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 610
Lab 1: Creating a Message and Sending It to a Queue . . . . . . . . . . . . . . . . . . 610
Lesson Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 617
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618
Lesson 2: Securing Messaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620
Signing a Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620
Validating a Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 627
Encrypting a Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631
xxiv
Contents
Lab 2: Encrypting a Message and Verifying Its Authenticity
After Transmission. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 634
Lesson Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 637
Lesson Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 638
Chapter Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 640
Chapter Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 640
Key Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 640
Case Scenarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 641
Case Scenario 1: Online Shop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 641
Case Scenario 2: Batches. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 642
Suggested Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 642
Create, Delete, and Set Permissions on a Message Queue. . . . . . . . . . . . . . . . 642
Send and Receive Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643
Sign and Encrypt Messages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 644
Take a Practice Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 644
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 645
Glossary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 687
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 693
What do you think of this book? We want to hear from you!
Microsoft is interested in hearing your feedback about this publication so we can continually improve our books and learning resources for you. To participate in a brief online survey, please visit: www.microsoft.com/learning/booksurvey/
Acknowledgments
The authors of this book would like to gratefully acknowledge Lisa Kreissler from GrandMasters LLC, Maureen Zimmerman from Microsoft, and Ken Jones from Microsoft. In addition, the authors would like to thank the following people.
Bill Ryan This isn’t an easy acknowledgment to write because there are so many people who deserve my eternal gratitude. First I’d like to thank Laura Sackerman, Maureen Zim merman, and Ken Jones at Microsoft for helping me out so much along the way. You are a true joy to work with. I’d like to thank our technical editor, Jim Fuchs, for work ing so hard and providing such helpful feedback. I’d like to thank Lisa Kreissler, Derek Moore, and everyone at GrandMasters for being so supportive and helpful. Next I’d like to thank Magenic for providing a great work environment, which allowed me to take on such a project (and a special thanks to Chris Winland for being the coolest guy I’ve partnered with on a project). To all the readers of the 70-536 book, Mohammad Ali Khankan in particular, for providing all the invaluable feedback that you did, I’d like to say thank you, as well. Finally, I’d like to thank Herb Sewell, the Bellhaven family, Vernon Dozier, Chris Norton, Phil what’s his name, for keeping me smiling all year.
Mark Blomsma I’d like to thank Maurice de Beijer and Frank Boyne for their help while writing some of the materials for this book. Thanks guys!
xxv
Introduction
This training kit is designed for software developers who plan to take the Microsoft Certified Technology Specialist (MCTS) exam 70-529: Distributed Application Devel opment with the .NET Framework 2.0. The primary objective of this exam is to certify that developers know how to create and implement distributed applications using the .NET Framework 2.0. We assume that, before you begin using this kit, you have a working knowledge of application development with Microsoft Visual Studio 2005 and that you understand basic programming concepts such as object-oriented pro gramming. We also assume that you have a working knowledge of relational data bases and the use of Transact-SQL to query databases for information. Specifically, this training kit uses the Microsoft SQL Server 2005 Express Edition, which is avail able as a free download from Microsoft’s Web site. By using this training kit, you will learn how to: ■
Create and access XML Web services.
■
Configure and customize a Web service application.
■
Create, configure, and deploy remoting applications.
■
Implement asynchronous calls and remoting events.
■
Implement Web Services Enhancements (WSE) 3.0.
■
Create and access a serviced component and use message queueing.
Hardware Requirements We recommend that you use a computer that is not your primary workstation to do the practice exercises in this book because you will make changes to the operating sys tem and application configuration. You might also need to make security changes that will make your workstation vulnerable to security violations. The following hardware is required to complete the practice exercises: ■
Personal computer with a 600-Mhz Pentium III compatible or faster processor (1 GHz or faster recommended)
■
512 MB RAM or more (1 GB or more recommended)
■
600 MB free hard disk space (1 GB or more recommended)
xxvii
xxviii
Introduction
■
CD-ROM drive or DVD-ROM drive
■
Super VGA (1,024 × 768) or higher resolution video adapter and monitor
■
Keyboard and Microsoft Mouse, or compatible pointing device
Software Requirements The following software is required to complete the practice exercises. You need Visual Studio 2005 and SQL Server 2005 Express to complete the labs included with each chapter. Although these products can be installed on a production server, that is not recommended. It would be best to install these products and execute the labs in each chapter on a single development machine. ■
One of the following operating systems: ❑
Microsoft Windows 2000 Professional Service Pack 4
❑
Microsoft Windows 2000 Server Service Pack 4
❑
Microsoft Windows 2000 Advanced Server Service Pack 4
❑
Microsoft Windows 2000 Datacenter Server Service Pack 4
❑
Microsoft Windows XP Professional x64 Edition (WOW)
❑
Microsoft Windows XP Professional Service Pack 2
❑
Microsoft Windows XP Home Edition Service Pack 2
❑
Microsoft Windows XP Media Center Edition 2002 Service Pack 2
❑
Microsoft Windows XP Media Center Edition 2004 Service Pack 2
❑
Microsoft Windows XP Media Center Edition 2005
❑
Microsoft Windows XP Tablet PC Edition Service Pack 2
❑
Microsoft Windows Server 2003, Standard Edition Service Pack 1
❑
Microsoft Windows Server 2003, Enterprise Edition Service Pack 1
❑
Microsoft Windows Server 2003, Datacenter Edition Service Pack 1
❑
Microsoft Windows Server 2003, Web Edition Service Pack 1
❑
Microsoft Windows Server 2003, Standard x64 Edition Service Pack 1 (WOW)
❑
Microsoft Windows Server 2003, Enterprise x64 Edition Service Pack 1 (WOW)
Introduction
■
xxix
❑
Microsoft Windows Server 2003, Datacenter x64 Edition Service Pack 1 (WOW)
❑
Microsoft Windows Server 2003 R2, Standard Edition
❑
Microsoft Windows Server 2003 R2, Standard x64 Edition (WOW)
❑
Microsoft Windows Server 2003 R2, Enterprise Edition
❑
Microsoft Windows Server 2003 R2, Enterprise x64 Edition (WOW)
❑
Microsoft Windows Server 2003 R2, Datacenter Edition
❑
Microsoft Windows Server 2003 R2, Datacenter x64 Edition (WOW)
❑
Microsoft Windows Vista
Microsoft Visual Studio 2005 (A 180-day Evaluation Edition is available for free download from the MSDN Web site at http://msdn.microsoft.com/vstudio/. A 90 day Evaluation Edition of Visual Studio 2005 Professional Edition is included on the DVD with this book.) IMPORTANT
Evaluation edition is not the full retail product
The 90-day evaluation edition of Microsoft Visual Studio 2005 Professional Edition provided with this training kit is not the full retail product and is provided only for the purposes of training and evaluation. Microsoft and Microsoft Technical Support do not support this eval uation edition. Information about any issues relating to the use of this evaluation edition with this training kit is posted to the Support section of the Microsoft Press Web site (www.microsoft.com/learning/ support/books/). For information about ordering the full version of any Microsoft software, please call Microsoft Sales at (800) 426-9400 or visit www.microsoft.com. ■
Microsoft SQL Server 2005 Express Edition (This is available for free down load from the MSDN Web site at http://msdn.microsoft.com/vstudio/express/ sql/default.aspx.)
■
Adventure Works database (This is available as a separate download with the SQL Server 2005 samples at http://msdn.microsoft.com/vstudio/express/sql/default.aspx.)
■
Microsoft Internet Explorer 6.0, Service Pack 1 or later
■
Microsoft Internet Information Services (IIS) 5.0 or later
xxx
Introduction
Installing SQL Server 2005 Express SQL Server 2005 offers a free download Express Edition of its product. The Express Edition is required to run some of the code samples provided in this book. To down load and install this product, use the following instructions: 1. Browse to the link at http://msdn.microsoft.com/vstudio/express/sql/default.aspx and click Download Now. 2. Read and follow the instructions on the download page to download the SQL Server 2005 Express Edition Service Pack 1. You do not need to download the SQL Server Management Studio Express product. 3. Once the install executable has been downloaded to your local machine, execute the downloaded file (Sqlexpr.exe) to extract the setup files to your local devel opment machine. 4. Select the I Accept The Licensing Terms And Conditions check box and click Next to continue the installation. 5. In the Installing Prerequisites dialog box, click Install. Once complete, click Next to continue. 6. The installation then performs a system configuration check. In the Welcome dialog box, click Next to begin the installation. 7. Once the System Configuration Check is complete, click Next. 8. Enter name and company information in the Registration Information dialog box and click Next to continue. 9. In the Feature Selection dialog box, click Next to accept the defaults. 10. In the Instance Name dialog box, click Next to accept the defaults. 11. In the Logon Information dialog box, click Next to accept the defaults. 12. In the Error And Usage Report Settings dialog box, click Next to accept the defaults. 13. In the Ready To Install dialog box, click Install and wait for the installation to complete. 14. You now have to download and install the Adventure Works database, which is referenced in some of the chapter labs.
Introduction
xxxi
Installing the Adventure Works Database Adventure Works is a fictional retailer, and you can download and install a sample database that is referenced in some of the labs for this book. To install the sample database, execute the following instructions: 1. Browse to the link at http://msdn.microsoft.com/vstudio/express/sql/default.aspx and click Download Now. 2. Select the SQL Server 2005 Samples download and follow the instructions on the download page to save the case-insensitive collation DB installation package (AdventureWorksDBCI.msi) to your local development machine. 3. Once the installation package has finished downloading, double-click the exe cutable file to execute the installer. 4. In the Welcome dialog box, click Next. 5. Select the I Accept The Terms In The License Agreement check box and in the License Agreement dialog box, click Next. 6. In the Destination Folder dialog box, click Next to accept the defaults. 7. In the Ready To Install dialog box, click Install. 8. In the Wizard Completed dialog box, click Finish.
Using the CD and DVD A companion CD and an evaluation software DVD are included with this training kit. The companion CD contains the following: You can reinforce your understanding of how to develop .NET Framework 2.0 distributed applications by using electronic practice tests you customize to meet your needs from the pool of Lesson Review questions in this book. Or you can practice for the 70-529 certification exam by using tests cre ated from a pool of about 300 realistic exam questions, which is enough to give you many different practice exams to ensure that you’re prepared.
■ Practice tests
Most chapters in this book include sample files associated with the lab exercises at the end of every lesson. For some exercises, you will be instructed to open a project prior to starting the exercise. For other exercises, you will create a project on your own and be able to reference a completed project on the CD in the event you experience a problem following the exercise.
■ Code
xxxii
Introduction
An electronic version (eBook) of this book is included for times when you don’t want to carry the printed book with you. The eBook is in Portable Doc ument Format (PDF), and you can view it by using Adobe Acrobat or Adobe Reader.
■ eBook
The evaluation software DVD contains a 90-day evaluation edition of Visual Studio 2005 Professional Edition, in case you want to use it with this book.
How to Install the Practice Tests To install the practice test software from the companion CD to your hard disk, do the following: 1. Insert the companion CD into your CD drive, and accept the license agreement. A CD menu appears. NOTE
If the CD menu doesn’t appear
If the CD menu or the license agreement doesn’t appear, AutoRun might be disabled on your computer. Refer to the Readme.txt file on the CD-ROM for alternate installation instructions.
2. Click the Practice Tests item, and follow the instructions on the screen.
How to Use the Practice Tests To start the practice test software, follow these steps: 1. Click Start, All Programs, Microsoft Press Training Kit Exam Prep. A window appears that shows all the Microsoft Press training kit exam prep suites installed on your computer. 2. Double-click the lesson review or practice test you want to use. NOTE
Lesson reviews vs. practice tests
Select the (70-529) Microsoft .NET Framework 2.0–Distributed Application Development lesson review to use the questions from the “Lesson Review” sections of this book. Select the (70-529) Microsoft .NET Framework 2.0–Distributed Application Development practice test to use a pool of about 300 questions similar to those in the 70-529 certification exam.
Lesson Review Options When you start a lesson review, the Custom Mode dialog box appears so that you can configure your test. You can click OK to accept the defaults, or you can customize the
Introduction
xxxiii
number of questions you want, how the practice test software works, which exam objectives you want the questions to relate to, and whether you want your lesson review to be timed. If you’re retaking a test, you can select whether you want to see all the questions again or only those questions you missed or didn’t answer. After you click OK, your lesson review starts. ■
To take the test, answer the questions and use the Next, Previous, and Go To but tons to move from question to question.
■
After you answer an individual question, if you want to see which answers are correct—along with an explanation of each answer—click Explanation.
■
If you’d rather wait until the end of the test to see how you did, answer all the questions and then click Score Test. You’ll see a summary of the exam objectives you chose and the percentage of questions you got right overall and per objec tive. You can print a copy of your test, review your answers, or retake the test.
Practice Test Options When you start a practice test, you choose whether to take the test in Certification Mode, Study Mode, or Custom Mode: Closely resembles the experience of taking a certification exam. The test has a set number of questions, it’s timed, and you cannot pause and restart the timer.
■ Certification Mode
Creates an untimed test in which you can review the correct answers and the explanations after you answer each question.
■ Study Mode
Gives you full control over the test options so that you can cus tomize the options as you like.
■ Custom Mode
In all modes, the user interface you see when taking the test is basically the same, but with different options enabled or disabled depending on the mode. The main options are discussed in the preceding section, “Lesson Review Options.” When you review your answer to an individual practice test question, a “References” section is provided that lists where in the training kit you can find the information that relates to that question and provides links to other sources of information. After you click Test Results to score your entire practice test, you can click the Learning Plan tab to see a list of references for every objective.
xxxiv
Introduction
How to Uninstall the Practice Tests To uninstall the practice test software for a training kit, use the Add Or Remove Pro grams option in Windows Control Panel.
Microsoft Certified Professional Program The Microsoft certifications provide the best method to prove your command of cur rent Microsoft products and technologies. The exams and corresponding certifica tions are developed to validate your mastery of critical competencies as you design and develop, or implement and support, solutions with Microsoft products and tech nologies. Computer professionals who become Microsoft-certified are recognized as experts and are sought after industry-wide. Certification brings a variety of benefits to the individual and to employers and organizations. MORE INFO
All the Microsoft certifications
For a full list of Microsoft certifications, go to www.microsoft.com/learning/mcp/default.asp.
Technical Support Every effort has been made to ensure the accuracy of this book and the contents of the companion CD. If you have comments, questions, or ideas regarding this book or the companion CD, please send them to Microsoft Press by using either of the following methods: E-mail: tkinput@ Microsoft.com Postal Mail: Microsoft Press Attn: MCTS Self-Paced Training Kit (Exam 70-529): Microsoft .NET Framework 2.0 Distributed Application Development Editor One Microsoft Way Redmond, WA 98052–6399 For additional support information regarding this book and the CD-ROM (includ ing answers to commonly asked questions about installation and use), visit the Microsoft Press Technical Support Web site at www.microsoft.com/learning/support/ books/. For support information regarding Microsoft software, please connect to http://support.microsoft.com.
Introduction
xxxv
Foundation Edition Software Support The 90-day evaluation edition provided with this training kit is not the full retail prod uct and is provided only for the purposes of training and evaluation. Microsoft and Microsoft Technical Support do not support this evaluation edition. Information about any issues relating to the use of this evaluation edition with the training kit is posted to the Support section of the Microsoft Press Web site (www.microsoft.com/learning/support/books/). For information about ordering the full version of any Microsoft software, please call Microsoft Sales at (800) 426-9400 or visit www.microsoft.com.
Chapter 1
Creating an XML Web Service
This chapter reviews the steps necessary to create a Web service application in Microsoft Visual Studio. You will learn how to create and configure a Web application using the WebService declaration. You will also learn how to create and configure a public method by attaching the WebMethod attribute. Finally, you will learn how to test and deploy a Web service application by copying files to a Web server.
Exam objectives in this chapter: ■
Create and configure an XML Web service method. ❑
Create a public method.
❑
Attach the WebMethodAttribute attribute.
❑
Create a OneWay Web method.
❑
Configure a Web service method.
■
Configure a Web service method by using the properties of the WebMethodAttribute attribute.
■
Configure SOAP messages. ❑
Specify the basic information for a Web service application.
Lessons in this chapter: ■
Lesson 1: How to Create a Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
■
Lesson 2: How to Create a Web Service Method. . . . . . . . . . . . . . . . . . . . . . . . . 15
■
Lesson 3: How to Test and Consume a Web Service . . . . . . . . . . . . . . . . . . . . . 24
Before You Begin To complete the lessons in this chapter, you must have ■
A computer that meets or exceeds the minimum hardware requirements listed in the Introduction at the beginning of this book.
1
2
Chapter 1
Creating an XML Web Service
■
Visual Studio 2005 and the .NET Framework 2.0.
■
Knowledge of programming in Microsoft Visual Basic or C# and a basic under standing of Extensible Markup Language (XML).
Real World Sara Morgan Many developers want to know which distributed technology they should use—Web services, Remoting, or .NET Enterprise Services. The answer to this question depends a lot on what you need out of the application. There is no magic bullet that is right for all scenarios, but in my opinion, and Microsoft’s as well, you should start simple. Start with Web services and only use Remot ing or .NET Enterprise Services if necessary. Although every business problem is unique, here are some helpful rules to use as a starting point when trying to determine which distributed technology to pursue: ■
If the application or applications accessing your objects are located on dif ferent platforms, most often Web services will meet your needs. This is especially true if overall performance is not a primary concern.
■
If you know the client and server platforms will be .NET and efficiency is a primary concern, then Remoting is your best option. Remoting is most effi cient when you host the Remoting application inside a managed executable.
■
If your application requires access to specialized functionalities such as object pooling, queued components, and role-based security, then .NET Enterprise Services is likely your best option.
Lesson 1: How to Create a Web Service
3
Lesson 1: How to Create a Web Service This lesson begins by providing a general definition for a Web service. After walking through the steps for creating a Web service project in Visual Studio, this lesson explores the basics of what constitutes a Web service application. This includes one or more public methods that are exposed with the WebMethod attribute. The methods are wrapped inside a class file that is distinguished with a WebService declaration. First, however, this lesson just focuses on creating the Web service project and config uring the Web service with the WebService attribute. After this lesson, you will be able to: ■
Answer what a Web service is and why it is important to know how to create one.
■
Create a Web service project using the project template provided in Visual Studio.
■
Identify the code necessary to configure a Web service using the WebService attribute.
Estimated lesson time: 30 minutes
What Is a Web Service? A Web service is a piece of software that is used to expose data and functionality in a distributed environment. The data exposed can be as simple as a list of product cate gories or as complex as a multileveled dataset containing a complete customer pur chase history. The functionality exposed can also range in complexity, and generally complex tasks are broken down into a series of exposed public Web methods. Web services are XML based, and distributed applications access them over the Inter net using a communications protocol such as Simple Object Access Protocol (SOAP). SOAP provides a standard and simple way to present messages in an XML format. SOAP also allows developers to describe what the service does and make it available to other applications. Because the data returned by a Web service is in the form of a stan dardized XML message, applications residing on different platforms can access the same Web services. This capability makes Web services quite useful when multiple cli ents located over vast geographical areas need access to the same kind of information. NOTE
Selecting a distributed application method
When selecting a distributed application method, first consider Web services because Web services are highly interoperable and accessible from almost any platform.
4
Chapter 1
Creating an XML Web Service
You can think of a Web service application in the same way that you think of a regular ASP.NET application. The primary difference between a Web service application and an ASP.NET application is the interface. ASP.NET applications utilize traditional graphical user interface (GUI) elements, such as text boxes and labels. An XML Web service application has none of these elements. Instead, it relies on help pages and dis covery files to interface with its users. The user of a Web service is typically another application, so no traditional user interface is necessary. Web services are especially useful for applications that must expose key pieces of their data or functionality to outside and unknown sources. For instance, a retail company can utilize a complex application to manage sales and inventory. There is likely a need for outside vendors to access inventory levels specific to the products they provide. The retail company could expose a Web service method that allowed specific vendors to access this information in the form of an XML dataset. The retail company would not need to provide an interface specific to each vendor’s operating platform. Instead, they would only be responsible for exposing one Web service method that all vendors could utilize.
How to Create a New Web Service Project To create a Web service project, you need to create a project using the Web service template in Visual Studio 2005. Using the template adds certain files and directories to your project by default. This makes it easier to get started on building your first Web service. Figure 1-1 shows the New Web Site dialog box that is displayed when you cre ate a new ASP.NET Web service project.
Figure 1-1
Use the New Web Site dialog box to create a new ASP.NET Web service project
Lesson 1: How to Create a Web Service
5
The Web service project template creates two directories named App_code and App_data, along with an initial Web service file named Service.asmx (see Figure 1-2). The App_code directory contains source files for all classes and business objects. The App_data directory contains any data files needed for the project, including XML files or any other data storage files. NOTE
The .asmx file extension
The Service file included with your new Web service project has an .asmx file extension. All Web service files will have an .asmx extension, as opposed to ASP.NET applications that use files with an .aspx extension. The .asmx file will be referenced when another user or distributed application needs to access methods within the Web service.
Figure 1-2
A new Web service solution built with the Visual Studio installed template
After the new project has been created, the code pane displays the code for the Service class file, named Service.cs (or Service.vb for Visual Basic). By default, this class con tains references to the following .NET Framework namespaces: This namespace is used to represent the base data types in the .NET Framework.
■ System
6
Chapter 1
Creating an XML Web Service
■ System.Web
This namespace is used to enable browser-to-server communication.
This namespace contains the base classes needed to create XML Web services. This namespace includes the WebService and WebMethodAt tribute classes, which are needed when creating methods exposed within your Web service application.
■ System.Web.Services
This namespace contains the classes needed to transmit XML data across the Internet.
■ System.Web.Services.Protocols
The Web services template also inserts the following code into the Service class file: 'VB _ _ _ Public Class Service Inherits System.Web.Services.WebService _
Public Function HelloWorld() As String
Return "Hello World"
End Function
End Class
//C# [WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
public Service () { //Uncomment the following line if using designed components //InitializeComponent();
}
[WebMethod]
public string HelloWorld() {
return "Hello World";
}
}
If you do nothing to modify the code added to the Service class, your Web service will contain one method named HelloWorld. Once invoked, this method returns the string “Hello World”. At this point, you have created the shell of a Web service project. Next, we review what it takes to make an ordinary Web application a Web service and what you can do to specify the basic information for your Web service application.
Lesson 1: How to Create a Web Service
7
The WebMethods Framework ASP.NET includes a WebMethods framework for the purpose of easily creating Web services. Otherwise, developers would need to use a low-level technique and write a custom IHttpHandler class. The WebMethods framework allows developers to utilize built-in functionality that masks much of the complexity of implementing Web ser vices. The framework is used to map SOAP messages to the methods within a .NET class.
The WebService Declaration The WebService processing directive is the key to making an .asmx file a Web service. It tells the .asmx handler where to locate the class files that contain the Web methods. The directive also includes other attributes that identify how the Web service should be handled. To view the source code used in the Service.asmx file, right-click the file in Solution Explorer and Open With. You will then click Source Code (Text) Editor and click OK. In the Service.asmx file that was installed with the Web service tem plate, the following code was used to identify it as a Web service: 'VB //C#
The processing directive used in the Service.asmx file includes the following three important attributes: This attribute is used to identify the programming language used in the code-behind file. It can be set to VB, C#, or J#, which identifies the program ming language as Visual Basic, C#, or JScript.
■ Language
This attribute references the class file that will contain the Web methods. For the Web service template, the code-behind is located in a class file named Service and it is located in the App_code directory.
■ CodeBehind
This attribute is used to identify the class name referenced in the codebehind file.
■ Class
NOTE
Renaming the class file
If you want to rename the class file, you will also need to change the Class attribute in the WebSer vice processing directive.
8
Chapter 1
Creating an XML Web Service
It is possible for the Web service code and the .asmx file to reside in different assem blies. To specify this arrangement, you need to set the Class attribute to include the class name, followed by a comma, and then the assembly name. The following code gives you an example of what this would look like. 'VB //C#
NOTE
Specifying an assembly name
If you do not specify the assembly name when the class file resides in a different assembly, ASP.NET will be forced to search through all the assemblies in the application’s \bin folder. This will cause your Web service to perform slower than it should.
The WebService Class The WebService class is the base class used for all Web services, and it is part of the Sys tem.Web.Services namespace. Table 1-1 lists the attributes and members that are part of this class. Table 1-1
Public Properties of the WebService Class
Name
Description
Application
Retrieves the application object for the current Hypertext Transfer Protocol (HTTP) request. This object is used to store application state.
Container
Retrieves the container for the component. This container is used when passing properties such as MarshallByValue.
Context
Retrieves the HttpContext object for the current request. This object allows you to reference HTTP items for the cur rent Web service request.
DesignMode
Retrieves the value true if the component is in design mode.
Server
Retrieves the HttpServerUtility object for the current request. This object can be used when there is a need to instantiate Component Object Model (COM) objects.
Lesson 1: How to Create a Web Service
Table 1-1
9
Public Properties of the WebService Class
Name
Description
Session
Retrieves the HttpSessionState instance from the current Web service request. This object is used when referencing session state.
Site
Retrieves or sets the site of the component. If the value is not null, the value has been added to the container.
SoapVersion
Retrieves the version of the SOAP protocol used to make the Web service request. This property is new in the .NET Framework 2.0.
User
Retrieves the User object and can be used to determine whether a user is allowed to make a Web service request.
The WebServiceAttribute Class The WebService attribute used in the Service class file was very basic and did not use more than one property. It only specified the Namespace attribute, which should be set to a unique XML namespace value, as shown in the following code: 'VB //C# [WebService(Namespace = "http://tempuri.org/")]
By default, the Namespace property is set to http://tempuri.org for all Web services. It is acceptable to keep Namespace set this way while you are testing your Web service, but it needs to be changed before the Web service is moved into production. The XML namespace should be unique to distinguish it from all other Web services. Typically, you will use your company’s domain name as part of the value. In many cases, the namespace value will be a valid Uniform Resource Locator (URL), but it is not neces sary for the URL to be valid. Other properties that are available with the WebService attribute are Description and Name (see Table 1-2). The value for Description should be set to a short descriptive message that identifies the basic function of the Web service. The Name property should be set to a value that uniquely identifies the Web service. If you do not specify
10
Chapter 1
Creating an XML Web Service
a Name property, the .asmx handler uses the name of the class that implements your Web service. Table 1-2
Public Properties of the WebServiceAttribute Class
Name
Description
Description
Sets a message that describes the function and purpose of the Web service.
Name
Retrieves or sets the name of the Web service. If you do not specifically set this property, the .asmx handler uses the name of the class that implements the Web service.
Namespace
Retrieves or sets the XML namespace used for the Web ser vice. By default, this property is set to http://tempuri.org.
TypeId
Retrieves a unique identifier used to identify the attribute when it is implemented in a derived class.
Real World Sara Morgan I recently worked for a company that produces legislative software for city and state governments. The company’s flagship product is a complex ASP.NET appli cation that interfaces with a third-party vendor that manages video content. The ASP.NET application called Web service methods exposed by the third-party vendor. When we first started working with the vendor to integrate our applica tions, the applications exposed a Web service that used the default namespace http://tempuri.org. The namespace remained that way even after we moved the application into production. One day, the vendor decided to change the Namespace property but did not bother notifying us of the change. This imme diately broke our application because it could no longer identify the Web ser vice. There was some late-night scrambling to get the Web Services Description Language (WSDL) document updated and the application repaired. The moral of this story is that it is important to change the namespace of your Web services before you move your application into production. If your applica tions access external Web services, make sure they are not using the default namespace either.
Lesson 1: How to Create a Web Service
11
Lab 1: Creating a Web Service Project Using Visual Studio This lab steps you through how to create a Web service project using Visual Studio. It begins by creating a new Web service project using one of the project templates for a new Web site. The Web service project that is created in this lab does not have any Web methods attached to it. This lab is only meant to demonstrate how a Web service project is created using one of the Visual Studio templates. It also demonstrates how the basic information for a Web service can be set using the WebService attribute. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. Although the certification exam does not specifically cover the objective of creating a Web service project, it is important for you to know how to begin the process of cre ating a Web service. To do so, follow these steps : 1. Open Visual Studio 2005. 2. Click File, New, Web Site. 3. In the New Web Site dialog box (see Figure 1-1), select ASP.NET Web Service as the template and name the project file MyWebService. By default, Visual Studio saves the project in the \My Documents\VisualStudio 2005\WebSites\directory. 4. Set the language by selecting Visual Basic, Visual C#, or Visual J# from the Lan guage drop-down list box. By default, Visual Studio selects the language speci fied when you configured the development environment. 5. A new solution (see Figure 1-2) named MyWebService is created. By default, the solution file corresponding to the project is stored in your \My Docu ments\Visual Studio 2005\WebSites directory. The Web service project files themselves are stored in the directory specified in step 3. 6. In Solution Explorer, right-click the Service.asmx file and click Rename. Change the name to MyWebService.asmx. 7. Double-click the MyWebService.asmx file and replace the existing code with the following: 'VB //C#
12
Chapter 1
Creating an XML Web Service
8. In Solution Explorer, right-click the Service.cs (or Service.vb) file in the App_code directory and click Rename. Change the name to MyWebService.cs (or MyWebService.vs). 9. Right-click the MyWebService.cs (or MyWebService.vb) file and click View Code. 10. From the code window, replace all the existing code with the following: 'VB Imports Imports Imports Imports Imports Imports Imports Imports
System.Web
System.Web.Services
System.Web.Services.Protocols
System.Data.SqlClient
System.Data.SqlTypes
System.Data
System.Xml
System.Web.Services.Description
_ _ Public Class MyWebService
Inherits System.Web.Services.WebService
Public Sub New()
MyBase.New()
End Sub
End Class
//C# using using using using using using using using using
System;
System.Web;
System.Web.Services;
System.Web.Services.Protocols;
System.Data.SqlClient;
System.Data.SqlTypes;
System.Data;
System.Xml;
System.Web.Services.Description;
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [WebService(Name = "MyWebService", Description = "This Web Service is used to " + "experiment with some of the Web service concepts", Namespace = "http://www.mycompany.com/")] public class MyWebService : System.Web.Services.WebService
{
public MyWebService () { } }
Lesson 1: How to Create a Web Service
13
11. Build the project, and resolve any errors. You’ll be using this Web service in the following lessons, but for now leave the project alone.
Lesson Summary ■
Web services are software applications that expose data and functionality to multiple platforms over the Internet using a communication protocol known as SOAP.
■
The WebService declaration must be placed in each .asmx file of your Web service application. This declaration is used to tell the .asmx handler where to find the class file that implements the code for your Web service.
■
The WebService class is the base class for all Web services, and it is included in the System.Web.Services namespace.
■
The WebService attribute can be used to specify basic information about your Web service. This information can include a name, description, and unique namespace. It is a good idea to set the namespace to something other than the default before a Web service is deployed into production.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “How to Create a Web Service.” The questions are also available on the com panion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. What namespaces are included by default with the Visual Studio Web services template? (Select all that apply.) A. System.Web B. System.WebServices C. System.Web.Services D. System.Web.Services.Protocols
14
Chapter 1
Creating an XML Web Service
2. Identify the Visual Basic code that contains an invalid WebService declaration: A.
B.
C.
D.
3. What is the primary purpose of the Namespace property, which is used in the WebService attribute? A. To set the unique XML namespace that will be used to distinguish your Web service from other Web services B. To reference the organization that establishes standards related to all Web services published on the Internet C. To reference the URL where more information regarding this Web service can be found D. To reference your company’s Web site
Lesson 2: How to Create a Web Service Method
15
Lesson 2: How to Create a Web Service Method This lesson expands on Lesson 1 by going into what is involved in creating a publicly exposed Web method. After creating a public method, the lesson steps through the process of applying the WebMethod attribute. The Web method can then be config ured by setting one of several properties for the WebMethod attribute. The lesson con cludes by showing an example of a specialized Web method known as a one-way method. After this lesson, you will be able to: ■
Create a public Web method and apply the WebMethod attribute.
■
Configure a Web service method.
■
Identify the code necessary to configure a Web service using the WebService attribute.
Estimated lesson time: 30 minutes
Creating a Public Method At the most basic level, you can make a method accessible through a Web service application by attaching the WebMethod attribute to a public method. The public method is stored in the class file of your Web service file (the one with the .asmx extension). When you create a Web service project using the Web service project tem plate, one public method named HelloWorld is created for you by default. You can see the code for the HelloWorld method by right-clicking the Service.asmx file in Solution Explorer and clicking View Code. NOTE
Methods that aren’t public
Your Web service class can contain other methods that are not public, but they will not be accessi ble as Web methods. Instead, they will only be available to the other methods in your Web service class.
Applying the WebMethod Attribute The HelloWorld public method does not become available through the Web service until you attach the WebMethod attribute. This method must be applied to each method declaration that will be accessible. The following code shows how the WebMethod appears at the top of the HelloWorld method.
16
Chapter 1
Creating an XML Web Service
'VB _ Public Function HelloWorld() As String
Return "Hello World"
End Function //C# [WebMethod] public string HelloWorld() {
return "Hello World";
}
Configuring a Web Service Method Web methods can be configured using one of several different properties. These prop erties allow you to specify everything from the Web method description to how it will handle transactions. The different properties available with the WebMethod attribute are as follows: This property provides a general description for what the Web method is supposed to accomplish. Although the default value for this property is an empty string, it is a good idea to always provide at least one sentence that briefly describes your Web method. Later you will see how the value specified for the description is displayed on the Service help page.
■ Description
This property is the value that identifies your Web method in the XML sent with your SOAP message. By default, it is set to the name of the Web method, but you might want to change this.
■ MessageName
If session state is not specified directly, it is disabled for all Web service methods. This arrangement allows the methods to operate more effi ciently. If your Web method requires the use of session state, you need to include the EnableSession property and set the value to true. You will then be able to access the session state through the HttpContext.Current.Session property.
■ EnableSession
The value for this property specifies the number of seconds that ASP.NET will cache the Web method response. By default, this value is set to 0, so no caching will occur.
■ CacheDuration
Buffering can be used to improve the performance of your Web service because it minimizes the amount of communication needed between the worker process and Internet Information Services (IIS). By default, this property is set to true, but you might want to set it to false if you are returning a large amount of data and do not want it all stored in the buffer.
■ BufferResponse
Lesson 2: How to Create a Web Service Method
17
This property is used to indicate whether the Web method can participate in a transaction. By default, the transaction option is set to Dis abled, but you might need to change that value to either NotSupported, Supported, Required, or RequiresNew.
■ TransactionOption
NOTE
Requiring transactions
If your Web method performs updates on more than one database or if the sequence of updates is critical to the overall success of the Web method, then you should ensure that the TransactionOp tion is set to Required.
Creating a One-Way Method In many cases, the Web method you expose through your Web service does not require a response from the client. In cases such as this, you can specify that your Web method is a one-way method by applying the OneWay attribute. Applying this attribute makes your method perform more efficiently because it will not wait for a response from the client. Your method will only process input parameters and cannot be used to return referenced values or throw exceptions. The System.Web.Services.Protocols namespace includes the SoapDocumentMethodAt tribute (see Table 1-3) and SoapRpcMethodAttribute (see Table 1-4) classes, which rep resent the SoapDocumentMethod and the SoapRpcMethod. These methods are used to format SOAP messages and allow you to set the value of a OneWay property. This property accepts a Boolean value indicating whether the Web service client has to wait for a response from the Web server. If the property is set to false (the default value), then the Web service client waits for an acknowledgment from the Web server, even if no values need to be passed back. So, if you know that no values need to be passed back, the Web service client is immediately free to go on to other processing tasks. Table 1-3
Public Properties of the SoapDocumentMethodAttribute Class
Name
Description
Action
Retrieves or sets the SOAPAction HTTP header field for the Web service request.
Binding
Retrieves or sets the binding value, which is by default the name of the Web service followed by the word Soap.
18
Chapter 1
Creating an XML Web Service
Table 1-3
Public Properties of the SoapDocumentMethodAttribute Class
Name
Description
OneWay
Retrieves or sets the Boolean value that determines whether the method will be able to return any values from the request.
ParameterStyle
Retrieves or sets a value that determines where parame ters will be stored in the request’s Body element. The property can be set to Bare, Default, or Wrapped.
RequestElementName
Retrieves or sets the name of the XML element associ ated with the request method. By default, this property is set to the name of the Web service method.
RequestNamespace
Retrieves or sets the namespace of the request method binding. By default, this property is set to http://tem puri.org.
ResponseElementName
Retrieves or sets the name of the XML element associ ated with the response method. By default, this property is set to the name of the Web service method.
ResponseNamespace
Retrieves or sets the namespace of the response method binding. By default, this property is set to http://tempuri.org.
TypeId
Retrieves a unique identifier used to identify the attribute when it is implemented in a derived class.
Use
Retrieves or sets the name of the binding used when invoking the Web method. This property is new for the .NET Framework 2.0.
Lesson 2: How to Create a Web Service Method
Table 1-4
19
Public Properties of the SoapRpcMethodAttribute Class
Name
Description
Action
Retrieves or sets the SOAPAction HTTP header field for the Web service request.
Binding
Retrieves or sets the binding value, which is by default the name of the Web service followed by the word Soap.
OneWay
Retrieves or sets the Boolean value that determines whether the method will be able to return any val ues from the request.
RequestElementName
Retrieves or sets the name of the XML element asso ciated with the request method. By default, this property is set to the name of the Web service method.
RequestNamespace
Retrieves or sets the namespace of the request method binding. By default, this property is set to http://tempuri.org.
ResponseElementName
Retrieves or sets the name of the XML element asso ciated with the response method. By default, this property is set to be the name of the Web service method.
ResponseNamespace
Retrieves or sets the namespace of the response method binding. By default, this property is set to http://tempuri.org.
TypeId
Retrieves a unique identifier used to identify the attribute when it is implemented in a derived class.
Use
Retrieves or sets the name of the binding used when invoking the Web method. This property is new for the .NET Framework 2.0.
20
Chapter 1
Creating an XML Web Service
The following code shows how the property can be set for the HelloWorld method. 'VB _
Public Function HelloWorld() As String
Return "Hello World"
End Function
//C# [SoapDocumentMethod(OneWay=true)]
[WebMethod]
public string HelloWorld() {
return "Hello World";
}
Lab 2: Creating a Public Web Service Method This lab steps you through how to create a Web service method. The public method created in this lab is added to the Web service project created in the last lab. If you have not yet completed the steps in Lab 1, you need to do so before continuing on with this lab. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. The method created in this lab is named GetVendorList and it returns an XML string containing all active vendors. The vendor data come from the AdventureWorks data base, which is available as a free download with SQL Server Express at http:// www.microsoft.com/downloads/details.aspx?FamilyId=E719ECF7-9F46-4312-AF89 6AD8702E4E6E&displaylang=en. You can find helpful instructions on installing the database at http://msdn2.microsoft.com/en-us/library/ms310325.aspx. 1. Open Visual Studio 2005. 2. Click File, Open, Web Site. 3. In the Open Web Site dialog box, browse to the location where the MyWebSer vice project was created in Lab 1. 4. Within the MyWebService.cs or MyWebService.vb file, add the following code right under the class constructor: 'VB _ Public Function GetVendorList() as String
Dim sqlConn As New SqlConnection
Dim rdr As XmlReader
Dim ret As String = ""
Lesson 2: How to Create a Web Service Method
21
Try
'Open the connection to SQL Server Express
Dim connString As String = _
"server=.\sqlexpress;Integrated Security=SSPI;initial catalog=AdventureWorks" sqlConn.ConnectionString = connString sqlConn.Open() 'Return XML data using the FOR XML AUTO clause Dim sqlString As String = "SELECT VendorID, AccountNumber, " + _ "Name, CreditRating " + _ "FROM Purchasing.Vendor " + _ "WHERE ActiveFlag = 1 " + _ "ORDER BY Name " + _ "FOR XML AUTO " Dim command As New SqlCommand(sqlString, sqlConn) 'Execute the SQL query and return XML to an XMLReader
rdr = command.ExecuteXmlReader()
'Loop through the reader and get our XML
rdr.Read()
Do While rdr.ReadState System.Xml.ReadState.EndOfFile
ret += rdr.ReadOuterXml
Loop
'Add a root element
ret = " " + ret + ""
sqlConn.Close()
rdr.Close()
Catch ex As Exception 'Add some exception handling code here
End Try
Return ret
End Function //C# [WebMethod(Description = "Retrieve a list of active vendors ordered by name")]
public string GetVendorList()
{
SqlConnection sqlConn; XmlReader rdr;
string ret = "";
try
{
//Open the Connection to SQL Server Express string connString = @"server=.\sqlexpress;Integrated Security=SSPI;initial catalog=AdventureWorks"; sqlConn = new SqlConnection(connString);
22
Chapter 1
Creating an XML Web Service
sqlConn.Open(); // Return XML data using the FOR XML AUTO clause string sqlString = "SELECT VendorID, AccountNumber, Name, CreditRating " + "FROM Purchasing.Vendor " + "WHERE ActiveFlag = 1 " + "ORDER BY Name " + "FOR XML AUTO "; SqlCommand command = new SqlCommand(sqlString, sqlConn);
//Execute the SQL query and return XML to an XMLReader
rdr = command.ExecuteXmlReader();
//Loop through the reader and get our XML
rdr.Read();
while (rdr.ReadState != System.Xml.ReadState.EndOfFile) {
ret += rdr.ReadOuterXml();
}
//Add a root element
ret = " " + ret + "";
sqlConn.Close();
rdr.Close();
}
catch
{
//Do some exception handling here
}
return ret;
}
5. Build the project, and resolve any errors.
Lesson Summary ■
Methods exposed through a Web service must be declared as public. Private methods can exist in the Web service class file, but they will not be available for public consumption.
■
The WebMethod attribute must be applied to all public methods that need to be exposed through a Web service. Several public properties are exposed through this attribute that allow you to configure each Web service method.
■
The System.Web.Services.Protocols namespace includes the SoapDocumentMethodAttribute and SoapRpcMethodAttribute classes, which are used to format SOAP mes sages. Both of these methods allow you to set the value of a OneWay property.
Lesson 2: How to Create a Web Service Method
23
Lesson Review You can use the following questions to test your knowledge of the information in Les son 2, “How to Create a Web Service Method.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. What are the most basic requirements for making an ASP.NET application a Web service? (Select all that apply.) A. One or more public methods B. The WebService processing directive C. A WebService configuration section in the web.config file D. The WebMethod attribute attached to each method that will be exposed through the Web service 2. How do you allow your Web service to participate in a transaction? A. Transaction processing is not available with Web services in the .NET Framework 2.0. B. Set the Transaction property for the WebMethod attribute to one of the fol lowing values: Required or RequiredNew. C. Set the TransactionOption property for the WebMethod attribute to one of the following values: Required or RequiredNew. D. Set the Transaction property for the WebService attribute to true. 3. How do you create a one-way method? A. For the WebMethod attribute, set the OneWay property to true. B. For the SoapDocumentMethod and SoapRpcMethod methods, set the OneWay property to true. C. Import the System.Web.Services.Protocols namespace and set the SoapProto col property to OneWay. D. Either A or B is correct.
24
Chapter 1
Creating an XML Web Service
Lesson 3: How to Test and Consume a Web Service This lesson shows you how to test and consume a Web service application. Testing a Web service involves invoking each of the public Web methods and verifying that the results returned are accurate. This testing can be done directly using a URL that points to the Web service file or by invoking the Web method using a separate client application. After this lesson, you will be able to: ■
Test a Web service method using a debugging session or directly with a URL.
■
Consume a Web service method using a client application.
Estimated lesson time: 20 minutes
Testing Your Web Service The Service help page (see Figure 1-3) contains links to all the public methods exposed with the WebMethod attribute. In Lab 2, only one Web method was created, so if you were to compile and test the application from this lab, only one link would be displayed on the Service help page.
Figure 1-3 After executing the project created in Lab 1 and Lab 2, you should see this Service help page
Lesson 3: How to Test and Consume a Web Service
25
There are two methods you can use for testing your Web service. The first involves building your application and starting a debugging session so that you can step through the code of your application. This task is done by first selecting a start page for your application. For the application created in Lab 1, the start page would be the MyWebService.asmx file. You select the start page by right-clicking the file in Solution Explorer and clicking Set As Start Page. The next step is to initiate a debugging session by choosing Start Debugging on the Debug menu. Alternatively, you can press F5 to start debugging. The first time you initiate a debugging session for a Web project you’ll see the dialog box shown in Figure 1-4. Click OK to start debugging.
Figure 1-4
The Debugging Not Enabled dialog box
When you start the debugging session, the Service help page is displayed in your Web browser (see Figure 1-3). It should display a link to the GetVendorList Web method. If you were to click this link, you would then be brought to the Service method help page for GetVendorList (see Figure 1-5). The Service method help page allows you to invoke a particular Web method. If the Web method accepts parameters, you would also be prompted to enter those values. Not only can you invoke the Web method and test your application, but you can also see sample request and response SOAP messages for this Web method. SOAP mes sages are discussed further in Chapter 2, “Extending XML Web Services with SOAP Formatting, Headers, and Extensions.” After you click Invoke on the Service method help page, the code for that Web method executes. If you set a breakpoint in your code using the Toggle Breakpoint (F9) com mand, the code stops executing at this point and allows you to step through the remaining code using the Step Over (F10), Step Into (F11), or Step Out (Shift + F11) commands. If the Web method executes successfully and returns a value, the next page you see should contain the results.
26
Chapter 1
Figure 1-5
Creating an XML Web Service
The Service method help page for the GetVendorList Web method
In Lab 2, you created a Web method named GetVendorList. This Web method should return an XML string containing a list of active vendors from the AdventureWorks data base. The results should be displayed inside a new instance of your Web browser (refer to Figure 1-6). The GetVendorList Web method uses the FOR XML AUTO clause to automatically return a valid XML string directly from the database. If the Web method is unable to execute successfully and an exception is thrown, you should see a page displaying the exception encountered. It might be necessary to set a breakpoint using the Toggle Breakpoint function and step through the code until you are able to resolve the problem. NOTE
Refer to setup instructions
The Introduction for this Training Kit contains information that you need to execute and test Web services created in the labs. Refer to these instructions if you are having trouble executing and test ing your Web service.
Lesson 3: How to Test and Consume a Web Service
Figure 1-6
27
XML results returned after invoking the GetVendorList Web method created in Lab 2
The second method for testing a Web service is to use your Web browser to access the Web service page (the one with the .asmx extension) directly. Web services built on your local development machine can be accessed using the name Localhost to specify the machine name. The remaining portion of the URL should be the name of the Web service project, followed by the name of the Web service page. For example, the Web service method created in Lab 2 can be accessed directly with the following URL: http://localhost:/MyWebService/MyWebService.asmx. NOTE
Using a port number with Visual Studio 2005
Visual Studio 2005 utilizes port numbers when debugging Web sites. The exact port number that is assigned is displayed in a balloon that appears above your task tray when you begin debugging. Write down this port number and you can use a Web browser to navigate to your Web service using the port number for your machine.
28
Chapter 1
Creating an XML Web Service
Accessing the Web service method with the URL should bring you to the Service help page. This allows you to select any public Web method exposed with the WebMethod attribute. From there, you can invoke any Web method and verify that the results are returned as expected.
Consuming a Web Service Another alternative for testing your Web service is to create a separate Web applica tion project that acts as the client consumer of your Web service. This Web applica tion project can reside in the same solution file with your Web service project and allows you to simulate the experience of your Web service consumers. Using this method allows you to process and manipulate the data returned from your Web ser vice. Even though you can see the data returned just by browsing to the Web service URL, until you actually try to use those data, you might not be able to identify all potential issues surrounding its consumption.
Setting a Web Reference You can add a client Web application project to your Web service solution by clicking Add, New Web Site from the File menu. After adding your new project to the solution, you will be able to set a Web reference that refers to the Web service project. This task is done by right-clicking the newly created project in Solution Explorer and clicking Add Web Reference. The Add Web Reference dialog box (see Figure 1-7) can be used to set a Web reference to a Web service residing in your current solution. Once the Web reference is added, you are able to refer to Web methods from your Web service project as if you were referring to methods within class files in your current project. When you add a Web reference, you are telling Visual Studio to download the Web service description and generate a proxy class that can be used to communicate with the Web service. Assume you were to create a project that had a label control named lblDisplay. Further assume that you created a Web reference named WebService that referenced a Web ser vice on your local machine named Service.asmx. The code used to call the HelloWorld method and return a string to the lblDisplay control would appear as follows: 'VB Dim ws As New WebService.Service
lblDisplay.Text = ws.HelloWorld
//C# ws WebService = new WebService.Service();
lblDisplay.Text = ws.HelloWorld
Lesson 3: How to Test and Consume a Web Service
29
Figure 1-7 Use the Add Web Reference dialog box to set a Web reference to a project in the current solution
At this point, you can test how clients of your Web service will be able to interact with the data returned from your Web methods. By doing this testing, you will quickly be able to see whether the return values match the anticipated results. NOTE
Distribute a client application with your Web service
It might be a good idea to distribute a test ASP.NET application with your Web service that demon strates how clients can consume the data returned from your Web methods.
Lab 3: Testing and Consuming a Web Service This lab guides you through adding a test client application to the Web service appli cation created in Lab 1. This client application is used to demonstrate how the results of the GetVendorList Web method can be used to populate a drop-down list box con trol. If you have not yet completed the steps in Lab 1 and Lab 2, you need to do so before continuing on with this lab. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. 1. Open Visual Studio 2005. 2. Click File, Open, Web Site. 3. In the Open Web Site dialog box, browse to the location where the MyWebSer vice was created in Lab 1 and click Open.
30
Chapter 1
Creating an XML Web Service
4. On the File menu, click Add, New Web Site. Select the ASP.NET Web Site tem plate, enter MyWebServiceClient as the project name, and click OK. 5. Right-click the new project file and click Add Web Reference. In the Add Web Reference dialog box select Web Services In This Solution. 6. Click the MyWebService link, enter the name WebService in the Web Reference Name text box, and click Add Reference. 7. Right-click the Default.aspx file in Solution Explorer and click View Designer. 8. Click the Toolbox and drag a DropDownList control onto the design surface. Click the design surface to close the DropDownList Tasks window and then click the control. In the Properties window, set the ID property to ddlVendors. 9. Click the Toolbox and drag an XMLDataSource control onto the design surface. Click the design surface to close the XmlDataSource Tasks window. 10. Double-click the design surface to open the Page_load method in the code win dow. Add the following code to this method: 'VB Dim ws As New WebService.MyWebService
XmlDataSource1.Data = ws.GetVendorList
ddlVendors.DataSource = XmlDataSource1
ddlVendors.DataTextField = "name"
ddlVendors.DataValueField = "vendorid"
ddlVendors.DataBind()
//C# WebService.MyWebService ws = new WebService.MyWebService();
XmlDataSource1.Data = ws.GetVendorList();
ddlVendors.DataSource = XmlDataSource1;
ddlVendors.DataTextField = "name";
ddlVendors.DataValueField = "vendorid";
ddlVendors.DataBind();
11. Right-click the MyWebServiceClient project in Solution Explorer and click Set As StartUp Project. 12. Right-click the Default.aspx file in Solution Explorer and click Set As Start Page. 13. Build the project, and resolve any errors. 14. On the Debug menu, click Start Debugging (or press F5). The default.aspx page should open in a Web browser and display a single drop-down list box that lists all the active vendors in the AdventureWorks database.
Lesson 3: How to Test and Consume a Web Service
31
Lesson Summary ■
You can verify that the results returned from a Web service method are valid by using a debugging session from the Web service project. You can then step through the project and use Visual Studio’s debug commands to identify prob lems with your Web service.
■
Alternatively, you can invoke the Web method directly by browsing to the URL for the Web service file (the file with the .asmx extension).
■
The results from a Web service can also be verified by creating a client ASP.NET application that consumes Web methods from your Web service. By testing in this manner, you ensure that consumers of your Web service will be able to effec tively utilize the data returned from your method.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 3, “How to Test and Consume a Web Service.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. What page is used to display a list of links to your Web service? A. The Service method help page B. The Service help page C. The Web method help page D. All of the above 2. What methods can be used to verify the results of your Web service? (Select all that apply.) A. Calling the Web service directly by browsing to the .asmx file B. Using the Web service Object Browser to invoke a particular Web method C. Using a debugging session within the Web service project to step through the code D. Creating an ASP.NET client application to consume a particular Web ser vice method
32
Chapter 1
Creating an XML Web Service
3. What steps are required when creating an application to test your Web service? (Select all that apply.) A. Create a Web reference and point it to the .asmx file for your Web service. B. Create a routine that publishes your Web service to the destination Web server. C. Instantiate a new variable that points to the new Web reference. D. Call the Web method and access the results using the variable created in option C.
Chapter 1 Review
33
Chapter Review To further practice and reinforce the skills you learned in this chapter, you can per form the following tasks: ■
Review the chapter summary.
■
Review the list of key terms introduced in this chapter.
■
Complete the case scenarios. These scenarios set up real-world situations involv ing the topics of this chapter and ask you to create a solution.
■
Complete the suggested practices.
■
Take a practice test.
Chapter Summary ■
Web services are Web-based applications that are used to expose data and func tionality in a distributed and interoperable environment.
■
The WebMethods framework is used to easily create Web services by attaching a WebMethod declaration to a public Web method.
■
The WebService attribute is used to specify certain key information about your Web services.
■
Web Services can be tested in one of two ways. You can access Web methods by loading the Web service page in a Web browser and drilling down to each Web method using the Web method help startup page. Or, you can create a test project that sets a reference to the .asmx file. You can then call each Web method using this reference and manipulate the results that are returned from the Web service.
Key Terms Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book. ■
attribute
■
Simple Object Access Protocol (SOAP)
■
template
34
Chapter 1 Review
■
Web Services Description Language (WSDL)
■
Extensible Markup Language (XML)
Case Scenarios In the following case scenarios, you apply what you’ve learned about how to validate input using regular expressions and how to process text files with different encoding types. You can find answers to these questions in the “Answers” section at the end of this book.
Case Scenario 1: Deciding to Use Web Services Your company maintains a large database of customer data. Customer service repre sentatives utilize a complex ASP.NET application to access and manipulate the data in this database. Your boss comes to you and tells you that one of your partner compa nies needs access to some of this customer data. She explains that it is necessary to expose only a portion of the data to this partner company. The partner company does have programming resources available to them and they are requesting that they be given access to the database through a SQL Server port. Your boss has asked what you think is the best way to handle this situation. What do you suggest?
Case Scenario 2: Testing your Web Service In the previous scenario, it was determined that a Web service was the correct distrib uted technology to utilize. You have now designed and created the Web service, and you are ready to let the partners begin accessing the data. You believe that the queries used to retrieve data for your Web methods are all valid, efficient, and well tested. Your boss now comes to you and asks whether you can publish the newly created Web service to your Web servers that day. What do you tell your boss?
Suggested Practices To help you successfully master the exam objectives presented in this chapter, com plete the following tasks: ■
Choose to utilize Web services as your distributed technology, unless your appli cation requires functionality only available with Remoting and .NET Enterprise Services.
Chapter 1 Review
35
■
Change the namespace of your Web service from the default value of http:// tempuri.org before the application is deployed to production.
■
Always test your Web services before making them accessible to the public. It is wise to create a test application that simulates the way clients will consume the data from your Web methods. Ensure that the data are inclusive and formatted properly in order to be useful to the consumer.
Take a Practice Test The practice tests on this book’s companion CD offer many options. For example, you can test yourself on just the content covered in this chapter, or you can test yourself on all the 70-529 certification exam content. You can set up the test so that it closely sim ulates the experience of taking a certification exam, or you can set it up in study mode so that you can look at the correct answers and explanations after you answer each question. MORE INFO
Practice tests
For details about all the practice test options available, see the section titled “How to Use the Prac tice Tests” in this book’s Introduction.
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions This chapter begins by reviewing the steps necessary to format and extend your Extensible Markup Language (XML) Web services. The .NET Framework version 2.0 provides a method-based way to alter the format of your Simple Object Access Proto col (SOAP) messages, giving you a finer level of control over the way your request and response messages are formatted. This formatting can be done by specifying the parameter encoding and encapsulation. You can also specify the binding that SOAP uses and customize the SOAP message using XML serialization. Lesson 2 focuses on the ability to implement SOAP headers. A custom SOAP header can be used to control how a request is executed. Lesson 3 moves on to the ability to implement custom SOAP extensions. This process involves creating a custom SOAP extension and then configuring it using a custom attribute.
Exam objectives in this chapter: ■
Configure SOAP messages. ❑
■
■
Configure the formatting of SOAP messages for a Web service method.
Implement SOAP headers. ❑
Add a custom SOAP header class.
❑
Create a public instance of the custom SOAP header class in a Web service class.
❑
Apply a SoapHeader attribute to a Web method.
❑
Access and process a SOAP header in the Web method.
❑
Set the direction of a SOAP header.
Implement SOAP extensions. ❑
Create a custom SOAP extension.
❑
Configure the SOAP extension. 37
38
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions
Lessons in this chapter: ■
Lesson 1: How to Configure SOAP Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
■
Lesson 2: How to Implement SOAP Headers . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
■
Lesson 3: How to Implement SOAP Extensions . . . . . . . . . . . . . . . . . . . . . . . . . 72
Before You Begin To complete the lessons in this chapter, you must have ■
A computer that meets or exceeds the minimum hardware requirements listed in the Introduction at the beginning of the book.
■
Microsoft Visual Studio 2005 and the .NET Framework 2.0.
■
Knowledge of programming in Microsoft Visual Basic .NET or C# and a basic understanding of XML.
■
Completed the lab provided in Lesson 1 of Chapter 1, “Creating an XML Web Service,” and know how to create a Web service project using Visual Studio 2005.
Lesson 1: How to Configure SOAP Messages
39
Lesson 1: How to Configure SOAP Messages This lesson begins by examining the different ways you can configure SOAP mes sages. The SOAP specification allows for several different formatting styles through the Style and Use attributes. The .NET Framework 2.0 allows you to configure these styles using the System.Web.Services.Protocols namespace. Further configuration can be done by specifying the parameter encoding and encapsulation. By default, the .NET Framework 2.0 supports the Web Services Interoperability (WS I) Basic Profile through the WebServicesBinding attribute. Using this attribute, you can assign multiple bindings to your Web service and assign a certain one to each Web method. Further customization of your SOAP messages can be done using the Sys tem.Xml.Serialization namespace and the XmlElement attribute. After this lesson, you will be able to: ■
Understand what information is included on the Service method help page.
■
Understand what basic elements are included in a SOAP message.
■
Specify paramater encoding and encapsulation.
■
Configure SOAP bindings using the WebServiceBinding attribute.
■
Customize a SOAP message with XML serialization.
Estimated lesson time: 60 minutes
Configuring SOAP Messages By default, each Web service page in your project will have a Service help page (see Figure 2-1). This Web page contains a listing of all the public Web methods available with your Web service. Each method includes a link and a description. The descrip tion displayed on this page comes directly from the Description property that was set with the WebMethod attribute (refer to Lesson 1 of Chapter 1). The link can be used to drill down to the Service method help page, which can be used to invoke a partic ular Web method (see Figure 2-2).
40
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions
Figure 2-1 After executing the project created in Lab 1 of Chapter 1, you should see a Service help page such as this
The Service Method Help Page The Service method help page (see Figure 2-2) not only contains a button that can be used to invoke your Web method, but it also provides sample request and response SOAP messages. Because the .NET Framework 2.0 supports both versions of SOAP (1.1 and 1.2), sample messages for each version are included on this page. By default, Web pages with an .asmx extension are handled by aspnet_isapi.dll, which is the standard Internet Server API (ISAPI) extension for the .NET Framework. This handling is seen if you go to the application configuration properties for your Web service (see Figure 2-3). Messages that are sent to this handler are forwarded to the .asmx handler, which takes care of handling the XML, SOAP, and Web Services Description Language (WSDL) for you. It essentially shields you, the developer, from all the nasty details behind the scenes. The .asmx handler inspects the Hypertext Transfer Protocol (HTTP) message and looks for information that tells it how to handle the SOAP request. Standard SOAP messages rely on an industry-defined schema known as WSDL to define what exactly goes into a SOAP message. The latest version of the SOAP specification is version 1.2, which is based on the XML Infoset. The purpose of the XML Infoset is to ensure that
Lesson 1: How to Configure SOAP Messages
41
the document is well-formed and satisfies certain namespace constraints. The previ ous version of the SOAP specification, version 1.1, was based on XML 1.0. The previ ous version of the SOAP specification relied exclusively on HTTP POST headers to transmit SOAP messages. The new version also allows for the HTTP GET header. The .NET Framework 2.0 supports both versions of the SOAP protocol because version 1.1 is still being used widely in the industry.
Figure 2-2
The Service method help page displayed by default when you create a Web method
Figure 2-3 The Internet Information Services (IIS) Application Mappings for the .asmx extension show that it maps to the aspnet_isapi.dll file
42
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions
SOAP Message Formats To get an idea of what a SOAP message built for the SOAP 1.2 specification looks like, you can look at a sample of the request message associated with the GetVendorList Web method, which was created in Lab 1 of Chapter 1: POST /mywebservice/service.asmx HTTP/1.1
ost: localhost
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
Because the .NET Framework 2.0 supports both the SOAP 1.1 and 1.2 specifications, you can also use the Service help page to see what the request message formatted with the SOAP 1.1 specification looks like: POST /MyWebService/MyWebService.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: http://www.mycompany/GetVendorList
NOTE
SOAP specification versions
The element prefix for messages created for SOAP version 1.2 is soap12. The prefix for messages created for SOAP version 1.1 is just soap. You will not be tested on the differences between each version, but it is useful for you to know how to recognize each version.
Both SOAP 1.1 and 1.2 messages contain the following three elements: This element is the highest level and it contains both the Header and Body elements.
■ Envelope
Lesson 1: How to Configure SOAP Messages
43
This element contains any metadata that describes what is in the Body element. This element is optional, but it must be placed before the Body element.
■ Header
This element is the main portion of the message, where all the data is located. This is also where most of the special formatting occurs.
■ Body
The elements in the SOAP message are used by the .asmx handler to handle message dispatching. Basically, the handler reads the values in these elements to figure out what to do. Therefore, if you want the handler to do something different with your SOAP request, you can alter the values in these elements. MORE INFO
The XML Protocol Working Group
If you are interested in learning more about SOAP and the latest specifications recommended and approved by the World Wide Web Consortium (W3C), you can refer to the Web page for the XML Protocol Working Group at http://www.w3.org/2000/xp/Group/.
The SOAP specification allows for two formatting options: Style and Use. Style is used when formatting the Body element. The Style attribute can be set with one of the fol lowing two values: In the early days of the SOAP specification, Remote Procedure Call (RPC) was the only formatting alternative. This style requires that the Body element fol low a very specific schema and must contain a set of parameters. More than likely the only reason you would need to utilize the RPC style was if you knew you were interfacing to a client that required the RPC style.
■ RPC
This is the default style used when creating Web services with Visual Studio. It was introduced as a way to allow for unstructured documents, and it allows for greater flexibility in the way it handles parameters.
■ Document
The .NET Framework 2.0 provides an attribute mechanism to alter the formatting of your SOAP messages. This mechanism allows you to specifically control the way the SOAP message will be handled without modifying the message directly. The Sys tem.Web.Services.Protocols namespace includes the SoapDocumentMethodAttribute (see Table 1-3) and SoapRpcMethodAttribute (see Table 1-4) classes, which represent the SoapDocumentMethod and SoapRpcMethod. These methods are used to format SOAP messages; as you can guess, the SoapDocumentMethod is used to format messages using the Document style, and SoapRpcMethod is used to format messages using the RPC style.
44
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions
Real World Sara Morgan I recently did contract work for a company that consumed Web services to access remote data in real time. The data was remote because it was provided by the company’s headquarters, which was located in another state within the United States. The local office had no real development staff and relied on con tractors to make changes to some of the local applications they had developed internally. For some reason, the company headquarters decided to use an RPC-encoded style to implement their Web services. This resulted in a tight coupling between the Web service and the client. Every time a developer at the corporate office would make a change to one of the Web method interfaces, the change would cause the local company’s application to break. The change could have been a simple one in which a parameter previously defined as a double was changed to be an integer. For RPC-encoded messages, the type is encoded within the mes sage and so a change to the parameter type breaks the contract between the Web service and the client. Every time this problem occurred, which for a period of time was about once a month, the local office would have to call me in to look at the problem. The fix usually just involved getting a new copy of the WSDL from the corporate office, but the fact that the tight coupling caused such problems when changes occurred demonstrated how fragile such a situation can be. This situation dem onstrates that there is no real benefit to using an RPC-encoded style when changes to the interface are possible.
Specifying Parameter Encoding As stated earlier, the SOAP specification allows for two formatting options: Style and Use. The Use attribute relates to Web method parameters and return values. By spec ifying encoding values, you effectively are telling SOAP to attach certain attributes to an XML element so that it can be identified correctly. In this case, it can be set with one of the following two encoding values: This encoding value is the default, and it indicates that the SOAP mes sage is formatted exactly as dictated by the schema.
■ Literal
Lesson 1: How to Configure SOAP Messages
45
This encoding value indicates that the SOAP message is formatted as specified by XML Schema definitions specified in the WSDL. SOAP messages that are formatted with the SoapRpcMethod attribute always use the encoded value.
■ Encoded
NOTE
Parameter encoding enumeration
In addition to the Literal and Encoded values, the .NET Framework 2.0 allows for a third enumeration value of Default. The Default value indicates that the SOAP message Use attribute will be set to an empty string.
The System.Web.Services.Description namespace includes a SoapBindingUse enumera tion, which allows you to specify what encoding style your Web method will utilize. To utilize this enumeration, you must include a reference to this namespace at the top of your Web service class file. To specify parameter formatting using the encoding style for a GetVendorList Web method, you would use the following code: 'VB _ Public Sub GetVendorList() Dim sqlConn As New SqlConnection Dim rdr As XmlReader Dim ret As String = "" '... End Sub //C# [SoapDocumentMethod(Use=SoapBindingUse.Encoded)] [WebMethod(Description="Retrieve a list of active vendors ordered by name")] public string GetVendorList() { SqlConnection sqlConn;
XmlReader rdr;
string ret = "";
//...
}
Specifying Parameter Encapsulation The System.Web.Services.Protocols namespace includes a SoapParameterStyle enumera tion that can be used to specify how parameters are formatted in a SOAP message. This involves a process known as encapsulation, in which certain members of the object will be hidden and only what is needed is exposed to the client. It can be used to specify one of the following values:
46
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions
All parameters will be placed within individual XML elements directly below the Body element of the SOAP message.
■ Bare
Indicates that this method will use the SoapParameterStyle that is applied to the entire Web service. The method will thus essentially inherit its value from the Web service. The default value for a Web service is Wrapped.
■ Default
All parameters will be wrapped within a single XML element and placed below the Body element of the SOAP message.
■ Wrapped
To specify parameter encapsulation using the SoapParameterStyle enumeration you must ensure that there is a reference to the System.Web.Services.Protocols namespace at the top of your Web service class file. You can then use the following code to specify the parameter using the SoapDocumentMethod attribute: 'VB _ Public Sub GetVendorList()
Dim sqlConn As New SqlConnection
Dim rdr As XmlReader
Dim ret As String = ""
'...
End Sub //C# [SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare, Use=SoapBindingUse.Literal)] [WebMethod(Description="Retrieve a list of active vendors ordered by name")] public string GetVendorList() { SqlConnection sqlConn;
XmlReader rdr;
string ret = "";
//...
}
Configuring SOAP Bindings The .NET Framework 2.0 supports the WS-I Basic Profile. The Basic Profile was devel oped by the Web Services Interoperability Organization with the goal of ensuring that Web services are built for the “real world.” MORE INFO
The Web Services Interoperability Organization
You can obtain more information about the WS-I from the following Web site: http://www.ws-i.org/ deliverables/workinggroup.aspx?wg=basicprofile.
Lesson 1: How to Configure SOAP Messages
47
You can build a Web service that complies with the WS-I Basic Profile by using the WebServiceBinding attribute on your Web service class file. The WebServiceBindingAt tribute class (see Table 2-1) allows you to declare one or more bindings for your Web service. Bindings represent a method of ensuring that the Web service conforms to cer tain specifications. Web services built using the Web services template are WS-I-compliant by default, as demonstrated by the WebServiceBinding attribute that was added to the Service.asmx file in Lab 1 in Chapter 1.The following code can be used to make the Web service cre ated in the last chapter WS-I-compliant and ensure that compliance claims are included in the WSDL that accompanies this Web service: 'VB _ //C# [WebServiceBinding(ConformsTo=WsiProfiles.BasicProfile1_1, EmitConformanceClaims=true)] [WebService(Namespace = "http://tempuri.org/")]
Table 2-1
Public Properties of the WebServiceBindingAttribute Class
Name
Description
ConformsTo
Retrieves or sets the WS-I specification to which the binding conforms. This property is new for the .NET Framework 2.0.
EmitConformanceClaims
Also new with the .NET Framework 2.0, this property retrieves or sets a Boolean value indicating whether the binding emits conformance claims.
Location
Retrieves or sets the location where the binding is defined. By default, this property is set to the Uniform Resource Locator (URL) for the Web service.
Name
Retrieves or sets the name of the binding. By default, this prop erty is the name of the Web service with the word Soap appended.
Namespace
Retrieves or sets the namespace of the binding. By default, this property is set to http://tempuri.org.
TypeId
Retrieves a unique identifier used to identify the attribute when it is implemented in a derived class.
48
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions
Specifying a Binding for Each Web Method In some cases you might want to specify multiple bindings for your Web service. To determine which binding is associated with each Web method, you use the Binding property for SoapDocumentMethod attribute. The following code is an example of a Web service that uses two different bindings and assigns a separate binding to each of its two Web methods: 'VB WebServiceBinding(Name := "BindingCompany1", _
Namespace := "http://www.company1.com/MyBinding"), _
WebServiceBinding(Name := "BindingCompany2", _
Namespace := "http://www.company2.com/MyBinding"> _
Public Class MultipleBindings _
Public Function BindingMethod1() As String
Return "Calling Binding Method 1"
End Function
_
Public Function BindingMethod2() As String
Return "Calling Binding Method 2"
End Function
End Class //C# [WebServiceBinding(Name="BindingCompany1",
Namespace="http://www.company1.com/MyBinding")]
[WebServiceBinding(Name="BindingCompany2",
Namespace="http://www.company2.com/MyBinding")]
public class MultipleBindings { [SoapDocumentMethod(Binding="BindingCompany1")] [WebMethod(Description="Binding used when calling the first Web method")] public string BindingMethod1() { return "Calling Binding Method 1";
}
[SoapDocumentMethod(Binding="BindingCompany2")] [WebMethod(Description="Binding used when calling the second Web method") ] public string BindingMethod2() { return "Calling Binding Method 2";
}
}
Lesson 1: How to Configure SOAP Messages
49
Customizing SOAP Messages with XML Serialization The System.Xml.Serialization namespace contains the XmlSerializer class, which is the underlying transport mechanism for Web services built with the .NET Framework 2.0. This class is what is used by the .asmx handler to map the XML in a SOAP mes sage to an actual .NET object through a process called serialization. Deserialization is the process of generating a .NET object from XML. The XmlSerializer class can be used to customize SOAP messages using the XmlEle ment attribute. By controlling the XML, you can apply special attributes to class mem bers and method parameters. Table 2-2 lists the public properties available with the XmlSerializer class. Table 2-2 Public Methods of the XmlSerializer Class
Name
Description
CanDeserialize
Gets a Boolean value that indicates whether the XML docu ment can be deserialized.
Deserialize
Used to deserialize the document for a Stream, TextReader, XmlReader, XmlSerializationReader, or String.
Equals
Used to determine whether two objects are equal.
FromMappings
Retrieves an instance of the XmlSerializer class from the spec ified mappings.
FromTypes
Retrieves an array of XmlSerializer object types.
GenerateSerializer
Returns an assembly of serializers.
GetHashCode
Used in hashing algorithms and represents a hash function for a particular type.
GetType
Retrieves the type of the current instance.
GetXmlSerializerAssemblyName
Retrieves the name of the assembly used to serialize or deserialize the types.
Serialize
Used to serialize an object into an XML document.
ToString
Retrieves a string that represents the object it references.
50
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions
Changing an Element Name Using Serialization You can override the serialization of your objects and alter the name of certain fields using the XmlElementAttribute class. Therefore, you can choose what to set as the name of an element based on the value of some variable, which allows you a finer level of control over the XML your Web method generates. Assume you have a Vendor class that can be used to generate XML for one of your Web methods. The Vendor class might appear as follows: 'VB Public Class Vendor
Public VendorID As Integer
Public AccountNumber As String
Public Name As String
End Class //C# public class Vendor {
public int VendorID;
public string AccountNumber;
public string Name;
}
If you were to use this class to generate XML for your Web method, the output might look like the following:
9
ADATUM0001
A. Datum Corporation
Suppose that you want to alter the element name AccountNumber to read ReferenceNumber. To do this renaming, you would first need to add references to the Sys tem.Xml.Serialization and System.IO classes such as the following: 'VB Imports System.Xml.Serialization
Imports System.IO
//C# using System.Xml.Serialization;
using System.IO;
You would then need to create an instance of the XmlElementAttribute class and assign an alternative name for your element. You would then add your element to the
Lesson 1: How to Configure SOAP Messages
51
XmlAttributes class and create an instance of the XmlAttributesOverrides class that can be used to specify the element to change. After the Vendor fields were assigned values, a new instance of the XmlSerializer class could be used to serialize the XML stream into an XML document. The following code could be used to accomplish this task: 'VB ' Create an instance of the XmlElementAttribute class
Dim myElementAttribute As New XmlElementAttribute
' Define the alternative element name
myElementAttribute.ElementName = "ReferenceNumber"
' Create an instance of the XmlAttributes class
Dim myAttributes As New XmlAttributes
' Add the newly created Element attribute
myAttributes.XmlElements.Add(myElementAttribute)
' Create an instance of the XmlAttributeOverrides class
Dim myOverrides As New XmlAttributeOverrides
' Specify the object to be overridden
myOverrides.Add(GetType(Vendor), "AccountNumber", myAttributes)
' Create an instance of the XmlSerializer class
Dim mySerializer As New XmlSerializer(GetType(vendor), myOverrides)
' Create a new instance of the Vendor class and set the values
Dim Ven As New Vendor
Ven.VendorID = 9
Ven.AccountNumber = "ADATUM0001"
Ven.Name = "A. Datum Corporation"
' Creates a StreamWriter to write the XML stream to.
Dim myWriter As New StreamWriter("Vendor.xml")
' Serialize the object to an XML document
mySerializer.Serialize(myWriter, Ven)
//C# // Create an instance of the XmlElementAttribute class XmlElementAttribute myElementAttribute = new XmlElementAttribute(); //Define the alternative element name myElementAttribute.ElementName = "ReferenceNumber"; //Create an instance of the XmlAttributes class XmlAttributes myAttributes = new XmlAttributes(); //Add the newly created Element attribute
52
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions
myAttributes.XmlElements.Add(myElementAttribute); //Create an instance of the XMLAttributeOverrides class
XmlAttributeOverrides myOverrides = new XmlAttributeOverrides();
//Specify the object to be overridden
myOverrides.Add(typeof(Vendor), "AccountNumber", myAttributes);
//Create an instance of the XmlSerializer class
XmlSerializer mySerializer = new XmlSerializer(typeof(Vendor), myOverrides);
//Create a new instance of the Vendor class and set the values
Vendor Ven = new Vendor();
Ven.VendorID = 9;
Ven.AccountNumber = "ADATUM0001";
Ven.Name = "A. Datum Corporation";
// Creates a StreamWriter to write the XML stream to.
StreamWriter myWriter = new StreamWriter("Vendor.xml");
//Serialize the object to an XML document
mySerializer.Serialize(myWriter, Ven);
Lab 1: How to Configure SOAP Messages This lab steps you through the process of configuring SOAP messages using the method-based alternative available with the .NET Framework 2.0. You will add to the Web service project created in Lab 1 of Chapter 1 and create two Web methods that can be used to compare the results of encoding your SOAP messages. The actual code used in the Web method is not important. What is important is the format of the SOAP message depending on which encoding method is utilized. By completing this lab, you will be able to compare the resulting SOAP messages for each Web method. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. 1. Open Visual Studio 2005 and load the project named MyWebService that was created in Lab 1 of Chapter 1. 2. Right-click the MyWebService.asmx file in Solution Explorer and click View Code. Remove the following line of code from near the top of the MyWebSer vice.asmx class file: 'VB _ //C# [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
Lesson 1: How to Configure SOAP Messages
53
3. Add the following code below the last Web method in the MyWebService.asmx class file: 'VB _ Public Function GetReturnValue(ByVal parm1 As Integer, ByVal parm2 As String) As String Return Convert.ToString(parm1) + parm2 End Function _
_
Public Function GetReturnValueEncoded(ByVal parm1 As Integer, _
ByVal parm2 As String) As String Return Convert.ToString(parm1) + parm2
End Function
//C# [WebMethod(Description = "Get a Return Value, but use the default use of Literal")]
public string GetReturnValue(int parm1, string parm2)
{
return Convert.ToString(parm1) + parm2;
}
[SoapDocumentMethod(Use = SoapBindingUse.Encoded)]
[WebMethod(Description = "Get a Return Value, but set the use attribute to be encoded")]
public string GetReturnValueEncoded(int parm1, string parm2)
{
return Convert.ToString(parm1) + parm2;
}
4. Build the project, and resolve any errors. 5. Right-click the MyWebService project in Solution Explorer and click Set As StartUp Project. 6. Right-click the MyWebService.asmx file in Solution Explorer and click Set As Start Page. 7. Press Ctrl+F5 to start without debugging. Your Web browser should open and point to the MyWebService.asmx file in your local Web service project. You will be brought to the Service help page for your Web service. It should display links to the following three methods: GetReturnValue, GetReturnValueEncoded, and GetVendorList. 8. Click the link for each of the first two methods and you will be brought to the Service method help page for that Web method. From here you can compare the sample SOAP messages for each Web method. Note that the Body element for the GetReturnValueEncoded Web method will appear as follows:
54
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions
int string
9. Alternatively, the Body element for the GetReturnValue Web method will appear as follows:
int
string
Lesson Summary ■
The Service method help page can be used to invoke each public Web method. It also displays sample request and response SOAP messages in both the SOAP 1.1 and 1.2 specifications.
■
Envelope is the highest element in a SOAP message. It can contain an optional Header element and must contain a required Body element.
■
Style and Use are the two formatting attributes used to format SOAP messages.
■
RPC and Document are the two values available for the Style attribute. The .NET Framework 2.0 provides an attribute mechanism for altering these values. To do so you would apply the SoapRpcMethod and SoapDocument attributes.
■
Literal and Encoded are the two values available for specifying parameter encod ing as it applies to the Use attribute. Literal is the default value.
■
Bare, Default, and Wrapped are the enumeration values available for specifying parameter encapsulation.
■
One or more bindings can be specified with the WebServiceBindingAttribute class. This capability allows you to make your Web service components conform to the WS-I Basic Profile.
■
Web services utilize a two-step process known as serialization and deserializa tion to map XML generated by Web methods to .NET objects. The XmlSerializa tion class, which is part of the System.Xml.Serialization namespace, can be used to customize the SOAP messages generated by your Web methods.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “How to Configure SOAP Messages.” The questions are also available on the companion CD if you prefer to review them in electronic form.
Lesson 1: How to Configure SOAP Messages
NOTE
55
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. What are the three main sections for a SOAP message? A. SOAP messages formatted with the SOAP 1.1 specification might contain a Header, Action, and Body element. B. SOAP messages formatted with the SOAP 1.1 or 1.2 specification might contain a Header, Action, and Body element. C. SOAP messages formatted with the SOAP 1.1 or 1.2 specification must con tain an Envelope, Header, and Body element. D. SOAP messages formatted with the SOAP 1.1 or 1.2 specification might contain an Envelope, Header, and Body element. 2. What is the purpose of the Encoded value as it applies to SOAP message formatting? A. This encoding value indicates that the SOAP message will be formatted as specified by XML Schema definitions in the WSDL. B. This encoding value indicates that the Web method will be secured using an encryption method. C. This is the default value used to specify parameter encoding. D. This is the default value used to specify parameter encapsulation. 3. Identify the Visual Basic code used to specify that a Web service complies with the WS-I Basic Profile. A.
B.
C. D.
4. What public method from the XmlSerializer class is used to serialize an object into an XML document? A. ToSerial B. Deserialize C. Serialize D. XmlSerialize
56
Chapter 2
Extending XML Web Services with SOAP Formatting, Custom Headers, and Extensions
Lesson 2: How to Implement SOAP Headers This lesson examines the different ways to implement a custom SOAP header. It begins by defining what a SOAP header is and what information is typically included in one. It then demonstrates how a header block can be specified through the WebMethod attribute. You will see how to create a custom class file that inherits from the System.Web.Services.Protocols.SoapHeader namespace. The Web method that imple ments the custom SOAP header will use the SoapHeader attribute to reference the cus tom class file. After this lesson, you will be able to: ■
Understand what basic elements are included in a SOAP header.
■
Know how to add a custom SOAP header class.
■
Build a client that processes the SOAP header.
■
Know how to handle unknown SOAP headers.
Estimated lesson time: 45 minutes
What Is a SOAP Header? Headers are one of the three elements that a SOAP message can contain. According to both the SOAP 1.1 and 1.2 specifications, the Header element is optional. You might recall from Lesson 1 that a SOAP message can contain up to three elements: the Enve lope (or the root element), the Header (the only optional element), and a Body (which contains the bulk of the message). When the Header is included, it should be placed directly below the Envelope element. Typically, the Header element should contain metadata, which describes what will be contained in the body of the message. The element can also specify how the SOAP request should be processed. In many cases the element contains information about authentication and transactions. According to the SOAP specification, there are three attributes that can be used in a Header element. They are as follows: Specifies how data should be encoded. This attribute is applica ble when the header is used to handle authentication requests.
■ encodingStyle
Specifies the actual node that will process the header block. For the SOAP 1.2 specification, this attribute was renamed to role.
■ actor/role
Binary value that specifies whether the block conforms to cer tain specifications.
■ mustUnderstand
Lesson 2: How to Implement SOAP Headers
57
The Header element can contain multiple header blocks, but each header block must have its own unique namespace. The following is an example of what a single SOAP header block might look like for a Web method named GetPurchaseTotal.
D.
2. Which attribute must be used when configuring an IPC channel? (Select the best answer.) A. portName B. port C. portNumber D. useIPAddress
164
Chapter 4 Review
Chapter Review To further practice and reinforce the skills you learned in this chapter, you can per form the following tasks: ■
Review the chapter summary.
■
Review the list of key terms introduced in this chapter.
■
Complete the case scenarios. These scenarios set up real-world situations involv ing the topics of this chapter and ask you to create a solution.
■
Complete the suggested practices.
■
Take a practice test.
Chapter Summary ■
Remoting is a distributed technology that allows you to efficiently call objects located on different machines as if they resided on the same machine.
■
You can create single call or singleton objects, which are server-activated, or you can create a client-activated object.
■
Remote objects are hosted within a console application, Windows executable, ASP.NET application, or Windows service.
■
Remote applications can be configured programmatically by using the RemotingConfiguration class.
■
Remote applications can also be configured using a configuration file. This is the most flexible option because it allows you to make changes without requiring a code recompile.
Key Terms Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book. ■
access control list (ACL)
■
application domain
■
asynchronous
■
Common Language Runtime (CLR)
165
Chapter 4 Review
■
supercomputer
■
Uniform Resource Identifier (URI)
Case Scenarios
In the following case scenarios, you will apply what you’ve learned about creating a remoteable type and configuring your remoting application. You can find answers to these questions in the “Answers” section at the end of this book.
Case Scenario 1: Suggesting a Distributed Technology You are the lead developer for a scientific research institute. One of the scientists has requested that you design an application to process large amounts of data collected from several of the labs’ equipment. The data collected will need to be analyzed imme diately and any data outliers reported immediately to the responsible scientist. What type of distributed technology would be appropriate to use in this scenario?
Case Scenario 2: Using a Configuration File to Configure Your Remoting Application Expanding on Case Scenario 1, imagine that you are the lead developer for the scien tific research institute and you are now developing the remoting application proposed in that scenario. Because data needs to be collected from several pieces of equipment and very often there is a need to swap out this equipment, you realize that it is often necessary to change the port assignments assigned to the client applications. In order to effectively handle this situation, what method do you suggest should be used to configure your remoting solution?
Suggested Practices ■
Host your remote objects in IIS as an ASP.NET application so that you can take advantage of all the built-in functionality available. This functionality is espe cially true for security because you can take advantage of SSL for a secure line.
■
Use the binary formatter whenever possible because it is typically faster than the SOAP formatter.
166
Chapter 4 Review
■
Consider writing to an event log or log file when your object starts up or encoun ters an error. Include a timestamp, name of the remote object, and a port num ber. This information can be very useful for identifying and debugging problems with remoting.
Take a Practice Test The practice tests on this book’s companion CD offer many options. For example, you can test yourself on just the content covered in this chapter, or you can test yourself on all the 70-529 certification exam content. You can set up the test so that it closely sim ulates the experience of taking a certification exam, or you can set it up in study mode so that you can look at the correct answers and explanations after you answer each question. MORE INFO
Practice tests
For details about all the practice test options available, see the section titled “How to Use the Prac tice Tests” in this book’s Introduction.
Chapter 5
Creating a Remoting Client Application This chapter elaborates on the process of using Microsoft Visual Studio 2005 to create a client application capable of consuming services provided by a remote object. You learn about proxies, activating remote objects, and accessing remote methods. You also learn about channels. Finally, you learn how to manage the remoting process through a configuration file.
Exam objectives in this chapter: ■
Create a client application to access a remote object. ❑
Create a remote object.
❑
Configure a client application programmatically.
❑
Configure a client application manually by using configuration files.
❑
Access the remoting service by calling a remote method.
❑
Call a remote method synchronously in a client application.
Lessons in this chapter: ■
Lesson 1: Creating a Client Application to Access a Remote Object . . . . . . . 169
■
Lesson 2: How to Configure a Client Application Using a Configuration File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
■
Lesson 3: How to Access a Remote Method . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Before You Begin To complete the lessons in this chapter, you must have ■
A computer that meets or exceeds the minimum hardware requirements listed in the Introduction at the beginning of the book.
■
Visual Studio 2005 and the .NET Framework 2.0.
167
168
Chapter 5
Creating a Remoting Client Application
■
Familiarity with how to create a console application using either Microsoft Visual Basic or Microsoft Visual C#.
■
Familiarity with how to add references to projects.
Real World Shannon Horn Last year I worked on a project for a client located in Phoenix, Arizona. The client creates distributed applications for medical facilities. The applications are used to manage patient data for the facility. The applications provide a distinct user interface for each department in the medical facility, and each user interface can have one or more templates applied to it that customize the look and feel of the user interface. The templates used by the application function very similarly to cascading stylesheets used in Web applications. Furthermore, each user inter face is created as a separate .NET application and uses .NET remoting to connect to the central business tier and data tier, which reside in different application domains. The application also provides synchronization for doctors that use Microsoft Windows mobile devices to collect data during patient visits. However, integrat ing mobile devices involves a bit more complexity because, as of version 2.0, the .NET Compact Framework does not support .NET remoting. The use of .NET remoting to connect the distributed components of my client’s medical application proved to be a wise one. .NET remoting offers a high level of flexibility when adding new user interfaces to accommodate new departments and types of users. .NET remoting also provides the ability to easily relocate the components of the distributed application, provides very granular control over permissions and security, and fits the bill perfectly for this client because each component of the application is managed by a different development team.
Lesson 1: Creating a Client Application to Access a Remote Object
169
Lesson 1: Creating a Client Application to Access a Remote Object This lesson describes and demonstrates the process of creating a client application that consumes the services of a remote object using Visual Studio 2005. Successfully creating a remoting client application includes configuring the object activation mode and the communication channel. After this lesson, you will be able to: ■
Programmatically create and configure an application to serve as a client that con sumes services of a remote object.
■
Identify how proxy objects are used to relay data between remoting endpoints.
■
Identify and configure communication channels and the benefits inherent in each type of channel.
■
Describe how formatters are used to serialize and deserialize data in transit between remoting endpoints.
Estimated lesson time: 60 minutes
How to Create a Client Instance of a Remote Object Before diving headlong into a demonstration on creating an instance of a remote object in a client application, you must gain some background knowledge. Under standing the terminology introduced in the following sections is vital to mastering the client side of .NET remoting.
What Is a Proxy Object? A proxy is a substitute for a real item and is typically used as a security barrier between a real item and other items. For example, a proxy server is commonly used as a gate way between a corporate network and the Internet to provide a higher level of security when the network is exposed to the Web. As it relates to .NET remoting, in general terms, when a client application accesses a remote object, a proxy object is created on the client that represents and behaves in the same manner as the real remote object. There are two types of remote objects, mar shal-by-value (MBV) and marshal-by-reference (MBR). When an MBV remote object is accessed by a client application, a copy of the remote object is serialized on the server, passed to the client application, and deserialized on
170
Chapter 5
Creating a Remoting Client Application
the client, where it becomes a local copy of the remote object for the client application. Because the MBV object is copied local to the client application, a proxy is not neces sary to access the MBV object. MBV remote objects typically provide better perfor mance than MBR remote objects. However, after an MBV remote object is copied to the client, it might no longer be able to access resources available only on the server. Conversely, when an MBR remote object is accessed by a client application, the remote object is instantiated on the server and a reference to the object is passed to the client application. A proxy object is created on the client that represents and behaves like the MBR remote object on the server. All calls made to the remote object by the client application are made to the proxy object that is local to the client appli cation. The proxy object, in turn, serializes the call to the remote object and passes it to the remote object. The server receives the call from the client proxy, deserializes it, and forwards the call to the remote object on the server. The response from the remote object on the server is serialized by the server, passed back to the client proxy, and then passed to the client application. Hence, the proxy object gives the client application the impression that the remote object is local to the client application. MBR remote objects don’t normally perform as fast as MBV remote objects but do provide access to resources that reside on the server. In terms of the .NET Framework as previously described, a proxy object created to represent a remote object is called a transparent proxy. A transparent proxy is derived from the System.Runtime.Remoting.Proxies.RealProxy class. The RealProxy class is a managed class, hence transparent proxies are also managed by the Common Lan guage Runtime (CLR). Furthermore, the RealProxy class can be extended to create custom proxy classes.
Discovering Application URLs The first step in consuming the services of a remotable object is to locate and gather information about the remotable object. The process of gathering information about a remote object or service is commonly referred to as discovery. It can be challenging to discover information about a Web service created by a third party due to the wide open environment of the Web. However, .NET remoting objects are most commonly used in intranet environments and, as such, information about remotable objects is typically well known to the developers of an intranet that hosts remotable objects. Beyond the name of the remote object, the key piece of information that needs to be discovered about a remotable object is the application URL (Uniform Resource Locator).
Lesson 1: Creating a Client Application to Access a Remote Object
171
URLs are a form of Uniform Resource Identifier (URI) and the standard method used to identify a unique location for a resource using Internet protocols. The URL stan dard was created by Timothy Berners-Lee, the founder of the W3C (http:// www.w3.org/), and is governed by the W3C and the Internet Engineering Task Force (http://www.ietf.org). The URLs used in the code examples are shown here. Application URLs http://localhost:9000/Person.rem tcp://localhost:9000/Person.rem
The syntax of a URL consists of four segments. The first segment identifies the proto col to be used. In the preceding examples, http and tcp are the protocols being used. The second segment identifies the server where the URL is located. In the preceding examples, localhost is the server. The third segment identifies the port to be used. The default port used by Web servers for the Hypertext Transfer Protocol (HTTP) protocol is port 80. In the preceding examples, 9000 is the port. The fourth segment identifies the unique identifier for the resource known as the URI. In the preceding examples, Person.rem is the URI. MORE INFO
URIs, URNs, and URLs
For more information on URIs, URNs, and URLs, visit the overview provided by the W3C at http:// www.w3.org/Addressing/.
How to Configure a Client Application Programmatically The .NET Framework provides classes and methods to assist in programmatically configuring a client application to access a remote object. These classes are located in the System.Runtime.Remoting namespace and child namespaces. The following topics discuss how to programmatically configure a .NET remoting client application.
Configuring the Communication Channel The next step in the process of preparing to create a client instance of a remote object is to determine the communication channel to be used. A communication channel establishes a communication conduit or connection between two endpoints using a particular protocol. In terms of .NET remoting, a channel is used to communicate across remoting boundaries such as application domains or computers.
172
Chapter 5
Creating a Remoting Client Application
Channels can be used to send messages as they commonly do from a client. Channels can be used to listen for and receive incoming messages as they commonly do from a server. Additionally, a single channel can be used to both send and receive messages. As a practical analogy, a communication channel is similar to a television channel or radio channel. A signal is transmitted using a television or radio channel. If you would like to consume a particular television or radio signal, you must tune to the channel on which the signal is transmitted. The .NET Framework includes three prebuilt com munication channels: the HTTP channel, the Transmission Control Protocol (TCP) channel, and the interprocess communication (IPC) channel. Additionally, custom communication channels can be created by implementing the channel interfaces. A client application makes calls to methods on a remote object and receives the results of method calls by transporting messages across a communication channel. Messages can include advanced parameters and types that cannot be transported across a chan nel in their native form. To remedy this situation, all messages transported over a com munication channel are serialized before being transported and deserialized on arrival and processing. There are points along a communication channel where messages are prepared for serialization and deserialization as they are transported. These points are known as sinks. There are several types of sinks used by each channel. Sinks utilize serialization formatters to perform serialization and deserialization. The type of serialization implemented is determined based on the type of channel utilized. A remote object can enable communication using multiple types of channels simultaneously, but the cli ent application must use one of the communication channels implemented by the remote object to establish communication with the remote object. Recall the televi sion or radio analogy where you, as a consumer, must tune to the correct channel to receive a desired signal. The most common formats used to serialize messages transported across channels are binary format and Simple Object Access Protocol (SOAP) format. Binary format is more compressed than SOAP format but is not very portable. If portability is a con cern, such as when transporting messages over the Web, the SOAP format should be used. SOAP, being an Extensible Markup Language (XML) grammar, is extremely por table. However, in contrast to the binary format, the SOAP format is considered bloated.
Lesson 1: Creating a Client Application to Access a Remote Object
173
The .NET Framework provides classes in namespaces under the System.Runtime .Remoting.Channels namespace that represent each channel. To utilize a communica tion channel, an instance of a communication channel class must be created. After you determine the type of channel to be used and create an instance of the channel, the channel instance must be registered with the .NET Framework remoting system on the client machine. When you register a channel with the .NET Framework remoting sys tem, the remoting system enables the channel for communication. The System.Runtime .Remoting.Channels.ChannelServices class exposes static (Shared) members that are used to manage channel registration. The HTTP channel is used to transport messages across remoting boundaries using HTTP. The HTTP channel is instantiated on the client using the System.Runtime .Remoting.Channels.Http.HttpClientChannel class. The HttpClientChannel class can be configured to transport messages into either binary format or SOAP format. However, the HTTP channel is most commonly used with a SOAP formatter.
HTTP
The primary justification in using the HTTP channel to transport messages is the wide support for the HTTP protocol. Most Web servers open port 80 as the default port for sending and receiving HTTP messages to enable communication over the Web. Due to this arrangement, use of the HTTP channel enables .NET remoting without firewall blocks. The HTTP channel is typically used when remotable objects are hosted using Microsoft Internet Information Services (IIS). Instantiating the HttpClientChannel Class 'VB Imports Imports Imports Imports Imports
System System.Runtime System.Runtime.Remoting System.Runtime.Remoting.Channels System.Runtime.Remoting.Channels.Http
Namespace MSLearning.Chapter5.Standard.ClientHttp Module HttpModule Sub Main() ' Create an instance of the HTTP client channel. Dim channel As HttpClientChannel = New HttpClientChannel() ' Register the channel with the remoting system.
ChannelServices.RegisterChannel(channel, False)
' more code to follow...
End Sub
174
Chapter 5
Creating a Remoting Client Application
End Module
End Namespace
//C# using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Http;
namespace MSLearning.Chapter5.Standard.ClientHttp {
class Program
{
static void Main(string[] args)
{
// Create an instance of the HTTP client channel. HttpClientChannel channel = new HttpClientChannel(); // Register the channel with the remoting system. ChannelServices.RegisterChannel(channel,false); // more code to follow...
}
}
}
The TCP channel is used to transport messages across remoting boundaries using TCP. The TCP channel is instantiated using the System.Runtime.Remoting.Chan nels.Tcp.TcpServerChannel class. The TcpServerChannel class can be configured to trans port messages into either binary format or SOAP format. The TCP channel is most commonly used with a binary formatter. Unlike the HTTP channel, the TCP channel can easily be blocked by firewalls and an open and valid port must be selected to com municate with the remote object.
TCP
Instantiating the TcpClientChannel Class 'VB Imports Imports Imports Imports Imports
System
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
Namespace MSLearning.Chapter5.Standard.ClientTcp Module TcpModule Sub Main()
Lesson 1: Creating a Client Application to Access a Remote Object
175
' Create an instance of the TCP client channel.
Dim channel As TcpClientChannel = New TcpClientChannel()
' Register the channel with the remoting system.
ChannelServices.RegisterChannel(channel, False)
' more code to follow... End Sub End Module End Namespace //C# using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
namespace MSLearning.Chapter5.Standard.ClientTcp
{ class Program { static void Main(string[] args)
{
// Create an instance of the TCP client channel.
TcpClientChannel channel = new TcpClientChannel();
// Register the channel with the remoting system.
ChannelServices.RegisterChannel(channel, false);
// more code to follow... } } }
The IPC channel is used to transport messages using Windows IPC. IPC is used to transport messages across remoting boundaries such as application domains that reside on the same computer. The IPC channel outperforms the HTTP channel and the TCP channel when remoting across boundaries on the same computer.
IPC
The IPC channel is instantiated using the System.Runtime.Remoting.Channels.Ipc.IpcServerChannel class. The IpcServerChannel class can be configured to transport mes sages into either binary format or SOAP format. The IPC channel is most commonly used with a binary formatter. Instantiating the IpcClientChannel Class 'VB Imports System Imports System.Runtime
176
Chapter 5
Creating a Remoting Client Application
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Ipc
Namespace MSLearning.Chapter5.Standard.ClientIpc Module IpcModule Sub Main() ' Create an instance of the IPC client channel. Dim channel As IpcClientChannel = New IpcClientChannel() ' Register the channel with the remoting system. ChannelServices.RegisterChannel(channel, False) ' more code to follow...
End Sub
End Module
End Namespace
//C# using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Ipc;
namespace MSLearning.Chapter5.Standard.ClientIpc {
class Program
{
static void Main(string[] args)
{
// Create a new instance of the IPC client channel. IpcClientChannel channel = new IpcClientChannel(); // Register the channel with the remoting system. ChannelServices.RegisterChannel(channel, false); // more code to follow...
}
}
}
Remote Object Activation Thus far we have mentioned that to create an instance of a remote object on a client, you must discover information about the remote object including the object name and the URL where the object is hosted. Typically, discovery should not be difficult
Lesson 1: Creating a Client Application to Access a Remote Object
177
for remote objects, particularly those that are hosted on your local intranet. We also mentioned that you must configure a channel for communicating with the remote object. Code on the client that creates an instance of the remote object does not compile unless it has access to the remote object during compilation. The remote object is accessible to code when using Visual Studio by adding a reference to the remote object for the project being compiled. The process of adding a reference to a project in Visual Studio creates a copy of the remote object in the local project bin directory. To add a ref erence to a project in Visual Studio 2005, right-click the project in Solution Explorer and click Add Reference. The Add Reference dialog box, shown in Figure 5-1, is dis played. In .NET remoting, you add a reference to a .NET assembly. To navigate to a user-defined assembly, click the Browse tab, navigate to the assembly, and click OK.
Figure 5-1
The Visual Studio 2005 Add Reference dialog box
After you add a reference to the remote object to a project, any code that instantiates the remote object must fully qualify the namespace and name of the object when instantiating it or address the namespace with a using statement or Imports statement. Client code should compile after a reference is added to the project; however, at run time, the client calling code should be directed to the actual remote object instead of the local copy.
178
Chapter 5
Creating a Remoting Client Application
The next step in the remotable object discovery process is to determine the activation mode for the remote object. Server-Activated Objects A remote object must be activated, or created and initial ized just as any other object, before a client can call methods on the remote object. Because MBV remote objects are serialized, copied, and deserialized on the client machine, they are treated like a local client object and activated on deserialization. The .NET Framework remoting system supports two methods for activating an MBR remote object: server activation and client activation.
Server-activated objects come in two flavors: Singleton and SingleCall. Singleton serveractivated objects implement the Singleton design pattern. Only a single instance of a Singleton object is instantiated and that single instance services all incoming client calls to methods on that object. The lifetime of an instance of a Singleton object can span multiple calls from a client and, as such, it can maintain state between client calls. In contrast to Singleton objects, a new instance of a SingleCall server-activated object is created for every client method call. Likewise, SingleCall objects do not maintain state between client calls. A client application must register server-activated objects on the client to be able to instantiate the object. Server-activated objects are generally registered programmati cally on a client using the WellKnownClientTypeEntry class. The WellKnownClientTypeEntry class is used identically the same for the HTTP channel and the TCP channel. The IPC channel is only used to access objects on the same machine, hence it is not necessary to use the WellKnownClientTypeEntry class to register a server-acti vated object when using the IPC channel. The following code sample illustrates reg istering a remote object named IPerson using the TCP channel. The IPerson remote object is located at the URL tcp://localhost:9000/Person.rem and is in the namespace MSLearning.Chapter8.Standard.Server. After the WellKnownClientTypeEntry class is used to register the remote object with the client remoting system, the RemotingConfiguration class is used to complete the object registration. The RemotingConfiguration class exposes several static methods includ ing the RegisterWellKnownClientType method and the Configure method. The most commonly used of the two methods is the RegisterWellKnownClientType method as shown in the following code example.
Lesson 1: Creating a Client Application to Access a Remote Object
Registering Server-Activated Object on Client 'VB Imports Imports Imports Imports Imports Imports
System System.Runtime System.Runtime.Remoting System.Runtime.Remoting.Channels System.Runtime.Remoting.Channels.Tcp MSLearning.Chapter5.Standard.Interfaces
Namespace MSLearning.Chapter5.Standard.ClientTcp Module TcpModule Sub Main() ' Create an instance of the TCP client channel.
Dim channel As TcpClientChannel = New TcpClientChannel()
' Register the channel with the remoting system.
ChannelServices.RegisterChannel(channel, False)
' Register the remote object. Dim remoteObject As WellKnownClientTypeEntry = _ New WellKnownClientTypeEntry(GetType(IPerson), _ "tcp://localhost:9000/Person.rem") RemotingConfiguration.RegisterWellKnownClientType(remoteObject) ' more code to follow... End Sub End Module End Namespace //C# using using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter5.Standard.Interfaces;
namespace MSLearning.Chapter5.Standard.ClientTcp
{ class Program { static void Main(string[] args)
{
// Create an instance of the TCP client channel.
TcpClientChannel channel = new TcpClientChannel();
// Register the channel with the remoting system.
ChannelServices.RegisterChannel(channel, false);
179
180
Chapter 5
Creating a Remoting Client Application
// Register the remote object. WellKnownClientTypeEntry remoteObject =
new WellKnownClientTypeEntry(typeof(IPerson),
"tcp://localhost:9000/Person.rem");
RemotingConfiguration.RegisterWellKnownClientType(
remoteObject);
// more code to follow... } } }
As with many methods in the .NET Framework, the RegisterWellKnownClientType method is overloaded to provide a shortcut. The preceding code example using the WellKnownClientTypeEntry method and the RegisterWellKnownClientType method can be consolidated into a single call to the RegisterWellKnownClientType method, as the following shows. Registering Server-Activated Object on Client 'VB Imports Imports Imports Imports Imports Imports
System System.Runtime System.Runtime.Remoting System.Runtime.Remoting.Channels System.Runtime.Remoting.Channels.Tcp MSLearning.Chapter5.Standard.Interfaces
Namespace MSLearning.Chapter5.Standard.ClientTcp Module TcpModule Sub Main() ' Create an instance of the TCP client channel.
Dim channel As TcpClientChannel = New TcpClientChannel()
' Register the channel with the remoting system.
ChannelServices.RegisterChannel(channel, False)
' Register the remote object. RemotingConfiguration.RegisterWellKnownClientType( _ GetType(IPerson), "tcp://localhost:9000/Person.rem") ' more code to follow... End Sub End Module End Namespace //C# using System; using System.Runtime; using System.Runtime.Remoting;
Lesson 1: Creating a Client Application to Access a Remote Object
181
using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using MSLearning.Chapter5.Standard.Interfaces; namespace MSLearning.Chapter5.Standard.ClientTcp { class Program { static void Main(string[] args)
{
// Create an instance of the TCP client channel.
TcpClientChannel channel = new TcpClientChannel();
// Register the channel with the remoting system.
ChannelServices.RegisterChannel(channel, false);
// Register the remote object. RemotingConfiguration.RegisterWellKnownClientType( typeof(IPerson), "tcp://localhost:9000/Person.rem"); // more code to follow... } } }
As mentioned, when calling client code references the remote object in Visual Studio, a copy of the remote object is created in the project bin directory. How does the client application handle changes that might be made to the remote object on the server? One option is to delete the reference to the remote object in the client project and add a new reference to the new version of the remote object. However, this option is not practical, as it requires that the developer remains aware of any updates to the remote object and recompiles the client project for each update. A design alternative to referencing the remote object in the client project is to refer ence an interface implemented by the remote object. The client project does not require full implementation of the remote object to compile; an interface will suffice. This approach is best implemented if utilized from the beginning of the remote object development cycle. The preceding code examples implemented the interface design approach, and this is evident when the remote object instance is created from the IPerson interface. The primary benefit of designing remote objects to implement an interface is that the full remote object does not need to be distributed to the client. This approach increases the stability of your application that uses .NET remoting by allowing it to easily accommodate functional updates to methods in the remote object, as the method implementation is not distributed with the interface. Furthermore, this
182
Chapter 5
Creating a Remoting Client Application
design approach increases security for the server that hosts the remote object, as infra structure details on the server accessed by code in the remote object are not exposed. Client-Activated Objects The .NET Framework remoting system also supports clientactivated objects. Client-activated objects are instantiated on the client instead of on the server. Each instance of a client-activated object is unique, and the client manages the lifetime of the object. Because the client is in complete control of the lifetime, clientactivated objects can maintain state between client calls.
Client-activated objects are generally registered programmatically using the Remot ingConfiguration.RegisterActivatedClientType method. The RegisterActivatedClientType method is used to register a remote object on the client as an object that can be accessed on the server. The following code illustrates using the RegisterActivatedClientType method and the TCP channel to create an instance of a remote object that implements the IPerson inter face, is located at the URL tcp://localhost:9000/Person.rem, and is in the namespace MSLearning.Chapter5.Standard.Server. Registering Client-Activated Object on Client 'VB Imports Imports Imports Imports Imports Imports
System System.Runtime System.Runtime.Remoting System.Runtime.Remoting.Channels System.Runtime.Remoting.Channels.Tcp MSLearning.Chapter5.Standard.Interfaces
Namespace MSLearning.Chapter5.Standard.ClientTcp Module TcpModule Sub Main() ' Create an instance of the TCP client channel.
Dim channel As TcpClientChannel = New TcpClientChannel()
' Register the channel with the remoting system.
ChannelServices.RegisterChannel(channel, False)
' Register the remote object.
RemotingConfiguration.RegisterActivatedClientType( _
GetType(IPerson), "tcp://localhost:9000/Person.rem")
' more code to follow...
End Sub
End Module
Lesson 1: Creating a Client Application to Access a Remote Object
183
End Namespace //C# using using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter5.Standard.Interfaces;
namespace MSLearning.Chapter5.Standard.ClientTcp
{ class Program { static void Main(string[] args)
{
// Create an instance of the TCP client channel.
TcpClientChannel channel = new TcpClientChannel();
// Register the channel with the remoting system.
ChannelServices.RegisterChannel(channel, false);
// Register the remote object.
RemotingConfiguration.RegisterActivatedClientType(
typeof(IPerson), "tcp://localhost:9000/Person.rem");
// more code to follow... } } }
Just as with the RegisterWellKnownClientType method, the RegisterActivatedClientType method is overloaded twice. The first overload accommodates you registering the remote type first and then passing that object to the RegisterActivatedClientType method. The second overload accommodates you passing the registration informa tion directly to the RegisterActivatedClientType method and it handles registering the object. The latter overload is used in the preceding code examples.
Calling a Remote Object Method Now that the groundwork has been set for programmatically configuring a .NET remoting client, our next task is to create an instance of the remote object and call methods on the object. There are several ways to call a method on a remote object. Each means for calling remote methods is covered in detail in Lesson 3. In the follow ing code example, we illustrate how to create an instance of a remote object represent ing a patient by instantiating it as any other object.
184
Chapter 5
Creating a Remoting Client Application
Instantiating a Remote Object 'VB Imports Imports Imports Imports Imports Imports
System
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter5.PatientClasses
Namespace MSLearning.Chapter5.Standard.ClientTcp Module TcpModule Sub Main() ' Create an instance of the TCP client channel. Dim channel As TcpClientChannel = New TcpClientChannel() ' Register the channel with the remoting system. ChannelServices.RegisterChannel(channel, False) ' Register the remote object. RemotingConfiguration.RegisterActivatedClientType( _ GetType(Patient), "tcp://localhost:9000/Patient.rem") ' Create an instance of the remote Patient class. Dim newPatient As Patient = New Patient() ' Call the PersonalInformation method on the remote object. Console.WriteLine(newPatient.PersonalInformation()) End Sub End Module End Namespace //C# using using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter5.PatientClasses;
namespace MSLearning.Chapter5.ClientTcp {
class Program
{
static void Main(string[] args) { // Create an instance of the TCP client channel. TcpClientChannel channel = new TcpClientChannel();
Lesson 1: Creating a Client Application to Access a Remote Object
185
// Register the channel with the remoting system. ChannelServices.RegisterChannel(channel, false); // Register the remote object. RemotingConfiguration.RegisterWellKnownClientType( typeof(Patient), "tcp://localhost:9000/Patient.rem"); // Create an instance of the remote Patient class. Patient newPatient = new Patient(); // Call the PersonalInformation method on the remote object.
Console.WriteLine(newPatient.PersonalInformation());
}
}
}
Lab 1: Creating a Client Application This lab is a simplified simulated version of an application that manages patient data. The lab walks you through the process of creating and programmatically configuring a .NET remoting server, remote object, and client. The remote object includes meth ods to retrieve data for a sample patient. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. �
Exercise 1: Creating a Remote Object
1. Start Visual Studio 2005, and on the File menu, click New, Project to create a new project that will serve as the remote object. The remote object simulates an object that provides patient information. a. In the Project Types pane, select your language of choice (Visual Basic or Visual C#). b. In the Templates pane, click Class Library. c. In the Name text box, type RemotePatient. d. In the Location drop-down list, type or browse to the location where you would like to create the new project. e. Click OK. 2. In the Class1.cs file (Visual C#) or the Class1.vb file (Visual Basic), add the fol lowing namespace references: 'VB Imports System
Imports System.Runtime.Remoting
186
Chapter 5
Creating a Remoting Client Application
//C# using System;
using System.Runtime.Remoting;
3. Rename Class1 to Patient and modify the Patient class to extend the MarshalB yRefObject as the following shows. Wrap the Patient class in a namespace named MSLearning.Chapter5.PatientClasses. 'VB Namespace MSLearning.Chapter5.PatientClasses
Public Class Patient
Inherits MarshalByRefObject
End Class
End Namespace
//C# namespace MSLearning.Chapter5.PatientClasses {
public class Patient : MarshalByRefObject
{
}
}
4. In the Patient class, create two functions that return a string. The first function should be named PersonalInformation and the second should be named History. The functions are shown here: 'VB Public Function PersonalInformation() As String Return "Patient ID: 001 :: Demo Patient"
End Function
Public Function History() As String Return "01/12/2001; Diagnosis: cold | Treatment: take 2 and call " & _ "me in the morning." + Chr(13) + Chr(10) + _ "03/04/2005; Diagnosis: broken toe | Treatment: nothing." End Function //C# public string PersonalInformation()
{
return "Patient ID: 001 :: Demo Patient";
}
public string History()
{
Lesson 1: Creating a Client Application to Access a Remote Object
187
return "01/12/2001; Diagnosis: cold | Treatment: take 2 and call " + "me in the morning.\n\r03/04/2005; Diagnosis: broken toe | " + "Treatment: nothing."; }
5. On the Build menu, click Build RemotePatient to compile the project. �
Exercise 2: Creating a Server
1. On the File menu, click New, Project to create a new project that will serve as the server application that hosts the Patient remote object. a. In the Project Types pane, select your language of choice (Visual Basic or Visual C#). b. In the Templates pane, click Console Application. c. In the Name text box, type LabServer. d. In the Location drop-down list box, type or browse to the location where you would like to create the new project. e. Click OK. 2. Right-click the LabServer project in Solution Explorer and click Add Reference to add references to the Remoting namespace and the Patient remote object. a. On the .NET tab, scroll to the System.Runtime.Remoting namespace and double-click it. b. Right-click on the project in Solution Explorer and click Add Reference again. c. On the Browse tab, navigate to the RemotePatient\Bin\Debug directory and double-click the RemotePatient.dll file. 3. In the Program.cs file (Visual C#) or the Module1.vb file (Visual Basic), add the following namespace references: 'VB Imports Imports Imports Imports //C# using using using using
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
188
Chapter 5
Creating a Remoting Client Application
4. Wrap the Program class (C#) or Module1 (Visual Basic) in a namespace named MSLearning.Chapter5.Server. 'VB Namespace MSLearning.Chapter5.Server Module Module1 Sub Main() End Sub
End Module
End Namespace
//C# namespace MSLearning.Chapter5.Server {
class Program
{
static void Main(string[] args)
{
}
}
}
5. Right-click the project in Solution Explorer and click Add, New Item. In the Add New Item dialog box, select Application Configuration File and click Add. Mod ify the application configuration file as shown here:
6. In the Main method (Visual C#) or Sub Main (Visual Basic), type the following code. This code is used to load the application configuration file and load the remote object. 'VB RemotingConfiguration.Configure("LabServer.exe.config", False)
Lesson 1: Creating a Client Application to Access a Remote Object
189
Console.WriteLine("Press return to exit..." + Chr(10))
Console.ReadLine()
//C# RemotingConfiguration.Configure("LabServer.exe.config", false);
Console.WriteLine("Press return to exit...\n");
Console.ReadLine();
7. On the Build menu, click Build LabServer to compile the project. After the server successfully builds, press Ctrl+F5 to start the server without debugging. A con sole window should be displayed for the server; minimize this window. �
Exercise 3: Creating a Remote Client
1. On the File menu, click New, Project to create a new project that will serve as the client application to consume the Patient remote object from the LabServer. a. In the Project Types pane, select your language of choice (Visual Basic or Visual C#). b. In the Templates pane, click Console Application. c. In the Name text box, type LabConsumer. d. In the Location drop-down list box, type or browse to the location where you would like to create the new project. e. Click OK. 2. Right-click the LabConsumer project in Solution Explorer and click Add Refer ence to add references to the Remoting namespace and the Patient remote object. a. On the .NET tab, scroll to the System.Runtime.Remoting namespace and double-click it. b. Right-click the project in Solution Explorer and click Add Reference again. c. On the Browse tab, navigate to the RemotePatient\Bin\Debug directory and double-click the RemotePatient.dll file. 3. In the Program.cs file (Visual C#) or the Module1.vb file (Visual Basic), add the following namespace references: 'VB Imports Imports Imports Imports Imports
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter5.PatientClasses
//C# using System.Runtime;
190
Chapter 5
using using using using
Creating a Remoting Client Application
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter5.PatientClasses;
4. In the Main method (Visual C#) or Sub Main (Visual Basic), type the following code. This code is used to instantiate an instance of the TCP channel. 'VB Dim channel As TcpClientChannel = New TcpClientChannel() //C# TcpClientChannel channel = new TcpClientChannel();
5. Below the TcpClientChannel instance, type the following code to register the channel: 'VB ChannelServices.RegisterChannel(channel, False) //C# ChannelServices.RegisterChannel(channel, false);
6. Below the channel registration, type the following code to register the remote Patient object: 'VB RemotingConfiguration.RegisterWellKnownClientType( _ GetType(Patient), "tcp://localhost:9000/Patient.rem") //C# RemotingConfiguration.RegisterWellKnownClientType( typeof(Patient), "tcp://localhost:9000/Patient.rem");
7. Below the channel registration, type the following code to create an instance of the remote Patient type: 'VB Dim newPatient As Patient = New Patient() //C# Patient newPatient = new Patient();
8. Below the code that creates an instance of the Patient object, type the following code or something similar to utilize and test the PersonalInformation method and the History method on the remote object: 'VB Console.WriteLine(newPatient.PersonalInformation())
Console.WriteLine(newPatient.History())
Lesson 1: Creating a Client Application to Access a Remote Object
191
//C# Console.WriteLine(newPatient.PersonalInformation());
Console.WriteLine(newPatient.History());
9. On the Build menu, click Build LabConsumer to compile the project. After the project successfully builds, press Ctrl+F5 to start the program without debug ging. The console output should render something similar to this: Patient ID: 001 :: John Doe :: 123 Main Street :: Someplace, AZ 85000 01/12/2001; Diagnosis: cold | Treatment: take 2 and call me in the morning. 03/04/2005; Diagnosis: broken toe | Treatment: nothing.
10. Close the console window for the LabConsumer project and the LabServer project.
Lesson Summary ■
There are two types of remotable objects: marshal-by-value and marshal-by reference.
■
Marshal-by-value objects are serialized on the hosted server, transported to the client machine, deserialized, and behave as a locally defined object.
■
Marshal-by-reference objects reside on the hosted server and calls from the client are transported to the object.
■
A proxy object is used to serve as a local client representation of a marshal-by reference object so that the object appears to be a local object although it is on the server.
■
There are two types of proxy objects: transparent proxy and real proxy. Real proxy is the base class for the transparent proxy. Real proxy can be extended to create custom proxy classes.
■
The URL where a remote object can be located is required to successfully con nect to the object when connecting via HTTP or TCP.
■
A communication channel is used to establish a communication conduit between your calling client code and the object on the server. The .NET Frame work provides three types of channels: HTTP, TCP, and IPC.
■
Before a remote object can be accessed, it must be activated. The .NET Frame work provides two types of activation: server activation and client activation. Server-activated objects can be configured to be Singleton or SingleCall.
192
Chapter 5
Creating a Remoting Client Application
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “Creating a Client Application to Access a Remote Object.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which channel offers the best performance if the remote object and the client reside on the same computer? (Select the best answer.) A. The IPC channel B. The HTTP channel C. The TCP channel D. The FTP channel 2. What methods does the .NET Framework provide for activating remote objects? (Select all that apply.) A. Server-activated Singleton B. Client-activated C. Server-on-demand activation D. Server-activated SingleCall 3. How do marshal-by-value objects compare to marshal-by-reference objects? (Select all that apply.) A. Marshal-by-value objects maintain state between client calls. B. A proxy object is created for marshal-by-value objects. C. Marshal-by-reference objects can directly access resources on the server. D. A proxy object is created for marshal-by-reference objects. E. Marshal-by-value objects can directly access resources on the server.
Lesson 2: How to Configure a Client Application Using a Configuration File
193
Lesson 2: How to Configure a Client Application Using a Configuration File In Lesson 1, we focused on how to programmatically configure a client application to be able to access a remote object. Programmatically configuring a client application is perfectly viable, but client applications can also be configured using .NET Framework configuration files. To add a configuration file to a project using Visual Studio, rightclick the project in Solution Explorer and click Add, New Item, Application Configu ration File. By default, if your application is an ASP.NET application, the configuration file is named Web.config; in all other application types, the configuration file is named App.config. After this lesson, you will be able to: ■
Create and configure an application to serve as a client that consumes services of a remote object by using an application configuration file.
■
Identify and configure communication channels and the benefits inherent in each type of channel.
Estimated lesson time: 60 minutes
The .NET Framework provides several classes that comprise an entire infrastructure to work with configuration files. .NET Framework configuration files utilize a custom XML grammar that is fully documented in the MSDN Library. The primary advantage of configuring .NET remoting applications using configuration files is that many of the options can be modified without recompiling the application. Additionally, if your application includes .NET remoting clients that are written using different languages (such as Visual Basic or C#), a single configuration file can be used for all languages. MORE INFO
.NET configuration files
For more information on .NET configuration files, visit the topic titled “Configuration Files” at http://msdn2.microsoft.com/.
The XML grammar, or schema, implemented by the standard .NET Framework con figuration file includes a topmost element named configuration. There are many pos sible child elements that exist under the element, including custom elements; however, Table 5-1 lists the commonly used child elements related to .NET remoting.
194
Chapter 5
Creating a Remoting Client Application
Table 5-1
.NET Remoting Configuration File Commonly Used Elements
Element
Description
This is the root element for .NET remoting configuration settings. This element can appear only once per configuration file. The attribute of the element is listed below. ■
Identifies the .NET remoting version to which the settings apply. version
This element includes application-specific information. This ele ment is a child of the element. This ele ment can appear only once per configuration file. The attribute of the element is listed below. ■
Identifies the name of the application to which the settings apply.
name
This element is used to define the lifetime of all client-activated objects. This element is a child of the element. This element can appear only once per configuration file. The attributes of the element are listed below. Identifies the amount of time that a client-acti vated object is leased for, or the extent of its lifetime. The default lease time is 5 minutes.
■ leasTime
Identifies the length of time that the lease manager waits for a lease to be renewed after it has expired. The default timeout is 2 minutes.
■ sponsorshipTimeout
Identifies the lease time for a client-acti vated object where the lease is extended by this amount on each call to a method on the object. The default timeout is 2 minutes.
■ renewOnCallTime
Identifies how often the lease man ager checks for expired client-activated object leases. The default poll time is 10 seconds.
■ leaseManagerPollTime
Lesson 2: How to Configure a Client Application Using a Configuration File
Table 5-1
195
.NET Remoting Configuration File Commonly Used Elements
Element
Description
This element identifies all services hosted by this application. This element is a child of the element. This element can appear zero or more times per configuration file.
This element identifies the objects that the application con sumes. This element is a child of the element. This element can appear zero or more times per configuration file. The attributes of the element are listed below. Identifies the URL for a client-activated object and is required.
■ url
■ displayName
Identifies one client object from another cli
ent object.
This element identifies the well-known objects provided by the service. This element is a child of the element or the element. This element can appear zero or more times per configuration file. The attributes of the element are listed below. Identifies the type of server activation: SingleCall or Singleton. This attribute is required.
■ mode
Identifies the type name and the assembly that con tains the type. This attribute is required.
■ type
Identifies the URI that will be used to connect to an object. This attribute is required and is only allowed in elements under elements.
■ objectUri
Identifies the URL that will be used to connect to a ser vice. This attribute is required and is only allowed in elements under elements.
■ url
■ displayName
ent object.
Identifies one client object from another cli
196
Chapter 5
Creating a Remoting Client Application
Table 5-1
.NET Remoting Configuration File Commonly Used Elements
Element
Description
This element identifies the client-activated objects provided by the service. This element is a child of the element or the element. This element can appear zero or more times per configuration file. The attribute of the element is listed below. ■
Identifies the type name and the assembly that con tains the type. This attribute is required. type
This element is used to configure existing channels or define new channels. This element is a child of the element or the element.
This element is used to identify a channel to be created or config ured. This element is a child of the element. This ele ment can appear zero or more times per configuration file. The attributes of the element are listed below. Identifies the ID used to refer to the channel definition. This attribute is required.
■ id
Identifies the full type name of the channel. This attribute is required.
■ type
Identifies the channel being configured, if one already exists.
■ ref
■ name
Identifies the name of the channel.
Identifes a priority of channels to be used. A higher priority channel will be used for communication, if possible.
■ priority
■
displayName Identifies one channel from another channel.
The .NET remoting section of the configuration file can contain more elements and attributes than those listed here. For a complete list of possible elements and attributes, see the MSDN article titled “Remoting Settings Schema” at http:// msdn2.microsoft.com/en-us/library/z415cf9a.aspx.
Lesson 2: How to Configure a Client Application Using a Configuration File
197
When an application is configured using a configuration file, you must instruct your code to retrieve configuration information from the file. The RemotingConfiguration class is used to manage the configuration of the .NET remoting system. The Configure method of the RemotingConfiguration class is used to load a configuration file in code. Using the most common overload of the method, the method accepts two parame ters: the name of the configuration file to load and a Boolean value indicating whether to enable security.
Configuring the Communication Channel In Lesson 1, we defined channels and how they are used to form a communication conduit between a calling client and a hosting server in a .NET remoting environ ment. The .NET Framework provides three prebuilt channels that implement the IChannel interface. The IChannel interface can be implemented to create custom channels as well. Each of the channel types, HTTP, TCP, and IPC, can be programmatically configured, as in Lesson 1. Each type of communication channel can also be configured using a .NET configuration file.
HTTP Channel The HTTP channel is used to serialize, deserialize, and transport data using HTTP. HTTP is primarily used to communicate over the Web. The next code example is iden tical to the code example used to illustrate the HTTP channel in Lesson 1. However, the code example here is somewhat simplified and utilizes the configuration file shown here. Instantiating the HttpClientChannel Class Using a Configuration File CONFIGURATION FILE
'VB Imports System
198
Chapter 5
Imports Imports Imports Imports
Creating a Remoting Client Application
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Http
Namespace MSLearning.Chapter5.Standard.ClientHttp Module HttpModule Sub Main() ' Load the configuration file. RemotingConfiguration.Configure( _ "RemoteClientHttpCS.exe.config", False) ' more code to follow...
End Sub
End Module
End Namespace
//C# using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Http;
namespace MSLearning.Chapter5.Standard.ClientHttp {
class Program
{
static void Main(string[] args)
{
// Load the configuration file. RemotingConfiguration.Configure( "RemoteClientHttpCS.exe.config", false); // more code to follow...
}
}
}
TCP Channel The TCP channel is used to serialize, deserialize, and transport data using TCP. TCP is primarily used to communicate over the Web and networks. The following code example is identical to the code example used to illustrate the TCP channel in Lesson 1. However, the code example here is somewhat simplified and utilizes the configura tion file shown here.
Lesson 2: How to Configure a Client Application Using a Configuration File
Instantiating the TcpClientChannel Class Using a Configuration File CONFIGURATION FILE
'VB Imports Imports Imports Imports Imports Imports
System System.Runtime System.Runtime.Remoting System.Runtime.Remoting.Channels System.Runtime.Remoting.Channels.Tcp MSLearning.Chapter5.PatientClasses
Namespace MSLearning.Chapter5.Standard.ClientTcp Module TcpModule Sub Main() ' Load the configuration file.
RemotingConfiguration.Configure( _
"RemoteClientTcpCS.exe.config", False)
' more code to follow... End Sub End Module End Namespace //C# using using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter5.PatientClasses;
namespace MSLearning.Chapter5.ClientTcp
{ class Program { static void Main(string[] args)
{
199
200
Chapter 5
Creating a Remoting Client Application
// Load the configuration file. RemotingConfiguration.Configure( "RemoteClientTcpCS.exe.config", false); // more code to follow...
}
}
}
IPC Channel The IPC channel is used to serialize, deserialize, and transport data using the Win dows interprocess communication. IPC is used to consume remotable objects resid ing on the same machine and, in this scenario, the IPC channel offers better performance than the HTTP channel or TCP channel. The following code example is identical to the code example used to illustrate the IPC channel in Lesson 1. However, the code example here is somewhat simplified and utilizes the configuration file shown here. Instantiating the IpcClientChannel Class Using a Configuration File CONFIGURATION FILE
'VB Imports Imports Imports Imports Imports
System
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Ipc
Namespace MSLearning.Chapter5.Standard.ClientIpc Module IpcModule Sub Main() ' Load the configuration file. RemotingConfiguration.Configure( _ "RemoteClientIpcVB.exe.config", False)
Lesson 2: How to Configure a Client Application Using a Configuration File
201
' more code to follow...
End Sub
End Module
End Namespace
//C# using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Ipc;
namespace MSLearning.Chapter5.Standard.ClientIpc {
class Program
{
static void Main(string[] args)
{
// Load the configuration file. RemotingConfiguration.Configure( "RemoteClientIpcCS.exe.config", false); // more code to follow...
}
}
}
Configuring the Activation Mode Just as the communication channel can be configured via a configuration file, the remote object activation mode can also be configured via a configuration file. The .NET Framework provides two methods for activating an object: server activation and client activation. Each activation mode can be configured using a .NET configuration file.
Server-Activated Objects Server-activated objects can be in the form of a Singleton object or a SingleCall object. A Singleton object is one where only a single instance of the object is created to service all client calls. The lifetime of a Singleton object can span multiple client calls, hence a Sin gleton object can maintain state between client calls. A SingleCall object is one where a new instance of the object is created to handle each client call. Each instance of the object is destroyed and released from memory after it services a client call. SingleCall objects do not maintain state between client calls. The client making calls only needs to be aware that the object is a server-activated object but not how the object is handled on the server.
202
Chapter 5
Creating a Remoting Client Application
The following code example is identical to the code example used to illustrate regis tering a server-activated object in Lesson 1, but it illustrates configuring the object activation mode using a .NET configuration file. As you can see, when using a config uration file, the client calling code becomes much simpler. Registering a Server-Activated Object on Client Using a Configuration File CONFIGURATION FILE
'VB Imports Imports Imports Imports Imports Imports
System System.Runtime System.Runtime.Remoting System.Runtime.Remoting.Channels System.Runtime.Remoting.Channels.Tcp MSLearning.Chapter5.PatientClasses
Namespace MSLearning.Chapter5.Standard.ClientTcp Module TcpModule Sub Main() ' Load the configuration file.
RemotingConfiguration.Configure( _
"RemoteClientTcpCS.exe.config", False)
' Create an instance of the remote Patient class.
Dim newPatient As Patient = New Patient()
' Call the PersonalInformation method on the remote object. Console.WriteLine(newPatient.PersonalInformation()) End Sub End Module End Namespace
Lesson 2: How to Configure a Client Application Using a Configuration File
//C# using using using using using using
203
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter5.PatientClasses;
namespace MSLearning.Chapter5.ClientTcp
{ class Program { static void Main(string[] args)
{
// Load the configuration file.
RemotingConfiguration.Configure(
"RemoteClientTcpCS.exe.config", false);
// Create an instance of the remote Patient class.
Patient newPatient = new Patient();
// Call the PersonalInformation method on the remote object. Console.WriteLine(newPatient.PersonalInformation()); } } }
Client-Activated Objects Client-activated objects are remote objects that are instantiated local to the client. Thus, the client treats the object as any other locally defined object and controls the lifetime of the object. Calls made to the object are serialized, transported to the server, and deserialized for processing. The lifetime of a client-activated object might span multiple client calls, hence it can maintain state between client calls. The following code example is identical to the code example used to illustrate regis tering a client-activated object in Lesson 1, but it illustrates configuring the object acti vation mode using a .NET configuration file. The element used for server activations has been replaced here with the element used for client activa tions. As you can see, when using a configuration file, the client calling code becomes much simpler. Registering a Client-Activated Object on Client Using a Configuration File CONFIGURATION FILE
204
Chapter 5
Creating a Remoting Client Application
'VB Imports Imports Imports Imports Imports Imports
System
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter5.PatientClasses
Namespace MSLearning.Chapter5.Standard.ClientTcp Module TcpModule Sub Main() ' Load the configuration file. RemotingConfiguration.Configure( _ "RemoteClientTcpCS.exe.config", False) ' Create an instance of the remote Patient class. Dim newPatient As Patient = New Patient() ' Call the PersonalInformation method on the remote object. Console.WriteLine(newPatient.PersonalInformation()) End Sub End Module End Namespace //C# using using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter5.PatientClasses;
namespace MSLearning.Chapter5.ClientTcp {
class Program
{
static void Main(string[] args)
Lesson 2: How to Configure a Client Application Using a Configuration File
205
{ // Load the configuration file. RemotingConfiguration.Configure( "RemoteClientTcpCS.exe.config", false); // Create an instance of the remote Patient class. Patient newPatient = new Patient(); // Call the PersonalInformation method on the remote object.
Console.WriteLine(newPatient.PersonalInformation());
}
}
}
Lab 2: Creating a Client Application This lab is a simplified simulated version of an application that manages patient data. The lab walks you through the process of creating and configuring a .NET remoting client using a configuration file. The client can access the server hosting a remote object. The remote object includes methods to retrieve data for a sample patient. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. 1. Using Visual Studio 2005, open the LabServer solution. On the Build menu, click Build LabServer to recompile the lab server. After the server successfully builds, press Ctrl+F5 to start the server without debugging. A console window should be displayed for the server; minimize this window. 2. On the File menu, click New, Project to create a new project that will serve as the client application to consume the Patient remote object from the LabServer. a. In the Project Types pane, select your language of choice (Visual Basic or Visual C#). b. In the Templates pane, click Console Application. c. In the Name text box, type LabConsumer. d. In the Location drop-down list box, type or browse to the location where you would like to create the new project. e. Click OK.
206
Chapter 5
Creating a Remoting Client Application
3. Right-click the LabConsumer project in Solution Explorer and click Add Refer ence to add references to the Remoting namespace and the Patient remote object. a. On the .NET tab, scroll to the System.Runtime.Remoting namespace and double-click it. b. Right-click the project in Solution Explorer and click Add Reference again. c. On the Browse tab, navigate to the RemotePatient\Bin\Debug directory and double-click the RemotePatient.dll file. 4. Right-click the LabConsumer project in Solution Explorer and click Add, New Item, Application Configuration File to add a new configuration file to the project. Modify the configuration file as follows:
5. In the Program.cs file (Visual C#) or the Module1.vb file (Visual Basic), add the following namespace references: 'VB Imports Imports Imports Imports Imports //C# using using using using using
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter5.PatientClasses
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter5.PatientClasses;
Lesson 2: How to Configure a Client Application Using a Configuration File
207
6. In the Main method (Visual C#) or Sub Main (Visual Basic), type the following code. This code is used to load the configuration file. 'VB RemotingConfiguration.Configure("LabConsumer.exe.config", False) //C# RemotingConfiguration.Configure("LabConsumer.exe.config", false);
7. Below the channel registration, type the following code to create an instance of the remote Patient type: 'VB Dim newPatient As Patient = New Patient() //C# Patient newPatient = new Patient();
8. Below the code that creates an instance of the Patient object, type the following code or something similar to utilize and test the PersonalInformation method on the remote object: 'VB Console.WriteLine(newPatient.PersonalInformation())
Console.WriteLine(newPatient.History())
//C# Console.WriteLine(newPatient.PersonalInformation());
Console.WriteLine(newPatient.History());
9. On the Build menu, click Build LabConsumer to compile the project. After the project successfully builds, press Ctrl+F5 to start the program without debug ging. The console output should render something similar to this: Patient ID: 001 :: John Doe :: 123 Main Street :: Someplace, AZ 85000 01/12/2001; Diagnosis: cold | Treatment: take 2 and call me in the morning. 03/04/2005; Diagnosis: broken toe | Treatment: nothing.
10. Close the console window for the LabConsumer project and the LabServer project.
Lesson Summary ■
Most facets of a .NET remoting client application can be configured both pro grammatically as well as by using configuration files.
■
.NET Framework configuration files implement a custom XML grammar, or schema.
208
Chapter 5
Creating a Remoting Client Application
■
Server-activated objects are configured using the element in the configuration file.
■
Client-activated objects are configured using the element in the con figuration file.
■
The RemotingConfiguration.Configure method is used to load an application con figuration file for .NET remoting.
Lesson Review You can use the following questions to test your knowledge of the information in Lesson 2, “How to Configure a Client Application Using a Configuration File.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which class is used to load a configuration file for .NET remoting? (Select the best answer.) A. WellKnownClientTypeEntry B. ChannelServices C. RemotingConfiguration D. RemotingServices 2. What is the root element for .NET remoting configuration information? (Select the best answer.) A. B. C. D.
Lesson 3: How to Access a Remote Method
209
Lesson 3: How to Access a Remote Method In Lessons 1 and 2, we illustrated how to create an instance of a remote object using the new (New) operator. This is the same approach you would use to create a new instance of any local object and is a simple solution if the client application has already been configured to connect to the remote object. However, the .NET Framework also provides the Activator object to activate various types. The Activator object includes a method named GetObject that can be used to cre ate an instance of a remote object. The GetObject method does not require that the remoting client be configured first as you pass the configuration information to the GetObject method. After this lesson, you will be able to: ■
Create a client instance of a remote object.
■
Synchronously call methods on an instance of a remote object.
Estimated lesson time: 30 minutes
After an instance of a remote object is successfully created, you can call methods on that object just as you would with any local object. The .NET Framework provides the ability to call remote methods both synchronously and asynchronously. Asynchro nous calls to remote object methods are a bit complex and are covered in Chapter 8, “Method Invocations and Event Management with .NET Remoting.” Because we’ve already illustrated how to create an instance of a remote object and syn chronously calling methods using the new operator, we focus here on using the Activator .GetObject method. The following code illustrates using the Activator.GetObject method to create an instance of the Patient remote object used in Lessons 1 and 2 and then call the PersonalInformation method. As you can see from the example code, if you prefer to write less code, the Activator.GetObject method is the way to go. Create an Instance of a Remote Object Using Activator.GetObject 'VB Imports Imports Imports Imports Imports
System.Runtime System.Runtime.Remoting System.Runtime.Remoting.Channels System.Runtime.Remoting.Channels.Tcp MSLearning.Chapter5.PatientClasses
Namespace MSLearning.Chapter5.ActivatorGetObject Module GetObjectModule
210
Chapter 5
Creating a Remoting Client Application
Sub Main() ' Create an instance of the Patient class. Dim newPatient As Patient = _ CType(Activator.GetObject(GetType(Patient), _ "tcp://localhost:9000/Patient.rem"), Patient) ' Call a remote method.
Console.WriteLine(newPatient.PersonalInformation())
End Sub
End Module
End Namespace
//C# using using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter5.PatientClasses;
namespace MSLearning.Chapter5.ActivatorGetObject {
class Program
{
static void Main(string[] args)
{
// Create an instance of the Patient class. Patient newPatient = (Patient)Activator.GetObject(typeof(Patient), "tcp://localhost:9000/Patient.rem"); // Call a remote method.
Console.WriteLine(newPatient.PersonalInformation());
}
}
}
Lab 3: How to Access a Remote Method The labs for Lessons 1 and 2 walked you through creating an instance of a remote object using the new (New) operator. This lab walks you through creating an instance of a remote object using the Activator.GetObject method. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. 1. Using Visual Studio 2005, open the LabServer solution. On the Build menu, click Build LabServer to recompile the lab server. After the server successfully
Lesson 3: How to Access a Remote Method
211
builds, press Ctrl+F5 to start the server without debugging. A console window should be displayed for the server; minimize this window. 2. On the File menu, click New, Project to create a new project that will serve as the client application to consume the Patient remote object from the LabServer. a. In the Project Types pane, select your language of choice (Visual Basic or Visual C#). b. In the Templates pane, click Console Application. c. In the Name text box, type LabConsumer. d. In the Location drop-down list box, type or browse to the location where you would like to create the new project. e. Click OK. 3. Right-click the LabConsumer project in Solution Explorer and click Add Refer ence to add references to the Remoting namespace and the Patient remote object. a. On the .NET tab, scroll to the System.Runtime.Remoting namespace and double-click it. b. Right-click the project in Solution Explorer and click Add Reference again. c. On the Browse tab, navigate to the RemotePatient\Bin\Debug directory and double-click the RemotePatient.dll file. 4. In the Program.cs file (Visual C#) or the Module1.vb file (Visual Basic), add the following namespace references: 'VB Imports Imports Imports Imports Imports //C# using using using using using
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter5.PatientClasses
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter5.PatientClasses;
5. In the Main method (Visual C#) or Sub Main (Visual Basic), type the following code. This code creates an instance of the remote Patient object. 'VB Dim newPatient As Patient = _
CType(Activator.GetObject(GetType(Patient), _
212
Chapter 5
Creating a Remoting Client Application
"tcp://localhost:9000/Patient.rem"), Patient) //C# Patient newPatient =
(Patient)Activator.GetObject(typeof(Patient),
"tcp://localhost:9000/Patient.rem");
6. Below the code that creates an instance of the Patient object, type the following code or something similar to utilize and test the PersonalInformation method on the remote object: 'VB Console.WriteLine(newPatient.PersonalInformation())
Console.WriteLine(newPatient.History())
//C# Console.WriteLine(newPatient.PersonalInformation());
Console.WriteLine(newPatient.History());
7. On the Build menu, click Build LabConsumer to compile the project. After the project successfully builds, press Ctrl+F5 to start the program without debug ging. The console output should render something similar to this: Patient ID: 001 :: John Doe :: 123 Main Street :: Someplace, AZ 85000 01/12/2001; Diagnosis: cold | Treatment: take 2 and call me in the morning. 03/04/2005; Diagnosis: broken toe | Treatment: nothing.
8. Close the console window for the LabConsumer project and the LabServer project.
Lesson Summary ■
Before methods can be called on a remote object, an instance of the remote object must be created.
■
The .NET Framework provides the new (New) operator and the Activator.GetObject method to create an instance of a remote object.
■
Activator.GetObject is passed the configuration information directly so it requires fewer lines of code to be written.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 3, “How to Access a Remote Method.” The questions are also available on the companion CD if you prefer to review them in electronic form.
Lesson 3: How to Access a Remote Method
NOTE
213
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. What approach to creating an instance of a remote object requires the fewest lines of code to be written? (Select the best answer.) A. new operator in C# B. Activator.GetObject method C. New operator in Visual Basic D. Activator.Create method 2. What approach to creating an instance of a remote object requires programmatic configuration or configuration via a configuration file? (Select all that apply.) A. new operator in C# B. Activator.GetObject method C. New operator in Visual Basic D. Activator.Create method
214
Chapter 5 Review
Chapter Review To further practice and reinforce the skills you learned in this chapter, you can per form the following tasks: ■
Review the chapter summary.
■
Review the list of key terms introduced in this chapter.
■
Complete the case scenarios. These scenarios set up real-world situations involv ing the topics of this chapter and ask you to create a solution.
■
Complete the suggested practices.
■
Take a practice test.
Chapter Summary ■
A server can host and expose the services of a remote object. Client applications of any type can consume the services exposed by remote objects.
■
Creating a client application to consume a remote object can be fairly straight forward if all required information is known about the remote object.
■
Information about the remote object that might be required includes the URL where the object is located, the communication channel being used by the remote object, and the method of activation implemented by the remote object.
■
Remote objects can be marshaled to the client by value or reference. Remote objects can be activated on the server or the client. Remote objects that are acti vated on the server can be activated as Singleton objects or SingleCall objects.
■
Client applications that consume remote objects can be configured and man aged programmatically or by using .NET Framework configuration files.
■
The methods exposed by remote objects can be called by client applications either synchronously or asynchronously.
■
There are two approaches for creating an instance of a remote object: using the new (New) operator or using the Activator.GetObject method.
Chapter 5 Review
215
Key Terms Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book. ■
application URL
■
channel
■
marshal-by-reference (MBR)
■
marshal-by-value (MBV)
■
proxy object
■
remote object activation
Case Scenarios In the following case scenarios, you apply what you’ve learned about how to create cli ent applications that consume remote objects. You can find answers to these ques tions in the “Answers” section at the end of this book.
Case Scenario 1: Creating a Secure Globally Distributed Application You are the lead developer for the AdventureWorks organization. AdventureWorks is in the business of selling outdoor gear and sporting equipment globally. AdventureWorks is looking to create a centralized data entry application and data warehouse at its New York headquarters. To increase security, the data entry application should not be available through a Web interface but will use the Web to transport data. Data will be entered into the application using a custom Windows application created using the .NET Framework. The data entry application will be deployed to approximately 40 workstations at satellite offices globally. Because the application will not be pub licly available, you have decided to use .NET remoting and host a remote object on the server using IIS. The remote object will provide services to remote client applications.
216
Chapter 5 Review
Questions Before beginning application development, you must answer questions posed by your project manager as follows: 1. What communication channel should you use with the remote object? 2. What mode should you use when activating the remote object?
Case Scenario 2: Improving the Remoting Client Using Interfaces You are a developer for an application service provider that provides secure health ser vices to hospitals and insurance companies. The services provided must be HIPAAcompliant. HIPAA compliance requires the most secure environment available. To provide the most secure environment, you decided to provide services through .NET remoting and have created server-activated, marshal-by-reference objects. You suc cessfully deployed services to clients that include a copy of the remote object so that the client application can compile. However, you now realize that each client applica tion includes a full copy of the source code for the remote object.
Questions You have decided to redesign your application model somewhat to improve security. ■
How can you modify your design to best secure your remote object source code?
Suggested Practices ■
Use the HTTP channel if consuming an object over the Web.
■
Use the TCP channel if consuming an object on an intranet or internal network.
■
Use the IPC channel if consuming an object on the same machine as the client.
■
Configure objects to be SingleCall if your object is performing many requests and maintaining state is not important.
■
Configure objects to be Singleton if the object must directly access resources on the server and maintaining state is important.
■
Configure objects to be client-activated if maintaining state is important and your application must control the lifetime of each independent object.
■
Use the Activator.GetObject method to simplify the client coding process.
Chapter 5 Review
217
Take a Practice Test The practice tests on this book’s companion CD offer many options. For example, you can test yourself on just the content covered in this chapter, or you can test yourself on all the 70-529 certification exam content. You can set up the test so that it closely sim ulates the experience of taking a certification exam, or you can set it up in study mode so that you can look at the correct answers and explanations after you answer each question. MORE INFO
Practice tests
For details about all the practice test options available, see the section titled “How to Use the Prac tice Tests” in this book’s Introduction.
Chapter 6
Debugging and Deploying Remote Applications This chapter illustrates using Microsoft Visual Studio 2005 to debug and deploy a .NET remoting application. You learn how to deploy a .NET remoting application to various hosting environments. You also learn how to debug remoting applications and monitor them by utilizing performance counters. Finally, you will learn about remote object lifetimes and how to manage them.
Exam objectives in this chapter: ■
■
Debug and deploy a remoting application. ❑
Use performance counters to monitor a remoting application.
❑
Debug a remoting application.
❑
Host and deploy a remoting application.
Manage the lifetime of remote objects. ❑
Initialize the lifetime of a remote object.
❑
Renew the lifetime of a remote object.
Lessons in this chapter: ■
Lesson 1: How to Deploy a Remoting Application . . . . . . . . . . . . . . . . . . . . . . 222
■
Lesson 2: How to Debug a Remoting Application . . . . . . . . . . . . . . . . . . . . . . 239
■
Lesson 3: How to Manage the Lifetime of Remote Objects . . . . . . . . . . . . . . . 255
Before You Begin To complete the lessons in this chapter, you must have ■
A computer that meets or exceeds the minimum hardware requirements listed in the Introduction at the beginning of the book.
■
Visual Studio 2005 and the .NET Framework 2.0.
219
220
Chapter 6
Debugging and Deploying Remote Applications
■
Familiarity with using the Visual Studio 2005 debugger.
■
Familiarity with using performance counters and managing them using the .NET Framework 2.0.
■
Familiarity with creating console applications using Visual Studio 2005.
Real World Shannon Horn One of my clients creates and markets software that is used by schools, grades K–12, to align curriculum with objectives. Most states now require students to pass an exam prior to graduation that represents minimum educational stan dards that must be met in that state. My client’s software allows school officials to easily define the necessary standards that must be met. Teachers can then use the software to plan curriculum accordingly to teach in line with the defined standards. A primary requirement for the software is that it be capable of sharing data and synchronizing student personal information with the existing student informa tion system used by the school. Initially, my client considered creating the appli cation to fully integrate with existing student information systems used by schools. However, the student information system software industry is large and includes many competitors; hence creating a software package capable of inte grating with all possible systems was not feasible. My client had to come up with a solution to allow its own alignment software to synchronize with existing stu dent information systems. Education is the fourth largest industry in the United States, and the scenario just described proliferates through most K–12 schools. Microsoft realized this and joined in an industry effort to attempt to correct this problem. The industry effort is called the Schools Interoperability Framework (SIF). One way that Microsoft has contributed to the effort is by providing the plumbing used to cre ate the framework. The framework is built on Microsoft Windows 2003 Server, Microsoft SQL Server, Microsoft BizTalk Server, and the .NET Framework. The specialized collection of these items for this purpose is called a Zone Integration Server (ZIS).
221
The SIF is used to accommodate multiple student information systems to trans port data among themselves. This arrangement seemed to be a perfect solution for integrating my client’s alignment application with other systems. To use the framework, clients must create a SIF software agent that is responsible for per forming synchronization. This task proved a challenge when attempted a few years ago as my client (through me) created the first SIF agent on the market. Due to a collection of factors that included security concerns and performance, we decided to create the SIF agent using .NET remoting and host it using a Win dows service application. As a Windows service hosted object, our SIF agent was easily accessible to the entire school while providing high performance and tight security. Deploying the SIF agent correctly was initially a bit tricky because Visual Stu dio does not provide a direct means for packaging a Windows service applica tion for deployment. We selected the deployment method that has become the standard method used in the industry for deploying Windows service applica tions created using the .NET Framework, a Visual Studio setup project with custom actions defined. The remote objects hosted by our Windows service were fairly easy to deploy in themselves because they were just an object used by the service.
MORE INFO
Schools Interoperability Framework
For more information on the SIF, visit http://www.sifinfo.org/index.asp.
222
Chapter 6
Debugging and Deploying Remote Applications
Lesson 1: How to Deploy a Remoting Application This lesson describes using Visual Studio 2005 to deploy a .NET remoting applica tion. There are multiple deployment options available depending on how secure the source code contained in the remote object must be, as well as how often the remote object is likely to be updated. Furthermore, the method of deployment selected will differ based on the target hosting environment. After this lesson, you will be able to: ■
Evaluate and determine the appropriate hosting environment.
■
Identify the correct method of deployment.
■
Describe how to create and configure Visual Studio 2005 Web setup projects.
Estimated lesson time: 60 minutes
.NET Remoting Deployment When we discuss deployment in terms of .NET remoting, we are typically refering to deployment of a server application that hosts a remote object or deployment of a remote object to a client or server. A server application that hosts a remote object might be deployed from a development or testing environment to a production envi ronment. Likewise, a remote object might be deployed from a development or testing environment to a production environment. A remote object might also be deployed from a server to a client.
Hosting Remote Objects You should have an idea of the environment that will host a remote object in the early stages of development. A remote object should be thoroughly tested in the hosting envi ronment prior to deployment. The remote object should not be tested in the production version of the hosting environment, but rather in a development version of the hosting environment that resembles the production version as closely as possible. Tests should include security testing and performance testing under estimated production loads. A hosting environment is comprised of the operating system, memory, disk space, con nectivity, software and applications that might include SQL Server, Microsoft Active Directory directory service, Microsoft Internet Information Services (IIS), and Microsoft Message Queuing (MSMQ), as well as the application type used to create the remote server. There are four common application types used to create a remote server.
Lesson 1: How to Deploy a Remoting Application
223
Console Application Console applications are the simplest type of application that can be created using Visual Studio 2005 and can execute at the command prompt. As such, they are the easiest method available for creating a .NET remoting client or server. Console appli cations are also the easiest type of .NET remoting server to deploy. However, as described in Chapter 4, “Creating a Remoting Server Application,” console applica tions require a user to start the application if it is not executing and, by default, con sole applications execute using the security context of the user that starts the application. When manually starting the application is acceptable, console applica tions are a common solution to creating a .NET remoting server. If a console application is not currently executing, it can simply be copied to a new location. The simplest method of deployment utilized in the .NET Framework is deployment using the xcopy command at the command prompt. If the xcopy com mand is used to deploy a console application, the application folder containing the console application should be copied, the bin subfolder should be copied, and any other subfolders containing dependent files should also be copied. When a console application is created using Visual Studio 2005 and a reference is added to a remote object, a copy of the remote object is copied to the bin subfolder of the con sole application. Hence, if the bin subfolder is copied when the application is deployed, the remote object is deployed with the application. Deployment using the xcopy com mand as previously described might not be a practical solution if the console application contains dependencies located outside the application folder such as references to cus tom assemblies that reside in the global assembly cache (GAC). In cases such as this, a deployment solution more advanced than the xcopy command might be better suited.
Windows Executable Application Windows executable applications are comprised of Windows forms and accommo date better usability than console applications through the Windows user interface. However, in most scenarios, after a server that hosts a remote object is executing, it does not require much user interactivity. However, as stated in Chapter 4, Windows executable applications require a user to start the application if it is not executing and, by default, Windows executable applications execute using the security context of the user who starts the application. Windows executable applications are not as common a solution as a console application is for creating a .NET remoting server. The primary method of deploying a Windows executable application is to use a Windows setup project. Windows setup projects offer configuration options almost
224
Chapter 6
Debugging and Deploying Remote Applications
identical to those of Web setup projects (described later) with the exception that some of the configuration options are specific to Windows applications instead of Web applications.
Windows Service Application A Windows service application is an application that can be automatically started when Windows starts and can execute under its own security context. Examples of Windows service applications are SQL Server and IIS. Although a Windows service application does overcome the need to manually start the application and offer a dif ferent security context, .NET remoting servers created using Windows service appli cations are not as common a solution as a console application is for creating a .NET remoting server. The primary method of deploying a Windows service application is to use a Windows setup project. Windows setup projects offer configuration options almost identical to those of Web setup projects (described later) with the exception that some of the con figuration options are specific to Windows applications instead of Web applications. When a Windows service application is deployed using a Windows setup project, the project must be configured to take some custom actions while installing the applica tion. Custom installation programs can be created using the .NET Framework and might be better suited to installing a Windows service in some scenarios.
ASP.NET Web Application The most common solution for creating a .NET remoting server is to create it as an ASP.NET application. ASP.NET applications are hosted by IIS. The .NET remoting infrastructure built into the .NET Framework is comprehensive, but it does lack secu rity features. When designing a .NET remoting application, care should be taken to utilize all the security features of the .NET Framework. Because the Web is one of the primary communication platforms used today, there are also many security features available to Web applications. Hosting a remote object using IIS allows you to imple ment Web application security features in your .NET remoting applications. An appli cation created using ASP.NET to serve as a .NET remoting server is automatically started when IIS starts and can execute using its own security context. Generally, .NET remoting applications are designed for use in intranets and are not exposed to the Web. Another benefit of hosting a remote object using IIS is that it accommodates exposing your remote objects over the Web. An application created using ASP.NET to serve as a .NET remoting server utilizes the Hypertext Transfer
Lesson 1: How to Deploy a Remoting Application
225
Protocol (HTTP) communication channel but might use a Simple Object Access Protocol (SOAP) formatter, a binary formatter, or a custom formatter. However, the SOAP formatter is used by default. The easiest method for deploying a .NET remoting server created as an ASP.NET application is to use a Web setup project (described later).
Deploying Remote Objects Remote objects are created as classes. A common practice in object-oriented program ming languages is to place each class in a separate physical file that is assigned the same name as the class definition that it contains. A remote object can contain one or more classes and each remote object is generally stored in a separate Visual Studio 2005 project. Each Visual Studio 2005 project compiles to a single DLL file located in the bin subfolder of the project. The .NET remoting server and any clients that con sume the object must add a reference to the object in the Visual Studio 2005 Solution Explorer. When a reference is added in Visual Studio 2005, a copy of the remote object is copied to the bin subfolder of the project to which the reference was added.
Deploying a Remote Object to the Server Remote objects must be deployed to both the server that hosts them as well as to each client that references them. Servers must reference a copy of a remote object so that the server can call methods contained in the remote object when requests are made by client applications. Client applications must reference a copy of a remote object so that the client code can compile. Deploying a copy of a remote object with the server that hosts the remote object is a simple process. As described earlier, when a reference to the remote object is added to the server, a copy of the remote object is copied to the bin subfolder of the server. When the server is deployed, the copy of the remote object is deployed as well. However, deploying a remote object to each client requires a bit more forethought.
Deploying a Remote Object to the Client There are three common approaches to deploying a remote object to a client: deploy the entire remote object assembly, deploy an interface implemented by the remote object, or use the .NET Framework SoapSuds utility to configure the remote object for deployment. The easiest method of deploying a remote object to a client is to deploy the entire remote object assembly. This method is the same as that used to deploy a remote
226
Chapter 6
Debugging and Deploying Remote Applications
object to a .NET remoting server application. A disadvantage to deploying an entire remote object assembly is that the code contained in the assembly is also copied to the client machine. Exposing the code to all clients to which the remote object is deployed could be a significant security risk and could expose intricate server details such as database connection information. As mentioned in Chapter 4 and Chapter 5, “Creating a Remoting Application,” there are two types of remote objects, marshal-by value (MBV) and marshal-by-reference (MBR). If you are creating an MBV remote object, the entire remote object assembly is serialized and copied to the client machine when a request is made by the client. Hence there is no way to really protect the code in the remote object assembly, and you should opt to copy the entire remote object assembly to improve performance. Another option for deploying a remote object is to deploy only an interface that the remote object implements. If you are creating an MBR remote object, this solution is perfect. When a class implements an interface, the class must override the function members declared in the interface. Thus the interface describes the function mem bers declared by any classes that implement it. A class can declare more function members than those that are declared by an interface. However, if you intend to use an interface for deployment, be sure that the interface accurately reflects the function members declared in any classes that implement the interface. To deploy an interface, declare the interface in its own project in Visual Studio 2005. This arrangement isolates the interface from any classes that implement it. When classes are created that implement the interface using Visual Studio 2005, a reference must be added to the assembly that contains the interface. Client applications must add a reference to the assembly that contains the interface using Visual Studio 2005 as well. Client applications are then able to make calls to function members declared in the interface from client code and successfully compile. However, the benefit offered by deploying an interface is that the interface contains no code, only function member signatures. Thus the code contained in the remote object is not exposed if an interface implemented by the remote object is deployed. Finally, the SoapSuds.exe (or SoapSuds) .NET Framework utility can be used to extract metadata from a remote object. In the previous two methods described for deploying a remote object—deploying the entire remote object assembly and deploying an interface implemented by the remote object assembly—deployment is being pushed from the server to the client. If you use the SoapSuds.exe utility, you can pull meta data about a remote object from the server and initiate deployment from the client. The SoapSuds.exe utility can be used to extract and re-create the entire remote object
Lesson 1: How to Deploy a Remoting Application
227
assembly on the client, extract and re-create a metadata-only remote object assembly on the client, extract a Web Services Description Language (WSDL) file describing the remote object assembly, and convert a WSDL file into source code on the client or into an assembly on the client. Extracting a WSDL file and converting it in a code file or assembly is more often used with Web services than with remote objects. The SoapSuds.exe utility is included in the .NET Framework Software Development Kit. Assuming we open the command prompt and navigate to the folder containing the RemotePatient.dll remote object assembly, the utility can be executed as shown here. SoapSuds Utility soapsuds -types:MSLearning.Chapter5.PatientClasses.Patient,RemotePatient -oa:Sample.dll
In this example, the –types switch is used to identify the class and remote object assembly to be analyzed. The –oa switch is used to identify the output assembly to be generated. The most commonly used switches of the SoapSuds.exe utility are described in Table 6-1. Table 6-1
SoapSuds.exe .NET Framework Utility Commonly Used Switches
Switch
Description
–inputassemblyfile: assemblyfile
This switch is used to identify the assembly for the utility to analyze and extract meta data from.
(–ia:assemblyfile) –inputdirectory:directory (–id:directory) –outputassemblyfile:assemblyfile (–oa:assemblyfile) –outputdirectory:outputdirectory (–od:outputdirectory)
This switch is used to identify the directory that contains the input assembly. This switch is used to identify the name of the output assembly file generated. This switch is used to identify the directory where the output assembly file will be stored.
–types: type1,assemblyname
This switch is used to identify the type and assembly to extract metadata from.
–domain:domain
This switch is used to identify the domain to be used for authentication.
(–d:domain)
228
Chapter 6
Debugging and Deploying Remote Applications
Table 6-1
SoapSuds.exe .NET Framework Utility Commonly Used Switches
Switch
Description
–username:username
This switch is used to identify the user name to be used for authentication.
(–u:username) –password:password (–p:password)
This switch is used to identify the password to be used for authentication.
Creating a Web Setup Project As mentioned earlier, the two most common targets to deploy a .NET remoting server to are a console application and an ASP.NET Web application hosted using IIS. Deploying to a console application is very easy, but deploying to IIS as a Web application can be a bit more challenging. Although a Web application can be deployed and configured manually, using a Visual Studio 2005 Web setup project automates the process. A Web setup project is used to package a Web project for deployment. When you create a Web setup project using Visual Studio 2005, you are presented with six editors. Each editor accommodates configuring an aspect of the setup project. After the setup project is configured, you build it using Visual Studio 2005. The result of building the setup project is either an executable or a Microsoft Installer file that can be used to install and configure the Web project when it is deployed. Web setup projects are typically added as an additional project to the solution that contains the Web project you are deploying. To create a Web setup project using Visual Studio 2005 follow this procedure: 1. Open the solution that contains the Web project you are deploying and click File, Add, New Project. 2. In the Add New Project dialog box, click Other Project Types, Setup And Deploy ment, Web Setup Project. 3. Assign the project a name and location, and click OK. The Web setup project is added to the open solution, and you are presented with the File System editor. The additional editors are accessible from the View menu by clicking Editor when the Web setup project is selected in Solution Explorer, right
Lesson 1: How to Deploy a Remoting Application
229
clicking the Web setup project in Solution Explorer and clicking View, or by click ing the toolbar button for each editor at the top of Solution Explorer when the Web setup project is selected.
File System Editor The File System editor is used to configure how the file system of the destination sys tem is affected by installing the Web application. Web folders, project output, files, and assemblies are added to the Web setup project by right-clicking the Web Applica tion Folder icon in the File System editor and clicking Add or from the Project menu by clicking Add when the File System editor is displayed. Project output includes the primary assembly created when the Web project is built; satellite assemblies used for localization; debug symbol files created when the Web project is built in Debug mode; and project content files such as images, source code files, Extensible Markup Lan guage (XML) documentation, and serialization files. Shortcuts to start the Web appli cation can also be added to various locations, such as the Windows desktop, when the application is installed. Additionally, when the File System editor is displayed, the Properties dialog box is used to configure settings for the destination virtual directory created when the Web application is installed. The Visual Studio 2005 Web Setup Project File System editor is shown in Figure 6-1.
Figure 6-1
The Visual Studio 2005 Web Setup Project File System editor
230
Chapter 6
Debugging and Deploying Remote Applications
Registry Editor The Registry editor is used to configure how the registry of the destination system is affected by installing the Web application. New keys and values are added to the Web setup project by right-clicking one of the registry node icons in the Registry editor. The Visual Studio 2005 Web Setup Project Registry editor is shown in Figure 6-2.
Figure 6-2
The Visual Studio 2005 Web Setup Project Registry editor
File Types Editor The File Types editor is used to configure how the file types of the destination system are affected by installing the Web application. Windows associates files with the appli cations used to manage them by using the file extension. For instance, if Microsoft Office is installed and you double-click a file with a .doc extension, Windows attempts to open the file using Microsoft Word. When you install an application, you can asso ciate file extensions and the actions taken on the files in Windows by your application using the File Types editor. File extensions and actions are added to the Web setup project by right-clicking the Files Types On Target Machine icon in the File Types edi tor and clicking Add File Type. The Visual Studio 2005 Web Setup Project Files Types editor is shown in Figure 6-3.
Lesson 1: How to Deploy a Remoting Application
Figure 6-3
231
The Visual Studio 2005 Web Setup Project File Types editor
User Interface Editor The User Interface editor is used to configure the user interface of the installation pro gram. The User Interface editor accommodates configuring both a standard installa tion interface and a separate administrative installation interface where you can give more detailed information and options to administrators. You can add, remove, and customize the dialog boxes displayed to the user as the application is installed by right-clicking a process step or dialog box in the User Interface editor. The Visual Stu dio 2005 Web Setup Project User Interface editor is shown in Figure 6-4.
232
Chapter 6
Figure 6-4
Debugging and Deploying Remote Applications
The Visual Studio 2005 Web Setup Project User Interface editor
Custom Actions Editor The Custom Actions editor is used to define any custom actions you would like to be performed when the Web application is installed. For instance, setup applications commonly display a text file after setup is complete that informs the user of updates to the application or how to get started. You can define a custom action to perform by right-clicking an icon in the Custom Actions editor and clicking Add Custom Action. The Select Item In Project dialog box is displayed, which allows you to add a file, out put, or assembly as a custom action definition. You can define a custom action to be performed prior to installation, on successful installation, on a failed installation, or on uninstallation. The Visual Studio 2005 Web Setup Project Custom Actions editor is shown in Figure 6-5.
Lesson 1: How to Deploy a Remoting Application
Figure 6-5
233
The Visual Studio 2005 Web Setup Project Custom Actions editor
Launch Conditions Editor The Launch Conditions editor is used to define any conditions that must be met prior to the Web application being installed. For instance, you might need to verify a par ticular version of IIS or the .NET Framework 2.0 to support the functionality enabled in your Web application. You can add a new launch condition to perform prior to installation beginning by right-clicking the Launch Conditions folder icon in the Launch Conditions editor and clicking Add Launch Condition. Launch conditions are typically used to verify a configuration setting or version. You can verify that a file, registry entry, or other application exists on the target machine by right-clicking the Search Target Machine folder icon in the Launch Conditions editor and selecting a search type. The Visual Studio 2005 Web Setup Project Launch Conditions editor is shown in Figure 6-6.
234
Chapter 6
Figure 6-6
Debugging and Deploying Remote Applications
The Visual Studio 2005 Web Setup Project Launch Conditions editor
Build, Install, and Uninstall After you configure the Web setup project, the next step is to build the project to gen erate the setup executable and Microsoft Installer (.msi) file. To build the Web setup project, select the option to build it from the Build menu or right-click the Web setup project in Solution Explorer and click Build. As with any other project type in Visual Studio 2005, if build errors are encountered, they must be corrected until the build is successful. When the build is successful, you can navigate to the folder where you created the Web setup project to locate the setup executable and Microsoft Installer file. If you built the project in Debug mode, you’ll find the files in the Debug subfolder; other wise you’ll find the files in the Release subfolder. You can double-click the Setup.exe file or the Microsoft Installer file to install and test the Web application. You can also install the Web application by right-clicking the Web setup project in Solution Explorer and clicking Install. Applications installed using a Web setup project and any dependent actions taken during installation are registered in the Add/Remove Applications Control Panel utility for easy uninstallation.
Lesson 1: How to Deploy a Remoting Application
235
The setup executable and Microsoft Installer files can be easily deployed to accommo date installing your Web application on a target machine. If the target machine has the latest Microsoft Installer updates applied, the Microsoft Installer file should be deployed; otherwise the setup executable should be deployed. Deployment of the setup files might include any method of copying the files from one location to another such as download, e-mail, direct file copy, and removable media.
Lab 1: Creating and Installing a Web Setup Project This lab walks you through the process of creating, building, and installing a Web setup project that will install a remote object to be hosted using IIS. The lab utilizes the RemotePatient remote object utilized in Chapter 5. �
Exercise 1: Creating a Web Setup Project
1. Add a Web Setup Project to the RemotePatient solution. a. Start Visual Studio 2005 and open the RemotePatient solution included with Lesson 1 of Chapter 6. b. On the File menu, click Add, New Project. c. In the Add New Project dialog box, click Other Project Types, Setup And Deployment, Web Setup Project. Assign the project a name such as RemotePatientSetup and click OK. 2. Add the remote object to the setup project. a. In the File System editor, in the Web Application Folder icon, right-click the bin subfolder. Click Add, Project Output. b. In the Add Project Output Group dialog box, click Primary Output and click OK. �
Exercise 2: Build and Install a Web Setup Project
1. Build the Web setup project. a. In Visual Studio 2005 Solution Explorer, right-click the bin subfolder and click Build. b. Be sure that the project builds successfully. If any build errors are encoun tered, resolve them and rebuild the project until it builds successfully.
236
Chapter 6
Debugging and Deploying Remote Applications
2. Install the Web setup project. a. The Web setup project can be installed in Solution Explorer by right-click ing the project and clicking Install. However, to better simulate an actual deployment, navigate to the location where the Web setup project was cre ated. In the Debug (or Release depending on the mode you built the project in) subfolder, double-click the .msi file to install the project. b. Work through the dialog boxes presented by the installation program to complete the installation. 3. Verify that the project was successfully installed. a. Open IIS by clicking Start, Control Panel, Administrative Tools, Internet Information Services (IIS) Manager. Using the IIS Manager, browse into the Default Website to the virtual folder created by the Web setup project installation as shown in Figure 6-7.
Figure 6-7 Services
The RemotePatientSetup virtual folder hosted by Internet Information
If you want to uninstall the Web setup project, use the Add/Remove Pro grams Control Panel utility or right-click the Web setup project in Visual Studio 2005 Solution Explorer and select Uninstall.
Lesson 1: How to Deploy a Remoting Application
237
Lesson Summary ■
.NET remoting server applications are typically deployed from a development environment to a testing environment to a production environment.
■
.NET remote objects are typically deployed from a development environment to a testing environment to a production environment and can be deployed to both the server and the client, depending on the type of remote object created.
■
.NET remote objects are hosted using a console application, a Windows execut able application, a Windows service application, or an ASP.NET Web applica tion. The most common hosts are either a console application or an ASP.NET Web application.
■
When you deploy a remote object, you can deploy the entire remote object assembly or an interface implemented by the remote object assembly, or you can use the SoapSuds.exe .NET Framework utility to read metadata from the remote object and generate an assembly on the client.
■
If you deploy a remote object as an ASP.NET Web application, you should deploy it using a Web setup project.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “How to Deploy a Remoting Application.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which application types are used to host .NET remote objects? (Select all that apply.) A. Windows service application B. Class library application C. ASP.NET Web application D. Smart device application 2. Which method should be used to deploy a remote object to a client if security of the remote object source code is a primary concern and the remote object is
238
Chapter 6
Debugging and Deploying Remote Applications
hosted using a console application on the server? (Select the best answer.) A. Deploy the entire remote object assembly. B. Deploy an interface implemented by the remote object assembly. C. Extract and re-create the entire remote object assembly using SoapSuds. D. Extract and re-create a metadata-only remote object assembly using SoapSuds. 3. What type of file is generated when a Web setup project is built? (Select the best answer.) A. Microsoft Installer file (msi) B. .NET managed assembly (dll) C. .NET managed assembly (exe) D. .NET configuration file (config)
Lesson 2: How to Debug a Remoting Application
239
Lesson 2: How to Debug a Remoting Application It is nearly impossible to write more than a few lines of code without there being some type of error in the execution of the code. There are three types of errors in software: syntax and compilation errors, runtime errors, and logic errors. Syntax and compila tion errors occur due to incorrect language syntax and command usage and normally prevent compilation. Runtime errors are errors that do not prevent compilation and are due to some element that is not detectable until runtime, such as the effect of invalid user input that is not validated and causes an error to occur. Another example of a runtime error could be an expected resource not being available at run time. Logic errors are sometimes categorized as runtime errors and normally do not prevent com pilation. Logic errors are commonly associated with if statements or loops where the logic driving the statement is not accurate. After this lesson, you will be able to: ■
Use the Visual Studio 2005 debugger to debug .NET remoting applications.
■
Handle .NET remoting exceptions using the RemotingException class.
■
Track the performance of a .NET remoting application using performance counters.
■
Monitor calls made to methods on a remote object using the .NET remoting track ing services.
Estimated lesson time: 60 minutes
Debugging is the process of identifying the causes of errors in software so that they can be corrected. Depending on the type of application created, debugging the appli cation might be relatively easy or it might be complex. Debugging .NET remoting applications is generally considered complex due to the distributed nature of the application. There are multiple options available for debugging and monitoring .NET remoting components.
Visual Studio 2005 Debugger Visual Studio 2005 is a comprehensive development environment, particularly in its full rendition, Visual Studio Team System. However, all editions of Visual Studio 2005 include the most robust visual debugger available on the market. The Visual Studio 2005 debugger can be used to fully analyze executing code including the ability to step line by line through source code, inspect memory registers in use, and manage
240
Chapter 6
Debugging and Deploying Remote Applications
unlimited breakpoints. The debugger can also be used to analyze code executing out side of Visual Studio 2005 by attaching to the process executing the code. When the debugger is attached to a process, provided you have access to the source code being executed by the process, you can control what the process is doing using the debug ger. Some application types cannot be debugged directly within Visual Studio 2005 but must be debugged by attaching the debugger to the process executing the appli cation code after the application is running. These applications include Windows ser vices and .NET remoting applications. Bear in mind that .NET remoting applications are composed of multiple components that include one or more remote objects, a server, and one or more clients. A remote object typically is created using a class library. Class libraries cannot be executed directly but must be executed by another process. Thus there is no way to debug a remote object directly without involving another process. In terms of .NET remoting, the process that acts as the server executes a remote object. Client applications then make calls to the server to access the remote object. To begin debugging a .NET remoting application, the server application must first be started. Starting the server, in turn, accommodates debugging remote objects hosted by the server. However, the server typically does not make calls to remote object meth ods on its own behalf but makes calls to remote object methods to fulfill client requests. Hence, to test and debug a .NET remoting application, you should simulate client applications by creating test scripts or a simple application, such as a console application, to make calls to remote object methods. There is essentially a single point from which to initiate debugging in a .NET remoting application: the client. A client application might require no special debug handling until a call is made to the remote object. At the point where a call is made to a remote object, execution transfers to another process, the server process. Typically, if you are debugging .NET remoting using the Visual Studio 2005 debugger, you debug it from the client. The Visual Studio 2005 debugger is capable of debugging multiple pro cesses, but only a single process can be displayed in the debugger at a given time. To debug a .NET remoting server from a client, you should attach to the server prior to executing the client. The server must be executing and listening for client requests. If you attach to the server process when it is listening for requests and not fulfilling requests, the debugger might have no activity to display. However, after a client request is made, you should be able to follow application execution in the server using
Lesson 2: How to Debug a Remoting Application
241
the debugger. To attach to the server process, open the client application project in Visual Studio 2005 and on the Debug menu, click Attach To Process. The Attach To Process dialog box is displayed in Figure 6-8.
Figure 6-8
The Visual Studio 2005 Attach To Process dialog box
The Attach To Process dialog box initially displays processes running on the local machine. If the .NET remoting server is executing on another machine, you need to select the process by connecting to the other machine. You can locate the executing process in the Available Processes list box and click Attach. After you attach to the pro cess, execute the client application. Be sure to set breakpoints at the location where you would like to break in the debugger, build all components in Debug mode, and configure all machines for remote debugging, if necessary. Furthermore, after execu tion is transferred to the server, you need to select the server as the process to display in the debugger by double-clicking the process to display in the Processes window. MORE INFO
Debugging multiple processes
For more information on debugging multiple processes using Visual Studio 2005, visit the topic titled “Attaching to Running Processes” at http://msdn2.microsoft.com/en-us/library/3s68z0b3.aspx.
242
Chapter 6
Debugging and Deploying Remote Applications
The RemotingException Class You should always expect the unexpected. When writing code, never assume that everything will go smoothly and all resources will be available. All code written using any programming language should be designed to adequately handle errors that could be encountered. Object-oriented programming languages, particularly .NET-compliant languages, handle errors through structured exception handling. When an error is encountered, an object is instantiated that represents the error, application execution halts, and the Common Language Runtime (CLR) begins searching for a catch statement to handle the exception. When errors are encountered in a .NET remoting application, the RemotingException class is instantiated to represent the error. Typically, the only code that might possibly throw an exception in a .NET remoting client application is the code that attempts to connect to the server and any code that makes calls to remote object methods. In the following code example, the catch statement is used to handle any remoting excep tions encountered. Handling Remoting Exceptions 'VB Imports Imports Imports Imports Imports
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter6.PatientClasses
Namespace MSLearning.Chapter6.ActivatorGetObject Module GetObjectModule Sub Main() Try ' Create an instance of the Patient class. Dim newPatient As Patient = _ CType(Activator.GetObject(GetType(Patient), _ "tcp://localhost:9000/Patient.rem"), Patient) ' Call a remote method. Console.WriteLine(newPatient.PersonalInformation()) Catch ex As RemotingException Console.WriteLine(ex.Message)
End Try
End Sub
Lesson 2: How to Debug a Remoting Application
243
End Module
End Namespace
//C# using using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter6.PatientClasses;
namespace MSLearning.Chapter6.ActivatorGetObject {
class Program
{
static void Main(string[] args)
{
try { // Create an instance of the Patient class. Patient newPatient = (Patient)Activator.GetObject(typeof(Patient), "tcp://localhost:9000/Patient.rem"); // Call a remote method. Console.WriteLine(newPatient.PersonalInformation()); } catch (RemotingException ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
Using Performance Counters Windows includes a utility to monitor application performance called the Perfor mance Monitor. The Performance Monitor can be started by using the Control Panel shortcut under Administrative Tools or by using the console utility, Perfmon.exe. The Performance Monitor tracks application performance through the use of performance counters. A counter is updated periodically to reflect an updated status. An application might maintain many counters to track different performance aspects. Counters can
244
Chapter 6
Debugging and Deploying Remote Applications
be grouped together into categories to make locating them easier when many are available for tracking. Many applications install Performance Monitor counters when they are installed. For instance, SQL Server 2005 installs several categories of counters to track performance of the database including the number of transactions committed per second and the number of deadlocks encountered. The .NET Framework is another example of an application that installs several categories of Performance Monitor counters. Counters can also be added to the Performance Monitor programmatically using the classes in the System.Diagnostics namespace. The Performance Monitor utility is shown in Figure 6-9.
Figure 6-9
The Windows Performance Monitor utility
The .NET Framework provides a category of counters specifically designed to track the performance of .NET remoting operations. As with all other counters, the .NET remoting counters can be added to the Performance Monitor and tracked manually or programmatically. To manually add counters to track, in the Performance Monitor, click the Add button in the toolbar. In the Add Counters dialog box, select the cate gory of counters to track from the Performance Object drop-down list box, select the counter type from the list box on the left, select the counter instance from the list box on the right, and click Add. The Add Counters dialog box is shown in Figure 6-10.
Lesson 2: How to Debug a Remoting Application
245
Figure 6-10 The Windows Performance Monitor Add Counters dialog box
The counters included in the .NET Framework for tracking .NET remoting operations are described in Table 6-2. Table 6-2
.NET Remoting Performance Counters
Counter
Description
Channels
This counter tracks the total number of chan nels registered across all application domains for a particular application.
Context Proxies
This counter tracks the total number of proxies created in a particular process.
Context-Bound Classes Loaded
This counter tracks the current number of context-bound classes that are loaded.
Context-Bound Objects Alloc/sec
This counter tracks the current number of context-bound objects allocated per second.
Contexts
This counter tracks the current number of con texts in a particular application.
246
Chapter 6
Debugging and Deploying Remote Applications
Table 6-2
.NET Remoting Performance Counters
Counter
Description
Remote Calls/sec
This counter tracks the current number of remote procedure calls (RPC) invoked per second.
Total Remote Calls
This counter tracks the total number of RPCs invoked for a particular application.
MORE INFO
Performance counters in the .NET Framework
For more information on performance counters, visit the topic titled “Performance Counters in the .NET Framework” at http://msdn2.microsoft.com/en-us/library/w8f5kw2e.aspx.
.NET Remoting Tracking Services Although performance counters are extremely useful, the .NET Framework includes .NET remoting tracking services to provide more granular control to define custom behavior each time a .NET remoting operation is performed. The tracking services are located in the System.Runtime.Remoting.Services namespace. There are basically two facets involved in utilizing the .NET remoting tracking services: the TrackingServices class and the ITrackingHandler interface. The TrackingServices class is used to register user-defined classes with the remoting system. The user-defined classes implement the ITrackingHandler interface and are notified when particular remoting operations occur. The TrackingServices class includes the two methods described in Table 6-3 for managing user-defined classes that implement ITrackingHandler. Table 6-3
TrackingServices Methods
Method
Description
RegisterTrackingHandler
This method is used to register a user-defined class with the remoting services as a class that should be notified when remoting operations occur.
UnregisterTrackingHandler
This method is used to unregister a user-defined class that is currently registered with the remoting services.
Lesson 2: How to Debug a Remoting Application
247
To utilize tracking services, one or more user-defined classes are created that imple ment the ITrackingHandler interface. The ITrackingHandler interface includes three methods, as described in Table 6-4. These methods are called on all registered classes when the associated operation occurs. Table 6-4
ITrackingHandler Methods
Method
Description
DisconnectedObject
This method is called when an object is disconnected from its proxy.
MarshaledObject
This method is called when an object is marshaled.
UnmarshaledObject
This method is called when an object is unmarshaled.
The following code example illustrates a user-defined class that implements ITrackingHandler and simply writes to the console when remoting operations are performed. ITrackingHandler Interface Implementation 'VB Imports Imports Imports Imports
System System.Runtime System.Runtime.Remoting System.Runtime.Remoting.Services
Namespace MSLearning.Chapter6.Server Class Tracker
Implements ITrackingHandler
Public Sub DisconnectedObject(ByVal obj As Object) Implements _
System.Runtime.Remoting.Services.ITrackingHandler.DisconnectedObject
Console.WriteLine("Object disconnected...")
End Sub
Public Sub MarshaledObject(ByVal obj As Object, _ ByVal [or] As System.Runtime.Remoting.ObjRef) Implements _ System.Runtime.Remoting.Services.ITrackingHandler.MarshaledObject Console.WriteLine("Object marshaled...")
End Sub
Public Sub UnmarshaledObject(ByVal obj As Object, _ ByVal [or] As System.Runtime.Remoting.ObjRef) Implements _ System.Runtime.Remoting.Services.ITrackingHandler.UnmarshaledObject
248
Chapter 6
Debugging and Deploying Remote Applications
Console.WriteLine("Object unmarshaled...")
End Sub
End Class
End Namespace
//C# using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Services;
namespace MSLearning.Chapter6.Server {
class Tracker : ITrackingHandler
{
#region ITrackingHandler Members public void DisconnectedObject(object obj)
{
Console.WriteLine("Object disconnected...");
}
public void MarshaledObject(object obj, ObjRef or) { Console.WriteLine("Object marshaled...");
}
public void UnmarshaledObject(object obj, ObjRef or) { Console.WriteLine("Object unmarshaled...");
}
#endregion
}
}
After a class is created that implements the ITrackingHandler interface, it can be regis tered to be notified when remoting operations occur using the TrackingServices class as shown in the following code example. TrackingServices Class 'VB Imports Imports Imports Imports Imports
System
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
System.Runtime.Remoting.Services
Lesson 2: How to Debug a Remoting Application
249
Namespace MSLearning.Chapter6.Server Class Program Shared Sub Main() RemotingConfiguration.Configure( _ "StandardServer.exe.config", False) TrackingServices.RegisterTrackingHandler(New Tracker()) Console.WriteLine("Press return to exit...\n")
Console.ReadLine()
End Sub
End Class
End Namespace
//C# using using using using using
System;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
System.Runtime.Remoting.Services;
namespace MSLearning.Chapter6.Server {
class Program
{
static void Main(string[] args)
{
RemotingConfiguration.Configure(
"StandardServer.exe.config", false);
TrackingServices.RegisterTrackingHandler(new Tracker()); Console.WriteLine("Press return to exit...\n");
Console.ReadLine();
}
}
}
Now that a handler is registered, when a client makes a call to a method in the class used in the preceding code, the handler class is notified. In the preceding example, the handler class simply writes status updates to the console.
Lab 2: Debugging a Remoting Application This lab illustrates handling .NET remoting exceptions and using the Visual Studio 2005 debugger to attach to and debug a remoting application. Exception handling
250
Chapter 6
Debugging and Deploying Remote Applications
should be included in all application code, and proper use of the debugger is essential knowledge for all .NET developers. �
Exercise 1: Handle Remoting Exceptions
1. Open the LabConsumer project. a. Start Visual Studio 2005 and open the LabConsumer project included with Lesson 2 of Chapter 6. b. Open the Main method. The Main method is located in Module.vb for the Visual Basic version of the project and Program.cs for the C# version of the project. 2. Add exception handling code to the consumer. a. Wrap the code that creates an instance of the Patient class and the code that calls methods on the Patient class in a try statement. b. Create at least one catch statement to handle any RemotingException instances that are encountered. A generic catch can also be created to catch any other exceptions that are encountered. The following code illustrates the exception handling code. Exception Handling Example 'VB Imports Imports Imports Imports Imports Imports
System
System.Runtime
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter6.PatientClasses
Namespace LabConsumer Class Program Shared Sub Main() RemotingConfiguration.Configure( _
"LabConsumer.exe.config", False)
Try Dim newPatient As Patient = New Patient() Console.WriteLine(newPatient.PersonalInformation()) Console.WriteLine(newPatient.History()) Catch ex As RemotingException
Lesson 2: How to Debug a Remoting Application
Console.WriteLine(ex.Message) Catch ex As Exception Console.WriteLine(ex.Message) End Try End Sub End Class End Namespace //C# using using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter6.PatientClasses;
namespace LabConsumer
{ class Program { static void Main(string[] args)
{
RemotingConfiguration.Configure(
"LabConsumer.exe.config", false);
try
{
Patient newPatient = new Patient(); Console.WriteLine(newPatient.PersonalInformation()); Console.WriteLine(newPatient.History()); }
catch (RemotingException ex)
{
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message); } } } }
251
252
Chapter 6
�
Debugging and Deploying Remote Applications
Exercise 2: Debug a Remoting Application
1. Set breakpoints in the RemotePatient solution. a. Using Visual Studio 2005, open the RemotePatient solution. b. Navigate to the location in code at which you would like to break. The RemotePatient example object used in the provided solution contains two methods, PersonalInformation and History. Set breakpoints in the code in the remote object. c. Build the object in Debug mode. 2. Start the server. a. Open the LabServer solution. b. Start the server without debugging by pressing Ctrl+F5. If you start the server in Debug mode, the instance of the debugger in use is attached to the server, and only a single instance of a debugger can be attached to a process at a given time. 3. Start the remote consumer. a. Open the LabConsumer solution. b. Set a breakpoint in the consumer code prior to the code that creates an instance of the remote object. c. Start debugging the consumer by pressing F5. The consumer should break at the breakpoint. d. Prior to stepping through the consumer code, attach the debugger to the server process on the Debug menu by clicking Attach To Process and select ing the server process. 4. Step into the remote object code. a. In the consumer, step into the code that creates an instance of the remote object and calls methods on the remote object. b. When a method on the remote object is called, display the remote object code by selecting the process in the Processes window. If the Processes win dow is now displayed in the debugger, display it from the Windows sub menu of the Debug menu by clicking Processes. c. Step through the remainder of the code to complete the consumer code and then press Enter twice in the server window to close it.
Lesson 2: How to Debug a Remoting Application
253
Lesson Summary ■
Debugging .NET remoting applications can be challenging. A remote object can not be executed on its own but is executed by a server application. However, a server does not call methods on a remote object that it hosts. Consumers call methods on remote objects and, hence, are the point where debugging occurs.
■
A server process is remote from a client process. The Visual Studio 2005 debugger must be attached to the remote process to be able to debug the remote process.
■
The Visual Studio 2005 debugger can debug multiple processes concurrently but can only display a single process at a time. The Processes window is used to tog gle between processes being debugged using the Visual Studio 2005 debugger.
■
The RemotingException class is used to represent exceptions thrown by .NET remoting applications.
■
The .NET Framework includes and installs several performance counters that are used to track the performance of .NET remoting applications.
■
The .NET Framework includes a tracking services class that is used to register user-defined classes that implement the tracking service interface for notifica tion of .NET remoting operations.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 2, “How to Debug a Remoting Application.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. At which point(s) in a .NET remoting application is debugging initiated? (Select the best answer.) A. Server application B. Remote object C. Proxy object D. Client application
254
Chapter 6
Debugging and Deploying Remote Applications
2. Which of the following performance counters are used to track .NET remoting application performance? (Select all that apply.) A. Total Remote Calls B. Proxies Instantiated C. Context Proxies D. Channels 3. Which .NET remoting operations is an object registered using tracking services notified about? (Select all that apply.) A. Marshaled B. Disconnected C. Connected D. Unmarshaled
Lesson 3: How to Manage the Lifetime of Remote Objects
255
Lesson 3: How to Manage the Lifetime of Remote Objects A foundational behavioral principle of the .NET Framework CLR is the use of the gar bage collector (GC) to manage memory. The GC functions by automatically and nondeterministically cleaning and compacting memory allocated to applications managed by the CLR. The GC prevents memory leaks that are prevalent in many runt ime environments that are not managed by the CLR. In object-oriented programming languages, the duration an instance of an object exists in memory is called the object’s lifetime. After this lesson, you will be able to: ■
Understand how the lifetime of a remote object is managed.
■
Initialize the lifetime of a remote object programmatically and by using a configu ration file.
■
Renew the lifetime of a remote object.
Estimated lesson time: 60 minutes
The CLR manages both value types and reference types. The GC manages the lifetime of reference types by monitoring all references to an object. When all references to an object have released the object, the GC flags the object for collection and eventually destroys the instance of the object. The GC behavior just described is a perfect solu tion for most applications created using the .NET Framework. However, the GC is only able to monitor references to a remote object that exist in the same application domain and process as the instance of the remote object. How does this behavior affect .NET remoting applications? Client applications that consume remote objects reside in processes and application domains that are separate from the process and application domain where the remote object resides. Hence, even if there are many client applications making calls to meth ods contained in a remote object, the GC cannot see these client applications that ref erence the remote object and flag the remote object instance for collection. Eventually, the remote object is destroyed by the GC, possibly while it is fulfilling client requests. .NET remoting applications are distributed applications where the components that comprise the application can reside on opposite sides of the world. The .NET Frame work is designed to simplify distributed environments by providing components that act as the glue to hold distributed components together. For instance, depending on
256
Chapter 6
Debugging and Deploying Remote Applications
how the remote object is activated, a client application might require a proxy object to be created that represents the remote object. Likewise, on the server side, an instance of a remote object must have at least one reference to it maintained; otherwise the GC collects the remote object. However, all objects that hold a reference to an instance of a remote object typically do not reside on the server. To remedy this situation, an object similar to a proxy object is created on the server that represents a client application and holds a reference to a remote object on the server for the client application. Thus, a reference to an instance of a remote object is detected by the GC and the instance of the remote object is not collected by the GC. The proxy-type object created on the server to represent a client application and hold a reference to the instance of the remote object is called a lease object. A server can host many remote objects, and multiple instances of a remote object can exist in memory. Each instance of a remote object is assigned its own lease object. All lease objects on a server are managed by another object called the lease manager. A lease object is assigned a duration to determine how long to hold the reference to the instance of the remote object. When a lease expires, the instance of the remote object is collected by the GC. However, each client application can register a sponsor object that allows the client application to extend the duration of the lease. When a lease expires, prior to a remote object being made available for collection, the lease manager determines whether any sponsors want to extend the lifetime of the lease.
Initializing the Lifetime of a Remote Object Lease objects are assigned default values that determine their behavior. However, default values can be overridden either programmatically or through an application configuration file. Modifications to configuration values for a lease object must be made when the lease object is initialized; otherwise the configuration value modifica tions have no effect. There are several properties that are used to modify the configuration of a lease object. Table 6-5 lists commonly used lease object configuration properties. Table 6-5
Lease Lifetime Properties
Property
Description
CurrentLeaseTime
This property is used to specify the amount of time until a lease object expires.
Lesson 3: How to Manage the Lifetime of Remote Objects
Table 6-5
257
Lease Lifetime Properties
Property
Description
InitialLeaseTime
This property returns the initial lease time and is used to specify the lifetime duration for a lease object. The default value is 5 minutes. To specify that the lease does not expire, set this property to 0.
LeaseManagerPollTime
This property is used to determine how often the lease manager should check for expired leases. The default value is 10 seconds.
RenewOnCallTime
This property returns the time remaining on the lease and is used to extend the lifetime of a lease each time a call is made to a method contained in the remote object referenced by the lease. The default value is 2 minutes. However, the lifetime of the lease is extended only if the amount of time remaining in the lease object lifetime duration is less than the RenewOnCallTime. For instance, if a lease still has 3 minutes until it expires, a call with a RenewOnCallTime does not extend its duration. However, if a lease only has 1 minute until it expires, a call with a RenewOnCallTime extends the duration of the lease.
SponsorshipTimeout
This property is used to determine how long the lease manager should wait for a sponsor to respond when it attempts to determine if a sponsor wants to extend a lease. The default value is 2 minutes.
To modify the configuration of a lease object programmatically, the InitializeLife timeService method must be overridden as shown in the following code example. Initializing a Lease Programmatically 'VB Imports Imports Imports Imports
System System.Runtime.Remoting System.Runtime.Remoting.Lifetime ControlChars = Microsoft.VisualBasic.ControlChars
Namespace MSLearning.Chapter6.PatientClasses
258
Chapter 6
Debugging and Deploying Remote Applications
Public Class Patient
Inherits MarshalByRefObject
Public Overrides Function InitializeLifetimeService() As Object Dim lease As ILease = CType( _ MyBase.InitializeLifetimeService(), ILease) If lease.CurrentState = LeaseState.Initial Then lease.InitialLeaseTime = TimeSpan.FromMinutes(15) lease.RenewOnCallTime = TimeSpan.FromMinutes(5) End If Return lease
End Function
Public Function PersonalInformation() As String Return "Patient ID: 001 :: Demo Patient" End Function Public Function History() As String Return "01/12/2001; Diagnosis: cold | Treatment: take " & _ "2 and call me in the morning. " & ControlChars.CrLf & _ "03/04/2005; Diagnosis: broken toe | Treatment: nothing." End Function
End Class
End Namespace
//C# using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Lifetime;
namespace MSLearning.Chapter6.PatientClasses {
public class Patient : MarshalByRefObject
{
public override object InitializeLifetimeService() { ILease lease = (ILease) base.InitializeLifetimeService(); if (lease.CurrentState == LeaseState.Initial) { lease.InitialLeaseTime = TimeSpan.FromMinutes(15); lease.RenewOnCallTime = TimeSpan.FromMinutes(5); }
Lesson 3: How to Manage the Lifetime of Remote Objects
259
return lease;
}
public string PersonalInformation()
{
return "Patient ID: 001 :: Demo Patient";
}
public string History()
{
return "01/12/2001; Diagnosis: cold | Treatment: take " + "2 and call me in the morning.\n\r03/04/2005; " + "Diagnosis: broken toe | Treatment: nothing."; } } }
To modify the configuration of a lease object using an application configuration file, use the element of the configuration file. The element contains attributes with the same name and functionality as their programmatic counterparts as shown in the following example. Initializing a Lease Using a Configuration File
Renewing the Lifetime of a Remote Object After a lease has been created, modifying the properties of the lease has no effect on the lease. The lease duration can be renewed by a sponsor object or by manually call ing the Renew method of the ILease interface from a client application as shown in the following code example. Renewing a Lease 'VB Imports Imports Imports Imports Imports Imports Imports
System System.Runtime System.Runtime.Remoting System.Runtime.Remoting.Lifetime System.Runtime.Remoting.Channels System.Runtime.Remoting.Channels.Tcp MSLearning.Chapter6.PatientClasses
Namespace LabConsumer
260
Chapter 6
Debugging and Deploying Remote Applications
Class Program Shared Sub Main() RemotingConfiguration.Configure( _ "LabConsumer.exe.config", False) Dim newPatient As Patient = New Patient() Console.WriteLine(newPatient.PersonalInformation()) Console.WriteLine(newPatient.History()) ' Renew the lease. Dim lease As ILease = CType( _ RemotingServices.GetLifetimeService(newPatient), ILease) Dim expireTime As TimeSpan = _ lease.Renew(TimeSpan.FromMinutes(5)) Console.WriteLine(expireTime.ToString())
End Sub
End Class
End Namespace
//C# using using using using using using using
System;
System.Runtime;
System.Runtime.Remoting;
System.Runtime.Remoting.Lifetime;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter6.PatientClasses;
namespace LabConsumer {
class Program
{
static void Main(string[] args) { RemotingConfiguration.Configure( _ "LabConsumer.exe.config", false); Patient newPatient = new Patient(); Console.WriteLine(newPatient.PersonalInformation()); Console.WriteLine(newPatient.History()); // Renew the lease. ILease lease = (ILease)RemotingServices.GetLifetimeService(newPatient); TimeSpan expireTime = lease.Renew(TimeSpan.FromMinutes(5)); Console.WriteLine(expireTime.ToString()); } } }
Lesson 3: How to Manage the Lifetime of Remote Objects
261
Lab 3: Managing the Lifetime of Remote Objects This lab illustrates configuring the lease object for a remote object. �
Exercise 1: Initializing a Lease
1. Open the RemotePatient project. a. Start Visual Studio 2005 and open the RemotePatient project included with Lesson 3 of Chapter 6. b. The Patient class already extends the MarshalByRefObject class, so now it just needs to override the necessary method. 2. Override the InitializeLifetimeService function. a. Declare a new instance of the ILease interface to represent the lease for the RemotePatient object. b. Modify the configuration property values to extend the length of the lease duration when the lease is in its initial state.
Lesson Summary ■
Lease objects are used to hold a reference to a remote object on the server in place of a client application.
■
A lease object is initialized by overriding the InitializeLifetimeService function in the remote object.
■
Lease object configuration properties must be configured before the lease is ini tialized.
■
A lease object duration can be extended by calling the ILease.Renew method from a client application.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 3, “How to Manage the Lifetime of Remote Objects.” The questions are also avail able on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
262
Chapter 6
Debugging and Deploying Remote Applications
1. Which function must be overridden to initialize a lease? (Select the best answer.) A. InitializeLease B. InitializeLifetimeService C. InitializeLeaseService D. InitializeLifetime 2. What is the default value for the InitialLeaseTime property? (Select the best answer.) A. 5 minutes B. 0 minutes C. 15 minutes D. 2 minutes 3. How can a client application extend the duration of a lease? (Select the best answer.) A. Instantiate a new lease object on the server. B. Register a sponsor for the object on the server. C. Register a new lease object in the lease manager programmatically. D. Register a new lease object in the lease manager by using a configuration file.
Chapter 6 Review
263
Chapter Review To further practice and reinforce the skills you learned in this chapter, you can per form the following tasks: ■
Review the chapter summary.
■
Review the list of key terms introduced in this chapter.
■
Complete the case scenarios. These scenarios set up real-world situations involv ing the topics of this chapter and ask you to create a solution.
■
Complete the suggested practices.
■
Take a practice test.
Chapter Summary ■
There are four common .NET application types used to host .NET remoting applications: ASP.NET Web applications, console applications, Windows exe cutable applications, and Windows service applications.
■
The most common host application type is an ASP.NET Web application. A Web setup project is the easiest way to deploy an ASP.NET Web application.
■
The Visual Studio 2005 debugger must be attached to a remote process to be able to debug the remote process. The Visual Studio 2005 debugger can attach to and debug multiple processes simultaneously but can only display a single process at a time.
■
Debugging .NET remoting applications can be complex. Debugging is initiated from a calling client application.
■
The RemotingException class is used to describe .NET remoting exceptions.
■
The .NET Framework installs several performance counters to track the perfor mance of .NET remoting applications.
■
The .NET Framework includes services for tracking .NET remoting operations.
■
A lease is used to maintain a reference to a remote object on a server in place of a client application.
■
A lease can be initialized by overriding the InitializeLifetimeService function.
■
A lease can be manually renewed by calling the ILease.Renew method.
264
Chapter 6 Review
Key Terms Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book. ■
lease
■
Microsoft Installer
■
performance counter
■
SoapSuds
■
sponsor
■
Web setup project
Case Scenarios In the following case scenarios, you apply what you’ve learned about how to debug and deploy remote objects. You can find answers to these questions in the “Answers” section at the end of this book.
Case Scenario 1: Tracking and Improving the Performance of a Distributed Application You are the chief software architect for the AdventureWorks organization. AdventureWorks is in the business of selling outdoor gear and sporting equipment globally. You have created a distributed order tracking system that is used across AdventureWorks so that all orders are entered directly into a central database. Your order tracking sys tem utilizes remote objects to serve as its business and data components. The client base and order demand at AdventureWorks have been growing exponentially and you need to ensure that there are enough servers hosting your remote objects to sup port loads being placed on the objects as orders are placed.
Questions To accurately gauge the resources needed to support order loads, you must answer the following questions: 1. How can you track how many instances of your remote objects are being created and which methods are being called most often? 2. How can you ensure that order loads are being balanced across your remote objects as efficiently as possible?
Chapter 6 Review
265
Case Scenario 2: Simplifying Remote Object Deployment You are the chief software architect for the Baldwin Museum of Science. You are cre ating a remote object that will allow other partner museums to search your inventory of exhibits. You have new museums partnering with your museum on a continual basis. New partners will need to be able to search your inventory. You are hosting the remote objects using IIS and have implemented authentication for each partner.
Questions When creating the enterprise architecture for the museum, you need to answer the fol lowing question so that others can maintain the application you are creating in the future. ■
How can you simplify the deployment of your remote objects to trusted partner museums in the future?
Suggested Practices ■
Host remote objects using either IIS or a Windows service application. .NET remoting applications are typically only hosted using a console application in teaching and testing environments for simplicity.
■
Deploy .NET remoting applications to an IIS host using a Visual Studio 2005 Web setup project.
■
Deploy remote objects to client applications using an interface implemented by the remote object.
■
Be sure that remote objects have been thoroughly tested and debugged prior to deployment.
■
Ensure that all remote object code includes exception handling, generally using the RemotingException class.
■
Test remote objects to verify the necessary lifetime and lease durations needed for normal operation. You want remote objects to remain in memory while in use but not longer than necessary.
■
Configuring lease properties using a configuration file will be much easier to maintain in the future if modifications are necessary.
266
Chapter 6 Review
Take a Practice Test The practice tests on this book’s companion CD offer many options. For example, you can test yourself on just the content covered in this chapter, or you can test yourself on all the 70-529 certification exam content. You can set up the test so that it closely sim ulates the experience of taking a certification exam, or you can set it up in study mode so that you can look at the correct answers and explanations after you answer each question. MORE INFO
Practice tests
For details about all the practice test options available, see the section titled “How to Use the Prac tice Tests” in this book’s Introduction.
Chapter 7
Method Invocations and Event Management with .NET Web Services The Microsoft .NET Framework provides two primary mechanisms for created dis tributed applications, .NET Web services and .NET remoting. Both technologies will be consolidated into the Windows Communication Foundation (WCF) in the near future, but the need to create and manage .NET Web services (and remoting compo nents) will only increase as applications move toward distributed architectures. This chapter is concerned with the process of creating and using .NET Web services. MORE INFO
Windows Communication Foundations (WCF)
For more information on the WCF, visit the WCF section of MSDN Online at http:// www.msdn.microsoft.com/webservices/indigo/default.aspx.
Exam objectives in this chapter: ■
Call Web methods asynchronously. ❑
Call a Web method.
❑
Poll for the completion of a Web method.
❑
Get Web method results.
❑
Implement callback.
❑
Call a OneWay Web method.
Lessons in this chapter: ■
Lesson 1: Calling Web Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
■
Lesson 2: Polling Web Methods for Completion . . . . . . . . . . . . . . . . . . . . . . . 284
267
268
Chapter 7
Method Invocations and Event Management with .NET Web Services
Before You Begin To complete the lessons in this chapter, you must must have ■
A computer that meets or exceeds the minimum hardware requirements listed in the Introduction of the book.
■
Microsoft Visual Studio 2005 and the .NET Framework 2.0.
■
Successfully completed all lessons in Chapters 1 though 6.
You should also be familiar with Visual Basic or C# and be comfortable with the fol lowing tasks: ■
Creating a console application in Visual Studio using Visual Basic or C#.
■
Adding references to system class libraries to a project.
■
Adding a simple Web reference to a project.
■
Having at least a basic understanding of remoting methodologies (XML Web services, DCOM, .NET remoting, CORBA, and so on). NOTE
The StockLookupService project
For each exercise, we use the StockLookupService project located on the companion CD in the Chapter 7 – Web Service folder.
Real World William Ryan Back in 2002, the company I was with had quickly grown beyond what its tech nological infrastructure could support. The company decided to create a totally new management system using the.NET Framework 1.0. Because it was a fairly straightforward in-house application, a decision was made to architect it as a standard client/server application. This structure worked wonderfully for a while, but as business grew, so did customer demands. Little by little, the com pany needed to expose more and more functionality to the outside world. Because the .NET Framework was being used as the existing platform, creating components that customers could consume (via .NET Web services) externally was a fairly straightforward endeavor. Four years after the original application was built, we now have quite a few customers that access our products via Web services, and the application has turned into one of our major selling points.
Lesson 1: Calling Web Methods
269
Lesson 1: Calling Web Methods In many respects, calling a Web method is no different from calling any other method. For instance, the following procedures are common to both tasks: ■
Add a reference to the library that contains the application logic you want to invoke.
■
Declare a new instance of the object.
■
Call the method belonging to that object.
The only substantive difference between traditional method invocation and Web method invocation is in how the library is referenced and that the data is transferred to the client in the form of XML. It’s worth noting, however, that Visual Studio 2005 does much of the grunt work associated with adding Web references for you; hence, if you can add a project reference to an ordinary dynamic link library (DLL), you can do the same for a Web library. Behind the scenes, a lot has to happen to allow you to call a Web method, but this process is almost totally abstracted from the developer. If you use Visual Studio 2005 and if you were only looking at the instantiation of the object and the method call to it, there’s no way you could distinguish whether a method call was to a Web method or a traditional method. After this lesson, you will be able to: ■
Call a Web method synchronously.
■
Call a Web method asynchronously.
■
Use the built-in CancelAsync delegate.
Estimated lesson time: 20 minutes
Calling a Web Method Synchronously To explain how to call a Web method synchronously, it’s beneficial to just show the code to call a Web method first, and then move on to the discussion. 'VB Public Sub GetTickerSymbol()
Dim Lookup As New StockLookupService()
Console.WriteLine(Lookup.GetCompanyName("MSFT"))
End Sub //C# public static void GetTickerSymbol()
{
270
Chapter 7
Method Invocations and Event Management with .NET Web Services
StockLookupService Lookup = new StockLookupService();
Console.WriteLine(Lookup.GetCompanyName("MSFT"));
}
Now, to call these methods, you would do the following: 'VB Sub Main()
GetTickerSymbol()
Console.ReadLine()
End Sub //C# static void Main(string[] args) {
GetTickerSymbol();
Console.ReadLine();
}
Unless you have some supernatural skills heretofore unknown, you can’t tell that there’s a Web object being instantiated or a Web method being called. This abstrac tion is by design. A brief explanation of what is going on behind the scenes is in order. When you add a Web reference to your project using the Add Web Reference dialog, your code looks pretty much like it would if you were just adding a traditional library reference. In this case, however, looks are deceiving. Visual Studio 2005 creates what is known as a proxy class that for all outward appearances causes the object to behave just like any other object. NOTE
Dissecting a Web reference
If you open the Web References node in Solution Explorer and click Show All Files, you’ll see a tree of items appear under the node. One of those items is named Reference.map. If you expand this node, you’ll see a Reference.vb file if you’re using Visual Basic .NET or a Reference.cs file if you’re using C#. Inside this class is a copy of every property and method of the Web service to which you added a reference. If you have never done so, you should examine the class just so you see what is going on behind the scenes.
When you add a Web reference, Visual Studio 2005 creates what is known as a proxy class that, for all outward appearances, causes the object to behave just like any other object. This class is generated in the language that’s being used to consume the web service, so you can create and consume web services in any language you can create projects in with Visual Studio .NET 2005. If you examine the proxy, this mechanism will probably start making more sense quite quickly.
Lesson 1: Calling Web Methods
BEST PRACTICES
271
Keep Web references current!
Change is inevitable in most applications. Because a Web reference is based on a snapshot of the Web service at the time this reference was created, the reference doesn’t know about any changes that might have occurred after it was added. Before doing a production build, it’s a good idea to right-click the Web reference and click Update Web Reference. This command causes the discovery process to commence, and the proxy class will be updated. Don’t assume that just because your method takes, for instance, one string parameter and returns a string that such will always be the case. Like anything else, introducing breaking changes to a Web service can cause a lot of prob lems, but it does happen and you need to be prepared if it does.
I’ll leave it up to you to examine the entire proxy class that Visual Studio 2005 gener ated for the StockSymbolConsumer project, although the specific method that was invoked in the preceding example is shown here. (Visual Studio generates slightly dif ferent code depending on whether your project is written in Visual Basic .NET or C#. As you’ll see shortly, a lot more code was generated for the GetCompanyName method than what’s shown here, but this is the only code that was needed to make that par ticular Web method call.) 'VB _ Public Function GetCompanyName(ByVal tickerSymbol As String) As String Dim results() As Object = Me.Invoke("GetCompanyName", _ New Object() {tickerSymbol}) Return CType(results(0),String) End Function //C# [System.Web.Services.Protocols.SoapDocumentMethodAttribute( "http://tempuri.org/GetCompanyName", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle= System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] public string GetCompanyName(string tickerSymbol) { object[] results = this.Invoke("GetCompanyName", new object[] { tickerSymbol}); return ((string)(results[0])); }
272
Chapter 7
Method Invocations and Event Management with .NET Web Services
This might seem simple and that’s because, in its simplest form, it is simple. Yet this is far from an exhaustive summary of what can be done with Web methods. Suffice to say, however, that for synchronous calls, this is pretty much all there is to it.
Calling a Web Method Asynchronously There are a lot of potential problems associated with synchronous method calls that can severely affect the end user experience. A few of the major ones are listed here: ■
The Web method might end in an error. If this happens, you will only learn of the error after the Timeout period expires, which might be quite a long time. (Most people consider an application freeze for even five seconds a long time.)
■
The Web method might complete successfully but take a long time. Again, with synchronous invocations, the end user application freezes up, which is disturb ing to end users.
■
In most instances you will call more than one Web method, and waiting for each call to complete individually is potentially wasteful. (Imagine a case where the first method doesn’t return properly. The user might wait 30 seconds or so for this method to return and the next method to be called. If the methods were called asynchronously, both requests would be fired at close to the same time and the overall wait time could be drastically reduced.)
A nonresponsive user interface might have been acceptable 20 years ago when there was little competition in the marketplace, but today users expect more from their soft ware. Few things annoy users more than having the user interface freeze up. This sit uation often results in users killing the application process, which has the added negative externality of possibly leaving your application in an unknown state. Consid ering how simple it is to work around this issue, it’s unacceptable to do otherwise in most cases. As mentioned in the last section, there’s a lot more that can be done with respect to calling Web methods. Again, however, calling a Web method doesn’t differ in any sub stantive way from calling a regular method, although there’s a little more behind-the scenes work done for you. To call a Web method asynchronously, you have the follow ing choices: ■
Create a new thread and call the method from there. (The subject of threading is complex and beyond the scope of this particular subject matter, so it is not cov ered here.) Suffice it to say that calling a Web method from a new thread is done identically to the way it would be done with any other method.
Lesson 1: Calling Web Methods
273
■
Create a delegate object that matches the Web method signature and call it from there.
■
Use one of the delegates that the proxy class provides for you.
Again, to show how these mechanisms work, it is probably best to start with a code example. The following is all of the code that Visual Studio generated for the GetTickerSymbol method: 'VB Public Event GetCompanyNameCompleted As GetCompanyNameCompletedEventHandler _ Public Function GetCompanyName(ByVal tickerSymbol As String) As String Dim results() As Object = Me.Invoke("GetCompanyName", _ New Object() {tickerSymbol}) Return CType(results(0), String) End Function ''' Public Overloads Sub GetCompanyNameAsync(ByVal tickerSymbol As String) Me.GetCompanyNameAsync(tickerSymbol, Nothing) End Sub ''' Public Overloads Sub GetCompanyNameAsync(ByVal tickerSymbol As String, _ ByVal userState As Object) If (Me.GetCompanyNameOperationCompleted Is Nothing) Then Me.GetCompanyNameOperationCompleted = _ AddressOf Me.OnGetCompanyNameOperationCompleted
End If
Me.InvokeAsync("GetCompanyName", New Object() {tickerSymbol}, _
Me.GetCompanyNameOperationCompleted, userState) End Sub Private Sub OnGetCompanyNameOperationCompleted(ByVal arg As Object) If (Not (Me.GetCompanyNameCompletedEvent) Is Nothing) Then Dim invokeArgs As _ System.Web.Services.Protocols.InvokeCompletedEventArgs = _ CType(arg, _ System.Web.Services.Protocols.InvokeCompletedEventArgs) RaiseEvent GetCompanyNameCompleted(Me, _
New GetCompanyNameCompletedEventArgs(invokeArgs.Results, _
invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState))
End If End Sub
274
Chapter 7
Method Invocations and Event Management with .NET Web Services
Public Shadows Sub CancelAsync(ByVal userState As Object) MyBase.CancelAsync(userState) End Sub //C# public event GetCompanyNameCompletedEventHandler GetCompanyNameCompleted; [System.Web.Services.Protocols.SoapDocumentMethodAttribute( "http://tempuri.org/GetCompanyName", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle= System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] public string GetCompanyName(string tickerSymbol) { object[] results = this.Invoke("GetCompanyName", new object[] { tickerSymbol}); return ((string)(results[0])); } /// public void GetCompanyNameAsync(string tickerSymbol) { this.GetCompanyNameAsync(tickerSymbol, null); } /// public void GetCompanyNameAsync(string tickerSymbol, object userState) { if ((this.GetCompanyNameOperationCompleted == null)) { this.GetCompanyNameOperationCompleted = new System.Threading.SendOrPostCallback( this.OnGetCompanyNameOperationCompleted); } this.InvokeAsync("GetCompanyName", new object[] { tickerSymbol}, this.GetCompanyNameOperationCompleted, userState); } private void OnGetCompanyNameOperationCompleted(object arg) { if ((this.GetCompanyNameCompleted != null)) { System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs) (arg)); this.GetCompanyNameCompleted(this, new GetCompanyNameCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); } } [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "2.0.50727.42")] public delegate void GetCompanyNameCompletedEventHandler( object sender, GetCompanyNameCompletedEventArgs e);
Lesson 1: Calling Web Methods
275
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "2.0.50727.42")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetCompanyNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { private object[] results; internal GetCompanyNameCompletedEventArgs(object[] results, Exception exception, bool cancelled, object userState) :
base(exception, cancelled, userState) {
this.results = results;
} ///
public string Result {
get {
this.RaiseExceptionIfNecessary();
return ((string)(this.results[0]));
} } }
If you examine this code closely, you’ll probably see the specific code that allows for asynchronous invocation. (Hint: look for async.) Now, change the original code defi nition slightly to call the GetCompanyNameAsync method: 'VB Public Sub GetTickerSymbol() Dim Lookup As New StockLookupService() Console.WriteLine(Lookup.GetCompanyNameAsync("MSFT")) End Sub //C# public static void GetTickerSymbol() { StockLookupService Lookup = new StockLookupService(); Console.WriteLine(Lookup.GetCompanyNameAsync("MSFT")); }
If you try to compile and run this code, it fails, indicating that the return type from the method call isn’t valid. Lest there be any confusion, this modified code won’t work. That’s because calling an asynchronous method is a little more involved. The answer to why the return type from the method call is no longer valid rests in the proxy class, the relevant portions of which are provided here: 'VB Public Overloads Sub GetCompanyNameAsync(ByVal tickerSymbol As String) Me.GetCompanyNameAsync(tickerSymbol, Nothing)
276
Chapter 7
Method Invocations and Event Management with .NET Web Services
End Sub Public Overloads Sub GetCompanyNameAsync(ByVal tickerSymbol As String, _
ByVal userState As Object)
If (Me.GetCompanyNameOperationCompleted Is Nothing) Then
Me.GetCompanyNameOperationCompleted = _ AddressOf Me.OnGetCompanyNameOperationCompleted End If Me.InvokeAsync("GetCompanyName", New Object() {tickerSymbol}, _ Me.GetCompanyNameOperationCompleted, userState)
End Sub
//C# public void GetCompanyNameAsync(string tickerSymbol) {
this.GetCompanyNameAsync(tickerSymbol, null);
}
public void GetCompanyNameAsync(string tickerSymbol, object userState) {
if ((this.GetCompanyNameOperationCompleted == null)) {
this.GetCompanyNameOperationCompleted =
new System.Threading.SendOrPostCallback(
this.OnGetCompanyNameOperationCompleted);
}
this.InvokeAsync("GetCompanyName", new object[] {
tickerSymbol}, this.GetCompanyNameOperationCompleted,
userState);
}
The most glaring difference is the return type. Notice that a string is no longer returned. In fact, the Visual Basic .NET method signature now specifies a Sub (which indicates there is no return type) and the C# return type now specifies void (which also indicates lack of a return type). How do you handle this situation and make an asynchronous call? CAUTION
Be careful when introducing changes that break existing code
One adage of object-oriented design is that classes should be closed for modification but open for extension. Although I could write a few chapters on this principle alone, it essentially means that after you create a class, the interface for it shouldn’t change. If you have a method named GetStockSymbol that accepts a single string argument and returns a string, you should not change the return type, change the parameter type, or add or remove parameters. People incorrectly assume that this principle means that the actual implementation of the method should never be changed. If this were the case, then every time you fixed a bug you’d be violating good object-oriented pro gramming design principles, which clearly isn’t the case. For some reason, many developers who are otherwise careful not to change the interface of a tra ditional class have no such compunction when it comes to Web services. Keep in mind that from the client’s perspective, there is literally no difference between calling a Web service’s method and any other method. Remember that a proxy class is created that matches the Web service, and objects instantiated from this proxy class contain the methods that are invoked. Any changes that
Lesson 1: Calling Web Methods
277
break existing code should be approached with great caution. If you must introduce such changes (typically due to changing requirements or design flaws), make sure you give any consumers of the service enough advance notice to allow them to respond. Make sure as well that you don’t violate any contractual obligations, or you might end up with legal complications.
When Visual Studio creates the proxy, it automatically creates an event for the Com pleted event of each of your methods. This proxy creation is done by default and requires no work on the part of the developer. Basically you simply need to create an event handler to respond to the event and then wire it up, as shown here. (Again, the Visual Basic .NET code is markedly different from the C# code. These differences are due to differences in the language. Semantically, these code snippets behave the same way and the output is identical.) 'VB Private WithEvents AsyncLookup As StockLookupService Sub Main() AsyncLookup = New StockLookupService() AsyncLookup.GetCompanyNameAsync("MSFT") Console.ReadLine() End Sub Public Sub GetTickerSymbolCompletedHandler(ByVal sender As Object, _ ByVal args As GetCompanyNameCompletedEventArgs) _ Handles AsyncLookup.GetCompanyNameCompleted If Not args.Error Is Nothing Then
Console.WriteLine("Error: {0} Message: {1}", args.UserState, _
args.Error.Message)
End If
If args.Cancelled Then
Console.WriteLine("Method invocation was cancelled") Else Console.WriteLine("Method completed: {0}", args.UserState) End If End Sub //C# static void Main(string[] args) { GetTickerServiceAsyncGood(); Console.ReadLine(); } static void GetTickerServiceAsyncGood() { StockLookupService Lookup = new StockLookupService(); Lookup.GetCompanyNameCompleted += new GetCompanyNameCompletedEventHandler(
278
Chapter 7
Method Invocations and Event Management with .NET Web Services
GetTickerSymbolCompletedHandler);
Lookup.GetCompanyNameAsync("MSFT");
}
private static void GetTickerSymbolCompletedHandler(object sender,
GetCompanyNameCompletedEventArgs args)
{
if (args.Error != null)
{
Console.WriteLine("Error: {0} Message: {1}", args.UserState, args.Error.Message);
}
if (args.Cancelled)
{
Console.WriteLine("Method invocation was cancelled");
}
else {
Console.WriteLine("Method completed: {0}", args.UserState);
}
}
So what’s important in this code? ■
The GetTickerSymbolCompletedHandler method handles the Completed event from the asynchronous call.
■
GetTickerSymbolCompletedHandler is wired to the Completed event of the GetTickerSymbolCompleted event.
■
The GetTickerSymbolAsync method invokes the method asynchronously.
Getting data back from asynchronous calls and responding to the various events asso ciated with such calls is discussed in depth in the following lesson. One item remains to be discussed: canceling a call. After you call a method asynchro nously, you can simply call its Cancel method and it cancels the call. Let’s review the previous code sample; the relevant portions are highlighted in bold: 'VB Private WithEvents AsyncLookup As StockLookupService Sub Main()
AsyncLookup = New StockLookupService()
AsyncLookup.CancelAsync() Console.ReadLine()
End Sub
Public Sub GetTickerSymbolCompletedHandler(ByVal sender As Object, _
ByVal args As GetCompanyNameCompletedEventArgs) _
Handles AsyncLookup.GetCompanyNameCompleted
Lesson 1: Calling Web Methods
279
If Not args.Error Is Nothing Then
Console.WriteLine("Error: {0} Message: {1}", args.UserState, _
args.Error.Message)
End If
If args.Cancelled Then
Console.WriteLine("Method invocation was cancelled") Else Console.WriteLine("Method completed: {0}", args.UserState) End If End Sub //C# static void GetTickerServiceAsyncGood() { StockLookupService Lookup = new StockLookupService(); Lookup.GetCompanyNameCompleted += new GetCompanyNameCompletedEventHandler(
GetTickerSymbolCompletedHandler);
Lookup.CancelAsync(); Console.ReadLine(); } private static void GetTickerSymbolCompletedHandler(object sender, GetCompanyNameCompletedEventArgs args) { if (args.Error != null) { Console.WriteLine("Error: {0} Message: {1}", args.UserState, args.Error.Message);
}
if (args.Cancelled)
{
Console.WriteLine("Method invocation was cancelled");
}
else {
Console.WriteLine("Method completed: {0}", args.UserState); } }
Cancelling the call is particularly relevant because there are times when you might decide that you don’t want to make the user wait longer for a method to complete, so you decide to cancel execution. Polling, a system that checks the execution status of an asynchronous method and responds accordingly, is discussed in the next lesson, but for the time being, assume that you have somehow made a determination that you want to cancel execution. All that is needed is a call to the built-in CancelAsync method. If the event handler is set up properly (this is an automatic process) and you have code to check for a cancellation as shown in the previous code example, there’s really nothing more that needs to be done to handle this situation.
280
Chapter 7
Method Invocations and Event Management with .NET Web Services
Lab 1: Calling a Web Method In this lab, you create add a Web reference to your project and then call one of its Web methods from a client application. �
Exercise: Calling a Web Method
In this exercise, you create a console application that calls the StockLookupService project’s GetCompanyName method. 1. Open Visual Studio 2005 and create an empty project. 2. Add a new C# or Visual Basic .NET console application project named StockSymbolConsumerCS or StockSymbolConsumerVB, depending on which lan guage you choose to write in. 3. Select File, Add, Existing Project and add the StockLookupService project to this solution. 4. Build the StockLookupService project. 5. Right-click the StockSymbolConsumer project and select Add Web Reference. You should see a dialog box similar to the one shown in Figure 7-1.
Figure 7-1
Use the Add Web Reference dialog box to find a Web service
6. Click Web Services In This Solution, and then click Service for the StockLook upService project. 7. Use the default value of localhost in the Web Reference Name text box. 8. Click Add Reference.
Lesson 1: Calling Web Methods
281
9. Open the StockSymbolConsumer project’s Program.cs file if you’re using C# or Module1.vb if you’re using Visual Basic .NET. 10. At the top of the module or class, add the following: 'VB Imports StockSymbolConsumerVB.localhost //C# using StockSymbolConsumerCS.localhost;
11. Add the following code to the Main method of the respective project: 'VB GetTickerSymbol()
Console.ReadLine()
//C# GetTickerSymbol();
Console.ReadLine();
12. Add the following methods to the class: 'VB Public Sub GetTickerSymbol()
Dim Lookup As New StockLookupService
Console.WriteLine(Lookup.GetCompanyName("MSFT"))
End Sub //C# static void GetTickerSymbol() { StockLookupService Lookup = new StockLookupService(); Console.WriteLine(Lookup.GetCompanyName("MSFT")); }
13. Build the project, and resolve any errors. Run the application and you should see the word Microsoft in the console window.
Lesson Summary ■
The Add Web Reference feature of Visual Studio allows you to add a reference to a Web service to your project.
■
If the Web service is contained in the same solution, you can use the Web Services In This Solution command to add a Web service to your project.
■
If the Web service is contained on the local machine but not in the same solu tion, the Web Services On The Local Machine option can be used to add a Web ref erence to your project.
282
Chapter 7
Method Invocations and Event Management with .NET Web Services
■
If the Web service is located on a remote server, you can specify the address in the Uniform Resource Locator (URL) address box of the Add Web Reference dia log box.
■
When a Web reference is added, Visual Studio 2005 adds a proxy class to the program for you.
■
Client code creates and calls an instance of the proxy class, which in turn han dles the calls to the Web service. The Web service itself is not called directly by the client code.
■
Depending on the circumstances, the Web service for which a reference is cre ated is likely to change after it has first been referenced. Right-click the Web ref erence and click Update Web Reference to generate a proxy matching the Web service.
■
Be careful not to introduce breaking changes (in other words, don’t change the method signatures) on existing Web services. If you must introduce breaking changes, ensure that the customer is notified early enough to respond to the change.
■
Asynchronous methods are provided by default. They use the syntax of asnyc.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “Calling Web Methods.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. What needs to be done to call a Web method asynchronously? (Select all that apply.) A. Add a Web reference to the service that contains the method you want to call. B. Declare and instantiate an instance of the service object. C. Call the method by name. D. Call the method that contains the name postfixed by the word async.
Lesson 1: Calling Web Methods
283
2. How do asynchronous methods differ from traditional ones? (Select all that apply.) A. They have a different signature and return type in most cases. B. Other methods can be processed while the call to the Web method is processing. C. The Completed event must be handled to get the results back. D. The method call is for all intents and purposes identical to the synchronous counterpart.
284
Chapter 7
Method Invocations and Event Management with .NET Web Services
Lesson 2: Polling Web Methods for Completion The previous lesson introduced both synchronous and asynchronous invocations of Web services. In this lesson, we delve a little deeper into the process of asynchronous invocations. After this lesson, you will be able to: ■
Use a delegate to invoke a Web service.
■
Use polling to check the state of completion and respond accordingly.
■
Use built-in delegates.
Estimated lesson time: 20 minutes
Polling When a method is called asynchronously, you don’t really know when it has finished. Typically, programs execute in a linear fashion, but that’s not the case with asynchro nous calls. Consider the following situation: ■
A program calls the GetTickerSymbol method of a Web service to look up a com pany’s stock ticker symbol.
■
The program next calls another method of the Web service, passing a second company’s stock ticker symbol, to retrieve the company’s closing stock price.
■
When the first method returns, the program uses the results to call to retrieve the first company’s closing stock price.
In a synchronous scenario, you can be sure that the second and third calls will not execute until the first call is completed. You can also be sure that the third call won’t complete until the second call is completed. In an asynchronous scenario, you don’t know when any of the calls are going to return and you certainly can’t count on them finishing in the sequence in which they were called. (If the calls do finish in the sequence in which they were issued it’s only due to random chance.)
Lesson 2: Polling Web Methods for Completion
285
Real World William Ryan Many people are intimidated by threading and asynchronous invocation. How ever, the difference between applications that users enjoy and ones that anger users is often asynchronous invocation. In the previous Real World example, I spoke of our move to Web services. In the beginning we had a few problems. We typically built customized client applications and used standard invocations. However, the Web isn’t always stable and available, so our applications would often freeze up while waiting for methods to return. Fortunately, changing from a synchronous application model to an asynchronous application model was very simple. Using asynchronous delegates made everything a lot easier, and, fortunately, it was an easy port in most cases.
Begin and IAsyncResult Without any extra work, there are two items that are of great use when performing asynchronous invocations. In the first lesson we saw one way to determine when the method completed. However, that example was a bit of a simplified version. Each asynchronous Web method has a Begin method associated with it. The Begin methods have a return type of IAsyncResult. Follow these steps to call a Begin method, check its execution status, and respond accordingly. 1. Create an IAsyncResult variable and set it to the return value of the Web service’s Begin method. 2. Poll the IsCompleted property of the IAsyncResult variable until it turns true. 3. Call the End method. This process might sound complex but it’s actually quite simple, as the following code illustrates: 'VB Public Sub BeginGetTickerSymbol()
Dim Lookup As New StockLookupService()
Dim CompanyName As IAsyncResult = _
Lookup.BeginGetCompanyName("MSFT", Nothing, Nothing)
While Not CompanyName.IsCompleted
'Do Whatever you need here
End While
286
Chapter 7
Method Invocations and Event Management with .NET Web Services
Console.WriteLine(Lookup.EndGetCompanyName(CompanyName)) End Sub //C# static void BeginGetTickerSymbol() { StockLookupService Lookup = new StockLookupService(); IAsyncResult CompanyName = Lookup.BeginGetCompanyName("MSFT", null, null); while (!CompanyName.IsCompleted) { //Do whatever you need here } Console.WriteLine(Lookup.EndGetCompanyName(CompanyName)); }
CAUTION
Avoid passing only null parameters in delegates
In most real-world scenarios, you’ll want to avoid passing null or Nothing as both parameters. It’s done here only because at this point we won’t cover the concepts of dealing with null values.
This method isn’t as elegant in most respects as wiring up the event handler as we did in the previous lesson because we have to poll for the results. This polling is running in the same thread, so it does block execution on that thread. (Blocking is the inter ruption of a thread until a value is returned.) A more elegant solution, similar to the first method that was illustrated, is using a call back method. Callback methods are exactly what their name implies: They call back to the application, metaphorically saying “I’m done.” Using them is equally simple. In this case, you just need to specify an AsyncCallBack object and specify a target for it. Processing occurs asynchronously, and when it’s completed, the callback notifies the application that it’s finished. At that point, any return values can be retrieved: 'VB Public Sub GetTickerSymbolCallBack() Dim Lookup As New StockLookupService() Dim Callback As New AsyncCallback(AddressOf CompanyReturned) Dim Result As IAsyncResult = _ Lookup.BeginGetCompanyName("MSFT", Callback, Nothing) End Sub Public Sub CompanyReturned(ByVal result As IAsyncResult) Dim Lookup As New StockLookupService() Console.WriteLine(Lookup.EndGetCompanyName(result)) End Sub
Lesson 2: Polling Web Methods for Completion
287
//C# static void GetTickerSymbolCallBack() { StockLookupService Lookup = new StockLookupService(); AsyncCallback Callback = new AsyncCallback(CompanyReturned); IAsyncResult Result = Lookup.BeginGetCompanyName("MSFT", Callback, null); } static void CompanyReturned(IAsyncResult result) { StockLookupService Lookup = new StockLookupService(); Console.WriteLine(Lookup.EndGetCompanyName(result)); }
If you invoked the GetTickerSymbolCallBack method with the following code, you’d see the output I’m running asynchronously before you’d see the return value in the con sole window. 'VB GetTickerSymbolCallBack() Console.WriteLine("I'm running asynchronously") Console.ReadLine() //C# GetTickerSymbolCallBack(); Console.WriteLine("I'm running asynchronously"); Console.ReadLine();
In the CompanyReturned method we’re using the End to retrieve the result of the method call. Although the signature of Begin is totally dif ferent from the method it covers, the End is of the same return type and in fact, returns the value of the method. However, in the first lesson we didn’t see the details of the retrieval of values from the Web method. We wired up an event handler to respond when the method was fin ished executing, but we didn’t respond with respect to the value returned by the method. Fortunately, it’s trivial to get the return value using the event handler method. Using virtually identical code to the event handler example in Lesson 1, retrieving the return value of the method is shown here in bold: 'VB Private WithEvents AsyncLookup As StockLookupService Sub Main() AsyncLookup = New StockLookupService() AsyncLookup.GetCompanyNameAsync("MSFT") Console.ReadLine()
288
Chapter 7
Method Invocations and Event Management with .NET Web Services
End Sub Public Sub GetTickerSymbolCompletedHandler(ByVal sender As Object, _ ByVal args As GetCompanyNameCompletedEventArgs) _ Handles AsyncLookup.GetCompanyNameCompleted If Not args.Error Is Nothing Then Console.WriteLine("Error: {0} Message: {1}", args.UserState, _ args.Error.Message)
End If
If args.Cancelled Then
Console.WriteLine("Method invocation was cancelled") Else Console.WriteLine("Method completed: {0} Value: {1}", _ args.UserState, args.Result) End If End Sub //C# static void Main(string[] args) {
GetTickerServiceAsnycGood();
Console.ReadLine();
} static void GetTickerServiceAsnycGood() { StockLookupService Lookup = new StockLookupService(); Lookup.GetCompanyNameCompleted += new GetCompanyNameCompletedEventHandler(
GetTickerSymbolCompletedHandler);
Lookup.GetCompanyNameAsync("MSFT");
}
private static void GetTickerSymbolCompletedHandler(object sender, GetCompanyNameCompletedEventArgs args) {
if (args.Error != null)
{
Console.WriteLine("Error: {0} Message: {1}", args.UserState, args.Error.Message);
}
if (args.Cancelled)
{
Console.WriteLine("Method invocation was cancelled"); } else { Console.WriteLine("Method completed: {0} Value: {1}", args.UserState, args.Result); } }
Lesson 2: Polling Web Methods for Completion
289
Essentially all that needs to be done is to check that the value isn’t null and that it’s not cancelled (if it is, trying to retrieve the Result property will probably result in an exception or unpredictable behavior at best) and simply use the Result property of the specific EventArgs object that’s being trapped. To summarize, there are essentially three primary methods for using delegates to asynchronously invoke a Web method: ■
Use a CompletedHandler. Check that the return value is not null and that the method wasn’t cancelled. If neither condition is true, interrogate the Result property of the EventArgs argument.
■
Use a Begin delegate and poll for completion.
■
Use a Callback and let the callback notify you on completion.
There’s one other case, however, that doesn’t really fit in with any of the discussions thus far. Imagine a scenario in which you want to “fire and forget,” meaning that you want to call the method and, irrespective of the results, you just want to continue pro cessing. This situation is common when dealing with Web services because some methods don’t have return values, so there’s sometimes no reason to know when the services have completed. Again, because the proxy is generated for you, handling this scenario is extremely simple. Web services (and, as we’ll see in the next chapter, remoting methods) can be called in a unilateral fashion. To do this, a given method simply needs to be decorated with the OneWay attribute. Assume that you have the following Web method definition: [WebMethod] [SoapDocumentMethod(OneWay = true)] public void ThrowException() { throw new ApplicationException("You'll never know I was thrown"); }
If you don’t use the OneWay attribute and you call this method, the .NET Framework propagates a SoapException to the client that calls this method. However, if you actu ally call this method as it’s shown here, processing continues in your program with out interruption, proving that everything in the Web method was ignored. The following code uses a hypothetical service to demonstrate that a one-way method is truly “fire and forget”: 'VB Dim DemoService As New Service() DemoService.ThrowException()
290
Chapter 7
Method Invocations and Event Management with .NET Web Services
Console.WriteLine("I worked as expected")
Console.ReadLine()
//C# Service DemoService = new Service();
DemoService.ThrowException();
Console.WriteLine("I worked as expected");
Console.ReadLine();
If you compile and run the preceding code it will show I worked as expected in the con sole window, proving it worked as expected. If this doesn’t prove it to you, remove the OneWay attribute (or set it to false) and rerun the application.
Lab 2: Calling a Web Method Asynchronously In this lab, you create and add a Web reference to your project and then call one of its Web methods from a client application asynchronously. In the next exercise you make use of the OneWay attribute to verify that a call is working correctly. �
Exercise 1: Calling a Web Method Asynchronously
In this exercise, you’ll create a console application that asynchronously calls the StockLookupService project’s GetCompanyName method. 1. Follow steps 1 through 10 in the lab from Lesson 1. 2. Add the following code to the Programs.cs class if you’re using C# or to Module1.vb if you’re using Visual Basic .NET: 'VB Private WithEvents AsyncLookup As StockLookupService Sub Main()
AsyncLookup = New StockLookupService()
AsyncLookup.GetCompanyNameAsync("MSFT")
Console.WriteLine("I'm running async")
Console.ReadLine()
End Sub Public Sub GetTickerSymbolCompletedHandler(ByVal sender As Object, _ ByVal args As GetCompanyNameCompletedEventArgs) _ Handles AsyncLookup.GetCompanyNameCompleted If Not args.Error Is Nothing Then Console.WriteLine("Error: {0} Message: {1}", args.UserState, _ args.Error.Message)
End If
If args.Cancelled Then
Console.WriteLine("Method invocation was cancelled") Else Console.WriteLine("Method completed: {0} Value: {1}", _
Lesson 2: Polling Web Methods for Completion
291
args.UserState, args.Result)
End If
End Sub
//C# static void Main(string[] args) {
GetTickerServiceAsyncGood();
Console.WriteLine("I'm running async");
Console.ReadLine();
}
static void GetTickerServiceAsyncGood()
{
StockLookupService Lookup = new StockLookupService();
Lookup.GetCompanyNameCompleted +=
new GetCompanyNameCompletedEventHandler(
GetTickerSymbolCompletedHandler);
Lookup.GetCompanyNameAsync("MSFT");
}
private static void GetTickerSymbolCompletedHandler(object sender,
GetCompanyNameCompletedEventArgs args)
{
if (args.Error != null)
{
Console.WriteLine("Error: {0} Message: {1}", args.UserState, args.Error.Message);
}
if (args.Cancelled)
{
Console.WriteLine("Method invocation was cancelled"); } else { Console.WriteLine("Method completed: {0} Value: {1}",
args.UserState, args.Result);
}
}
3. Build the project, and resolve any errors. Run the application and you should see the words I’m running async output to the console window followed by the word Microsoft. �
Exercise 2: Calling a Web Method Decorated with the OneWay Attribute
In this exercise, you create a console application that calls the StockLookupService project’s GetCompanyName method but uses the OneWay attribute. 1. Open Visual Studio 2005 and create an empty project. 2. Add a new C# or Visual Basic .NET console application named StockSymbolConsumerCS or StockSymbolConsumerVB, depending on which language you choose to write in.
292
Chapter 7
Method Invocations and Event Management with .NET Web Services
3. Select File, Add, Existing Project and add the StockLookupService project to this solution. 4. Build the StockLookupService project. 5. Right-click the project and click Add Web Reference. You should see a dialog box similar to the one shown earlier in Figure 7-1. 6. Click Web Services In This Solution, and then click Service for the StockLook upService project. Use the default value of localhost in the Web Reference Name text box. 7. Click Add Reference. 8. Open the StockSymbolConsumer project’s Program.cs file if you’re using C#, or Module1.vb if you’re using Visual Basic .NET. 9. At the top of the module or class, add the following: 'VB Imports StockSymbolConsumerVB.localhost //C# using StockSymbolConsumerCS.localhost;
10. Add the following code to the Main method of the respective project: 'VB Dim DemoService As New Service()
DemoService.ThrowException()
Console.WriteLine("I made it so the OneWay attribute worked!")
//C# Service DemoService = new Service();
DemoService.ThrowException();
Console.WriteLine("I made it so the OneWay attribute worked!");
11. Build the project, and resolve any errors. Run the application and you should see the word Microsoft in the console window.
Lesson Summary ■
Asynchronous invocation should be used whenever an application cannot wait for a method to finish processing.
■
Methods can be invoked asynchronously by using either a delegate or a separate thread.
Lesson 2: Polling Web Methods for Completion
293
■
The Begin and End methods are used to start and complete an asynchronous invocation.
■
By default, Web methods can be invoked asynchronously by using the CompletedEventHandler.
■
The CancelAsync method of a Web service can be used to cancel an asynchro nous call.
■
Polling can be performed by either using an IAsyncResult object and waiting for its IsCompleted property to turn true, or via a callback method that notifies the cli ent when it has completed.
■
The OneWay attribute can be used to provide true “fire and forget” functionality.
■
Care must be taken with the OneWay attribute because the method will not be able to retrieve any return values. Therefore, serious problems can be masked using the attribute.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 2, “Polling Web Methods for Completion.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which method is an appropriate means to poll for a Web method’s completion? (Select the best answer.) A. Use an IAsyncResult object in conjunction with a standard invocation and poll for the IsCompleted property to turn true. B. Use an IAsyncResult object in conjunction with an asynchronous invocation and poll for the IsCompleted property to turn true. C. Use a callback method and wait for its IsCompleted property to turn true. D. Mark the method with a OneWay attribute.
294
Chapter 7
Method Invocations and Event Management with .NET Web Services
2. How do calls through a delegate differ from traditional ones? (Select all that apply.) A. They have a different signature and return type in most cases. B. Other methods can be processed while the call to the Web method is processing. C. The Completed event must be handled to get the results back. D. The method call is for all intents and purposes identical to the synchronous counterpart. 3. Which of the following characterize Web method calls? (Select all that apply.) A. Client code calls the Web service directly. B. Client code calls the proxy class that corresponds to the Web service. C. Web services can only be called asynchronously by using separate threads. D. Asynchronous calls can be implemented using delegates or separate threads.
Chapter 7 Review
295
Chapter Review To further practice and reinforce the skills you learned in this chapter, you can per form the following tasks: ■
Review the chapter summary.
■
Review the list of key terms introduced in this chapter.
■
Complete the case scenarios. These scenarios set up real-world situations involv ing the topics of this chapter and ask you to create a solution.
■
Complete the suggested practices.
■
Take a practice test.
Chapter Summary ■
.NET Web services allow you to expose data to outside consumers as Extensible Markup Language (XML).
■
Web methods of those Web services behave virtually identically to how normal methods behave.
■
.NET Web services can be created and consumed in any .NET language.
■
.NET Web services can be consumed by anything that can parse XML, which is how the data is returned.
■
A Web reference is needed to consume a Web service.
■
After a Web reference is added to a project, Visual Studio 2005 creates a class that maps to the Web service’s methods and properties, which allows you to interact with the Web service as though it were local.
■
Web methods can be called just like traditional methods. Invocations can be done either asynchronously or synchronously.
■
To implement “fire and forget” functionality, you can use the OneWay attribute on Web methods.
■
The two methods you can use to determine when a Web method has finished executing are polling and callbacks.
■
Semantically, callbacks are much more elegant than polling.
296
Chapter 7 Review
Key Terms Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book. ■
asynchronous
■
blocking
■
callback
■
fire and forget
■
synchronous
■
Web method
■
Web service
Case Scenarios In the following case scenarios you apply what you’ve learned about how to use Web methods and Web services. You can find answers to these questions in the “Answers” section at the end of this book.
Case Scenario 1: Exposing Functionality Through .NET Web Services Many companies expand operation over time and need much of their functionality exposed to external customers. .NET Web services and .NET remoting are two of the primary vehicles that make this available. The following case study concerns itself with developing a Web services strategy.
Interviews Following is a list of company personnel interviewed and their statements: “This company has been around for awhile and the original IT infrastructure was comprised of many different vendors’ proprietary prod ucts. This legacy has been one of the biggest obstacles we’ve faced in moderniz ing operations. None of the systems talk to each other and that causes a lot of manual data entry, not to mention data entry errors. We’ve consolidated a lot of this information into a SQL Server database, but a few of our products still run on OleDb databases. We need to expose this functionality to our trading part ners and customers so they can see real-time data and enter data as well. We started trying to use Java to fix these problems, but the solution was riddled with
■ IT Department Head
Chapter 7 Review
297
problems. We needed this fixed last year and would love to move to the .NET Framework, but only if it can help us with this problem.” “Because we have a lot of shortcomings with real-time sys tems, we end up having to do a lot of extra things, which typically involve giving stuff away or selling it at a discount, not to mention having to keep a lot of extra data entry personnel on staff. This is a problem but we can live with it. However, we can’t live with customers leaving our company because other companies have real-time tracking and transmission information available to them. And a reason this is happening is because other companies have updated their pro curement and delivery systems with their trading partners so a lot can be done without manual intervention. Without a doubt, this is the most pressing issue we face and it needs to be addressed as soon as possible.”
■ Lead Business Analyst
“We’re still stuck in the ’90s in many respects; heck, we’re in the ’80s in a few respects. This situation needs to be fixed. We tried a huge Java initiative a while back, but it was nothing but problems. Most developers in the area didn’t know it so we had to hire a bunch of really expensive consultants. A few issues were fixed but it ran slow and nothing was accomplished in exposing our sys tems externally. To be honest, external exposure was the only reason I was will ing to sink the resources into trying it in the first place and I lost my shirt on this. I’ve got one last chance to fix this but I’ll be gambling everything on it. Yes, I’m skeptical, very skeptical in some respects, that fixing it is even possible. Well, I know it can be, but not without causing downtime and so many problems that it isn’t viable.”
■ CIO
Questions Answer the following questions for your manager: 1. CORBA fixed a lot of their problems but the main one they need fixed is expos ing their data externally. What’s the best technology to use to give them this? 2. Their office is in a small, secluded town and they don’t have a lot of cutting-edge developers. No matter what we come up with, it has to be something that an average Joe can learn. Will .NET remoting or Web services fit the bill? 3. How can we consolidate all of their data into one place so that people can get to it externally?
298
Chapter 7 Review
Case Scenario 2: Refining the Use of Web Services After getting the Web services in place and usable, refinement needs to be performed. Only after deployment will many issues come up, performance in particular. The main focus now is getting a responsive client that has predictable and consistent behavior, and that’s the focus of this case study.
Interviews The following is a list of company personnel interviewed and their statements: “Now that we’ve got our functionality moved to Web ser vices, things are a lot better. But, and this is a big but, we still haven’t worked out our bandwidth issues, and the client applications we’ve distributed often hang up. We are getting so many support calls that it’s starting to be as big of a prob lem as not having the functionality exposed in the first place.”
■ IT Department Head
“It’s hard to believe that 6 months ago our biggest issue was that data wasn’t available to customers. Now that is hardly ever mentioned but we sure do get a lot of complaints about performance. We’ve had a ton of unhandled exceptions and the application hangs all the time. This is the biggest problem I hear about and the biggest one that needs to be fixed.”
■ Lead Business Analyst
“Our support calls are going through the roof and it’s costing us a fortune. What’s more, we get so many calls that our support people don’t have much time to do anything else. We need this to stop and stop immediately.”
■ CIO
Questions Answer the following questions for your manager: 1. How can we keep the application from hanging up? 2. How can the application be developed so that noncritical methods never throw exceptions?
Suggested Practices To successfully master the objectives covered in this chapter, complete the following tasks. You should complete at least Practices 1 and 2. You can complete Practice 3 for a more in-depth understanding of interoperation.
Chapter 7 Review
299
Create a Web service project and add a console application project. In the console application, add a Web reference to the Web service and examine the contents of the proxy class.
■ Practice 1
Use the console application created in Practice 1 and call a Web method in a regular fashion. Then, invoke the same method asynchronously.
■ Practice 2
Add the OneWay attribute to one of the Web methods in the Web ser vice you created in Practice 1. Modify the method so that it intentionally throws an exception. Call the Web method from the console application and ensure that no exception is thrown back to the client.
■ Practice 3
Take a Practice Test The practice tests on this book’s companion CD offer many options. For example, you can test yourself on just the content covered in this chapter, or you can test yourself on all the 70-529 certification exam content. You can set up the test so that it closely sim ulates the experience of taking a certification exam, or you can set it up in study mode so that you can look at the correct answers and explanations after you answer each question. MORE INFO
Practice tests
For details about all the practice test options available, see the section titled “How to Use the Prac tice Tests” in this book’s Introduction.
Chapter 8
Method Invocations and Event Management with .NET Remoting It’s been noted that there are two primary technologies that the .NET Framework pro vides for implementing a communications layer in distributed applications, .NET XML Web Services and .NET Remoting. With respect to the client code, there are many similarities to using either technology because of the nature of the distributed communications. For instance, both typically require an additional machine to host a separate application. Because of latency inherent in network communications, issues regarding responsive user interfaces need to be addressed. And, as with all applica tions, asynchronous invocations may be necessary to enhance usability. This chapter will address remote method invocations and event management in .NET Remoting.
Exam objectives in this chapter: ■
■
Call remoting methods asynchronously. ❑
Implement one-way methods by using the OneWay attribute.
❑
Call a remote method asynchronously.
❑
Implement a callback.
Implement events in remoting. ❑
Create and fire events.
❑
Implement an event handler for events of the remote object.
Lessons in this chapter: ■
Lesson 1: Calling Remoting Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
■
Lesson 2: Callbacks and Remoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
■
Lesson 3: Implementing and Responding to Events . . . . . . . . . . . . . . . . . . . . 336
301
302
Chapter 8
Method Invocations and Event Management with .NET Remoting
Before You Begin To complete the lessons in this chapter, you must have ■
A computer that meets or exceeds the minimum hardware requirements listed in the Introduction at the beginning of the book.
■
Microsoft Visual Studio 2005 and the .NET Framework 2.0.
■
Successfully completed all lessons in Chapters 1 through 6.
You should be familiar with Microsoft Visual Basic or C# and be comfortable with the following tasks: ■
Creating a console application in Visual Studio using Visual Basic or C#.
■
Adding references to system class libraries to a project.
■
Adding a simple Web reference to a project.
■
A basic understanding of remoting methodologies (XML Web services, DCOM, .NET remoting, CORBA, and so on).
■
A familiarity with formatters and serialization in the .NET Framework.
NOTE
Ensure that a server application is running before attempting to use client code samples.
The server project for each exercise, included on the companion CD, must be running for the processes to work correctly.
Real World William Ryan A few years ago, my company implemented a fairly straightforward client/server application in ASP.NET. The application was implemented and customers were happy, but we received some new security guidelines from the government that involved implementing a new firewall. The application connected directly to the database from the Web server, which forced us to poke a hole in the firewall and opened up some potential security concerns. We decided that we should use .NET remoting to implement an application server. The Web server would only talk to the application server and the application server would talk to the database. This way, the database could never be accessed directly from the Web server, so even if the Web server were ever compromised, the attack surface would be greatly reduced. This solution was the single best enhancement that was made in the application, and we have used .NET remoting extensively since then.
Lesson 1: Calling Remoting Methods
303
Lesson 1: Calling Remoting Methods On the surface, calling a method using .NET remoting might not look any different from calling any other traditional method. However, looks are very deceiving in this case because the mechanics of calling a remote method differ greatly (and for the most part, these mechanics are hidden from the developer) from traditional method invoca tion or even Web method invocation. The following are some of the major differences: ■
Each time you call a remote method, the server must be contacted so it can pro cess the request.
■
With the exception of methods decorated with the OneWay attribute (discussed later), the developer cannot know at the time of invocation whether or not the method will complete successfully.
■
Much more care must be taken with respect to the passing of parameters and the size of the returned value because remoting methods involve network traffic and, depending on bandwidth availability, operations that would otherwise be trivial might cause performance concerns.
These issues are quite serious and need to be considered in depth before using .NET remoting. For instance, assume that your application had a Person object with a FirstName and LastName property, both of which are of type String. Provided that the prop erties were set properly, each could be referenced from calling code without any possibility of an exception being thrown. Yet if the object was remote, several factors, such as a server failure, would cause the method to fail. Similarly, on client machines, developers often take copious amounts of random access memory (RAM) on client machines for granted. Passing large objects around often makes sense, and most peo ple take the ability to do so for granted. However, in remoting scenarios, each param eter that is passed in, as well as each value that is returned, consumes bandwidth. This bandwidth might not be readily available, and things that used to work fine might suddenly start performing sluggishly (or worse, failing). After this lesson, you will be able to: ■
Call a remoting method.
■
Call a remoting method asynchronously.
■
Use events with remote objects.
Estimated lesson time: 20 minutes
304
Chapter 8
Method Invocations and Event Management with .NET Remoting
Calling a Remoting Method Synchronously In most cases, developers create classes and use those classes to create objects. A sim ple Person class with a FirstName method and a LastName method might look some thing like the following code: 'VB Sub ShowNormalInstantiation()
Dim SomePerson As New Person()
Console.WriteLine(SomePerson.LastName() + ", " + _
SomePerson.FirstName())
End Sub
//C# static void ShowNormalInstantiation() {
Person SomePerson = new Person();
Console.WriteLine(SomePerson.LastName() + ", " +
SomePerson.FirstName());
}
Depending on the exact approach you are using to create a given remote object, the invocation might look something like the following: 'VB Sub Main()
RemotingConfiguration.Configure("StandardClient.exe.config", False)
Dim person As IPerson = _
CType(Activator.GetObject(GetType(IPerson), _
"tcp://localhost:9000/Person.rem"), IPerson)
Console.WriteLine(person.LastName() & ", " & person.FirstName())
End Sub
//C# static void Main(string[] args) {
RemotingConfiguration.Configure("StandardClient.exe.config", false);
IPerson person = (IPerson)Activator.GetObject(typeof(IPerson),
@"tcp://localhost:9000/Person.rem");
Console.WriteLine(person.LastName() + ", " + person.FirstName());
}
The output for the preceding code should approximate that in Figure 8-1.
Lesson 1: Calling Remoting Methods
Figure 8-1
305
Console output of call to LastName and FirstName
The preceding example is just one way to create a remote object, and how an object is created isn’t really important to knowing how to invoke methods off of it. After the remote object is created, calls to its methods are identical, irrespective of how the object is instantiated (client-activated or server-activated instantiation). Hence, as far as the actual method call is concerned, there is no syntactical difference between local and remote calls. Although syntactically remote method calls might look no different from their nonre mote counterparts, operationally and architecturally they are very different. Accord ingly, there is an issue with the preceding code that the developer should be aware of. Notice that there is a call to both the FirstName method and the LastName method. As you might now realize, these calls, even if combined on one line as they are in this example, require two trips to the server. This fact greatly complicates traditional object-oriented usage because now referencing these two methods is not necessarily a trivial endeavor. The following issues arise here: ■
If you wrap the entire line in a try/catch block, the first call might succeed and the second call might fail. This situation requires more involved exception man agement and logging to be performed on the client so that the nature of any problems can be accurately discerned.
■
Instead of calling the two methods, it might be preferable to add another method to the class that performs the concatenation on the server instead of on the client. However, this preference isn’t always the case. After all, if you rarely use the methods in a combined format, you might just be adding unnecessary or confusing logic to your design.
■
Good design in a nonremoting environment might make for a terrible design in a remoting environment and vice versa. (This situation is often the case, partic ularly with respect to events, which are discussed later.)
306
Chapter 8
Method Invocations and Event Management with .NET Remoting
Stateless Invocation Using One-Way Calls Precisely because so many different things can go wrong when calling remote meth ods, you need to take much care in handling and responding to exceptions. Yet there are many times when a decision is made that something should be called in a fire and forget method. That effectively means that irrespective of what happens on the server side, processing should neither be delayed nor fail due to an exception. To provide this behavior, the System.Runtime.Remoting.Messaging namespace provides the OneWay attribute. The behavior that this attribute causes is functionally identical to the behavior observed when the OneWay attribute is used in a Web method. The following are the modifications to the IPerson interface and the Person class that are necessary to use the OneWay attribute: 'VB Public Interface IPerson
_
Function FirstName() As String
_
Function LastName() As String
End Interface Public Class Person
Inherits MarshalByRefObject
Implements IPerson
_
Public Function FirstName() As String _
Implements Interfaces.IPerson.FirstName Throw New ArgumentException("Intentionally thrown exception") End Function _
Public Function LastName() As String Implements _
Interfaces.IPerson.LastName
Throw New ArgumentException("Intentionally thrown exception")
End Function
End Class
//C# public interface IPerson
{
[OneWay()]
String FirstName();
[OneWay()]
String LastName();
}
Lesson 1: Calling Remoting Methods
307
public class Person : MarshalByRefObject, IPerson { #region IPerson Members
[OneWay()]
public string FirstName()
{
throw new ArgumentException("Intentionally thrown exception");
}
[OneWay()]
public string LastName()
{
throw new ArgumentException("Intentionally thrown exception");
}
#endregion }
Before I introduce the calling code, note what has changed here. ■
A reference to import the System.Runtime.Remoting.Messaging namespace was included in both the IPerson interface and the Person class.
■
The OneWay attribute was added to both the FirstName and LastName methods of the IPerson interface.
■
The OneWay attribute was added to both the FirstName and LastName methods of the Person class.
■
Instead of returning values in the FirstName and LastName methods, an Argu mentException is intentionally thrown.
Now, just to make the behavior here absolutely clear, note the following changes (in bold) to the previous code Main: 'VB Sub Main() RemotingConfiguration.Configure("StandardClient.exe.config", False) Dim person As IPerson = _ CType(Activator.GetObject(GetType(IPerson), _
"tcp://localhost:9000/Person.rem"), IPerson)
Dim FirstName As String = person.FirstName
Dim LastName As String = person.LastName
'If these calls were not marked as OneWay, we would never get to this
'line because an unhandled exception would interrupt processing.
Console.ReadLine() End Sub //C# static void Main(string[] args)
308
Chapter 8
Method Invocations and Event Management with .NET Remoting
{
RemotingConfiguration.Configure("StandardClient.exe.config", false);
IPerson person = (IPerson)Activator.GetObject(typeof(IPerson),
@"tcp://localhost:9000/Person.rem"); String FirstName = person.FirstName(); String LastName = person.LastName(); //If these calls were not marked as OneWay, we would never get to this //line because an unhandled exception would interrupt processing. Console.ReadLine();
}
Note that now, the application is calling the FirstName and LastName methods indi vidually, just to reinforce the point that execution will continue regardless of whether something goes wrong on the server. To verify this point, run the client application (such as the Lesson 1 StandardClient application on the companion disk) without starting the server application. After you run the application, you should notice that processing occurs through both methods without interruption, even though there isn’t a server running to actually return anything. If the OneWay attribute was missing (feel free to remove the OneWay attributes and rerun the application) on either or both of the methods, an exception would immediately be raised. Now run the client program, making sure that the server application has been started. After a call to the FirstName method, the server application will either crash (if it’s in Release mode) or break into the debugger (if it’s in Debug mode). How can this be if the OneWay attribute is supposed to protect against crashes? The answer is that the OneWay attribute does in fact protect the client against any exceptions (and every thing else) that might occur on the server, but it does not guard the server against unhandled exceptions or failures. This behavior is entirely desirable. Why? Assume that you have a client that runs some fire and forget procedure every 15 minutes. Imagine that the client machines become disconnected from the network or that the server code references a drive that doesn’t exist or performs some other operation that necessarily fails. If the OneWay attribute in the server code shielded the server application from all failures, how would you ever know of the problem? You’d proba bly figure it out after a while, but if the OneWay attribute subdued exceptions on the server, it would unquestionably cause many more problems than it ever solved. Hence, the price of a fire and forget method would far exceed any benefit you might get from it.
Lesson 1: Calling Remoting Methods
309
Calling a Remoting Method Asynchronously There are a lot of potential problems associated with synchronous method calls that can severely affect the end user experience. A few of the major ones are listed here: ■
The remoting method might end in an error. If this happens, you will only learn of the error after the Timeout period expires, which might be quite a long time. (Most people consider an application freeze for even 5 seconds a long time.)
■
The remoting method might complete successfully but take a long time to com plete. If the application is waiting for this method to complete, it might interfere with the user experience.
■
In most instances you call more than one remoting method, and waiting for each call to complete individually is potentially wasteful. (Imagine a case where the first method doesn’t return properly. The user might wait 30 seconds or so for the first method to return and the next method to be called. If the methods were called asynchronously, both requests would be fired at close to the same time and the overall wait time could be drastically reduced.)
This lesson isn’t intended to be an advertisement for asynchronous processing, but suffice it to say that in most professional applications, at least some degree of asyn chronous processing is called for. With this in mind, the choices available for asynchronous remoting calls are effec tively the same ones available with any other method (again, the notable differences are transparent to the developer): ■
Create a new thread and call the method.
■
Create a delegate object that matches the remoting method’s signature and use the delegate in place of the normal invocation.
Before we continue, a note of caution is needed. Although an asynchronous invoca tion using a delegate is fairly straightforward in its simplest form, it can be quite involved. For the purposes of this discussion, a simple example is used at first and the nuances are discussed in the following lessons. Using the same methods that were employed in the previous example, the following code adds asynchronous support: 'VB Module Module1
Public Delegate Function FirstNameDelegate() As String
Public Delegate Function LastNameDelegate() As String
Sub Main()
310
Chapter 8
Method Invocations and Event Management with .NET Remoting
RemotingConfiguration.Configure("StandardClient.exe.config", False) Dim person As IPerson = _ CType(Activator.GetObject(GetType(IPerson), _ "tcp://localhost:9000/Person.rem"), IPerson) Dim first_name As New FirstNameDelegate(AddressOf person.FirstName) Dim last_name As New LastNameDelegate(AddressOf person.LastName) Dim FirstNameResult As IAsyncResult = _ first_name.BeginInvoke(Nothing, Nothing) Console.WriteLine("I will probably process before " & _ "[firstNameResult] does") Dim LastNameResult As IAsyncResult = _ last_name.BeginInvoke(Nothing, Nothing) Console.WriteLine("I will probably process before " "[lastNameResult] does") Console.ReadLine() End Sub End Module //C# class Program {
delegate String GetFirstNameDelegate();
delegate String GetLastNameDelegate();
static void Main(string[] args) { RemotingConfiguration.Configure("StandardClient.exe.config", false); IPerson person = (IPerson)Activator.GetObject(typeof(IPerson), @"tcp://localhost:9000/Person.rem"); GetFirstNameDelegate first_Name = new GetFirstNameDelegate(person.FirstName); GetLastNameDelegate last_Name = new GetLastNameDelegate(person.LastName); IAsyncResult firstNameResult = first_Name.BeginInvoke(null, null); Console.WriteLine("I will probably process before " + "[firstNameResult] does"); IAsyncResult lastNameResult = last_Name.BeginInvoke(null, null); Console.WriteLine("I will probably process before " + "[lastNameResult] does");
Console.ReadLine();
}
}
The output from the preceding code should look like Figure 8-2.
Lesson 1: Calling Remoting Methods
Figure 8-2
311
Console output of asynchronous call to LastName and FirstName
So what just happened? These are the important points: ■
A delegate object was created that matches the signature of the FirstName method of the Person class.
■
A delegate object was created that matches the signature of the LastName method of the Person class.
■
A new instance of each delegate was created.
■
A new IAsyncResult object was created for each delegate.
■
The BeginInvoke method of each respective IAsyncResult object was called, pass ing in null (in C#) or Nothing (in Visual Basic .NET).
Getting data back from asynchronous calls and responding to the various events asso ciated with such calls is discussed in depth in the following lessons.
Lab 1: Invoking a Remoting Method In this lab, you create a console application that will call the FirstName and LastName methods of the Person class shown previously. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. 1. Open Visual Studio 2005 and create a new empty project. Name this solution StandardCallDemo. 2. Add a new C# or Visual Basic .NET console application named StandardClient. 3. In Solution Explorer, right-click the Properties folder and select Open. In the Default Namespace text box, type MSLearning.Chapter8.Standard.Client. 4. On the File menu, select Add, New Project. Click the Class Library template, enter the name StandardInterfaces, and click OK.
312
Chapter 8
Method Invocations and Event Management with .NET Remoting
5. In Solution Explorer, right-click the StandardInterfaces project’s Properties folder and select Open. In the Default Namespace text box, type MSLearning.Chapter8 .Standard.Interfaces. 6. In Solution Explorer, select the StandardInterfaces project. On the Project menu, select Add New Item. Click the Interface template, name the interface IPerson, and click Add. 7. Add the code shown in bold here to the IPerson interface: 'VB Public Interface IPerson
Function FirstName() As String
Function LastName() As String
End Interface //C# namespace MSLearning.Chapter8.Standard.Interfaces {
public interface IPerson
{
String FirstName();
String LastName(); }
}
8. Add another C# or Visual Basic .NET console application to the solution and name it StandardServer. 9. In Solution Explorer, right-click the StandardServer project’s Properties folder and click Open. In the Default Namespace text box, type MSLearning.Chapter8 .Standard.Server. 10. Right-click the StandardClient project’s References folder and select Add Refer ence. In the Add Reference dialog box, click the Projects tab, click the Stan dardInterfaces project, and click OK. Repeat this process to add a reference in the StandardInterfaces project to the StandardServer project. 11. Right-click each project, select Add Reference, and add a reference to System.Run time.Remoting. 12. Right-click the StandardClient project and select Add, New Item. Click Applica tion Configuration File, and click Add. 13. Edit the App.config application configuration file to look like the following:
Lesson 1: Calling Remoting Methods
313
14. Right-click the StandardServer project and click Add, New Item. Click Applica tion Configuration File and click Add. 15. Edit the App.config application configuration file to look like the following:
16. Open the StandardServer project’s Program.cs file (if you’re using C#) or the Module1.vb file (if you’re using Visual Basic .NET). Modify the file so that it looks like the following: 'VB Imports Imports Imports Imports Imports Imports
System
System.Collections.Generic
System.Text
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
Module Module1 Sub Main() RemotingConfiguration.Configure("StandardServer.exe.config", _ False)
Console.WriteLine("Press return to exit...\n")
Console.ReadLine()
314
Chapter 8
Method Invocations and Event Management with .NET Remoting
End Sub
End Module
//C# using using using using using using
System;
System.Collections.Generic;
System.Text;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
namespace MSLearning.Chapter8.Standard.Server {
class Program
{
static void Main(string[] args) { RemotingConfiguration.Configure("StandardServer.exe.config", false); Console.WriteLine("Press return to exit...\n"); Console.ReadLine(); }
}
}
17. Right-click the StandardServer project and click Add, Class. Name the class Person and click Add. The class needs to implement the IPerson interface. (This project should already have a reference to the StandardInterfaces project, which you added in step 9.) Edit the Person class so that it looks like the following: 'VB Imports Imports Imports Imports Imports Imports Imports
System
System.Collections.Generic
System.Text
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter8.Standard.Interfaces
Public Class Person
Inherits MarshalByRefObject
Implements IPerson
Public Function FirstName() As String _
Implements Interfaces.IPerson.FirstName
Return "Demo"
End Function
Public Function LastName() As String _
Implements Interfaces.IPerson.LastName
Lesson 1: Calling Remoting Methods
315
Return "Person"
End Function
End Class
//C# using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using MSLearning.Chapter8.Standard.Interfaces;
using System.Runtime.Remoting.Messaging;
namespace MSLearning.Chapter8.Standard.Server
{
public class Person : MarshalByRefObject, IPerson
{
#region IPerson Members public string FirstName()
{
return "Demo";
}
public string LastName()
{
return "Person";
}
#endregion
}
}
18. Open the StandardClient project’s Program.cs file (if you’re using C#) or the Module1.vb file (if you’re using Visual Basic .NET). Modify the file so that it looks like the following: 'VB Imports Imports Imports Imports
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter8.Standard.Interfaces
Module Module1 Sub Main() RemotingConfiguration.Configure("StandardClient.exe.config", False) Dim person As IPerson = _ CType(Activator.GetObject(GetType(IPerson), _ "tcp://localhost:9000/Person.rem"), IPerson) Console.WriteLine(person.LastName() & ", " & person.FirstName())
316
Chapter 8
Method Invocations and Event Management with .NET Remoting
End Sub End Module //C# using using using using
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter8.Standard.Interfaces;
namespace MSLearning.Chapter8.Standard.Client {
class Program
{
static void Main(string[] args) { RemotingConfiguration.Configure("StandardClient.exe.config", false); IPerson person = (IPerson)Activator.GetObject(typeof(IPerson), @"tcp://localhost:9000/Person.rem"); Console.WriteLine(person.LastName() + ", " + person.FirstName()); Console.ReadLine(); } } }
At this point, Solution Explorer should resemble Figure 8-3. (This figure shows all three projects being created in C#. However, the Visual Basic .NET equivalent should look similar except it will show the Visual Basic .NET project and source files.)
Figure 8-3
Solution Explorer with all three necessary projects
19. On the Visual Studio 2005 Project menu, click Build, Rebuild Solution. Resolve any build errors.
Lesson 1: Calling Remoting Methods
317
20. Right-click the StandardServer project and select Debug, Start New Instance to start an instance of the StandardServer program. 21. Right-click the StandardClient project and select Debug, Start New Instance to start an instance of the StandardClient program and make a synchronous call to the FirstName and LastName methods of the Person class. 22. Press Enter in both console windows to exit the StandardClient and StandardServer applications.
Lesson Summary ■
Methods of remote objects can be called using either Web services or .NET remoting.
■
Although the client-side usage is seemingly the same, methods called on remote objects can execute on the server instead of the client.
■
By creating a delegate with a matching signature, any method can be invoked by calling the delegate’s BeginInvoke method.
■
To use a delegate object to invoke a method, the delegate’s signature must match the signature of any method it will be used to invoke.
■
A delegate can be used to invoke different methods, but each method must have a matching return type and a matching parameter list.
■
The OneWay attribute can be used to provide fire and forget functionality.
■
Although the OneWay attribute allows a client method to process uninterrupted regardless of what happens on the server, the OneWay attribute does not shield the server-side method from failing.
■
If interfaces are used for remote objects, the OneWay attribute must be applied at both the interface and the class level.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “Calling Remoting Methods.” The questions are also available on the compan ion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
318
Chapter 8
Method Invocations and Event Management with .NET Remoting
1. Which is true of objects that use the OneWay attribute? (Select all that apply.) A. Each method that requires fire and forget functionality should be deco rated with the OneWay attribute. B. If an interface is used, each method that will be implemented in a fire and forget manner should be decorated with the OneWay attribute. C. The code block containing the call to a fire and forget method must be marked with the OneWay attribute. D. The OneWay attribute is not necessary so long as the method will be invoked via a delegate. 2. How do asynchronous methods differ from traditional ones? (Select all that apply.) A. They have a different signature and return type in most cases. B. Other methods can be processed while the call to the remoting method is processing. C. The Completed event must be handled to get the results back. D. The method call is for all intents and purposes identical to the synchronous counterpart, other than the way in which it is invoked.
Lesson 2: Callbacks and Remoting
319
Lesson 2: Callbacks and Remoting In the previous lesson, we were introduced to both synchronous and asynchronous invocations of remoting calls. In this lesson, we delve a little deeper into the process of asynchronous invocations. In particular, we cover the following: ■
Checking for completion using the IsCompleted method.
■
Using an AsyncCallback object.
After this lesson, you will be able to: ■
Use a delegate to invoke a remoting method.
■
Check the state of completion and respond accordingly.
Estimated lesson time: 45 minutes
Real World William Ryan Totally changing the architecture of the application referenced in the previous Real World example was a great move, but it was not without some problems along the way. One problem was with interface responsiveness. The same appli cation server used to serve data to the ASP.NET application was also used to serve data to our internal applications. Because everything was done synchro nously, the user interface froze quite a bit. Thanks to implementing asynchro nous techniques and callbacks, we then had a much more responsive application, which users greatly appreciated.
Polling When a method is called asynchronously, you don’t really know when it has reached completion. Typically, programs execute in a linear fashion, but that’s not the case with asynchronous calls. Consider the following situation: A call is made to a given method of a remote object. Next, another call is made to a different method, one that uses the return value from the first call as a parameter for the second call. In a synchronous scenario, you can be sure that the second call will not execute until the first is completed. In an asynchronous scenario, you don’t know when any of the methods are going to return and you certainly can’t count on them finishing in the
320
Chapter 8
Method Invocations and Event Management with .NET Remoting
sequence in which they were called. (If they do finish in the sequence in which they were called, it’s only due to random chance.)
Determining Whether an Asynchronous Call Has Finished In the last part of Lesson 1, a simple example invoked the FirstName and LastName methods of the Person class. However, in that example, the client code wasn’t con cerned with the return values from either of those methods. In fact, the example essentially ignored the return values because we were trying to show that processing truly occurred asynchronously. If, however, the calling code was unable to use return values whenever it invoked a method asynchronously, the usefulness of asynchro nous calls would be severely limited. After all, methods that return values that will be consumed by the client are just as likely as ones that won’t take a long time to com plete. If you had to hold up processing every time you needed a value back from a remote method, you’d require a Herculean effort just to decide when and when not to use a method. Not only can you retrieve a value from an asynchronous call if you need it, but there are several ways to accomplish the retrieval. We saw in Chapter 7 that to use an IAsyncResult object to perform an asynchronous invocation, a delegate object that matched the method’s signature was necessary. After this matching delegate was in place, all that was necessary to perform the asyn chronous invocation was calling the BeginInvoke method of the delegate, passing in the function’s name, and in our example, two null parameters. In the simplest asyn chronous return value retrieval scenario, all that is necessary is to call the EndInvoke method of the delegate, passing in the IAsyncResult object as a parameter. Assuming the same implementation of IPerson and Person that was used in the first example in Lesson 1, all that is needed to get the return values from FirstName and LastName is the following modification (new code is highlighted in bold): 'VB Imports Imports Imports Imports
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter8.Standard.Interfaces
Module Module1
Public Delegate Function FirstNameDelegate() As String
Public Delegate Function LastNameDelegate() As String
Sub Main() RemotingConfiguration.Configure("StandardClient.exe.config", False) Dim person As IPerson = _ CType(Activator.GetObject(GetType(IPerson), _
Lesson 2: Callbacks and Remoting
"tcp://localhost:9000/Person.rem"), IPerson) Dim first_name As New FirstNameDelegate(AddressOf person.FirstName)
Dim last_name As New LastNameDelegate(AddressOf person.LastName)
Dim FirstNameResult As IAsyncResult = _
first_name.BeginInvoke(Nothing, Nothing)
Console.WriteLine("Calling first_Name didn't block me...") Dim LastNameResult As IAsyncResult = _
last_name.BeginInvoke(Nothing, Nothing)
Console.WriteLine("Calling last_Name didn't block me either...")
Dim FirstNameValue As String = _
first_name.EndInvoke(FirstNameResult)
Dim LastNameValue As String = last_name.EndInvoke(LastNameResult)
Dim Output As String = String.Format("FirstName: {0} " & _
"LastName: {1}", FirstNameValue, LastNameValue)
Console.WriteLine(Output)
Console.ReadLine() End Sub End Module //C# using using using using
System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; MSLearning.Chapter8.Standard.Interfaces;
namespace MSLearning.Chapter8.Standard.Client { class Program { delegate String GetFirstNameDelegate();
delegate String GetLastNameDelegate();
static void Main(string[] args)
{
RemotingConfiguration.Configure("StandardClient.exe.config",
false);
IPerson person = (IPerson)Activator.GetObject(typeof(IPerson),
@"tcp://localhost:9000/Person.rem");
GetFirstNameDelegate first_Name =
new GetFirstNameDelegate(person.FirstName);
GetLastNameDelegate last_Name =
new GetLastNameDelegate(person.LastName);
IAsyncResult firstNameResult =
first_Name.BeginInvoke(null, null);
Console.WriteLine("Calling first_Name didn't block me..."); IAsyncResult lastNameResult =
last_Name.BeginInvoke(null, null);
Console.WriteLine(
"Calling last_Name didn't block me either...");
321
322
Chapter 8
Method Invocations and Event Management with .NET Remoting
String FirstNameValue = first_Name.EndInvoke( firstNameResult); String LastNameValue = last_Name.EndInvoke(lastNameResult); String Output = String.Format("FirstName: {0} LastName: {1}", FirstNameValue, LastNameValue);
Console.WriteLine(Output);
Console.ReadLine(); } } }
If the code is compiled and run as it was in the previous examples, the output should look similar to that of Figure 8-4.
Figure 8-4 CAUTION
Output from calls to FirstName and LastName after EndInvoke is called Avoid passing only null parameters in delegates
In most real-world scenarios, you’ll want to avoid passing null or Nothing as both parameters. It’s done here only because at this point we haven’t introduced some of the concepts with respect to dealing with null values.
Polling for Completion Each object we create of type delegate has the properties of the base class. The method most relevant to the discussion at hand is the IsCompleted method (which, as you might have gathered from the name, has a return type of Boolean). This method is very straightforward to use. After you invoke the method with BeginInvoke, you can poll for IsCompleted to be true, which indicates that processing has completed. The fol lowing example illustrates how to poll for completion (new code is highlighted in bold): 'VB Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Lesson 2: Callbacks and Remoting
Imports System.Runtime.Remoting.Channels.Tcp Imports MSLearning.Chapter8.Standard.Interfaces Module Module1 Public Delegate Function FirstNameDelegate() As String Public Delegate Function LastNameDelegate() As String Sub Main()
RemotingConfiguration.Configure("StandardClient.exe.config", False)
Dim person As IPerson = _
CType(Activator.GetObject(GetType(IPerson), _
"tcp://localhost:9000/Person.rem"), IPerson)
Dim first_name As New FirstNameDelegate(AddressOf person.FirstName)
Dim last_name As New LastNameDelegate(AddressOf person.LastName)
Dim FirstNameResult As IAsyncResult = _ first_name.BeginInvoke(Nothing, Nothing)
Console.WriteLine("Calling first_Name didn't block me...")
Dim LastNameResult As IAsyncResult = _
last_name.BeginInvoke(Nothing, Nothing)
Console.WriteLine("Calling last_Name didn't block me either...")
While (Not FirstNameResult.IsCompleted OrElse _
Not LastNameResult.IsCompleted)
If FirstNameResult.IsCompleted Then Console.WriteLine("The call to FirstName has finished...") End If If LastNameResult.IsCompleted Then Console.WriteLine("The call to LastName has finished..")
End If
End While
Dim FirstNameValue As String = _ first_name.EndInvoke(FirstNameResult)
Dim LastNameValue As String = last_name.EndInvoke(LastNameResult)
Dim Output As String = String.Format( _
"FirstName: {0} LastName: {1}", FirstNameValue, LastNameValue)
Console.WriteLine(Output)
Console.ReadLine()
End Sub End Module //C# using using using using
System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; MSLearning.Chapter8.Standard.Interfaces;
namespace MSLearning.Chapter8.Standard.Client { class Program {
323
324
Chapter 8
Method Invocations and Event Management with .NET Remoting
delegate String GetFirstNameDelegate();
delegate String GetLastNameDelegate();
static void Main(string[] args) { RemotingConfiguration.Configure("StandardClient.exe.config", false); IPerson person = (IPerson)Activator.GetObject(typeof(IPerson), @"tcp://localhost:9000/Person.rem"); GetFirstNameDelegate first_Name = new GetFirstNameDelegate(person.FirstName); GetLastNameDelegate last_Name = new GetLastNameDelegate(person.LastName); IAsyncResult firstNameResult = first_Name.BeginInvoke(null, null); Console.WriteLine("Calling first_Name didn't block me..."); IAsyncResult lastNameResult = last_Name.BeginInvoke(null, null); Console.WriteLine( "Calling last_Name didn't block me either..."); while (!firstNameResult.IsCompleted || !lastNameResult.IsCompleted) {
if (firstNameResult.IsCompleted)
{
Console.WriteLine( "The call to FirstName has finished..."); } if (lastNameResult.IsCompleted) { Console.WriteLine( "The call to LastName has finished.."); } } String FirstNameValue = first_Name.EndInvoke(firstNameResult); String LastNameValue = last_Name.EndInvoke(lastNameResult); String Output = String.Format("FirstName: {0} LastName: {1}", FirstNameValue, LastNameValue);
Console.WriteLine(Output);
Console.ReadLine();
}
}
}
Polling Using a Callback Method A more elegant solution, similar to the first method that was illustrated, is using a call back method. Callback methods are exactly what their name implies: They call back to the application, metaphorically saying “I’m done.”
Lesson 2: Callbacks and Remoting
325
Using them is equally simple. In this case, you just need to specify an AsyncCallback object and specify a target for it. Processing occurs asynchronously, and when it’s completed, the callback notifies the application that it’s finished. At that point, any return values can be retrieved. For the sake of simplicity, this example promotes a few of the local variables to the module/class level, purely for scoping reasons. With this change, the following is all that is required to use an AsyncCallback object: ■
Create a method to handle the callback object. This method takes one IAsyncResult object as a parameter.
■
Create an AsyncCallback object for each BeginInvoke you plan to use, specifying the name of the method you created.
■
Specify this callback object as the first parameter in each BeginInvoke instance.
The following code illustrates this process: 'VB Imports Imports Imports Imports
System.Runtime.Remoting System.Runtime.Remoting.Channels System.Runtime.Remoting.Channels.Tcp MSLearning.Chapter8.Standard.Interfaces
Module Module1 Public Delegate Function GetFirstNameDelegate() As String Public Delegate Function GetLastNameDelegate() As String Private demo_person As IPerson Private firstNameResult As IAsyncResult Private lastNameResult As IAsyncResult Private first_Name As GetFirstNameDelegate Private last_Name As GetLastNameDelegate Function DemoPerson() As IPerson
If demo_person Is Nothing Then
RemotingConfiguration.Configure("StandardClient.exe.config", _
False)
Dim person As IPerson = _
CType(Activator.GetObject(GetType(IPerson), _
"tcp://localhost:9000/Person.rem"), IPerson)
End If
Return demo_person
End Function
Sub Main() first_Name = _ New GetFirstNameDelegate(AddressOf DemoPerson.FirstName)
last_Name = New GetLastNameDelegate(AddressOf DemoPerson.LastName)
Dim FirstNameCallback = _
New AsyncCallback(AddressOf FirstNameReturned)
326
Chapter 8
Method Invocations and Event Management with .NET Remoting
Dim LastNameCallback = _ New AsyncCallback(AddressOf LastNameReturned) firstNameResult = first_Name.BeginInvoke( _ FirstNameCallback, first_Name) lastNameResult = last_Name.BeginInvoke(LastNameCallback, last_Name) Console.ReadLine()
End Sub
Sub FirstNameReturned(ByVal result As IAsyncResult)
Console.WriteLine("FirstNameCallback value: " & _
first_Name.EndInvoke(result))
End Sub
Sub LastNameReturned(ByVal result As IAsyncResult)
Console.WriteLine("LastNameCallback value: " & _
last_Name.EndInvoke(result))
End Sub
End Module //C# using using using using
System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; MSLearning.Chapter8.Standard.Interfaces;
namespace MSLearning.Chapter8.Standard.Client { class Program { delegate String GetFirstNameDelegate(); delegate String GetLastNameDelegate(); private static IPerson demo_person; static IAsyncResult firstNameResult; static IAsyncResult lastNameResult; static GetFirstNameDelegate first_Name; static GetLastNameDelegate last_Name; static IPerson DemoPerson() {
if (demo_person == null)
{
RemotingConfiguration.Configure( _ "StandardClient.exe.config", false); demo_person = (IPerson)Activator.GetObject( _ typeof(IPerson), @"tcp://localhost:9000/Person.rem"); }
return demo_person;
}
static void Main(string[] args)
Lesson 2: Callbacks and Remoting
327
{ first_Name = new GetFirstNameDelegate(DemoPerson().FirstName); last_Name = new GetLastNameDelegate(DemoPerson().LastName); AsyncCallback FirstNameCallback = _ new AsyncCallback(FirstNameReturned); AsyncCallback LastNameCallback = _ new AsyncCallback(LastNameReturned); firstNameResult = first_Name.BeginInvoke( _ FirstNameCallback, first_Name); lastNameResult = last_Name.BeginInvoke( _ LastNameCallback, last_Name) Console.ReadLine();
}
static void FirstNameReturned(IAsyncResult result) { Console.WriteLine("FirstNameCallback Value: " + first_Name.EndInvoke(result)); } static void LastNameReturned(IAsyncResult result) { Console.WriteLine("LastNameCallback Value: " + last_Name.EndInvoke(result)); } } }
The preceding code should result in output as shown in Figure 8-5.
Figure 8-5
Output from AsyncCallback methodology
Lab 2: Calling a Remoting Method Asynchronously In this lab, you will create a remotable object and call it asynchronously. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder.
328
Chapter 8
Method Invocations and Event Management with .NET Remoting
1. Open Visual Studio 2005 and create a blank solution. 2. Add a new C# or Visual Basic .NET console application named StandardClient. 3. In Solution Explorer, right-click the project’s Properties folder and select Open. In the Default Namespace text box, type MSLearning.Chapter8.Standard.Client. 4. On the File menu, select Add, New Project. Click the Class Library template, enter the name StandardInterfaces, and click OK. 5. In Solution Explorer, right-click the StandardInterfaces project’s Properties folder and select Open. In the Default Namespace text box, type MSLearning .Chapter8.Standard.Interfaces. 6. In Solution Explorer, select the StandardInterfaces project. On the Project menu, select Add New Item. Click the Interface template, name the interface IPerson, and click Add. 7. Add the code shown in bold to the IPerson interface: 'VB Public Interface IPerson
Function FirstName() As String
Function LastName() As String End Interface
//C# namespace MSLearning.Chapter8.Standard.Interfaces {
public interface IPerson
{
String FirstName();
String LastName();
}
}
8. Add another C# or Visual Basic .NET console application to the solution and name it StandardServer. 9. In Solution Explorer, right-click the StandardServer project’s Properties folder and select Open. In the Default Namespace text box, type MSLearning.Chapter8 .Standard.Server. 10. Right-click the StandardClient project’s References folder and select Add Refer ence. In the Add Reference dialog box, click the Projects tab, click the Stan dardInterfaces project, and click OK. Repeat this process to add a reference to the StandardInterfaces project and to the StandardServer project. 11. Right-click each project, select Add Reference, and add a reference to System.Run time.Remoting.
Lesson 2: Callbacks and Remoting
329
12. Right-click the StandardClient project and select Add, New Item. Click Applica tion Configuration File, and click Add. 13. Edit the App.config application configuration file to look like the following:
14. Right-click the StandardServer project and click Add, New Item. Click Application Configuration File and click Add. 15. Edit the App.config application configuration file to look like the following:
16. Open the StandardServer project’s Program.cs file (if you’re using C#) or the Module1.vb file (if you’re using Visual Basic .NET). Modify the file so that it looks like the following: 'VB Imports Imports Imports Imports
System
System.Collections.Generic
System.Text
System.Runtime.Remoting
330
Chapter 8
Method Invocations and Event Management with .NET Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Tcp
Module Module1 Sub Main() RemotingConfiguration.Configure("StandardServer.exe.config", False) Console.WriteLine("Press return to exit...\n") Console.ReadLine() End Sub
End Module
//C# using using using using using using
System;
System.Collections.Generic;
System.Text;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
namespace MSLearning.Chapter8.Standard.Server {
class Program
{
static void Main(string[] args) { RemotingConfiguration.Configure("StandardServer.exe.config", false); Console.WriteLine("Press return to exit...\n"); Console.ReadLine(); }
}
}
17. Right-click the StandardServer project, and click Add, Class. Name the class Person and click Add. The class needs to implement the IPerson interface. (This project should already have a reference to the Standard.Interfaces project, which you added in step 9.) Edit the Person class so that it looks like the following: 'VB Imports Imports Imports Imports Imports Imports Imports
System
System.Collections.Generic
System.Text
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter8.Standard.Interfaces
Public Class Person
Inherits MarshalByRefObject
Implements IPerson
Lesson 2: Callbacks and Remoting
331
Public Function FirstName() As String _
Implements Interfaces.IPerson.FirstName
Return "Demo"
End Function
Public Function LastName() As String _
Implements Interfaces.IPerson.LastName
Return "Person"
End Function
End Class
//C# using using using using using using using using
System;
System.Collections.Generic;
System.Text;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter8.Standard.Interfaces;
System.Runtime.Remoting.Messaging;
namespace MSLearning.Chapter8.Standard.Server {
public class Person : MarshalByRefObject, IPerson
{
#region IPerson Members public string FirstName()
{
return "Demo";
}
public string LastName()
{
return "Person";
}
#endregion
}
}
18. Open the StandardClient project’s Program.cs file (if you’re using C#) or the Module1.vb file (if you’re using Visual Basic .NET). Modify the file so that it looks like the following: 'VB Imports System
Imports System.Collections.Generic
Imports System.Text
332
Chapter 8
Method Invocations and Event Management with .NET Remoting
Imports Imports Imports Imports
System.Runtime.Remoting System.Runtime.Remoting.Channels System.Runtime.Remoting.Channels.Tcp MSLearning.Chapter8.Standard.Interfaces
Module Module1 Public Delegate Function GetFirstNameDelegate() As String Public Delegate Function GetLastNameDelegate() As String Private demo_person As IPerson Private firstNameResult As IAsyncResult Private lastNameResult As IAsyncResult Private first_Name As GetFirstNameDelegate Private last_Name As GetLastNameDelegate Function DemoPerson() As IPerson If demo_person Is Nothing Then RemotingConfiguration.Configure( _ "StandardClient.exe.config", False) Dim person As IPerson = _ CType(Activator.GetObject(GetType(IPerson), _ "tcp://localhost:9000/Person.rem"), IPerson) End If
Return demo_person
End Function
Sub Main() first_Name = New GetFirstNameDelegate( _ AddressOf DemoPerson.FirstName) last_Name = New GetLastNameDelegate(AddressOf DemoPerson.LastName) Dim FirstNameCallback = _ New AsyncCallback(AddressOf FirstNameReturned) Dim LastNameCallback = _ New AsyncCallback(AddressOf LastNameReturned) firstNameResult = first_Name.BeginInvoke( _ FirstNameCallback, first_Name) lastNameResult = last_Name.BeginInvoke(LastNameCallback, last_Name) Console.ReadLine()
End Sub
Sub FirstNameReturned(ByVal result As IAsyncResult) Console.WriteLine("FirstNameCallback value: " & _ first_Name.EndInvoke(result)) End Sub Sub LastNameReturned(ByVal result As IAsyncResult) Console.WriteLine("LastNameCallback value: " & _ last_Name.EndInvoke(result)) End Sub End Module
Lesson 2: Callbacks and Remoting
//C# using using using using using using using
System; System.Collections.Generic; System.Text; System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; MSLearning.Chapter8.Standard.Interfaces;
namespace MSLearning.Chapter8.Standard.Client { class Program { delegate String GetFirstNameDelegate();
delegate String GetLastNameDelegate();
private static IPerson demo_person;
static IAsyncResult firstNameResult;
static IAsyncResult lastNameResult;
static GetFirstNameDelegate first_Name;
static GetLastNameDelegate last_Name;
static IPerson DemoPerson() {
if (demo_person == null)
{
RemotingConfiguration.Configure( "StandardClient.exe.config", false); demo_person = (IPerson)Activator.GetObject( typeof(IPerson), @"tcp://localhost:9000/Person.rem"); }
return demo_person;
} static void Main(string[] args) { first_Name = new GetFirstNameDelegate(DemoPerson().FirstName); last_Name = new GetLastNameDelegate(DemoPerson().LastName); AsyncCallback FirstNameCallback = new AsyncCallback(FirstNameReturned); AsyncCallback LastNameCallback = new AsyncCallback(LastNameReturned); firstNameResult = first_Name.BeginInvoke( FirstNameCallback, first_Name); lastNameResult = last_Name.BeginInvoke( LastNameCallback, last_Name); Console.ReadLine();
}
static void FirstNameReturned(IAsyncResult result) {
333
334
Chapter 8
Method Invocations and Event Management with .NET Remoting
Console.WriteLine("FirstNameCallback Value: " + first_Name.EndInvoke(result)); } static void LastNameReturned(IAsyncResult result) { Console.WriteLine("LastNameCallback Value: " + last_Name.EndInvoke(result)); } } }
At this point, the solution tree should resemble Figure 8-3, shown previously. (This figure shows all three projects being created in C#. However, the Visual Basic .NET equivalent should look similar, except it will show the Visual Basic .NET project and source files.) 19. On the Visual Studio Build menu, select Rebuild Solution. 20. Right-click the StandardServer project and select Debug, Start New Instance to start an instance of the StandardServer program. 21. Right-click the StandardClient project and select Debug, Start New Instance to start an instance of the StandardClient program and make a call to the FirstName and LastName methods of the Person class asynchronously. 22. Press Enter in both console windows to exit the StandardClient and StandardServer applications.
Lesson Summary ■
BeginInvoke can be called without any parameter values, but it’s an ill-advised methodology.
■
Polling can be performed by either using an IAsyncResult object and waiting for its IsCompleted property to turn true, or via a callback method that notifies the cli ent when it has completed.
■
AsyncCallback objects can be used to notify the calling code when processing has completed. This negates the need to poll using IsCompleted or similar methodol ogies.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 2, “Callbacks and Remoting.” The questions are also available on the companion CD if you prefer to review them in electronic form.
Lesson 2: Callbacks and Remoting
NOTE
335
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which method is an appropriate means to poll for a remote method’s comple tion? (Select the best answer.) A. Use an IAsyncResult object in conjunction with a standard invocation and poll for the IsCompleted property to be true. B. Use an IAsyncResult object in conjunction with an asynchronous invocation and poll for the IsCompleted property to be true. C. Use a callback and wait for its IsCompleted property to turn true. D. Mark the method with a OneWay attribute. 2. Which of the following are benefits of using the OneWay attribute? (Select all that apply.) A. It provides fire and forget functionality. B. Client-side processing can continue irrespective of what happens on the server. C. Client-side processing can continue irrespective of client-side exceptions. D. The server methods can be marked with this attribute without any other changes being made.
336
Chapter 8
Method Invocations and Event Management with .NET Remoting
Lesson 3: Implementing and Responding to Events So far, we’ve focused on how to remote objects and their respective methods across boundaries. One more area needs to be discussed to complete our discussion of remoting: events. Any responsible discussion of remoting and events invariably con tains quite a few cautions. In many cases, the discussion contains a lot more informa tion about avoiding events in remoting scenarios than it does on how to use them. Let’s be clear about this: There are definitely times when using events with remoting is helpful or necessary, but those times are very few and very specific. It’s entirely plausible that a developer could use remoting extensively and never come across a need to handle server-side events. A few cautionary notes about remoting events are provided in the following list: ■
The typical use case for remoting events is in scenarios where different applica tions are running, applications that reference each other and are running on the same machine.
■
Because events are only useful when listeners are wired up to respond to them, scalability is seriously decreased as the number of listeners increases. This limi tation means that an application that performs well with, for instance, 10 clients might come to a crashing halt when 30 clients are added. Due to the number of different variables that can affect this, it’s next to impossible to determine the breaking point in advance. If you know an application will be distributed to mul tiple clients, using events is ill-advised.
■
Listening to events involves a server component being coupled with a client component and vice versa. This arrangement makes the application very suscep tible to failures outside of the application. The only responsible way to address this is through complex and sophisticated exception handling. This exception handling greatly complicates the code and makes maintenance much more dif ficult.
■
Any point on the network might fail (such as the switch, the server, or the net work card); however, the client is not necessarily notified of this failure. If the cli ent application makes infrequent calls to the server this failure can be particularly problematic. The client application might be expecting notifications for events, yet the server is no longer responding. The latency between the server failure and client notification can be very troublesome. There are solutions, such as polling, but these all entail an additional layer of complexity that greatly com plicates the code logic and makes maintenance much more difficult.
Lesson 3: Implementing and Responding to Events
337
■
If the OneWay attribute is used, the client is not notified of any failures on the server. With events, these calls might be frequent and each time an event is raised, the server tries to contact each of the listening machines. If these machines are no longer running, the server tries to contact them nonetheless. These attempts can cause a huge resource leakage.
■
Code Access Security does not work across process boundaries. This limitation raises many security concerns that are often rather difficult to properly address.
After this lesson, you will be able to: ■
Wire a client to listen for remote events.
Estimated lesson time: 45 minutes
Handled Remote Events Throughout the discussion of .NET Remoting, it’s been pointed out that in many ways dealing with remote objects is very similar to dealing with their nonremote counterparts. At the same time, the discussion has highlighted the differences. Event handling in .NET Remoting definitely falls into the same-but-different category. It’s the same as traditional event management in the semantic sense, as far as the code is concerned, and it appears to be done the same way other event management is. But it’s different in the underlying mechanics. Intuitively, if you wanted to add an event handler, you would assume that you could do something like the following: 'VB Module Module1
Public Delegate Function FirstNameDelegate() As String
Public Delegate Function LastNameDelegate() As String
Sub Main() RemotingConfiguration.Configure("StandardClient.exe.config", False) Dim person As IPerson = _ CType(Activator.GetObject(GetType(IPerson), _ "tcp://localhost:9000/Person.rem"), IPerson) AddHandler person.NameChange, AddressOf person_NameChange Console.ReadLine() End Sub Private Sub person_NameChange(ByVal sender As Object, _
ByVal e As NameChangeEventArgs)
Console.Write(e.ToString())
338
Chapter 8
Method Invocations and Event Management with .NET Remoting
End Sub
End Module
//C# class Program {
delegate String GetFirstNameDelegate();
delegate String GetLastNameDelegate();
static void Main(string[] args) { RemotingConfiguration.Configure( "StandardClient.exe.config", false); IPerson person = (IPerson)Activator.GetObject(typeof(IPerson), @"tcp://localhost:9000/Person.rem"); person.NameChange += new NameChangeEvent(person_NameChange); Console.ReadLine(); } private static void person_NameChange(object sender,
NameChangeEventArgs e)
{
Console.WriteLine(e.ToString());
}
}
Essentially you’d expect that you’d just create a handler for the remote object on the client and proceed from there. While this approach seems correct (and will compile without any problems), it won’t work as expected in the 2.0 Framework. The reason is that, in order to handle events on the client, two-way communication needs to occur. When just invoking remote methods, one-way communication is all that’s nec essary (from the server to the client), but in order to process events remotely, two-way communication must occur. Hence, a MarshalByRef object must be shared between the client and the server. Once this is done, however, event processing is handled exactly how it would otherwise. In short, to consume events in a remoting scenario, the following steps need to be performed: 1. Create a server project to host the remote object. 2. Create a client project to consume the object. 3. Create a shared assembly referenced in both the client and the server. 4. Define an event in the class created in the shared assembly. 5. Wire up a handler on the client to handle the event. If the point wasn’t made clearly at the beginning of this lesson, consuming events in a Remoting scenario carries many potential complications and should be approached
Lesson 3: Implementing and Responding to Events
339
with a fair degree of caution. While there’s nothing necessarily wrong with doing so, and there are times where it may be warranted, caution should be used before one implements remote events.
Lab 3: Handle Events from a Remote Object In this lab, you create a remotable object and respond to events it raises. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. 1. Open Visual Studio 2005 and create a blank solution. 2. Add a new C# or Visual Basic .NET console application named StandardClient. 3. In Solution Explorer, right-click the project’s Properties folder and select Open. In the Default Namespace text box, type MSLearning.Chapter8.Standard.Client. 4. On the File menu, select Add, New Project. Click the Class Library template, enter the name Shared, and click OK. 5. In Solution Explorer, right-click the Shared project’s Properties folder and select Open. In the Default Namespace text box, type MSLearning.Chapter8.Standard .Shared. 6. Add another C# or Visual Basic .NET console application to the solution and name it StandardServer. 7. In Solution Explorer, right-click the StandardServer project’s Properties folder and select Open. In the Default Namespace text box, type MSLearning.Chapter8 .Standard.Server. 8. Right-click the StandardClient project’s References folder and select Add Refer ence. In the Add Reference dialog box, click the Projects tab, select the Shared project, and click OK. Repeat this process to add a reference to the Shared project and to the StandardServer project. 9. Right-click each project, select Add Reference, and add a reference to System .Runtime.Remoting. 10. Right-click the StandardClient project and click Add, New Item. Click Applica tion Configuration File and click Add. 11. Edit the App.config application configuration file to look like the following:
340
Chapter 8
Method Invocations and Event Management with .NET Remoting
12. Right-click the StandardServer project and select Add, New Item. Click Applica tion Configuration File, and click Add. 13. Edit the App.config application configuration file to look like the following:
Lesson 3: Implementing and Responding to Events
341
14. Open the StandardServer project’s Program.cs file (if you’re using C#) or the Module1.vb file (if you’re using Visual Basic). Modify the file so that it looks like the following: 'VB Imports Imports Imports Imports Imports Imports
System
System.Collections.Generic
System.Text
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
Module Module1 Sub Main() RemotingConfiguration.Configure("StandardServer.exe.config", False) Console.WriteLine("Press return to exit...") Console.ReadLine() End Sub
End Module
//C# using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using MSLearning.Chapter8.Standard.Shared;
namespace MSLearning.Chapter8.Standard.Server
{
class Program {
static void Main(string[] args)
{
RemotingConfiguration.Configure("StandardServer.exe.config", false); Console.WriteLine("Press return to exit...\n"); Console.ReadLine(); }
}
}
15. Right-click the StandardServer project, and click Add, Class. Name the class Person and click Add. Edit the Person class so that it looks like the following: 'VB Imports Imports Imports Imports
MSLearning.Chapter8.Standard.Shared
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
342
Chapter 8
Method Invocations and Event Management with .NET Remoting
Imports System.Runtime.Remoting.Messaging
Public Class Person
Inherits MarshalByRefObject
Public Function FirstName(ByVal first_Name) As String Return first_Name End Function Public Function LastName(ByVal last_Name) As String Return last_Name End Function Public Event NameChangeEvent As EventHandler Private _shared As SharedClass = Nothing Public Overrides Function InitializeLifetimeService() As Object Return Nothing End Function Public Sub New() RemotingConfiguration.Configure("StandardClient.exe.config", False) _shared = New SharedClass AddHandler _shared.NameChangeEvent, AddressOf HandlesMyEvent End Sub Public Sub HandlesMyEvent(ByVal sender As Object, ByVal e As EventArgs) Console.WriteLine(String.Format("Event raised by {0}", sender.ToString())) End Sub End Class _ Public Delegate Sub NameChangeEvent(ByVal sender As Object, ByVal e As EventArgs) Public Class SharedClass Inherits MarshalByRefObject Public Event NameChangeEvent As EventHandler Public Sub RaiseThisEvent() RaiseEvent NameChangeEvent(Me, EventArgs.Empty) End Sub Public Overrides Function InitializeLifetimeService() As Object Return Nothing End Function End Class
//C# using using using using using
System;
System.Collections.Generic;
System.Text;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
Lesson 3: Implementing and Responding to Events
using System.Runtime.Remoting.Channels.Tcp; using System.Runtime.Remoting.Messaging; namespace MSLearning.Chapter8.Standard.Shared { [Serializable]
public delegate void NameChangeEvent(object sender, EventArgs e);
public class Shared : MarshalByRefObject
{
public event EventHandler NameChangeEvent;
public void RaiseEvent()
{
NameChangeEvent(this, EventArgs.Empty);
}
public override object InitializeLifetimeService()
{
return null;
}
}
[Serializable]
public class Person : MarshalByRefObject
{
private Shared shared = null; public override object InitializeLifetimeService()
{
return null;
}
public Person() {
RemotingConfiguration.Configure("StandardClient.exe.config", false);
shared = new Shared();
shared.NameChangeEvent += new EventHandler(HandlesMyEvent);
} public string FirstName(String firstName) {
HandlesMyEvent(this, null);
return firstName;
} public string LastName(String lastName)
{HandlesMyEvent(this, null);
return lastName;
}
343
344
Chapter 8
Method Invocations and Event Management with .NET Remoting
public void HandlesMyEvent(object sender, EventArgs e) { Console.WriteLine(string.Format("Event raised by {0}", sender.ToString())); } public event NameChangeEvent NameChange; }
}
16. Open the StandardClient project’s Program.cs file (if you’re using C#) or the Module1.vb file (if you’re using Visual Basic .NET). Modify the file so that it looks like the following: 'VB Imports Imports Imports Imports
System.Runtime.Remoting
System.Runtime.Remoting.Channels
System.Runtime.Remoting.Channels.Tcp
MSLearning.Chapter8.Standard.Shared
Module Module1 Sub Main() Dim Demoperson As New Person AddHandler Demoperson.NameChangeEvent, AddressOf person_NameChange 'Acknowledge that the event was successfully registered Console.WriteLine("Successfully registered Name Change Event") Demoperson.LastName("Demo Person") 'Force the event to fire Console.ReadLine() End Sub Private Sub person_NameChange(ByVal sender As Object, ByVal e As EventArgs) Console.Write(e.ToString()) End Sub End Module //C# using using using using using using using
System;
System.Collections.Generic;
System.Text;
System.Runtime.Remoting;
System.Runtime.Remoting.Channels;
System.Runtime.Remoting.Channels.Tcp;
MSLearning.Chapter8.Standard.Shared ;
namespace MSLearning.Chapter8.Standard.Client {
class Program
{
Lesson 3: Implementing and Responding to Events
345
static void Main(string[] args) { Person person = new Person(); person.NameChange += new NameChangeEvent(person_NameChange); //Acknowledge that the event was successfully registered Console.WriteLine("Successfully registered Name Change Event"); person.LastName("Demo Person"); //Force the event to fire Console.ReadLine(); } private static void person_NameChange(object sender, EventArgs { Console.WriteLine(e.ToString()); }
e)
} }
17. On the Visual Studio menu, select Rebuild Solution. 18. Right-click the StandardServer project and select Debug, Start New Instance to start an instance of the StandardServer program. 19. Right-click the StandardClient project and select Debug, Start New Instance to start an instance of the StandardClient program to set the LastName value of the person object and confirm that the event was registered and that it has being handled. 20. Press Enter twice in both console windows to exit the StandardClient and Stan dardServer applications.
Lesson Summary ■
When events are used, the client adds a listener to respond to the event. In this case, the client functions as a server as well as a client, because messages are sent to the client and acknowledgments are sent back.
■
Event management in remoting scenarios is very involved and should be approached with caution.
■
Any EventArgs that are created that will be consumed by both the client and the server must be decorated with the Serializable attribute.
Lesson Review You can use the following question to test your knowledge of the information in Les son 3, “Implementing and Responding to Events.” The questions are also available on the companion CD if you prefer to review them in electronic form.
346
Chapter 8
NOTE
Method Invocations and Event Management with .NET Remoting
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which is true of consuming events in remoting? (Select all that apply.) A. If the OneWay attribute is used with an event, the server continually sends notifications to clients that no longer exist. B. The EventArgs class must be marked as Serializable. C. Scalability might be severely limited. D. Events are best used in cases where different processes are running on the same machine.
Chapter 8 Review
347
Chapter Review To further practice and reinforce the skills you learned in this chapter, you can per form the following tasks: ■
Review the chapter summary.
■
Review the list of key terms introduced in this chapter.
■
Complete the case scenarios. These scenarios set up real-world situations involv ing the topics of this chapter and ask you to create a solution.
■
Complete the suggested practices.
■
Take a practice test.
Chapter Summary ■
Remoting methods can be invoked synchronously like traditional methods or asynchronously.
■
The OneWay attribute allows methods to be called and to continue processing regardless of any exceptions that might occur on the server. Processing contin ues without interruption, even if the server is not available.
■
Care should be taken when using the OneWay attribute because it can lead to resource leaks.
■
A delegate that matches the method’s signature can be used to invoke a method asynchronously.
■
The IsCompleted method of a delegate can be used to determine when processing has completed.
■
The EndInvoke method of a delegate should be used whenever a return value from a remote method (that was invoked asynchronously) is needed.
■
AsyncCallback objects can be used by a called method to notify the caller when processing is completed.
Key Terms Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book. ■
asynchronous
■
callback
Chapter 8 Review
■
fire and forget
■
one-way
■
remote method
■
synchronous
348
Case Scenarios In the following case scenario, you apply what you’ve learned about how to use Web methods and Web services. You can find answers to these questions in the “Answers” section at the end of this book.
Case Scenario 1: Building Robust, Scalable Enterprise Applications In the following case scenario, you are the lead developer at a fast-growing company and you must apply what you’ve learned to move from your existing client/server application architecture to a robust, scalable enterprise application architecture.
Interviews Following is a list of company personnel interviewed and their statements: “Our company has grown substantially in a short amount of time. We are no longer able to use a simple client/server model and are look ing to expand our options. Whatever we implement, I want a centralized appli cation server from which to run our applications. We will need at least three tiers: the client tier, the application logic tier, and the data tier. We need every lit tle piece of performance we can squeeze out.”
■ IT Department Head
“We are in a situation where changes have to be made to configurations frequently. If a database goes down, we need to be able to change the database. If an application server goes down, we need to be able to change the application server the clients are using. This change needs to happen fast, without our developers having to recompile the application and redistribute it. This might be one of the biggest problems I know of.”
■ Lead Business Analyst
“Every minute of downtime costs us a fortune. And now, we have a ton of it. When we switch out an application server, our clients are down for 30 min utes. The support calls flood us. We have employees leaving for breaks who don’t know when to come back because they don’t know when things will be back up. When things go down in the middle of the night, it takes hours before we get them back up. This situation needs to be fixed as soon as possible.”
■ CIO
Chapter 8 Review
349
Questions Answer the following questions for your manager: 1. How can the application logic be centralized? 2. How can changes be made to instruct the client to use a different server with a minimum of effort? 3. How can these procedures be implemented while maximizing performance so that total downtime is limited?
Suggested Practices To successfully master the objectives covered in this chapter, complete at least Prac tices 1 and 2. Create a class that implements MarshalByRefObject. Add whatever members you think would make the class useful, then invoke those members remotely.
■ Practice 1
Use the application created in the previous practice and invoke the same methods asynchronously. Now do the same using a callback object.
■ Practice 2
Add the OneWay attribute to one of the remoting methods you cre ated in Practice 1. Modify the method so that it intentionally throws an excep tion. Call the remoting method from the console application and ensure that no exception is thrown back to the client.
■ Practice 3
Take a Practice Test The practice tests on this book’s companion CD offer many options. For example, you can test yourself on just the content covered in this chapter, or you can test yourself on all the 70-529 certification exam content. You can set up the test so that it closely sim ulates the experience of taking a certification exam, or you can set it up in study mode so that you can look at the correct answers and explanations after you answer each question. MORE INFO
Practice tests
For details about all the practice test options available, see the section titled “How to Use the Prac tice Tests” in this book’s Introduction.
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications Although Extensible Markup Language (XML) Web services have been available for a few years, they are still relatively new when compared with other mechanisms for implementing distributed applications. One of the key reasons for the high degree of adoption that XML Web services have received is their use of XML and Simple Object Access Protocol (SOAP). As with any new technology, the business community has put quite a bit of effort into standardization and, although a lot of progress has been made in this respect, no definitive standard has been adopted. The standards that do exist are still evolving. Collectively, they are referred to as WS-*. Microsoft has pub lished a list of the standards it has implemented, which is available at http:// msdn.microsoft.com/webservices/webservices/understanding/specs/default.aspx. For a generic overview of these specifications as they evolve, Wikipedia has an entry avail able at http://en.wikipedia.org/wiki/List_of_Web_service_specifications. Web Services Enhancements 3.0 (WSE 3.0) is a composite of many existing standards and, as the name implies, provides enhanced functionality to XML Web services. Ben efits of WSE 3.0 include the following: ■
Simplified message security from endpoint to endpoint
■
Content-based routing
■
Highly customizable and configurable security
■
Performance benefits
Exam objectives in this chapter: ■
Enable WSE in client and server applications. ❑
Add references to WSE assemblies in client applications.
❑
Add a element under the element in a configuration file.
❑
Add a element and a section to a configuration file. 351
352
Chapter 9
■
Web Services Enhancements 3.0 in Client and Server Applications
Add a digital signature to a SOAP message and verify an existing SOAP message signature. ❑
Digitally sign a SOAP message.
❑
Verify a signed SOAP message.
Lessons in this chapter: ■
Lesson 1: Enabling and Referencing WSE 3.0. . . . . . . . . . . . . . . . . . . . . . . . . . 354
■
Lesson 2: Using soapExtensionTypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
■
Lesson 3: Using Digital Signatures to Ensure Message Integrity . . . . . . . . . . 379
Before You Begin To complete the lessons in this chapter, you must have ■
A computer that meets or exceeds the minimum hardware requirements listed in the Introduction of the book.
■
Microsoft Visual Studio 2005 and the .NET Framework 2.0.
■
Web Services Enhancements 3.0 installed before completing the examples in this chapter.
NOTE
Web Services Enhancements 3.0
The current version of WSE is 3.0. As of the time of writing, WSE 3.0 can be downloaded at http://www.microsoft.com/downloads/details.aspx?familyid=018a09fd-3a74-43c5-8ec1 8d789091255d&displaylang=en#Overview. The WSE developer’s center at MSDN can be found at http://msdn.microsoft.com/webservices/webservices/building/wse/default.aspx.
To complete the lessons in this chapter, you should be familiar with Visual Basic .NET or C# and be comfortable with the following tasks: ■
Creating a console application in Visual Studio 2005 using Visual Basic .NET or C#.
■
Creating and consuming a Web service using Visual Basic .NET or C#.
■
Adding both library references and Web references to a project.
■
Manipulating XML and SOAP documents.
Before You Begin
353
Real World William Ryan Web Services Enhancements can be described as an early adopter technology for many reasons. Web service adoption, although increasingly popular, is still in its early childhood. Although Web services have helped bridge many problems related to getting disparate systems to communicate with each other, features that simplify adoption invariably pose limitations on use. I worked at a company that jumped on the .NET bandwagon early on. We had a lot to get done and a really short deadline. Because we were still in the process of learning the .NET Framework when we started our first production application, we were still accustomed to doing things in a very traditional (non-.NET) manner. Eight months and 120,000 lines of code later, we launched our first large appli cation. The application worked well but we decided to do a postmortem analysis so we could document what we would have done differently with the better understanding of .NET we had gained. There was a good bit of disagreement on the specifics, but one thing everyone agreed on was that we would have changed the architecture from a two-tier, client/server application to one that used distrib uted technologies. Little by little, we started changing our components to a more service-oriented architecture approach and used remoting and Web services extensively. It wasn’t long before we were pushing the limits of what Web ser vices provided out of the box, and we moved on to Web Services Enhancements to help with performance and security. Just about the time we got everything refined using WSE 2.0, WSE 3.0 was released. Many of the features that we took advantage of in previous versions of WSE, such as Direct Internet Message Encapsulation (DIME) attachments and secu rity policy, were altogether changed or greatly enhanced. At each point, the learn ing curve got smaller and smaller, development times got shorter and shorter, and our application became faster and more secure. In terms of security and per formance, the benefits we’ve realized from the switch from WSE 2.0 to WSE 3.0 are as large as the benefits we realized when we switched to WSE in the first place. With the switch to WSE 3.0, however, we’ve released our application before a technology was released that replaced it! (In the not too distant future, Windows Communication Foundation will be released, which will ultimately replace WSE. This time, though, we’re far ahead of the curve.)
354
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
Lesson 1: Enabling and Referencing WSE 3.0 WSE 3.0 is the latest version of Web Services Enhancements that Microsoft has released; however, there are objectives on the exam that cover both WSE 2.0 and WSE 3.0. The preponderance of material, however, covers WSE 3.0. As a starting point, WSE 3.0 supports the following WS-* specifications: ■
XML, SOAP, WSDL
■
WS-Security
■
WS-Trust
■
WS-SecureConversation
■
WS-Addressing
■
Message Transmission Optimization Mechanism (MTOM)
NOTE
MTOM vs. DIME
Although there are many noteworthy features in WSE 3.0, one of the most profound changes was the replacement of DIME with MTOM. The primary purpose of both of these technologies is to enable a secure and efficient mechanism for sending attachments (such as an Excel file or Word document) along with the original message.
Much of the focus of WSE 3.0 has been on security and, as you’ll see shortly, there are many powerful features now available. Furthermore, it is rumored that Web Services, Web Services Enhancements, and Remoting are going to be consolidated in the Windows Communication Foundation (WCF). MORE INFO
WCF in the .NET Framework
At the time of writing, WCF is still in beta and, as such, is subject to change between now and its release. Therefore, it will not be discussed in any substantive manner in this book. However, WCF will be an integral part of the .NET Framework, and it is probably beneficial to be familiar with it. For more information, please visit the Windows Communication Foundation site on MSDN, avail able at http://msdn.microsoft.com/winfx/technologies/communication/default.aspx.
Fortunately, one of the other major design goals of WSE 3.0 was for an easy transition from WSE 3.0 to WCF. NOTE
Why standards matter
At first glance, many people question why there is such an apparent industry obsession with stan dardization. WSE 3.0 is an implementation of several standards, so it’s helpful to understand why
Lesson 1: Enabling and Referencing WSE 3.0
355
these standards matter. If you were asked how to implement an XML document that contained a customer name, it’s doubtful you would think much about it. Perhaps you’d create an element for FirstName, one for LastName, and one for MiddleName. But would this strategy work in all scenar ios? Well, no. Many people have a need to record prefixes (Dr., Mr., Mrs., and so on) and suffixes (PhD, II, and so on). But even if you were sure of all the fields that needed to be captured, would this be an easy process to get right in every case? Again, the answer is no. You could create a Per son element and contain each field in an attribute. You could create a separate element for each. You could make Prefix an attribute of FirstName and suffix an attribute of LastName. Each field adds to the number of permutations. The ultimate point is that, without standards, something as simple as capturing a person’s name can be very complicated. Many of these standards are discussed throughout this chapter.
Although Web Services Enhancements are very powerful and flexible, setting them up and using the basic features is actually quite simple. Like any technology, gaining an in-depth understanding of how the enhancements work and how to use them is not a trivial endeavor, but an average developer can get up and running with them after only a couple hours of studying time. The reason is because, as powerful as they are, there is a very comprehensive wrapper for the application programming interface (API). The interaction with Visual Studio 2005 is also entirely consistent with other programming tasks that you are probably already familiar with, so you can use your existing knowledge as a basis for learning WSE. You might remember that, in Chapter 7, “Method Invocations and Event Management with .NET Web Services,” and Chapter 8, “Method Invocations and Event Manage ment with .NET Remoting,” it was noted that the programming interface to use Web services and remoting was very straightforward from a developer’s perspective, although what’s happening behind the scenes is anything but trivial. The same is true for WSE. Although the API that a developer works with is simple, there’s a tremen dous amount of work that has to be performed behind the scenes to execute even the simplest of tasks with WSE. After this lesson, you will be able to: ■
Add a project reference to the WSE library.
■
Inherit from WebServicesClientProtocol.
■
Edit the proxy class.
Estimated lesson time: 20 minutes
356
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
Adding References to WSE Assemblies in Client Applications Before anything can be done that uses WSE 3.0, the correct version needs to be installed. After the correct version is installed, the following steps are all that’s neces sary to begin using WSE 3.0: CAUTION
Be careful to install and reference the correct WSE version
Currently, WSE is at version 3.0. As such, there are two previous versions that contain vast differ ences from version 3.0. Because the library names are very similar, it’s not hard to confuse them and reference the wrong version in your project. Make sure that you have downloaded the correct version before continuing. Also make sure that you reference the correct library and, if you are going to use any library aliases, make sure they reference WSE 3.0 as well (Microsoft.Web.Services3).
1. Add a reference to the Web Services Enhancements 3.0 library. If they were installed correctly, they will be available in the global assembly cache (GAC). They can also be referenced directly and, with a default installation and config uration, t he librar y should be available at [Drive Letter]:\Prog ram Files\Microsoft WSE\v3.0\Microsoft.Web.Services3.dll. 2. Alias the appropriate namespace. ❑
In Visual Basic .NET, this can be accomplished by typing the following at the top of a class or module: Imports Microsoft.Web.Services3
❑
In C#, this can be accomplished by typing the following at the top of a class: using Microsoft.Web.Services3;
Although aliases aren’t technically necessary to use objects in the Services3 namespace, if an alias isn’t included, each object declaration or reference needs to use fully qualified object names, which makes for much more verbose code declarations. To add a reference to the WSE 3.0 library, follow these steps: 1. Create a new project in either Visual Basic .NET or C#. (You might need to create a solution first depending on what type of project you are creating. If you’re cre ating a Windows Forms or a console application, you need to create a solution and add your project or projects to it. If you’re creating an ASP.NET Web site or a Web service, no solution is necessary.) 2. You should add a reference to the WSE 3.0 library. (Note that the process of add ing the WSE 3.0 reference to an ASP.NET Web service or ASP.NET Web site dif fers slightly from adding the reference to other project types.)
Lesson 1: Enabling and Referencing WSE 3.0
357
A. Right-click the project folder in Solution Explorer and click WSE Settings 3.0, as shown in Figure 9-1.
Figure 9-1
Selecting the WSE Settings 3.0 folder from a project’s Properties menu
B. You should see a dialog box similar to the one shown in Figure 9-2. The General tab contains two check boxes: Enable This Project For Web Ser vices Enhancements and Enable Microsoft Web Services Enhancement Soap Protocol Factory. You should select the first check box and, if your project requires it, the second check box.
Figure 9-2 box
Select the first check box and, if your project requires it, the second check
C. Click OK to add the Microsoft.Web.Services3 reference to your project.
358
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
D. The References folder that is visible in other project types isn’t available in ASP.NET Web site and Web service projects. To verify that the reference has been added correctly, right-click the project and click Properties. Click the References folder, and Microsoft.Web.Services3 should be visible in the right panel as shown in Figure 9-3.
Figure 9-3 Verify that a reference for Microsoft.Web.Services3 was correctly added to the project
Inheriting from the WebServicesClientProtocol Class As the name implies, Web Services Enhancements augment the functionality of stan dard .NET Web services. There are subtle differences in applications that use WSE functionality, but the biggest differences reside in those applications that consume Web services. In Chapter 7, we discussed what happens behind the scenes when you add a Web reference. As a refresher, here’s a summary: ■
Visual Studio 2005 generates a proxy class when you add a Web reference.
■
This proxy class mimics the Web service’s methods verbatim, and the client application actually invokes methods from the proxy.
■
The Web service can change after the proxy class is created, in which case there is a mismatch between the service and the proxy. Visual Studio 2005 provides a mechanism to regenerate the proxy to ensure that the current proxy is up to date.
There are subtle differences between the class generated by Visual Studio 2005 and the actual class, but most of those differences are additional methods to provide asyn chronous invocation support. Most noteworthy about the proxy class, however, is that it inherits from the System.Web.Services.Protocols.SoapHttpClientProtocol class. Just to reinforce the point, the following code excerpts show the outer class definition of the proxy class generated from the StockLookupService created in Chapter 7, with the relevant areas shown in bold:
Lesson 1: Enabling and Referencing WSE 3.0
NOTE
359
For illustration purposes only
The following code is provided only to show the class definition and the inheritance chain. This code, without any additional functionality, does nothing. 'VB _ Partial Public Class StockLookupService Inherits System.Web.Services.Protocols.SoapHttpClientProtocol //C# [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "2.0.50727.42")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute( Name="StockLookupServiceSoap", Namespace="http://a-datum.org/")] public partial class StockLookupService : System.Web.Services.Protocols.SoapHttpClientProtocol {}
To use WSE 3.0 features, the inheritance chain needs to be changed. Instead of inherit ing from the System.Web.Services.Protocols.SoapHttpClientProtocol class, the proxy class needs to inherit from the Microsoft.Web.Services3.WebServicesClientProtocol class. So the same Web service definition would now look like the following if it enabled WSE 3.0: 'VB _ Partial Public Class StockLookupService Inherits Microsoft.Web.Services3.WebServicesClientProtocol //C# [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "2.0.50727.42")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute( Name="StockLookupServiceSoap", Namespace="http://a-datum.org")] public partial class StockLookupService : Microsoft.Web.Services3.WebServicesClientProtocol {}
360
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
Inheriting from this class is the entry point for your client to consume all of the WSE 3.0 functionality. Although the following list is not exhaustive, it shows the main func tional areas that can be accessed via the WebServicesClientProtocol objects: 1. The SoapContext of SOAP requests and SOAP responses from XML Web services can be accessed through WebServicesClientProtocol objects. SOAP requests can be accessed through the RequestSoapContext property of the class, and SOAP responses can be accessed through the ResponseSoapContext property of the class. 2. SOAP headers defined in the WS-Security specification can be accessed through the SoapContext mentioned in item 1. 3. SOAP headers defined in the WS-Addressing specification can be accessed through the SoapContext mentioned in item 1. 4. If a WebServicesClientProtocol object is cast to a SoapHttpClientProtocol object, members of the WebServicesClientProtocol class that don’t exist in the SoapHttpClientProtocol class will be lost. Among these are the RequestSoapContext prop erty and the ResponseSoapContext property.
Editing the Proxy Class Because Visual Studio 2005 generates the proxy class for you, the class needs to be edited to make the proxy inherit from WebServicesClientProtocol. NOTE If you’re using Visual Studio 2005, there’s no need to change the inheritance manually as shown in this section. All you need to do is ensure that the client or consumer is WSE enabled when the reference to the Web service if referenced or updated. Visual Studio 2005 will detect this and automatically create a second proxy class that inherits from WebServicesClientProtocol.
To facilitate the process, the following tasks need to be performed: 1. Create a new project and add a reference to the Microsoft.Web.Services3 library. 2. Add a Web reference to this application pointing to the Web service you want to consume. Visual Studio 2005 automatically generates a proxy class for you. 3. In Solution Explorer, expand the Web Reference folder corresponding to the Web service that was added in the previous step. 4. Click Show All Files. 5. Expand the Reference Map folder to reveal the Reference.cs or Reference.vb class.
Lesson 1: Enabling and Referencing WSE 3.0
361
6. Double-click the Reference.cs or Reference.vb file to open it in the code window. Edit the class definition so that it inherits from WebServicesClientProtocol instead of SoapHttpClientProtocol. An example of the class declaration would look like the following code: 'VB _
Partial Public Class AccountManagerServiceWse Inherits Microsoft.Web.Services3.WebServicesClientProtocol //C# [System.CodeDom.Compiler.GeneratedCodeAttribute( "System.Web.Services", "2.0.50727.42")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute( Name="AccountManagerServiceSoap", Namespace="http://a-datum.org/")]
public partial class AccountManagerServiceWse :
Microsoft.Web.Services3.WebServicesClientProtocol
7. Edit the application’s configuration file with the following modifications that are necessary to use a specific configuration section (if you haven’t added a config uration file, you need to do so first) with the following entries:
At this point, the client is configured to use all of the features of WSE 3.0.
362
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
Real World William Ryan A friend of mine is the ultimate early adopter. Not only does he dive into new technologies as soon as they come out, but he quickly ends up pushing the enve lope on whatever he touches. He was using Web Services Enhancements since they first came out and went so far as to even port the technology over to the Compact Framework. One day he created one of the coolest applications I had come across. He used Web Services Enhancements to create a File Transfer Pro tocol (FTP) application that moved programs to and from his Web server. Using WSE, he designed the application so that it could move a newer version of itself to the Web server and then cause the new version to run. Because of this appli cation’s power, it needed to be both efficient and secure—after all, you wouldn’t want to just run any program on your server that could move all of your files and start running applications. Using the security features of Web Services Enhance ments and attaching the actual application as a DIME attachment (replaced with MTOM in the current version of WSE), he created an ultrasecure and efficient application that could seek out its own replacement, transfer the new applica tion, remove the attachments, and then run itself. With WSE, the possibilities are endless!
Lab 1: Adding WSE 3.0 to a Project In t his lab, you create a simple console application t hat references t he Microsoft.Web.Services3 library. 1. Open Visual Studio 2005 and create an empty project. 2. Add a new C# or Visual Basic .NET console application named WSEDemoCS or WSEDemoVB depending on which language you choose to write in. 3. In Solution Explorer, right-click the References folder and click Add Reference. 4. In the Add Reference dialog box, click the .NET tab. 5. Click Microsoft.Web.Services3 and click OK. If each step was performed correctly, the Microsoft.Web.Services3 library should be vis ible under the References folder in Solution Explorer.
Lesson 1: Enabling and Referencing WSE 3.0
363
Lesson Summary ■
Web Services Enhancements augment the default functionality of Web services.
■
Adding a reference to the WSE 3.0 library is performed the same way as adding a reference to other libraries, with the exception of Web applications. The WSE Settings 3.0 menu command is used to reference the WSE 3.0 library in Web applications.
■
The following WS-* standards are supported by WSE 3.0: XML, SOAP, WSDL, WS-Security , WS-Trust, WS-SecureConversation, WS-Addressing, and MTOM.
■
SOAP headers defined in the WS-Security specification can be accessed through the SoapContext.
■
The proxy class of any consumers that use WSE 3.0 needs to inherit from the WebServicesClientProtocol base class instead of the default SoapHttpClientProtocol base class.
■
If a WebServicesClientProtocol object is cast to a SoapHttpClientProtocol object, members of the WebServicesClientProtocol class that don’t exist in the SoapHttpClientProtocol class are lost. Among these are the RequestSoapContext property and the ResponseSoapContext property.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “Enabling and Referencing WSE 3.0.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which methods are valid for enabling Web Services Enhancements 3.0? (Select all that apply.) A. Use the Add Reference feature of Visual Studio 2005 to select the Microsoft .Web.Services3 library. B. Directly reference the C:\Program Files\Microsoft WSE\v3.0\Microsoft.Web .Services3.dll file. C. In the project, select the WSE 3.0 Settings menu command.
364
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
D. Add a reference to the Web service. If WSE is enabled, the reference is auto matically created through the proxy. 2. Which is true about using the WebServiceClientProtocol class? (Select the best answer.) A. All Web services (classes corresponding to the .asmx file) that will be con sumed by clients using WSE 3.0 must inherit from WebServiceClientProtocol. B. All classes that are consuming or being consumed in a project using WSE 3.0 must inherit from the WebServiceClientProtocol class. C. Any client class that is to consume a Web service and needs to use WSE 3.0 functionality must inherit from the WebServiceClientProtocol class. D. It can be used interchangeably with SoapHttpClientProtocol. 3. What happens if a WebServicesClientProtocol object is cast to a SoapHttpClientProtocol object? (Select the best answer.) A. An invalid cast exception is thrown. B. No exception is thrown. However, the casted object is null. The behavior is identical to that of the as keyword in C# in the context of casts, and when one type can’t be cast to another, null is returned as opposed to throwing an exception. C. No exceptions are thrown. However, any properties of the WebServicesClientProtocol class that aren’t present in the SoapHttpClientProtocol class evaluate to null in C# or Nothing in Visual Basic .NET. D. They both implement the IClientProtocol interface and, as such, can be cast from one to the other. If a property exists in one that is nonexistent in the other, then everything else converts except for such properties.
Lesson 2: Using soapExtensionTypes
365
Lesson 2: Using soapExtensionTypes In the previous lesson, we were introduced to the basics of adding references to the WSE 3.0 library. The information contained in Lesson 1 provided the infrastructure needed for a developer to consume a Web service and use WSE features such as RequestSoapContext and ResponseSoapContext. In the introduction, some uses for WSE were mentioned, in particular, enhanced security and messaging. Customization of these features is one of the biggest benefits to using WSE 3.0, and customization is handled via SoapExtensions. Like many areas of the .NET Framework, SoapExtensions can be handled through any of the following three methods: Code can be written to specifically build features that expand on WSE 3.0 features.
■ Imperatively
Attributes can be used to build in aspect-oriented features that expand on WSE 3.0 features.
■ Declaratively
To make an application as flexible as possible, configuration files can be used to inform the application that it should use a certain feature (or features).
■ Configuration
After this lesson, you will be able to: ■
Add a child element using the element.
■
Specify a type.
■
Specify a priority.
■
Specify a group.
Estimated lesson time: 50 minutes
When the .NET Framework was released, it introduced the System.Configuration namespace and all the functionality that comes with it. Many developers were accus tomed to handling configuration their way, and there was a good bit of debate about whether or not to use the .NET Configuration Framework and to what extent it should be used. There are probably still some people that argue about this topic, but on the whole, the jury has returned and the verdict has been unanimous: Using the .NET con figuration mechanism is the best way to handle background information that is unknown at development time or subject to change during the application’s lifetime. Although using configuration files can be very helpful across the board in .NET Frame work development, their usefulness is nothing short of astonishing in areas such as .NET remoting, Web services, logging, diagnostics, and Web Services Enhancements. The only downside to using configuration files (and I’m hesitant to call it a downside) is that editing configuration files can be a little confusing and error-prone to the uninitiated.
366
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
However, whatever disadvantage there might be in this respect, the benefits so outnum ber the costs that developers who want to be proficient using WSE 3.0 must familiarize themselves with using configuration files. Before moving into more advanced features of Web Services Enhancements, it’s worth discussing the basic structure of configuration elements related to WSE.
Configuration File Structure Web applications use a file named Web.config to store configuration information, whereas Windows Forms and console applications use a file named App.config. When compiled, Visual Studio renames the App.config file as ApplicationName.exe.config and stores the file in the same directory as the executable file. App.config and Web.config are essentially the same, and with respect to WSE 3.0, all relevant information is identical. NOTE
Differences between App.config and Web.config files
There are some differences in the default layout of a Web.config file as opposed to an App.config file, largely because certain values are not necessary in Windows Forms and console applications that are necessary in Web applications. For instance, having a default error page to which the application navigates doesn’t make much sense in a Windows Forms or a console application, whereas it makes perfect sense in a Web application. All of the differences between a default App.config file and a default Web.config file are attributable to differences in how the applications interact with the environment. None of these differences need to be understood to understand how to configure an application consuming WSE 3.0.
The best way to discuss the structure of a configuration file and how it relates to WSE 3.0 is to show a sample configuration file and discuss each portion of it from there. The basic hierarchy for configuration elements is shown in Figure 9-4.
Figure 9-4
Hierarchy of configuration elements in a *.config file
Lesson 2: Using soapExtensionTypes
367
At the top of the hierarchy is the element. This is the outermost container element that contains all other elements and attributes that the application will use. Underneath it is the element that contains all values related to the Sys tem.Web namespace. Within the element is the element that contains all values related to Web services configuration. Elements related to Web ser vices, such as , , and , are contained within the element. Each of these elements can have a different structure (different elements or attributes) and some might contain multiple values. This structure depends on the specific item being configured. Examine the following configuration file (this is the actual configuration file used in the WSClientProtocol Web service created in Lesson 1). Sections not directly related to WSE 3.0 were removed to avoid confusion:
Within the element of the configuration file, an entry was added (in bold) for the Microsoft.Web.Services3 namespace. The library type, version, default cul ture, and public key token were added so that the library can be accurately identified. This information is critical because if multiple versions of this library were loaded on the machine, an entry could be added for each version and this structure would allow side-by-side execution. The element is self-contained and can be used for any custom sec tions that are implemented (the section was removed for simplicity). The entry serves as a declaration for the namespace and as such, a matching entry needs to be entered that matches the specified section name attribute. After this information is in place, everything related to the Microsoft.Web.Services3 namespace should be included in the section.
368
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
CAUTION
can only be safely used in Internet Information Services
(IIS) To use the element in a Web service, the service itself must be hosted in Microsoft Internet Information Services. The behavior of this feature might differ on other Web servers.
At the beginning of this lesson, I mentioned that most of the power of WSE 3.0 lies within using SoapExtensions and soapExtensionTypes in particular. After they are cre ated in your code, they are referenced within the section of the config uration file. The following format is used to add soapExtensionTypes to your application:
Table 9-1 shows each of the relevant attributes coupled with their usage. Table 9-1
Attribute Usage
Attribute
Usage
type
The fully qualified object name of the soapExtensionTypes that will be used followed by a comma and a space, followed by the assem bly name. If a space is present after the assembly name, the space is assumed to be part of the name and results in an error. There can be a space after the comma separating the object name and the assem bly but not one after the names.
priority
The priority of the extension within a group; 1 is the highest prior ity an extension can have. The lower the priority number, the higher the priority. Because priority has an inverse correlation to the number it’s assigned, it’s a common mistake to assume larger numbers have higher priorities.
group
The group extension is a member of a priority. There are two possi ble values: 0 and 1. As with priority, lower numbers have higher importance. Hence, an assignment of 0 gives a group the highest priority, and an assignment of 1 gives a group the lowest priority.
Lesson 2: Using soapExtensionTypes
369
There are three actions that can be taken when using the ele ment, represented by three child elements. The three child elements are explained in Table 9-2. Table 9-2
Child Elements
Attribute
Usage
add
Adds a soapExtension to a Web service’s list of supported extensions.
clear
Removes all supported extensions.
remove
Removes an individual extension from the list of supported extensions.
Adding a Child Element with the Element As was touched on briefly earlier, to add an element, the subtag should be used within the element of the configuration file. In the following example, a library named MSPress.DemoExtension in the MSPressExtensions assembly is added to the application (to focus only on the item at hand, the priority and group subtags are ignored and the relevant code is shown in bold):
Specifying a Priority To specify a priority, simply specify a value for the priority attribute. The main thing to keep in mind is that the lower the numeric value, the higher the priority. The group subtag is intentionally left out of this example:
Specifying a Group To specify a group, simply specify a value for the group attribute. The main thing to keep in mind is that the lower the numeric value, the higher the priority of the group:
370
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
Lab 2: Creating a Class That Inherits from WebServicesClientProtocol In this lab, you create a Web service and a client consumer that use WSE 3.0. The cli ent application employs the use of a class that inherits from WebServicesClientProtocol. �
Exercise 1: Creating AuthorService Web Service
In this exercise, you create a very simple Web service that returns information about an author (return values from the Web service are hard-coded for the sake of simplic ity, but nothing would differ on the client side if this was a “hot” Web service). 1. Open Visual Studio 2005 and create an empty solution named WebServicesEnhancementsLab. 2. Right-click the solution file and select Add New, Web Site and click the ASP.NET Web Service template. 3. In the Location drop-down list box, click File System. 4. In the Language drop-down list box, click either Visual C# or Visual Basic depending on your language preference. 5. Enter WebDemo.cs or WebDemo.vb as the project name and click OK. 6. In Solution Explorer, right-click the project folder and click WSE Settings 3.0. 7. Click the General tab, select Enable This Project For Web Services Enhance ments and then click OK. 8. Right-click the solution folder and click Add, New Project. 9. Click your language of choice in the Project Types drop-down list box. 10. Click the Class Library template and in the Name text box, type WSEBusinessObjects and click OK. 11. Right-click the new WSEBusinessObjects project folder and click Properties. 12. In the Default Namespace text box, type MSPress.Chapter9.Lesson2.Lab. Close this window after setting the default namespace. 13. Right-click the new WSEBusinessObjects project folder and click WSE Settings 3.0. 14. Click the General tab and select Enable This Project For Web Services Enhance ments, then click OK. 15. Right-click the Class1.cs or Class1.vb file and click Rename. Change the class name to Authors.cs or Authors.vb. 16. Modify the class definition of the Authors class to look like the following:
Lesson 2: Using soapExtensionTypes
'VB Imports Imports Imports Imports
371
System
System.Collections.Generic
System.Text
System.Xml.Serialization
_ Public Class Author
Public FirstName As String
Public LastName As String
Public Function FullName() As String
Return String.Format("{0}, {1}", Me.LastName, Me.FirstName)
End Function
Public Public Public Public End Class //C# using using using using
WebSiteUrl As String
BlogUrl As String
BookTitles() As String
MVPAwardee As String
System;
System.Collections.Generic;
System.Text;
System.Xml.Serialization;
namespace MSPress.Chapter9.Lesson2.Lab
{
[XmlRoot(Namespace = "http://www.williamgryan.com/wse/books/529/09/Lab02")]
public class Author
{
public String FirstName;
public String LastName;
public String FullName()
{
return String.Format("{0}, {1}", this.LastName, this.FirstName);
}
public String WebSiteUrl;
public String BlogUrl;
public String[] BookTitles;
public Boolean MVPAwardee;
}
}
17. Right-click the Service.cs or Service.vb file in the Web Services project and rename it as AuthorService.cs or AuthorService.vb. Open the file and modify the class definition to look like the following:
372
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
'VB Imports System.Web Imports System.Web.Services Imports System.Web.Services.Protocols Imports System.Xml.Serialization Imports MSPress.Chapter9.Lesson2.Lab _ _ _ Public Class AuthorService Inherits System.Web.Services.WebService _ Public Function HelloWorld() As Author Dim SomeAuthor As New Author() SomeAuthor.FirstName = "William" SomeAuthor.LastName = "Ryan" SomeAuthor.MVPAwardee = True SomeAuthor.WebSiteUrl = "http://www.williamgryan.com" SomeAuthor.BlogUrl = "http://www.msmvps.com/WilliamRyan" SomeAuthor.BookTitles = New String() { "Professional ADO.NET 2.0", _
"Professional WinFX", _
"Advanced Microsoft Speech Server Development", _
"MCTS Self-Paced Training Kit (Exam 70-536)"}
Return SomeAuthor End Function End Class //C# using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
using MSPress.Chapter9.Lesson2.Lab;
[WebService(Namespace =
"http://www.williamgryan.com/wse/books/529/09/Lab02")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class AuthorService : System.Web.Services.WebService { public AuthorService () { //Uncomment the following line if using designed components //InitializeComponent(); } [WebMethod]
[SoapDocumentMethod(ResponseElementName = "Authors")]
[return: XmlElement("Authors")]
public Author GetAuthorInformation() {
Lesson 2: Using soapExtensionTypes
373
Author SomeAuthor = new Author();
SomeAuthor.FirstName = "William";
SomeAuthor.LastName = "Ryan";
SomeAuthor.MVPAwardee = true;
SomeAuthor.WebSiteUrl = "http://www.williamgryan.com";
SomeAuthor.BlogUrl = "http://www.msmvps.com/WilliamRyan";
SomeAuthor.BookTitles = new String[] {
"Professional ADO.NET 2.0",
"Professional WinFX",
"Advanced Microsoft Speech Server Development",
"MCTS Self-Paced Training Kit (Exam 70-536)"};
return SomeAuthor; }
}
18. Right-click the Web Services project folder and click Add Reference. In the Add Reference dialog box, click the Projects tab, click the WSEBusinessObjects project, and then click OK. 19. In the Web Service project, right-click the Service.asmx file and rename it AuthorService.asmx. 20. Open the AuthorService.asmx file and modify it to look like the following: 'VB
//C#
21. Click Build, Rebuild Web Site. 22. Right-click the Web service project and click Set As StartUp Project. 23. Right-click the AuthorService.asmx file and click Set As Start Page. 24. Run the application. The test page for the Web service should show if everything works correctly and should look like Figures 9-5 and 9-6.
374
Chapter 9
�
Web Services Enhancements 3.0 in Client and Server Applications
Figure 9-5
AuthorService Method tester
Figure 9-6
Output from GetAuthorInformation Web service
Exercise 2: Creating a Project Referencing WSE 3.0
In this exercise, you create a simple console application that references the Microsoft .Web.Services3 library. The primary class in this application inherits from the WebSer vicesClientProtocol class.
Lesson 2: Using soapExtensionTypes
375
1. Open Visual Studio 2005 and create an empty project. 2. Add a new C# or Visual Basic .NET console application named WSEDemoCS or WSEDemoVB, depending on which language you choose to write in. 3. In Solution Explorer, right-click the project folder and click Add Reference. 4. In the Add Reference dialog box, click the .NET tab. 5. Click Microsoft.Web.Services3 and click OK. 6. Right-click the project folder and click Add Web Reference. 7. Click Web Services On This Machine and click AuthorService. 8. In the Web Reference Name text box, type localhost and click Add Reference. 9. Click Localhost in the Web References folder and in Solution Explorer, click the Show All Files button. 10. Expand localhost and expand the Reference.map item. 11. Open the Reference.cs or Reference.vb file (this file is the Web service proxy class). 12. Ensure that the proxy class inherits from the WebServicesClientProtocol class. 13. If necessary, modify the class so that the definition looks like this: 'VB Partial Public Class AuthorServiceWse
Inherits Microsoft.Web.Services3.WebServicesClientProtocol
//C# public partial class AuthorServiceWse :
Microsoft.Web.Services3.WebServicesClientProtocol
14. In the WSEDemoVB project, open the Main.cs or Main.vb file and edit the code to look like the following: 'VB Imports Imports Imports Imports Imports
System
System.Collections.Generic
System.Text
Microsoft.Web.Services3
WSEDemoVB.localhost
Module Module1 Sub Main(ByVal args As String()) 'Alias the Microsoft.Web.Services3 and 'WebDemoCS.localhost namespaces Dim DemoService As AuthorService = New AuthorService() Dim DemoAuthor As Author = DemoService.GetAuthorInformation()
376
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
Console.WriteLine("First Name: " + DemoAuthor.FirstName) Console.WriteLine("Last Name: " + DemoAuthor.LastName) Console.WriteLine("Blog URL: " + DemoAuthor.BlogUrl) Console.WriteLine("Web Site URL: " + DemoAuthor.WebSiteUrl) Console.WriteLine("MVP Awardee? " + _ DemoAuthor.MVPAwardee.ToString()) Console.WriteLine("First Name: " + DemoAuthor.FirstName) For Each book As String In DemoAuthor.BookTitles Console.WriteLine("Book Title: " + book)
Next
Console.ReadLine()
End Sub End Module //C# using using using using using
System;
System.Collections.Generic;
System.Text;
Microsoft.Web.Services3;
WebDemoCS.localhost;
namespace WebDemoCS {
class Program
{
static void Main(string[] args) { //Alias the Microsoft.Web.Services3 //and WebDemoCS.localhost namespaces AuthorService DemoService = new AuthorService(); Author DemoAuthor = DemoService.GetAuthorInformation(); Console.WriteLine("First Name: " + DemoAuthor.FirstName); Console.WriteLine("Last Name: " + DemoAuthor.LastName ); Console.WriteLine("Blog URL: " + DemoAuthor.BlogUrl ); Console.WriteLine("Web Site URL: " + DemoAuthor.WebSiteUrl); Console.WriteLine("MVP Awardee? " + DemoAuthor.MVPAwardee.ToString()); Console.WriteLine("First Name: " + DemoAuthor.FirstName); foreach (String book in DemoAuthor.BookTitles) { Console.WriteLine("Book Title: " + book); } Console.ReadLine(); }
}
}
15. Right-click the WebDemoCS or WebDemoVB project and click Set As StartUp Project.
Lesson 2: Using soapExtensionTypes
377
16. Compile and run the application. If it worked correctly, the output should look as shown in Figure 9-7.
Figure 9-7
Output from WebDemoCS or WebDemoVB project
Lesson Summary ■
SoapExtensions can be configured through the element in a configuration file.
■
SoapExtensions can be added, removed, and cleared through configuration file entries.
■
With both the group and priority subtags, lower numeric values indicate higher priority than higher values.
■
Custom configuration sections, such as Microsoft.Web.Services3, can be added through the section of the configuration file.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 2, “Using soapExtensionTypes.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
378
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
1. Which soapExtensionTypes tasks can be done with a configuration file? (Select all that apply.) A. soapExtensionTypes can be added to the project. B. soapExtensionTypes can be removed from the project. C. All soapExtensionTypes that have been added can be cleared at once. D. soapExtensionTypes cannot be managed through a configuration file. 2. Which is true with respect to soapExtensionTypes declarations in a configuration file? (Select the best answer.) A. A priority value of 2 takes higher precedence than a priority value of 1. B. A group value of 1 takes higher precedence than a group value of 0. C. A priority value of 1 takes higher precedence than a priority value of 2. D. Numeric values do not control priority in either the priority or the group tags. 3. What modifications are necessary to use a specific configuration section? (Select all that apply.) A. Add an entry to the section of the configuration file. B. The assembly name, the assembly version, and the default culture can all be specified in the custom configuration section. C. An entry matching the name specified in the entry must be included outside of this section. D. An element with the name of the assembly must be included in the config uration file. A configSection attribute should be added to this element.
Lesson 3: Using Digital Signatures to Ensure Message Integrity
379
Lesson 3: Using Digital Signatures to Ensure Message Integrity One of the biggest selling points of Web Services Enhancements and Web Services Enhancements 3.0 in particular is the enhanced security features. Like the early WSE counterparts, WSE 3.0 implements the WS-Security 1.1 specification. MORE INFO
WS-Security in depth
General information about the WS-Security specification can be found at the following Uniform Resource Locator (URL): http://en.wikipedia.org/wiki/WS-Security.
The WS-Security 1.1 specification seeks to address three primary objectives with respect to messages: Ensure that the message was not tampered with during the transmis sion from sender to receiver.
■ Integrity
■ Authentication
Verify the identity of both the sender and receiver of the mes
sage. Obscure the contents of the message so that only authorized parties can read it.
■ Confidentiality
After this lesson, you will be able to: ■
Understand the basics of messaging security concepts.
■
Use security tokens.
Estimated lesson time: 45 minutes
Digital Signature Basics Until recently, written human signatures were the main way in which contractual agreements were executed. By signing a contract, I verify that I read it and that I accept the terms of the contract. If I sign a check, I acknowledge that I in fact want to engage in the transaction and that I thereby authorize it. However, there have always been dis honest people who have been able to benefit by forging signatures. As such, most institutions such as banks have signature cards on file so that they can compare a sig nature they know is authentic with any incoming checks. This system, however, as is the case with all handwriting verifications, involves a human being checking a new signature against a signature card to determine whether the signature is authentic. In
380
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
a bank, most employees are qualified to make this determination for the bank, but in larger transactions or disputes, handwriting experts are brought in to verify signature authenticity. Signature verification is always more art than science and, because the costs of being wrong can be very high, it is potentially problematic. Now that electronic commerce has become commonplace, signature verification is not feasible in many cases. Imagine that you need $20 to eat dinner or to purchase gas oline. If a human was necessary to verify your signature, the bank would be required to be open 24 hours a day to provide this service. Instead, most of us now use auto mated teller machine (ATM) cards. Each card has a personal identification number (PIN) that, if kept private, helps ensure that only the intended user can withdraw money from a given account. An ATM card corresponds to my account specifically. Short of a really sophisticated forgery mechanism, I can only use my ATM card to withdraw money from my account. If someone gets possession of my card but doesn’t have my PIN, he or she won’t be able to withdraw money from my account unless he or she can somehow procure my PIN. Digital signatures work much the same way. When a message is digitally signed, a user name and password are usually required to create the signature. This requirement ensures that senders are who they say they are. The message itself is used to create a hash value that mathematically can correspond only to this particular message (even a small change results in a different hash value) or a very small subset of possible messages. When the message is received, the hash can be verified and if it matches the one generated when the message was signed, it’s safe to assume that the message wasn’t tampered with. Although digital signatures aren’t 100 percent guaranteed to be authentic, the chances of tampering are so small that a digital signature’s security far exceeds the security of traditional methods such as handwriting. For SOAP messages, there are four mechanisms that can be used to digitally sign mes sages: ■
A UserNameToken object containing a user name and password combination.
■
A digital certificate such as X.509.
■
A Kerberos token. Kerberos is powerful but limited in use because it can only be issued and verified on a machine running Microsoft Windows XP Service Pack 1 or later or Microsoft Windows Server 2003.
Lesson 3: Using Digital Signatures to Ensure Message Integrity
381
X.509 Certificates X.509 certificates provide a clean, fairly easy-to-use mechanism for securing messages. A synopsis of how they are employed is provided here: 1. An installed X.509 certificate is used to generate a public and a private key pair. 2. The keys are first used to digitally sign an outgoing SOAP request. 3. On the server, the keys are used to decrypt the message that was encrypted in step 2. The most difficult part of using X.509 certificates is getting them installed on a machine. After they are installed, they can be used in the manner shown here: 'VB Private Sub X509Demo()
Dim DemoService As New ServiceWse()
Dim DemoToken As X509SecurityToken 'Retrieve 509 certificate
'from store
DemoService.RequestSoapContext.Security.Tokens.Add(DemoToken)
End Sub
//C# public static void X509Demo() {
ServiceWse DemoService = new ServiceWse();
X509SecurityToken DemoToken; //Retrieve X509SecurityToken from store
DemoService.RequestSoapContext.Security.Tokens.Add(DemoToken);
}
Kerberos Token Kerberos is a proprietary Microsoft standard that can be quite useful. In the context of WSE 3.0, it’s implemented through the KerberosToken class. The KerberosToken is passed within the SOAP headers, in much the same way the following UserNameToken is passed. After the call to the Web service, the machine is verified against the Ker beros domain and if the machine is authenticated, it can be permissioned to call the methods of the Web service: 'VB Private Sub KerberosDemo() Dim DemoService As New ServiceWse() Dim DNSName As String = System.Net.Dns.GetHostName() Dim kerbToken As New KerberosToken("host/" & DNSName & "@DomainName") Dim RequestContext As SoapContext = DemoService.RequestSoapContext RequestContext.Security.Tokens.Add(kerbToken) RequestContext.Security.Timestamp.TtlInSeconds = 60 RequestContext.Security.Elements.Add(New MessageSignature())
382
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
DemoService.ValidateResult("Bill.Ryan") End Sub //C# public static void KerberosDemo() {
ServiceWse DemoService = new ServiceWse();
String DNSName = System.Net.Dns.GetHostName();
KerberosToken kerbToken = new KerberosToken("host/" +
DNSName + "@DomainName"); SoapContext RequestContext = new DemoService.RequestSoapContext; RequestContext.Security.Tokens.Add(kerbToken); RequestContext.Security.Timestamp.TtlInSeconds = 60; RequestContext.Security.Elements.Add(new MessageSignature()); DemoService.ValidateResult("Bill.Ryan"); }
UserNameToken Probably the simplest mechanism in the security library is creating using the UserNameToken. To use UserNameToken, all that needs to be done is the following: 1. Declare a UserNameToken object. 2. Instantiate the UserNameToken, specifying a user name and a password. 3. Attach the UserNameToken to the RequestSoapContext’s Tokens collection. The following code illustrates the process of using a UserNameToken: 'VB Public Sub UserNameTokenSampe()
Dim DemoService As New ServiceWse()
Dim DemoUserName As String = "BillRyan"
Dim DemoToken As UsernameToken
Dim PasswordArray As Byte() = _
System.Text.Encoding.UTF8.GetBytes(DemoUserName) Array.Reverse(PasswordArray) Dim Base64Equivalent As String = Convert.ToBase64String(PasswordArray) DemoToken = New UsernameToken(DemoUserName, _ Base64Equivalent,PasswordOption.SendHashed)
DemoService.RequestSoapContext.Security.Tokens.Add(DemoToken)
End Sub
//C# public static void UserNameTokenSample() {
ServiceWse DemoService = new ServiceWse();
UsernameToken DemoToken = new UsernameToken();
String DemoUserName = "BillRyan";
Byte[] PasswordArray =
Lesson 3: Using Digital Signatures to Ensure Message Integrity
383
System.Text.Encoding.UTF8.GetBytes(DemoUserName); Array.Reverse(PasswordArray); String Base64Equivalent = Convert.ToBase64String(PasswordArray); DemoToken = new UsernameToken(DemoUserName, Base64Equivalent, PasswordOption.SendHashed); DemoService.RequestSoapContext.Security.Tokens.Add(DemoToken); DemoService.RequestSoapContext.Security.Elements.Add(null); }
Lab 3: Creating a Policy for Digital Signatures �
Exercise 1: Create the Web Service
1. Open Visual Studio 2005 and create an empty solution named DigitalSigPolicyLab. 2. Add a new C# or Visual Basic .NET Web service named SignatureEnabledServiceCS or SignatureEnabledServiceVB depending on which language you choose to write in. 3. In Solution Explorer, right-click the project folder, and click Add Reference. 4. In the Add Reference dialog box, click the .NET tab. 5. Click Microsoft.Web.Services3, and click OK. 6. Right-click the App_Code folder, and click Add New Item. Choose Class, name the class X509ProviderDemo, and click Add. 7. Add the following Imports or using statements near the top of the file: 'VB Imports Imports Imports Imports //C# using using using using
Microsoft.Web.Services3
Microsoft.Web.Services3.Security
Microsoft.Web.Services3.Security.Tokens
System.Security.Cryptography.X509Certificates
Microsoft.Web.Services3;
Microsoft.Web.Services3.Security;
Microsoft.Web.Services3.Security.Tokens;
System.Security.Cryptography.X509Certificates;
8. Add the following code to the class signature so the class inherits from SendSecurityFilter. The class definition for each should look like the following: 'VB Public Class X509ProviderDemo
Inherits SendSecurityFilter
//C# class X509ProviderDemo : SendSecurityFilter
384
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
9. Add a constructor that calls the base class’s constructor: 'VB Public Sub New()
MyBase.New("ServiceActor", True)
End Sub
//C# protected X509ProviderDemo(string serviceActor, bool isClient) : base("ServiceActor", true) { }
10. Override the SecureMessage method, and give it the following defintion: 'VB Public Overrides Sub SecureMessage( _ ByVal envelope As Microsoft.Web.Services3.SoapEnvelope, _ ByVal security As Microsoft.Web.Services3.Security.Security) Dim DemoCertificate As New X509Certificate2()
Dim DemoToken As New X509SecurityToken(DemoCertificate)
security.Tokens.Add(DemoToken)
Dim DemoSig As New MessageSignature(DemoToken)
security.Elements.Add(DemoSig)
End Sub //C# public override void SecureMessage(SoapEnvelope envelope, Security security) { X509Certificate2 DemoCertificate = new X509Certificate2(); X509SecurityToken DemoToken = new X509SecurityToken(DemoCertificate); security.Tokens.Add(DemoToken); MessageSignature DemoSig = new MessageSignature(DemoToken); security.Elements.Add(DemoSig); }
11. Compile the application. If done correctly, this should complete the first step necessary to send digitally signed messages. �
Exercise 2: Adding a Custom Policy
In this lab, you will create a custom policy to handle incoming messages that are dig itally singed. 1. Open the solution created in the previous exercise named DigitalSigPolicyLab. 2. Expand the App_Code folder in the Web service and select the Service.cs or Ser vice.vb file, depending on which language you are working in. At the top of the class, ensure that the following namespaces are included (using the using key word in C# or the Imports keyword in Visual Basic): Microsoft.Web.Services3;
System.Security.Principal;
Lesson 3: Using Digital Signatures to Ensure Message Integrity
385
Microsoft.Web.Services3.Design;
Microsoft.Web.Services3.Security;
Microsoft.Web.Services3.Security.Tokens;
System.Security.Cryptography.X509Certificates;
3. At the class declaration of the service, add the following line of code to specify a pol icy; this code should go under the WebService, WebServiceBinding, and DesignerGenerated attributes. (Only Visual Basic generates the DesignerGenerated attribute, so it is of no concern if you are using C#.) 'VB _ //C# [Policy(typeof(DemoPolicy))]
4. At the end of the class definition for the Web service, create another class that will define the policy. (This class could just as easily be added in a separate file, and there is no problem in doing so. However, for the sake of simplicity, add the class in the same file as the service because the service needs this class to run.) Add the following class definition after the end of the class definition of the ser vice: 'VB Public Class DemoPolicy
Inherits Policy
Public Const CERTIFICATE As String = "CERTIFICATE REFERENCE HERE" Public Sub New()
Dim DemoAssertion As UsernameForCertificateAssertion = _
New UsernameForCertificateAssertion()
DemoAssertion.X509TokenProvider = _ New X509TokenProvider( _ StoreLocation.LocalMachine, _ StoreName.My, _ CERTIFICATE, _ X509FindType.FindBySubjectDistinguishedName) Dim DemoProtection As EndpointProtectionRequirements = _ DemoAssertion.Protection Dim DemoOptions As SignatureOptions = _
SignatureOptions.IncludeAddressing Or _
SignatureOptions.IncludeTimestamp Or _
SignatureOptions.IncludeSoapBody
DemoProtection.Request.SignatureOptions = DemoOptions DemoProtection.Request.EncryptBody = True
386
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
DemoProtection.Response.SignatureOptions = DemoOptions DemoProtection.Response.EncryptBody = True DemoProtection.Fault.SignatureOptions = DemoOptions DemoProtection.Fault.EncryptBody = False DemoAssertion.MessageProtectionOrder = _ MessageProtectionOrder.SignBeforeEncrypt DemoAssertion.RequireDerivedKeys = True Me.Assertions.Add(DemoAssertion) End Sub End Class //C# public class DemoPolicy : Policy { private const String CERTIFICATE = "CERTIFICATE REFERENCE HERE"; public DemoPolicy() { UsernameForCertificateAssertion DemoAssertion = new UsernameForCertificateAssertion(); DemoAssertion.X509TokenProvider = new X509TokenProvider(StoreLocation.LocalMachine, StoreName.My, CERTIFICATE, X509FindType.FindBySubjectDistinguishedName); EndpointProtectionRequirements DemoProtection = DemoAssertion.Protection; DemoProtection.Request.SignatureOptions = SignatureOptions.IncludeAddressing | SignatureOptions.IncludeTimestamp | SignatureOptions.IncludeSoapBody; DemoProtection.Request.EncryptBody = true; DemoProtection.Response.SignatureOptions = SignatureOptions.IncludeAddressing | SignatureOptions.IncludeTimestamp | SignatureOptions.IncludeSoapBody; DemoProtection.Response.EncryptBody = true; DemoAssertion.MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt; DemoAssertion.RequireDerivedKeys = true; this.Assertions.Add(DemoAssertion); } }
Lesson 3: Using Digital Signatures to Ensure Message Integrity
387
5. To enable a client to use this policy, all that needs to be done is to copy the class definition to a method in the client and specify that the policy be used. If the cli ent had a method named GetPolicy, it would look like the following: 'VB Private Function GetPolicy() As SecureInvoiceServiceWse Public Const CERTIFICATE As String = "CERTIFICATE REFERENCE HERE" Dim DemoAssertion As UsernameForCertificateAssertion = _ New UsernameForCertificateAssertion() DemoAssertion.X509TokenProvider = _ New X509TokenProvider( _ StoreLocation.LocalMachine, _ StoreName.My, _ CERTIFICATE, _ X509FindType.FindBySubjectDistinguishedName) Dim DemoProtection As EndpointProtectionRequirements = _ DemoAssertion.Protection Dim DemoOptions As SignatureOptions = _
SignatureOptions.IncludeAddressing Or _
SignatureOptions.IncludeTimestamp Or _
SignatureOptions.IncludeSoapBody
DemoProtection.Request.SignatureOptions = DemoOptions DemoProtection.Request.EncryptBody = True DemoProtection.Response.SignatureOptions = DemoOptions DemoProtection.Response.EncryptBody = True DemoProtection.Fault.SignatureOptions = DemoOptions DemoProtection.Fault.EncryptBody = False DemoAssertion.MessageProtectionOrder = _
MessageProtectionOrder.SignBeforeEncrypt
DemoAssertion.RequireDerivedKeys = True
Me.Assertions.Add(DemoAssertion) End Function //C# private Policy GetPolicy() {
private String CERTIFICATE = "CERTIFICATE REFERENCE HERE";
UsernameForCertificateAssertion DemoAssertion =
new UsernameForCertificateAssertion();
DemoAssertion.X509TokenProvider =
388
Chapter 9
Web Services Enhancements 3.0 in Client and Server Applications
new X509TokenProvider(StoreLocation.LocalMachine, StoreName.My, CERTIFICATE, X509FindType.FindBySubjectDistinguishedName); EndpointProtectionRequirements DemoProtection = DemoAssertion.Protection; DemoProtection.Request.SignatureOptions =
SignatureOptions.IncludeAddressing |
SignatureOptions.IncludeTimestamp |
SignatureOptions.IncludeSoapBody;
DemoProtection.Request.EncryptBody = true; DemoProtection.Response.SignatureOptions =
SignatureOptions.IncludeAddressing |
SignatureOptions.IncludeTimestamp |
SignatureOptions.IncludeSoapBody;
DemoProtection.Response.EncryptBody = true;
DemoAssertion.MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt; DemoAssertion.RequireDerivedKeys = true; this.Assertions.Add(DemoAssertion);
}
6. Now before any call to the service, all that needs to be done is setting the policy: 'VB Dim DemoPolicy As Policy = GetPolicy()
proxy.SetPolicy(DemoPolicy)
//C# Policy DemoPolicy = GetPolicy();
proxy.SetPolicy(DemoPolicy);
Lesson Summary ■
Digital signatures can be used to verify a message’s integrity.
■
Authentication deals with verifying the sender and receivers of a message.
■
Confidentiality deals with ensuring that only approved entities can view a mes sage’s content.
■
Integrity deals with ensuring that a message has not been tampered with after it has been transmitted.
■
Tokens that can be attached to a message include X.509, a UserNameToken, Ker beros tokens, and custom binary tokens.
■
Tokens can be added to the Tokens collection of the RequestSoapContext of a WSE-enabled Web service or proxy.
Lesson 3: Using Digital Signatures to Ensure Message Integrity
389
Lesson Review You can use the following questions to test your knowledge of the information in Les son 3, “Using Digital Signatures to Ensure Message Integrity.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which of the following are objectives of digital signatures? (Select all that apply.) A. Authentication B. Authorization C. Integrity D. Confidentiality 2. What types of tokens can be used with a WSE 3.0 file? (Select all that apply.) A. Kerberos B. X.509 C. UserNameToken objects D. Custom binary
390
Chapter 9 Review
Chapter Review To further practice and reinforce the skills you learned in this chapter, you can per form the following tasks: ■
Review the chapter summary.
■
Review the list of key terms introduced in this chapter.
■
Complete the case scenarios. These scenarios set up real-world situations involv ing the topics of this chapter and ask you to create a solution.
■
Complete the suggested practices.
■
Take a practice test.
Chapter Summary ■
Web Services Enhancements provide extended functionality for Web services.
■
Web Services Enhancements build on the various WS-* specifications.
■
WSE can be used to provide powerful custom security features.
■
The WebServicesClientProtocol is the base class from which Web service proxies that use WSE functionality inherit.
■
A Web reference is needed to consume a Web service. Each proxy, however, must use the WebServicesClientProtocol instead of the SoapHttpClientProtocol.
■
Configuration files provide a powerful and flexible mechanism for using WSE 3.0.
Key Terms Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book. ■
authentication
■
digital signature
■
Direct Internet Message Encapsulation (DIME)
■
integrity
■
Kerberos
■
Message Transmission Optimization Mechanism (MTOM)
■
WS-*
Chapter 9 Review
391
Case Scenarios In the following case scenarios, you apply what you’ve learned about how to use Web methods and Web services. You can find answers to these questions in the “Answers” section at the end of this book.
Case Scenario 1: Where Does WSE Fit into My Application Development Strategy? This case scenario describes a typical deployment scenario whenever a company starts using Web services. At first, getting the services to work is first priority. But immediately afterward, issues such as security and performance become critical. This case scenario discusses how to address such concerns.
Interviews Following is a list of company personnel interviewed and their statements: “We are a little late into the game when it comes to Web ser vices. However, we’re trying to get on board as fast as possible. So far, we’ve seen so many protocols that it’s hard to decide what is hype and what is appropriate. The one thing we can definitely tell, though, is that rolling out our own solution doesn’t make much sense.”
■ IT Department Head
“The days of security by obscurity are over. Every client we have asks about our security procedures and they see right through fluff. They ask intelligent questions and demand intelligent answers. We need to have real answers for them. The only way to do this is to have flexible and exhaustive secu rity. Can WSE help here?”
■ Lead Business Analyst
“Taking security seriously is a matter of survival. I know better than anyone that security doesn’t get you any recognition until it’s not there, but I’m willing to do whatever I have to so I can sleep soundly at night knowing my customers’ data is secure. And I can’t listen to anything else about how you have to trade off security for performance. We need both!”
■ CIO
Questions Answer the following questions for your manager. 1. Do Web Service Enchancements fit into our development story? 2. If we use WSE, can we implement advanced security or performance features without taking a year to learn how to do it?
392
Chapter 9 Review
3. Can WSE provide both trusted security mechanisms and customization? Or do they employ a one-size-fits-all approach?
Case Scenario 2: Refining the Process? Assuming some enchancements were made and the process is working well, some consideration needs to be paid to maximizing performance. This scenario deals with how best to address this.
Interviews Following is a list of company personnel interviewed and their statements: “File transfers are killing us. We thought we had everything addressed, but we’re pushing some large multimedia files to the clients and it’s killed our network. Clients are complaining that everything is slow, we’re seeing a lot of network latency, and things just aren’t working as we planned from a per formance perspective.”
■ IT Department Head
“At first it seemed like once we put our business objects on the Web, our problems would all be resolved. Unfortunately, that’s not the case. It fixed many of our existing problems, but now we’ve inadvertently upped the bar for what clients expect, and performance is the key now.”
■ Lead Business Analyst
“Buy faster servers! Buy faster switches! Buy more bandwidth! This is insane. When I said I wanted these problems fixed, I didn’t mean I want them fixed even if it annihilates every bit of cash flow the company has. One of the main reasons we went with Web services is that it was supposed to be efficient, in terms of performance and cost. Yes, the new functionality has done a lot for us, but it’s bankrupting me, and “buy, buy, buy” can’t be the approach for every thing. Is there anything we can do now with the resources we have?”
■ CIO
Questions Answer the following questions for your manager. 1. Without purchasing new hardware or bandwidth, what can be used to enhance the efficiency of the data transfers? 2. Can we use one of the earlier WSE versions, or do we need WSE 3.0? 3. How much rework will any such approach entail?
Chapter 9 Review
393
Suggested Practices To successfully master the objectives covered in this chapter, complete the following tasks. Create a simple Web service and compile it. Pay close attention to the web.config file that’s generated by default. Afterward, use the WseConfigEditor3 to enable WSE and then examine what changes are made.
■ Practice 1
Using the Web service created in Practice 1, create a client application to consume it. Add a Web reference to the service and make note of the defini tion of the proxy class that is generated. Enable WSE 3.0, and refresh the Web reference. Re-examine the proxy class and note as many differences as you can.
■ Practice 2
Create a Web service that uses WSE 3.0. Create a client that con sumes it. Digitally sign a message and verify its authenticity.
■ Practice 3
Take a Practice Test The practice tests on this book’s companion CD offer many options. For example, you can test yourself on just the content covered in this chapter, or you can test yourself on all the 70-529 certification exam content. You can set up the test so that it closely sim ulates the experience of taking a certification exam, or you can set it up in study mode so that you can look at the correct answers and explanations after you answer each question. MORE INFO
Practice tests
For details about all the practice test options available, see the section titled “How to Use the Prac tice Tests” in this book’s Introduction.
Chapter 10
WSE Security
As mentioned in the previous chapter, Web Services Enhancements (WSE) provide advanced functionality that is not readily available otherwise. Security is one of the major benefits of using WSE, and, by using it, developers have access to a feature-rich and easy-to-use application programming interface (API) that allows them to imple ment extremely granular security solutions. Additional security, however, isn’t any thing new to WSE 3.0; enhanced security has been a primary feature of WSE since version 1.0. The primary difference between WSE 3.0 and previous versions is that, in previous versions, security was applied to each Simple Object Access Protocol (SOAP) message on an individual basis. Essentially, previous versions addressed security in a message-by-message fashion. Although this system was adequate for most developers’ purposes, the very nature of the implementation made it fairly complex to use. With the advent of WSE 3.0, message-level security can be applied to the actual message exchange mechanism itself. Therefore, WSE 3.0 enables you to apply security at a higher level using a set of security operations, which are known as security assertions. WSE also provides a set of predefined security assertions that are called turnkey secu rity assertions. Security assertions are applied using policies and policy assertions. An easy way to distinguish between these two approaches is a bodyguard metaphor. Assume that your company is charged with providing security for X number of peo ple. Using the security system used by previous versions of WSE, you’d assign a body guard to each person as they went to and from wherever it was they were going. If 50 people were traveling at once, you’d need at least 50 bodyguards. In the current ver sion of WSE, you effectively secure the transport vehicle and the transportation route as well as the origination point and destination, and then let all 50 people move along this channel as they pleased. Instead of trying to secure them individually, you basi cally secure a zone or route for them to travel, and they can rest assured that as long as they are within this zone, they are safe. The metaphor isn’t perfect, but it works to illustrate the conceptual differences between WSE 3.0 and previous versions.
Exam objectives in this chapter: ■
Implement a policy for a Web service application. ❑
Create a policy file manually.
❑
Declare the set of policies in a policy file. 395
396
Chapter 10
■
■
WSE Security
❑
Map policies to SOAP endpoints.
❑
Configure a policy file in a configuration file.
❑
Create and enforce a custom policy.
❑
Create a policy file by using the WseConfigEditor3 tool.
❑
Set a policy in a client application and in a client computer.
Encrypt and decrypt a SOAP message. ❑
Encrypt a SOAP message.
❑
Decrypt an encrypted SOAP message.
Implement filters in a Web service application. ❑
Add a filter.
❑
Remove a filter.
❑
Shuffle the order of the filters.
❑
Enable the Trace filter.
❑
Create custom input and output filters.
Lessons in this chapter: ■
Lesson 1: Web Service Policy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
■
Lesson 2: Custom Policies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
■
Lesson 3: Message Filters. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441
Before You Begin To complete the lessons in this chapter, you must have ■
A computer that meets or exceeds the minimum hardware requirements listed in the Introduction of the book.
■
Microsoft Visual Studio 2005 and the .NET Framework 2.0.
■
Knowledge of programming in Microsoft Visual Basic or C# and a basic under standing of Extensible Markup Language (XML).
In addition, each of the code examples in the chapter assumes that the following namespaces have been added at the top of the code file: 'VB Imports System
Before You Begin
Imports Imports Imports Imports Imports Imports Imports Imports //C#
using using using using using using using using
397
System.Collections.Generic
System.Text
System.Xml
System.Security.Cryptography.X509Certificates
Microsoft.Web.Services3
Microsoft.Web.Services3.Design
Microsoft.Web.Services3.Security
Microsoft.Web.Services3.Security.Tokens
System;
System.Collections.Generic;
System.Text;
System.Security.Cryptography.X509Certificates;
Microsoft.Web.Services3;
Microsoft.Web.Services3.Design;
Microsoft.Web.Services3.Security;
Microsoft.Web.Services3.Security.Tokens;
To import the Microsoft.Web.Services3 namespace and child namespaces, it is further assumed you have added a reference to the Microsoft.Web.Service3.dll assembly.
Real World William Ryan It wasn’t all that long ago that security concerns were largely ignored by the development community. I had worked for a while before returning to graduate school, so when I graduated, most of my friends who worked in the industry had substantially more real-world experience than I did. One day over lunch, I men tioned that the product they were developing sounded like it had a major flaw; it almost totally ignored security. When I mentioned the specifics, most of the people laughed at me, explaining that points such as the one I was making were made by people who spent too much time in school and not enough time in the field. Security was not something clients or venture capitalists could see, so focusing on it was something that was viewed by many as non-value added. It was almost exactly three years after we had this discussion that I was eating lunch with three of the five people at the original meeting. They were quite dejected because they had just been through a humiliating experience and lost a potential customer that they were sure they would win. They had moved a large part of their infrastructure to Web services and focused almost exclusively on performance and functionality. Just about everything that could be done inter nally could now be done by trading partners. They were convinced that security
398
Chapter 10
WSE Security
was pretty much an afterthought and didn’t plan on addressing it until after the application was live. During the demo, they showed their customer how easy it was to access the system externally. They showed the customer how much could be done remotely without having to develop a large amount of new code. Unfor tunately, one of the people performing the demo entered some values that cor responded to a different customer. He had intended to show the customer how easy it was to access all the customer’s financial data, but before he even realized what happened, he showed the customer a different company’s data. Although a few of the people at the demo were focusing on how great the new functionality was, the financial executive there was overwhelmed by the fact that he saw the bank accounts, names, addresses, and much more information of the other company’s customers. When the person doing the demo realized what happened, he tried to smooth over the mistake by saying “Well, this was just a careless mistake on my part; I used your company’s user name and password but specified a different company as the target.” The gentleman giving the demon stration thought this explanation would comfort the people watching. Instead, he just proved unequivocally how easy it would be for someone to accidentally view all of the client’s confidential data. Far from comforting the people watch ing the demo, this instead served to persuade them how dangerous having our company store their data would be. Not only did they not buy the expanded services, within a week they canceled the account, citing the debacle at the demo as their reason. The demo showed some impressive functionality with Web services, but because security wasn’t really implemented, a simple data entry error could expose very sensitive data. Using even simple security features, such as those provided in WSE 3.0, would have stopped another company’s data from being shown and might well have saved the account. Security should never be an afterthought when data is exposed via the Web. Securing this information is even more critical over the Web than over a traditional network. At best it’s professionally irresponsible not to take security seriously, and at worst it could be something that ruins your career or ends your company.
Lesson 1: Web Service Policy
399
Lesson 1: Web Service Policy WSE 3.0 enables Web services to be secured imperatively in code or declaratively using a security policy. WSE policies implement the WS-Policy specification. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwebsrv/html/ understwspol.asp for more information. In general, you should use a policy for imple menting your Web service security requirements, but see http://msdn.microsoft.com/ library/default.asp?url=/library/en-us/wse3.0/html/0610f54f-f8c4-4ded-9617 08384e5ed635.asp for information on securing a Web service without using a policy file. You should use a policy for implementing your Web service security requirements because a policy helps separate the business logic from the security requirements logic. The policy framework can also be extended so that any custom requirements can be added as extensions to the policy framework; think reuse instead of duplicate code. A policy helps your Web services validate that request messages conform to a defined security policy and automatically secures Web service responses. In addition, an administrator can manage a policy file once your Web service has been deployed to a production environment. The WSE 3.0 policy framework describes the constraints and requirements for com municating with Web services. A policy is compiled before any message exchange between a sender and a recipient takes place. So, a policy effectively becomes a run time component in the form of a SOAP message filter (see Lesson 3, “Message Filters,” for more information), and the policy requirements are applied at the sender or enforced at the recipient. Although policies can be used to apply various types of requirements, such as mes sage addressing, in this lesson you’ll see how WSE 3.0 policies are used to apply secu rity requirements. After this lesson, you will be able to: ■
Create a policy file manually.
■
Create a policy file using the WseConfigEditor3 tool.
■
Declare the set of policies in a policy file.
■
Map policies to SOAP endpoints.
■
Configure a policy file in a configuration file.
Estimated lesson time: 45 minutes
400
Chapter 10
WSE Security
Creating a Policy WSE 3.0 makes creating policy assertions files fairly straightforward. A policy assertion is a combination of the capabilities and requirements of your Web service. This means that an assertion is part of the SOAP processing pipeline. You can think of an assertion as a “factory” class that can be used for injecting input filters, output filters, or both into the processing pipeline. You’ll learn more about filters in Lessons 2 and 3. Follow these steps to create and implement a policy: 1. Create a stub to hold the policy elements. This task is accomplished by creating a .config file, known as a policy file or a policy cache file. 2. Create a set of policy assertions. There exists a fairly involved set of assertions that can be used. If these assertions aren’t sufficient for your application’s needs, you can develop custom assertions by simply creating a class that inherits from the SecurityPolicyAssertion or the PolicyAssertion classes. 3. Reference the policy file from the application configuration file. The following example shows the structure of a WSE policy file:
The structure is fairly intuitive, but a brief description is in order. The ele ment is the root element and you should specify the xmlns attribute as shown to avoid build warnings. The element, which is optional, is a container for one or more elements. The element is used to specify a policy exten sion, which is named standard or custom policy assertion. One example of a standard security policy assertion (extension) is usernameOverTransportSecurity:
The element is used to uniquely identify a policy in a policy file by way of the name attribute. It holds one or more child elements, each of which can be one of the standard policy assertions, such as the usernameOverTransportSecurity turnkey secu rity assertion, or a custom policy assertion. See http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/wse/html/88b311eb-03f2-4fec-b8f3-33755413a0ca.asp
Lesson 1: Web Service Policy
401
for more information about the policy file schema.
Creating a Policy File Manually The simplest way to get started with your Web service policy is to create a policy file manually. Basically, you create a blank XML file, which can have any valid filename, but to make the file easier to recognize, give it the standard .config extension. Then add the following structure to the XML file:
To this structure you can manually add the extensions using the and elements, and policy assertions using the and related child ele ments. However, it is much easier to use the WSE Settings 3.0 tool, also known as WseConfigEditor3, as described in the next section, “Creating, Configuring, and Enabling a Policy File Using WseConfigEditor3.”
Creating, Configuring, and Enabling a Policy File Using WseConfigEditor3 Many of the features of WSE 3.0 can be managed using the WSE Settings 3.0 tool, also known as WseConfigEditor3. WseConfigEditor3 is a tool with a graphical user inter face that relieves the workload of configuring the configuration files manually. Once WSE 3.0 has been installed on a computer, the WseConfigEditor3 is available on the Start menu as well as from Solution Explorer in the Microsoft Visual Studio 2005 Inte grated Development Environment. Using the configuration tool is much more convenient than it was in previous ver sions of WSE. Prior to the advent of WSE 3.0, developers could only access the con figuration tool from within Visual Studio 2005. What’s worse, it could only be accessed from inside an existing project. To create a new policy file using WseConfigEditor3, or attach an existing policy file, perform the following actions: 1. In Solution Explorer, right-click the project for which you want to create the pol icy; the Web Service project or the Web Service consumer project, such as a Con sole application or Windows application, and click WSE 3.0 Settings. 2. In the WseConfigEditor3 dialog box, click the Policy tab, as shown in Figure 10-1.
402
Chapter 10
WSE Security
Figure 10-1 The Policy tab of the WseConfigEditor3 tool
3. Select the Enable Policy check box. 4. Click Browse, and in the Open dialog box, navigate to the .config file that con tains the existing policy, or browse to the folder where you want to create a new policy file, type in the name of the file in the File Name box, and click Open. Unless you’re attaching an existing policy file that already contains policies (unlike the blank policy file you might have created manually following the instructions in the “Creating a Policy File Manually” section), you need to add new policy assertions (see the next section, “Adding Policy Assertions,” for more information). Once poli cies have been added to the policy file, you can click OK to enable the policy for your project.
Adding Policy Assertions A policy (file) without policy assertions is hardly useful, not to say it’s useless. In fact, if you click OK in the WseConfigEditor3 dialog box, after policy has been enabled, but no policy assertions have been added to the policy, you’re prompted that policy will be disabled. Unfortunately, the message box that prompts you doesn’t give you the option of canceling the action and returning to the WseConfigEditor3 dialog box. Instead, the policy is disabled for your project and the WseConfigEditor3 dialog box is closed.
Lesson 1: Web Service Policy
403
To add policy assertions to your policy using the WseConfigEditor3 dialog box, the following should be done: 1. Ensure that a policy file has been added to your project. See the previous section, “Creating, Configuring, and Enabling a Policy File Using WseConfigEditor3,” for more information. 2. In the WseConfigEditor3 dialog box, click the Policy tab, and in the Edit Appli cation Policy group, click Add as shown in Figure 10-2.
Figure 10-2 The Add button in the Edit Application Policy group
3. In the Add Or Modify Policy Friendly Name dialog box, shown in Figure 10-3, type the friendly name for the policy assertion in the text box and click OK. The WSE Security Settings Wizard will then walk you through setting an authentica tion method.
Figure 10-3 Add Or Modify Policy Friendly Name dialog box
404
Chapter 10
WSE Security
Once you specify a friendly name for the policy assertion and click OK, the WSE Secu rity Settings Wizard starts. Click Next on the welcome page. On the Authentication Settings page, which is shown in Figure 10-4, you have two options:
Figure 10-4 Authentication Settings page of WSE Security Settings Wizard
This one is fairly obvious: You sim ply select the Secure A Service Application option, if you’re configuring a Web service project, and the Secure A Client Application option if you’re configuring a Web service consumer or client application.
■ Do You Want To Secure A Service Or A Client?
The Choose Client Aut hentication Method group presents options for authentication settings. There are four choices, described here:
■ Choose Client Authentication Method
The client does not provide any credentials in the request. The service ensures that the client has secured the request with the server’s public certificate. The response message is secured with a symmetric key that was supplied by the client. Although this option is the easiest to use, it is also considered the weakest.
❑ Anonymous
The client provides a user name and password as credentials in the request, which the service authenticates. The service ensures that the client has secured the request with the server’s public certificate. The response message is secured with a symmetric key that was supplied by the client.
❑ Username
Lesson 1: Web Service Policy
405
The client provides a certificate as a credential in the request, which the service authenticates. The service ensures that the client has secured the request with the server’s public certificate. The response mes sage is secured with a symmetric key that was supplied by the client.
❑ Certificate
The client provides a Kerberos ticket as a credential in the request, which the service authenticates. The service ensures that the client has secured the request with a Kerberos ticket for the service. The response message is secured with the client’s Kerberos ticket.
❑ Windows
If you select the Username, Certificate, or Windows option and click Next, you have the option of specifying if you want to perform authorization on the User And Roles page. On the following pages you need to specify the protection requirements for the mes sages, which means if you want to enable WS-Security Extensions 1.1, the protection order, and if you want to establish a secure session. In addition, you need to specify the server certificate to be used for the secure message exchange. Finally, review your settings on the Create Security Settings page and click Finish to save the settings to the policy file. Here’s a sample policy file, with a policy named demoPolicy, that uses the usernameForCertificateSecurity security assertion:
406
Chapter 10
WSE Security
For more information about the WSE Security Settings Wizard and the WSE Settings 3.0 tool in general, see http://msdn.microsoft.com/library/default.asp?url=/library/enus /wse3.0/html/5a8f03b1-16ac-4c5c-9d9e-132a9c0b628a.asp.
Using a Policy File After a policy file has been created, it needs to be referenced from the Web.config or App.config file of your application so that the policy can be used for your applications message exchange, which also means that you’re mapping your policies to the SOAP endpoint of your application (this is really WSE 2.0 terminology):
Lab 1: Configuring Security In this lab, you use the WseConfigEditor3 tool to manage the policy your application is using. You create a Web service and use the WseConfigEditor3 to manage it. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. 1. Open Visual Studio 2005 and create a blank solution named WSEConfigEditorLab. 2. Add a new C# or Visual Basic Web service named WSEConfigEditorLab. 3. Right-click the project and click WSE Settings 3.0. 4. In the WseConfigEditor3 dialog box, on the General tab, select the Enable This Project For Web Services Enhancements and Enable Web Services Enhancement Soap Protocol Factory check boxes.
Lesson 1: Web Service Policy
407
5. Click the Policy tab. 6. Select the Enable Policy check box. 7. In the Edit Application Policy group, click Add. 8. In the Add Or Modify Policy Name dialog box, type demoPolicy in the text box, and click OK. 9. In the WSE Security Settings Wizard, on the Welcome page, click Next. 10. On the Authentication Settings page, in the Do You Want To Secure A Service Or A Client group, click the Secure A Service Application option. 11. In the Choose Client Authentication Method group, click the Anonymous option, and then click Next. 12. On the Message Protection page, in the Protection Order group, click the SignOnly option, and then click Next. 13. On the Server Certificate page, select the Specify The X.509 Certificate In Code check box, and then click Next. 14. On the Create Security Settings page, review the summary of the options you’ve chosen. If you made any mistakes or you want to change any of the options, you can navigate backward and change them. 15. Click Finish to close the wizard, and then click OK to close the WseConfig Editor3 dialog box. 16. Examine the newly created wse3policyCache.config file. It should look similar to the following:
408
Chapter 10
WSE Security
You have just successfully configured your Web service application using the WseConfigEditor3 tool.
Lesson Summary ■
The WseConfigEditor3 tool can be used to manage almost any element of WSE configuration.
■
In previous versions of WSE, the WseConfigEditor3 was only available through individual projects. Starting with WSE 3.0, the tool can be used within Visual Studio 2005 or as a stand-alone application.
■
The WseConfigEditor3 tool can be used to manage general settings, security set tings, policy settings, routing, token issuing, diagnostics, and messaging.
■
If a custom policy is implemented for an application, the WseConfigEditor3 tool’s Policy tab can be used to manage it. This feature provides access to a Security Set tings Wizard and has the abilty to provide very intricate policy configuration.
■
There are four authentication modes available with custom policy: anonymous, Windows, certificate, and username.
■
Anonymous authentication is the simplest mode to use but is considered by many to be the least secure of the available modes and as such should only be used with transport-level security.
■
Windows authentication uses Kerberos and provides both a high level of secu rity and reduced administration complexity.
■
By default, two types of applications can be configured: client applications and services.
■
The policy fileName attribute of the element of a Web.config file can be used to point the application to a custom policy expression file.
Lesson 1: Web Service Policy
409
■
Multiple policies can be added to an application by specifying additional elements.
■
If multiple policies are configured, the WseConfigEditor3 can be used to edit the configuration file manually.
■
If a policy is added manually, great care must be taken to ensure all necessary ele ments are entered and spelled correctly.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “Web Service Policy.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which are valid client authentication methods? (Select all that apply.) A. Anonymous B. Username C. Certificate D. Windows 2. How can a policy be specified in a Web.config or App.config file? (Choose the best answer.) A. Add a element and specify the policy information there. B. Add a element and add the policy information there. C. Add a element, which allows for automatic discovery. D. Add a element within the element and specify the policy name using a fileName attribute.
410
Chapter 10
WSE Security
Lesson 2: Custom Policies In the previous lesson you learned about policy (cache) files, policy assertions, policy extensions, and how to configure all of these using the WseConfigEditor3 tool. In this lesson you’ll learn how to create a custom policy assertion that encrypts the messages exchanged. You’ll see how a custom policy assertion can be built using the UsernameToken for a custom security solution. First we’ll cover encryption basics. Security was one of the primary areas of concentration when WSE 3.0 was developed. This shouldn’t be surprising because, as time progresses and Web services become more and more prevalent, the need to provide security that can cover more and more situ ations becomes critical. There are many aspects to security, but without a doubt, encryption and decryption are cornerstones of any secure solution. After this lesson, you will be able to: ■
Create and enforce a custom policy.
■
Set a policy in a client application and in a client computer.
■
Encrypt a SOAP message.
■
Decrypt a SOAP message.
Estimated lesson time: 55 minutes
Overview The most basic purpose of encryption is simply to prevent unwanted parties from see ing information (or at least to make it really difficult for them). Decryption entails the flip side of this: making sure that authorized parties can, in fact, read messages that have been encrypted. Encryption and decryption are used in almost every nontrivial application, and it’s hard to imagine any substantial enterprise application that doesn’t use either of them to at least some degree. In its simplest form, an encryption algorithm might turn the word “Microsoft” into the word “Nuxeiaidr.” The algorithm used in this case was simple substitution algorithm, substituting every letter in the word “Microsoft” with the letter that appears directly to the left of it on a standard key board. Without knowing the algorithm, you could encrypt a message and, thereby, make it difficult for unwanted parties to read. In this case, the algorithm is very sim ple, and any sophisticated attacker would definitely have it in his or her arsenal. Hashing is another component of security (although hashing is used outside of secu rity contexts, as well). Hashing uses mathematics to compute a value for the words
Lesson 2: Custom Policies
411
being obscured. If any portion of the hashed value is changed or tampered with, then the original value won’t be computed correctly when unhashed. Hence, by checking the unhashed value against the value you are expecting, one can determine with a high degree of confidence whether a message has been tampered with. These two concepts are used together to provide digital signatures. In Chapter 9, “Web Services Enhancements 3.0 in Client and Server Applications,” the concept of a digital signature was introduced. The primary objective of a digital signature is to pro vide nonrepudiation. NOTE
Definition of Nonrepudiation
The concept of nonrepudiation essentially holds that if a message is nonrepudiable, the sender cannot deny that the message was in fact sent from him or her and the recipient cannot deny that the message was received. Not only must the identity of both sender and receiver be verified, but a mechanism must be put in place to avoid someone saying something like, “Yes, I did in fact send a message; however, I didn’t send that message.”
Basic Encryption Although nonrepudiation is an important security concept, it might not be the pri mary objective of a given security scenario. Imagine a case where trade secrets are being transferred. Regardless of whether there’s a need to verify the sender and receiver, you’d like to ensure that if the message is lost or stolen, unauthorized third parties will not be privy to the information in the message. It’s worth mentioning that no encryption algorithm is 100 percent secure, meaning that there’s no guarantee that unauthorized parties will be unable to view the message. However, using strong commercial algorithms, it’s possible to make it so costly, time-consuming, or inconve nient that the chances of an unauthorized party accessing the information are minis cule. NOTE
Definition of Privacy
Although nonrepudiation might be important in many transactions (particularly in cases such as financial or health care scenarios), privacy is equally important in many other instances. If you receive a contract from a customer, you often want to verify that the contract was actually signed by the customer and was not tampered with after it was signed. There might be cases where, in addition to or irrespective of nonrepudiation, you might not want anyone other than the intended recipient to see the contents of a message. Essentially, you want to make sure that no one sees the contents of the message other than people you authorize to see it.
412
Chapter 10
WSE Security
To achieve the goal of privacy, two varieties of encryption can be used: The term private key encryption is often used as a synonym for symmetric algorithms. Symmetric algorithms use a key that must be known to both the sender and the recipient. Symmetric algorithms are generally consid ered less secure than their asymmetric counterparts because both the sender and the recipient must share the same key. The integrity of the algorithm is dependent on the key being kept secret; the risk of compromise increases with each additional party that has access to the key.
■ Symmetric algorithms
The term public key encryption is often used as a syn onym for asymmetric algorithms. To use these algorithms, the sender encrypts a message with the recipient’s public key. The recipient’s private key must be used to decrypt the message. This encryption method is generally considered more secure because only one party needs to have access to the private key. As long as the private key is kept private, the probability of the message being com promised is very small.
■ Asymmetric algorithms
MORE INFO
Additional resources on .NET Framework cryptography
Although this discussion focuses on encryption and decryption of SOAP messages using WSE 3.0, there are many different applications of cryptography. Microsoft’s Patterns and Practices group has built an application block for cryptography that has widespread applicability for many applications. Not only is the application block fairly easy to use, it also provides extensive documentation on the .NET Framework’s cryptographic capabilities, and examining it might be a very useful learning tool. As of the time of this writing, this application block can be accessed at http://msdn.microsoft.com/ library/default.asp?url=/library/en-us/dnpag2/html/EntLibJan2006_CryptoAppBlock.asp?frame=true.
WSE 3.0 provides a mechanism to use either type of encryption algorithm. To use asymmetric encryption, the Web service consumer needs to have access to the recip ient’s X.509 certificate that contains the public key matching the recipient’s private key. To use a symmetric algorithm with WSE 3.0, the private key must be distributed to both parties, so any Web service consumer, as well as the Web service itself, needs access to this private key. One common misconception regarding the encryption of SOAP messages is that the entire message is encrypted. This is not the case! Only the body part of the SOAP mes sage is encrypted (and accordingly, decrypted on the other side of the exchange). Therefore, if sensitive information is contained in other elements of the message such as the header, the information needs to be encrypted and decrypted separately.
Lesson 2: Custom Policies
CAUTION
413
Be careful with sensitive information outside of the body part
Any information not contained in the body part of the message is not encrypted and is visible to prying eyes. As such, it’s critical to examine your SOAP messages and know exactly what informa tion they contain.
Real World William Ryan I was doing a presentation on Web Services Enhancements 2.0 at a .NET user’s group a while ago. At the end of the presentation, many developers were gath ered in the room and talking shop. One person told me of a consulting firm his company had hired to build a messaging subsystem for them. This company had a very high billing rate and claimed to have extensive expertise in the area of security. The project they built was deployed for more than six months before a mandatory yearly audit was performed. One of the first things the auditors examined was the content of the SOAP messages being transmitted. What they discovered was very disheartening. By looking at the messages, they discovered that although everything in the body part of the message was properly secured, a substantial amount of very sensitive data was being transmitted in a custom SOAP header and the consultants failed to address this information. So although a substantial amount of time and money was spent to keep information in the body part private, most of the same information was contained in the header and readily visible in plain text. This situation was analogous to locking one of your car doors while leaving the other doors wide open. Now that you know about encryption basics, we need to combine this knowledge with a custom policy so that we can build a custom policy assertion that is used by both the Web service and the client, making sure all messages exchanged are encrypted.
Creating a Custom Policy Assertion A custom policy assertion should be created when one of the turnkey security asser tions doesn’t meet the needs of your application. The custom policy assertion is used to enforce security requirements or for modifying SOAP messages. As mentioned in Lesson 1, a custom policy assertion must be derived from either the PolicyAssertion or
414
Chapter 10
WSE Security
SecurityPolicyAssertion class. Because you’ll be creating a security assertion, you’ll derive yours from the SecurityPolicyAssertion class. Follow these steps to create a custom policy assertion that secures a SOAP message exchange: 1. Create a new Class Library project, rename the default class appropriately, and derive it from the SecurityPolicyAssertion class. Add two private variables, username and password, and the corresponding read-only properties for retrieving the values of the private variables. Then add two constructors, one default and parameterless, and one that accepts a username and a password. In the nonde fault constructor, save the passed username and password values to the private variables: 'VB Public Class CustomSecurityAssertion
Inherits SecurityPolicyAssertion
Private usn As String = Nothing
Private pwd As String = Nothing
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal userName As String, ByVal password As String) MyBase.New() Me.usn = userName
Me.pwd = password
End Sub
Public ReadOnly Property UserName() As String Get Return Me.usn End Get End Property public readonly property Password() as string Get Return Me.pwd
End Get
End Property
End Class
//C# public class CustomSecurityAssertion : SecurityPolicyAssertion {
Lesson 2: Custom Policies
415
private string usn = null;
private string pwd = null;
public CustomSecurityAssertion() : base()
{
}
public CustomSecurityAssertion(string userName, string password)
: base()
{
this.usn = userName;
this.pwd = password;
} public string UserName {
get
{
return this.usn;
}
}
public string Password {
get
{
return this.pwd;
}
}
}
2. Override the abstract CreateServiceInputFilter method of the SecurityPolicyAsser tion class: 'VB Public Overrides Function CreateServiceInputFilter(ByVal context As _
Microsoft.Web.Services3.Design.FilterCreationContext) As _
Microsoft.Web.Services3.SoapFilter
Throw New Exception("The method or operation is not implemented.")
End Function
//C# public override SoapFilter CreateServiceInputFilter(FilterCreationContext context)
{
throw new Exception("The method or operation is not implemented.");
}
3. Override the abstract CreateServiceOutputFilter method of the SecurityPolicyAsser tion class:
416
Chapter 10
WSE Security
'VB Public Overrides Function CreateServiceOutputFilter(ByVal context As _ Microsoft.Web.Services3.Design.FilterCreationContext) As _ Microsoft.Web.Services3.SoapFilter Throw New Exception("The method or operation is not implemented.") End Function //C# public override SoapFilter CreateServiceOutputFilter(FilterCreationContext context) { throw new Exception("The method or operation is not implemented."); }
4. Override the abstract CreateClientInputFilter method of the SecurityPolicyAsser tion class: 'VB Public Overrides Function CreateClientInputFilter(ByVal context As _ Microsoft.Web.Services3.Design.FilterCreationContext) As _ Microsoft.Web.Services3.SoapFilter Throw New Exception("The method or operation is not implemented.") End Function //C# public override SoapFilter CreateClientInputFilter(FilterCreationContext context) { throw new Exception("The method or operation is not implemented."); }
5. Override the abstract CreateClientOutputFilter method of the SecurityPolicyAsser tion class: 'VB Public Overrides Function CreateClientOutputFilter(ByVal context As _ Microsoft.Web.Services3.Design.FilterCreationContext) As _ Microsoft.Web.Services3.SoapFilter Throw New Exception("The method or operation is not implemented.") End Function //C# public override SoapFilter CreateClientOutputFilter(FilterCreationContext context) { throw new Exception("The method or operation is not implemented."); }
Lesson 2: Custom Policies
417
6. Override the ReadXml method of the SecurityPolicyAssertion class. Although the ReadXml method isn’t abstract and as such doesn’t have to be overridden like other methods described previously, overriding ReadXml is necessary when con figuration is done through a configuration file. If you don’t, a System.Net.WebEx ception is thrown because the operation times out, waiting for the start element to be read from the configuration file. So, in the ReadXml method, add code for parsing the custom XML elements and attributes, if any, that can be placed in the policy file for the security policy assertion. Security policy assertions (turnkey and custom) can provide an XML element that is used for specifying the options in the policy file. An XmlReader object is passed to the ReadXml method, and the reader is positioned on the XML element for the security policy assertion. The name of the XML element is specified when the security policy assertion is reg istered in the policy file with the extension element. Therefore, the XmlReader can be used to retrieve the options that are specified in the policy file: 'VB Public Overrides Sub ReadXml(ByVal reader As XmlReader, _
ByVal extensions As IDictionary(Of String, Type))
' Was an XmlReader object passed?
If reader Is Nothing Then
Throw New ArgumentNullException( _
"XmlReader parameter cannot be null")
End If
' Was an IDictionary object passed?
If extensions Is Nothing Then
Throw New ArgumentNullException( _
"IDictionary parameter cannot be null")
End If
Dim elementName As String = Nothing ' Loop through extensions For Each extensionName As String In extensions.Keys
' Our extension?
If extensions(extensionName) Is _
GetType(CustomSecurityAssertion) Then ' Save the extension name
elementName = extensionName
Exit For
End If
Next
' Read the start element
418
Chapter 10
WSE Security
reader.ReadStartElement(elementName)
End Sub
//C# public override void ReadXml(XmlReader reader,
IDictionary extensions)
{
// Was an XmlReader object passed?
if (reader == null)
throw new ArgumentNullException( "XmlReader parameter cannot be null"); // Was an IDictionary object passed? if (extensions == null) throw new ArgumentNullException( "IDictionary parameter cannot be null"); string elementName = null; // Loop through extensions foreach (string extensionName in extensions.Keys) { // Our extension?
if (extensions[extensionName] == typeof(CustomSecurityAssertion))
{
// Save the extension name elementName = extensionName; break;
}
}
// Read the start element
reader.ReadStartElement(elementName);
}
7. Create your Web service’s custom input filter by adding a new class to the class file created in step 1 that derives from Microsoft.Web.Services3.Security.ReceiveSecurityFilter. This is the base class for filtering incoming SOAP messages. You need to create a custom constructor, because the base class doesn’t have a parameterless constructor. The call to the overloaded base constructor takes two parameters; a SOAP actor and a Boolean value indicating whether the filter is to be used with a Web service client. You also need to override the abstract ReceiveSecurityFilter.ValidateMessageSecurity: 'VB Public Class ServiceFilterIn
Inherits ReceiveSecurityFilter
Public Sub New(ByVal parentAssertion As CustomSecurityAssertion) MyBase.New(parentAssertion.ServiceActor, False)
Lesson 2: Custom Policies
419
End Sub Public Overrides Sub ValidateMessageSecurity(ByVal envelope As _
Microsoft.Web.Services3.SoapEnvelope, _
ByVal security As Microsoft.Web.Services3.Security.Security)
End Sub
End Class
//C# public class ServiceFilterIn : ReceiveSecurityFilter
{
public ServiceFilterIn(CustomSecurityAssertion parentAssertion):
base(parentAssertion.ServiceActor, false)
{
}
public override void ValidateMessageSecurity(
Microsoft.Web.Services3.SoapEnvelope envelope,
Microsoft.Web.Services3.Security.Security security)
{
}
}
8. Create your Web service’s custom output filter by adding a new class to the class file created in step 1 that derives from Microsoft.Web.Services3.Security.SendSecu rityFilter. This is the base class for filtering outgoing SOAP messages. You need to create a custom constructor, because the base class doesn’t have a parameterless constructor. The call to the overloaded base constructor takes two parameters; a SOAP actor and a Boolean value indicating whether the filter is to be used with a Web service client. You also need to override the abstract SendSecurityFilter.SecureMessage: 'VB Public Class ServiceFilterOut
Inherits SendSecurityFilter
Public Sub New(ByVal parentAssertion As CustomSecurityAssertion)
MyBase.New(parentAssertion.ServiceActor, False)
End Sub
Public Overrides Sub SecureMessage(ByVal envelope As _
Microsoft.Web.Services3.SoapEnvelope, _
ByVal security As Microsoft.Web.Services3.Security.Security)
End Sub
End Class
//C# public class ServiceFilterOut : SendSecurityFilter
420
Chapter 10
WSE Security
{ public ServiceFilterOut(CustomSecurityAssertion parentAssertion) : base(parentAssertion.ServiceActor, false) { } public override void SecureMessage( Microsoft.Web.Services3.SoapEnvelope envelope, Microsoft.Web.Services3.Security.Security security) {
}
}
9. Create your Web service client’s custom input filter by adding a new class to the class file created in step 1 that derives from Microsoft.Web.Services3.Secu rity.ReceiveSecurityFilter: 'VB Public Class ClientFilterIn
Inherits ReceiveSecurityFilter
Public Sub New(ByVal parentAssertion As CustomSecurityAssertion) MyBase.New(parentAssertion.ServiceActor, True) End Sub Public Overrides Sub ValidateMessageSecurity(ByVal envelope As _ Microsoft.Web.Services3.SoapEnvelope, _ ByVal security As Microsoft.Web.Services3.Security.Security) End Sub
End Class
//C# public class ClientFilterIn : ReceiveSecurityFilter { public ClientFilterIn(CustomSecurityAssertion parentAssertion) : base(parentAssertion.ServiceActor, true) { } public override void ValidateMessageSecurity( Microsoft.Web.Services3.SoapEnvelope envelope, Microsoft.Web.Services3.Security.Security security) {
}
}
10. Create your Web service client’s custom output filter by adding a new class to the class file created in step 1 that derives from Microsoft.Web.Services3.Security.SendSecurityFilter. Create a private variable for saving the parent security assertion, which is passed to the constructor, and save it in the constructor:
Lesson 2: Custom Policies
421
'VB Public Class ClientFilterOut
Inherits SendSecurityFilter
Private parentAssertion As CustomSecurityAssertion = Nothing Public Sub New(ByVal parentAssertion As CustomSecurityAssertion) MyBase.New(parentAssertion.ServiceActor, True) Me.parentAssertion = parentAssertion
End Sub
Public Overrides Sub SecureMessage(ByVal envelope As _ Microsoft.Web.Services3.SoapEnvelope, _ ByVal security As Microsoft.Web.Services3.Security.Security) End Sub
End Class
//C# public class ClientFilterOut : SendSecurityFilter { private CustomSecurityAssertion parentAssertion = null; public ClientFilterOut(CustomSecurityAssertion parentAssertion) : base(parentAssertion.ServiceActor, true) { this.parentAssertion = parentAssertion; } public override void SecureMessage( Microsoft.Web.Services3.SoapEnvelope envelope, Microsoft.Web.Services3.Security.Security security) {
}
}
In the steps outlined, you create a single security policy assertion class or assembly, but depending on your preferences, you could just as easily split the class into two security policy assertion classes or assemblies; one for the Web service SOAP pipeline and one for the Web service client SOAP pipeline. The custom security assertion will s i g n a n d v e r i f y a n d e n c r y p t a n d d e c r y p t S OA P m e s s a ge s u s i n g t h e Microsoft.Web.Services3.Security.Tokens.UsernameToken as described in the next sec tion, “Encryption with a Security Token.”
Encryption with a Security Token Security tokens can be used to sign and encrypt SOAP messages and the WSE 3.0 class library provides a number of tokens in the Microsoft.Web.Services3.Security.Tokens
422
Chapter 10
WSE Security
namespace. There are many different types of tokens, each of which has its own respec tive strengths and weaknesses. Each token implementation in WSE 3.0 derives from the SecurityToken base class. The WSE 3.0 classes that inherit from SecurityToken are shown in the following list: ■
Microsoft.Web.Services3.Security.Tokens.BinarySecurityToken
■
Microsoft.Web.Services3.Security.Tokens.DerivedKeyToken
■
Microsoft.Web.Services3.Security.Tokens.EncryptedKeyToken
■
Microsoft.Web.Services3.Security.Tokens.IssuedToken
■
Microsoft.Web.Services3.Security.Tokens.UsernameToken
■
Microsoft.Web.Services3.Security.Tokens.X509SecurityToken
While an in-depth discussion about the strengths and weaknesses of each type of token is beyond the scope of this discussion, the WSE 3.0 documentation file pro vides a good bit of background about each token’s usage and benefits or weaknesses. To be used with the custom security policy assertion that was introduced in the pre vious section, “Creating a Custom Policy Assertion,” you’ll use the UsernameToken. A UsernameToken object contains a user name and/or a password. When a Web service receives a request, the user name and password values can be retrieved and compared against a store, such as a database or Windows Active Directory (or whatever other mechanism one chooses to implement), to determine whether the request is valid. The major benefit of using UsernameToken is its simplicity. To sign and encrypt a mes sage with a SecurityToken, the mechanism is fairly simple. In the context of WSE 3.0, we’re adding code to the Web service client’s custom output filter, the overridden SecureMessage method of the ClientFilterOut class: 1. Create a token type, such as UsernameToken. 2. Add credentials to the token. 3. Decide which PasswordOption value is appropriate. 4. Add the UsernameToken to the SOAP security header. 5. Sign the SOAP message using an XML signature object. 6. Create a new EncryptedData object, passing in the UsernameToken. 7. Add the EncryptedData object to the collection of signatures and encryption keys in the security context, as shown in the following code: 'VB ' Create token for signing and encryption
Lesson 2: Custom Policies
423
Dim userToken As New UsernameToken(parentAssertion.UserName, _
parentAssertion.Password, PasswordOption.SendPlainText)
' Add token to SOAP security header
security.Tokens.Add(userToken)
' Sign SOAP message using an XML signature object
Dim mesSignature As New MessageSignature(userToken)
security.Elements.Add(mesSignature)
' Encrypt message body
Dim encData As New EncryptedData(userToken)
' Add encrypted data to collection of signatures and
' encryption keys
security.Elements.Add(encData)
//C# // Create token for signing and encryption
UsernameToken userToken = new UsernameToken(parentAssertion.UserName,
parentAssertion.Password, PasswordOption.SendPlainText);
// Add token to SOAP security header
security.Tokens.Add(userToken);
// Sign SOAP message using an XML signature object
MessageSignature mesSignature = new MessageSignature(userToken);
security.Elements.Add(mesSignature);
// Encrypt message body
EncryptedData encData = new EncryptedData(userToken);
// Add encrypted data to collection of signatures and
// encryption keys
security.Elements.Add(encData);
Although the code doesn’t specify which type of authentication to use (that is, Win dows or custom), both types can be used with the UsernameToken. However, if you want custom authentication, that requires creating a class deriving from the User nameTokenManager and overriding the AuthenticateToken method to authenticate against a custom data store such as a database. The derived class must then be regis tered in the Web.config file for the Web service using the Type attribute of the element. Otherwise, the code is straightforward, and the only tricky part of the process is choosing the correct PasswordOption value to use. A brief discus
424
Chapter 10
WSE Security
sion of each PasswordOption value is provided in Table 10-1. Table 10-1 Table 10-1 The PasswordOption Enumeration
Value
Description
SendHashed
The password is included and the SHA-1 algorithm is used to hash it. To use this value, you must provide a class that inher its from the UsernameTokenManager class, which automati cally calls an overridden AuthenticateToken method to handle authentication.
SendNone
The password is not included in the transmission of the mes sage; however, it is used to sign the message and must be pro vided to allow the message to be decrypted.
SendPlainText
The password is sent in plain text. This option can be useful, but because it is transmitted in plain text, it has limited appli cability. It is only suitable for scenarios where a certificate is used to encrypt the UsernameToken value, or if the transport channel has been secured using Secure Sockets Layer (SSL).
Decryption Depending on the level of security the application requires, additional encryption or decryption might be desirable; however, the built-in functionality is quite powerful and more than suitable for most commercial applications. The way you’ve signed and encrypted the messages, assuming you have used valid Windows account credentials, means that the message is automatically authenticated and decrypted, because WSE 3.0 has a built-in UsernameTokenManager. If you don’t pass Windows account credentials, you must implement a custom Microsoft .Web.Services3.UsernameTokenManager class. The UsernameTokenManager class repre sents a security token manager for security tokens of type UsernameToken. There’s a token manager class for each of the security token classes described at the beginning of the previous section, “Encryption with a Security Token.”
Using a Security Token Issuing Service If instead of creating your own security tokens, you want to have the tokens created for you automatically when required, WSE 3.0 provides this capability. The SOAP message sender obtains a security token from the security token service and then uses
Lesson 2: Custom Policies
425
the token to digitally sign a message exchange. To request a token from the token ser vice, which must be trusted by the recipient of the message exchange, the SOAP mes sage sender sends the first SOAP message, also known as a Request Security Token (RST) message. The RST must be signed for the security token service to verify that the SOAP message sender is authorized to request the security token. Once the signa ture has been verified, authenticated, and authorized, a SOAP message, known as a Request Security Token Response (RSTR) message, which contains the security token, is returned to the requester. The issued security token can then be used for message exchanges until the token expires. WSE 3.0 provides a very elegant mechanism for issuing tokens, the SecurityContextTokenService class. For this class to handle token requests, the SOAP header needs to meet the following conditions: ■
The header must contain an element.
■
The value of the element must be set to http://schemas.xmlsoap.org/ws/ 2004/04/security/trust/RST/SCT.
CAUTION
SecurityTokenService base class for non-SecurityContextToken security tokens
If the application employs the use of any tokens that are not SecurityContextToken security tokens, the SecurityTokenService class must be used as a base class from which your custom token class inherits.
Although the SecurityContextTokenService class has many members, the primary method used in the context of issuing tokens is the IssueSecurityContextTokenRequest. This method has two overloads that accept a different object as a parameter, either RequestSecurityToken or RequestSecurityTokenResponse. The distinction regarding when to use which parameter is provided in Table 10-2. Table 10-2 Table 10-2 IssueSecurityContextTokenRequest Overloads
Parameter
Explanation
RequestSecurityToken
This object should be used whenever a security token request is processed within a ele ment. Provided the request is valid, a security token is returned.
426
Chapter 10
WSE Security
Table 10-2 Table 10-2 IssueSecurityContextTokenRequest Overloads
Parameter
Explanation
RequestSecurityTokenResponse
This object should be used whenever a security token request is received within a element.
The following code shows a sample invocation using a SecurityContextTokenService object as the parameter to the IssueSecurityContextTokenRequest method: 'VB Public Sub RequestSecurityTokenDemo() Dim ServiceInstance As New SecurityContextTokenService() Dim BaseToken As New RequestSecurityToken("TokenTypeHere") ServiceInstance.IssueSecurityContextTokenRequest(BaseToken) End Sub //C# public void RequestSecurityTokenDemo() { SecurityContextTokenService ServiceInstance = new SecurityContextTokenService(); RequestSecurityToken BaseToken = new RequestSecurityToken("TokenTypeHere"); ServiceInstance.IssueSecurityContextTokenRequest(BaseToken); }
The second overloaded IssueSecurityContextTokenRequest method accepts a RequestSecurityTokenResponse object and is ultimately invoked in the same manner: 'VB Public Sub RequestSecurityTokenResponseDemo() Dim ServiceInstance As New SecurityContextTokenService() Dim BaseTokenResponse As New RequestSecurityTokenResponse() ServiceInstance.IssueSecurityContextTokenRequest(BaseTokenResponse) End Sub //C# public void RequestSecurityTokenResponseDemo(){ SecurityContextTokenService ServiceInstance = new SecurityContextTokenService(); RequestSecurityTokenResponse BaseTokenResponse = new RequestSecurityTokenResponse(); ServiceInstance.IssueSecurityContextTokenRequest(BaseTokenResponse); }
In both cases, you create a RequestSecurityToken or a RequestSecurityTokenResponse object and use it as the parameter for the IssueSecurityContextTokenResponse method
Lesson 2: Custom Policies
427
call. This call in turn handles the token request and issues a token based on the object.
Lab 2: Signing and Encrypting a Message Exchange Using Custom Policy In this lab, you have an existing Web service, but you need to create a custom policy, and a Web service consumer. The latter signs and encrypts a message using the cus tom policy and sends it to the Web service, which uses the same policy to verify and decrypt the message. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. 1. Copy the solution named CustomPolicyLab, including all the files and projects from the Code folder on the companion CD. This solution, which exists in both Visual Basic and C#, contains a sample Web service named CustomPolicyWS with the infamous HelloWorld Web method. WSE 3.0 has been enabled on the Web service and a policy file with no policies defined has been created and ref erenced from the Web.config file. 2. Add a new Class Library project named CustomPolicyAssertion to the solution, and rename Class1.vb or Class1.cs to CustomSecurityAssertion.vb or CustomSecurityAssertion.cs. 3. To the CustomPolicyAssertion project, add a reference to the Microsoft.Web .Services3.dll and System.Web.Services.dll assemblies. 4. Replace the code in the CustomSecurityAssertion.vb or CustomSecurityAsser tion.cs file with the following code, to create your custom security policy assertion: 'VB Imports Imports Imports Imports Imports Imports
System.Xml
System.Collections.Generic
Microsoft.Web.Services3
Microsoft.Web.Services3.Design
Microsoft.Web.Services3.Security
Microsoft.Web.Services3.Security.Tokens
Public Class CustomSecurityAssertion
Inherits SecurityPolicyAssertion
Private usn As String = Nothing
Private pwd As String = Nothing
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal userName As String, ByVal password As String)
428
Chapter 10
WSE Security
MyBase.New() Me.usn = userName
Me.pwd = password
End Sub Public ReadOnly Property UserName() As String Get Return Me.usn End Get End Property Public ReadOnly Property Password() As String Get Return Me.pwd End Get End Property Public Overrides Function CreateClientInputFilter(ByVal context As _ Microsoft.Web.Services3.Design.FilterCreationContext) As _ Microsoft.Web.Services3.SoapFilter Return New ClientFilterIn(Me) End Function Public Overrides Function CreateClientOutputFilter(ByVal context As _ Microsoft.Web.Services3.Design.FilterCreationContext) As _ Microsoft.Web.Services3.SoapFilter Return New ClientFilterOut(Me) End Function Public Overrides Function CreateServiceInputFilter(ByVal context As _ Microsoft.Web.Services3.Design.FilterCreationContext) As _ Microsoft.Web.Services3.SoapFilter Return New ServiceFilterIn(Me) End Function Public Overrides Function CreateServiceOutputFilter(ByVal context As _ Microsoft.Web.Services3.Design.FilterCreationContext) As _ Microsoft.Web.Services3.SoapFilter Return Nothing
End Function
Public Overrides Sub ReadXml(ByVal reader As XmlReader, _ ByVal extensions As IDictionary(Of String, Type)) ' Was an XmlReader object passed? If reader Is Nothing Then Throw New ArgumentNullException( _ "XmlReader parameter cannot be null")
Lesson 2: Custom Policies
End If ' Was an IDictionary object passed? If extensions Is Nothing Then Throw New ArgumentNullException( _ "IDictionary parameter cannot be null") End If Dim elementName As String = Nothing ' Loop through extensions For Each extensionName As String In extensions.Keys ' Our extension? If extensions(extensionName) Is _ GetType(CustomSecurityAssertion) Then ' Save the extension name elementName = extensionName Exit For
End If
Next
' Read the start element
reader.ReadStartElement(elementName)
' Do all other processing here, including retrieving ' custom elements and attributes if needed End Sub End Class Public Class ServiceFilterIn Inherits ReceiveSecurityFilter Private parentAssertion As CustomSecurityAssertion = Nothing Public Sub New(ByVal parentAssertion As CustomSecurityAssertion) MyBase.New(parentAssertion.ServiceActor, False) Me.parentAssertion = parentAssertion
End Sub
Public Overrides Sub ValidateMessageSecurity(ByVal envelope As _ Microsoft.Web.Services3.SoapEnvelope, _ ByVal security As Microsoft.Web.Services3.Security.Security) Dim signed As Boolean = False
Dim encrypted As Boolean = False
If Not security Is Nothing Then ' Loop through collection of signatures and encryption keys For Each element As ISecurityElement In security.Elements ' Is this an XML signature?
429
430
Chapter 10
WSE Security
If TypeOf element Is MessageSignature Then ' Cast to XML Signature element Dim signature As MessageSignature = _ CType(element, MessageSignature) ' Is body signed? If ((SignatureOptions.IncludeSoapBody And _ signature.SignatureOptions) = _ SignatureOptions.IncludeSoapBody) Then ' Is message signed with UsernameToken? If (TypeOf signature.SigningToken _ Is UsernameToken) Then signed = True End If End If End If ' Is this encrypted data? If TypeOf element Is EncryptedData Then Dim encData As EncryptedData = _ CType(element, EncryptedData) encrypted = True End If Next End If If Not signed OrElse Not encrypted Then Throw New SecurityFault("Message does not contain " & _ “UsernameToken and/or message isn't encrypted") End If End Sub End Class Public Class ServiceFilterOut
Inherits SendSecurityFilter
Public Sub New(ByVal parentAssertion As CustomSecurityAssertion) MyBase.New(parentAssertion.ServiceActor, False) End Sub Public Overrides Sub SecureMessage(ByVal envelope As _ Microsoft.Web.Services3.SoapEnvelope, _ ByVal security As Microsoft.Web.Services3.Security.Security) End Sub
End Class
Public Class ClientFilterIn
Inherits ReceiveSecurityFilter
Public Sub New(ByVal parentAssertion As CustomSecurityAssertion)
Lesson 2: Custom Policies
MyBase.New(parentAssertion.ServiceActor, True)
End Sub
Public Overrides Sub ValidateMessageSecurity(ByVal envelope As _
Microsoft.Web.Services3.SoapEnvelope, _
ByVal security As Microsoft.Web.Services3.Security.Security)
End Sub End Class Public Class ClientFilterOut Inherits SendSecurityFilter Private parentAssertion As CustomSecurityAssertion = Nothing Public Sub New(ByVal parentAssertion As CustomSecurityAssertion)
MyBase.New(parentAssertion.ServiceActor, True)
Me.parentAssertion = parentAssertion
End Sub
Public Overrides Sub SecureMessage(ByVal envelope As _
Microsoft.Web.Services3.SoapEnvelope, _
ByVal security As Microsoft.Web.Services3.Security.Security)
' Create token for signing and encryption
Dim userToken As New UsernameToken(parentAssertion.UserName, _
parentAssertion.Password, PasswordOption.SendPlainText)
' Add token to SOAP security header
security.Tokens.Add(userToken)
' Sign SOAP message using an XML signature object
Dim mesSignature As New MessageSignature(userToken)
security.Elements.Add(mesSignature)
' Encrypt message body
Dim encData As New EncryptedData(userToken)
' Add encrypted data to collection of signatures and
' encryption keys
security.Elements.Add(encData)
End Sub End Class //C# using using using using using using using
System;
System.Data;
System.Configuration;
System.Collections.Generic;
System.Web;
System.Xml;
Microsoft.Web.Services3;
431
432
Chapter 10
WSE Security
using Microsoft.Web.Services3.Design; using Microsoft.Web.Services3.Security; using Microsoft.Web.Services3.Security.Tokens; namespace MSLearning.TK70_529.Chapter10.Lesson2 { /// /// Summary description for CustomSecurityAssertion /// public class CustomSecurityAssertion : SecurityPolicyAssertion { private string usn = null; private string pwd = null; public CustomSecurityAssertion() : base()
{
}
public CustomSecurityAssertion(string userName, string password) : base() { this.usn = userName; this.pwd = password; } public string UserName {
get
{
return this.usn; } } public string Password {
get
{
return this.pwd; } } public override SoapFilter CreateClientInputFilter(FilterCreationContext context) { return new ClientFilterIn(this); } public override SoapFilter CreateClientOutputFilter(FilterCreationContext context) { return new ClientFilterOut(this);
Lesson 2: Custom Policies
} public override SoapFilter CreateServiceInputFilter(FilterCreationContext context) { return new ServiceFilterIn(this); } public override SoapFilter CreateServiceOutputFilter(FilterCreationContext context) { return null; } public override void ReadXml(XmlReader reader,
IDictionary extensions)
{
// Was an XmlReader object passed?
if (reader == null)
throw new ArgumentNullException( "XmlReader parameter cannot be null"); // Was an IDictionary object passed? if (extensions == null) throw new ArgumentNullException( "IDictionary parameter cannot be null"); string elementName = null; // Loop through extensions foreach (string extensionName in extensions.Keys) { // Our extension? if (extensions[extensionName] == typeof(CustomSecurityAssertion)) { // Save the extension name elementName = extensionName; break;
}
}
// Read the start element
reader.ReadStartElement(elementName);
// Do all other processing here, including retrieving // custom elements and attributes if needed } } public class ServiceFilterIn : ReceiveSecurityFilter { CustomSecurityAssertion parentAssertion = null;
433
434
Chapter 10
WSE Security
public ServiceFilterIn(CustomSecurityAssertion parentAssertion) : base(parentAssertion.ServiceActor, false) { this.parentAssertion = parentAssertion; } public override void ValidateMessageSecurity( Microsoft.Web.Services3.SoapEnvelope envelope, Microsoft.Web.Services3.Security.Security security) { bool signed = false; bool encrypted = false; if (security != null) { // Loop through collection of signatures // and encryption keys foreach (ISecurityElement element in security.Elements) { // Is this an XML signature? if (element is MessageSignature) { // Cast to XML Signature element MessageSignature signature = (MessageSignature) element; // Is body signed? if ((SignatureOptions.IncludeSoapBody & signature.SignatureOptions) == SignatureOptions.IncludeSoapBody) { // Is message signed with UsernameToken? if (signature.SigningToken is UsernameToken) { signed = true; } } } // Is this encrypted data? if (element is EncryptedData) { EncryptedData encData = (EncryptedData) element; encrypted = true; } } } if (!signed || !encrypted) throw new SecurityFault("Message does not contain " + "UsernameToken and/or message isn't encrypted");
Lesson 2: Custom Policies
} } public class ServiceFilterOut : SendSecurityFilter { public ServiceFilterOut(CustomSecurityAssertion parentAssertion) : base(parentAssertion.ServiceActor, false)
{
}
public override void SecureMessage(
Microsoft.Web.Services3.SoapEnvelope envelope,
Microsoft.Web.Services3.Security.Security security)
{ } } public class ClientFilterIn : ReceiveSecurityFilter { public ClientFilterIn(CustomSecurityAssertion parentAssertion) : base(parentAssertion.ServiceActor, true)
{
}
public override void ValidateMessageSecurity(
Microsoft.Web.Services3.SoapEnvelope envelope,
Microsoft.Web.Services3.Security.Security security)
{ } } public class ClientFilterOut : SendSecurityFilter { private CustomSecurityAssertion parentAssertion = null; public ClientFilterOut(CustomSecurityAssertion parentAssertion)
: base(parentAssertion.ServiceActor, true)
{
this.parentAssertion = parentAssertion;
}
public override void SecureMessage(
Microsoft.Web.Services3.SoapEnvelope envelope,
Microsoft.Web.Services3.Security.Security security)
{
// Create token for signing and encryption
UsernameToken userToken = new
UsernameToken(parentAssertion.UserName, parentAssertion.Password, PasswordOption.SendPlainText); // Add token to SOAP security header
security.Tokens.Add(userToken);
435
436
Chapter 10
WSE Security
// Sign SOAP message using an XML signature object MessageSignature mesSignature = new MessageSignature(userToken); security.Elements.Add(mesSignature); // Encrypt message body
EncryptedData encData = new EncryptedData(userToken);
// Add encrypted data to collection of signatures and
// encryption keys
security.Elements.Add(encData);
}
}
}
5. Set the Root namespace (Visual Basic) or Default namespace (C#) for the Cus tomPolicyAssertion project to MSLearning.TK70_529.Chapter10.Lesson2. 6. To the CustomPolicyWS project, add a reference to the CustomPolicyAssertion project. 7. Add a new Visual Basic or C# Console Application project named CustomPolicyClient to the solution. 8. Set the CustomPolicyClient project as the startup project, and WSE 3.0 enable the project. 9. Set the Root namespace (Visual Basic) or Default namespace (C#) for the Cus tomPolicyClient project to MSLearning.TK70_529.Chapter10.Lesson2. 10. To the CustomPolicyClient project add a reference to the CustomPolicyAssertion project. 11. Using the Add Web Reference dialog box, add a reference to the CustomPolicyWS Web service and name it CustomPolicyWS. 12. In the CustomPolicyClient project, replace the code in Module1.vb or Pro gram.cs with the following code (make sure you replace the user name and pass word for the UsernameToken with values that are valid on your system): 'VB Imports Imports Imports Imports
Microsoft.Web.Services3
Microsoft.Web.Services3.Design
Microsoft.Web.Services3.Security
Microsoft.Web.Services3.Security.Tokens
Module Module1 Sub Main() Dim custPolicyWS As New CustomPolicyWS.ServiceWse() ' Create custom assertion Dim assertion As New _ CustomSecurityAssertion("WindowsUserName", _
Lesson 2: Custom Policies
437
"password") ' Create policy and add custom assertion
Dim pol As New Policy()
pol.Assertions.Add(assertion)
' Apply policy to Web service proxy
custPolicyWS.SetPolicy(pol)
' Call Web service method and display result
Console.WriteLine(custPolicyWS.HelloWorld())
End Sub
End Module
//C# using using using using using using using
System;
System.Collections.Generic;
System.Text;
Microsoft.Web.Services3;
Microsoft.Web.Services3.Design;
Microsoft.Web.Services3.Security;
Microsoft.Web.Services3.Security.Tokens;
namespace MSLearning.TK70_529.Chapter10.Lesson2 {
class Program
{
static void Main(string[] args)
{
CustomPolicyWS.ServiceWse custPolicyWS = new CustomPolicyWS.ServiceWse();
// Create custom assertion CustomSecurityAssertion assertion = new CustomSecurityAssertion("WindowsUserName", "password"); // Create policy and add custom assertion Policy pol = new Policy(); pol.Assertions.Add(assertion); // Apply policy to Web service proxy
custPolicyWS.SetPolicy(pol);
// Call Web service method and display result
Console.WriteLine(custPolicyWS.HelloWorld());
}
}
}
13. In the CustomPolicyWS project, replace the code in WSE with the following code, which creates a new policy named servicePolicy, which uses the CustomSe
438
Chapter 10
WSE Security
curityAssertion class you just created:
14. In the CustomPolicyWS project, apply the Policy attribute to the Service class: 'VB _ _ _ _ Public Class Service
Inherits System.Web.Services.WebService
//C# [WebService(Namespace = "http://contoso.com/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[Policy("servicePolicy")]
public class Service : System.Web.Services.WebService
{
15. Run the solution and watch the output “Hello World” in the console window. Press Enter to exit the console window. 16. To watch the signed and encrypted messages going from the client to the Web service, in Solution Explorer, right-click the CustomPolicyWS project, and click WSE Settings 3.0. Ignore the warning about not being able to load the CustomPolicyAssertion assembly. 17. In the WseConfigEditor3 tool, click the Diagnostics tab, select Enable Message Trace, and then click OK. 18. Rerun the solution and again watch the output “Hello World” in the console window. Press Enter to exit the console window. 19. In Solution Explorer, right-click the CustomPolicyWS project, and click Refresh Folder. Notice how two new files, InputTrace.webinfo and OutputTrace.webinfo, have been created by the WSE 3.0 infrastructure. Open the InputTrace.webinfo file and see the contents of the incoming messages.
Lesson 2: Custom Policies
439
Lesson Summary ■
The identity of a recipient or sender as well as verification that a transmission has not been tampered with can be accomplished through digital signatures.
■
The process of obscuring a message so that unauthorized parties cannot view it is known as encryption. Digital signatures and encrypted messages can be used separately, but used together they provide a very powerful framework for secur ing data.
■
The process of taking an encrypted message and making it readable is known as decryption.
■
To prevent unauthorized parties from viewing the contents of a message, that message must be encrypted.
■
By default, WSE 3.0 encryption only encrypts the body part of the message. If sensitive data is contained in the message outside of the body, the data will be accessible to unauthorized third parties unless it too is encrypted.
■
Symmetric encryption algorithms, also known as private key algorithms, neces sitate both the sender and the recipient having access to the same private key. Because this key must be distributed to at least two parties to be useful, symmet ric algorithms are generally considered less secure than their asymmetric coun terparts.
■
Asymmetric algorithms, also known as public key algorithms, only require the sender to have access to a public key. The sender uses this public key to encrypt messages that can only be decrypted by someone with access to the correspond ing private key.
■
The IssueSecurityContextTokenRequest method of the SecurityContextTokenService class can be used to address both the and the elements of a request.
■
A custom policy assertion should be created when one of the turnkey security assertions doesn’t meet the needs of your application.
■
A custom policy assertion must be derived from either the PolicyAssertion or Secu rityPolicyAssertion class.
440
Chapter 10
WSE Security
Lesson Review You can use the following questions to test your knowledge of the information in Les son 2, “Custom Policies.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. What is the primary difference between symmetric and asymmetric algorithms? (Choose the best answer.) A. Symmetric algorithms are considered more secure than their asymmetric counterparts. B. Asymmetric algorithms are considered more secure than their symmetric counterparts. C. Symmetric algorithms are the primary mechanism involved in creating dig ital signatures and have little value in terms of data hiding. D. Asymmetric algorithms are the primary mechanism involved in creating digital signatures and have little value in terms of data hiding. 2. By default, which element of a request is protected when a message is encrypted? (Choose the best answer.) A. The body part B. The header part of the request C. The element, if and only if it’s present D. The element, if and only if it’s present 3. Which of the following statements are true with respect to encrypting and decrypting SOAP requests? (Select all that apply.) A. The header or the body parts can be encrypted. B. A UsernameToken can be used. C. Certificates can be used to handle the key exchange. D. Both asymmetric and symmetric algorithms can be used.
Lesson 3: Message Filters
441
Lesson 3: Message Filters In Lesson 2 you were introduced to filters in the SOAP messaging processing pipeline. In this lesson we’ll take a slightly different approach to message filters. However, for the WSE 3.0 message filters, you can refer back to Lesson 2, and Lab 2 in particular. Message filters warrant a section of their own precisely because they are one of the major “breaking changes” between WSE 2.0 and WSE 3.0. In short, filters allow an application to define precisely how outgoing messages should be handled (secured, for instance) as well as specifying how incoming messages should be handled. After this lesson, you will be able to: ■
Migrate a WSE 2.0 InputFilter or OutputFilter to WSE 3.0.
■
Create a new filter.
■
Create a policy assertion to handle the filter.
Estimated lesson time: 35 minutes
The way filters are implemented has changed drastically, as evidenced by the follow ing text from the “How to: Migrate Custom Filters to Custom Policy Assertions” article in the MSDN documentation (http://msdn.microsoft.com/library/default.asp?url=/ library/en-us/wse3.0/html/4ffe5169-ec97-47ae-9df8-0bca49421777.asp): “Custom fil ters that were written for previous versions of the Web Services Enhancements for Microsoft .NET (WSE) must be migrated to custom policy assertions for WSE 3.0. Pol icy assertions are different from WSE 2.0 custom filters in that custom filters filter only a portion of the SOAP messages exchanged between a client and a Web ser vice….” By simply examining WseConfigEditor tool for both WSE 2.0 and WSE 3.0 (shown in Figure 10-5), the differences should be apparent (in case they aren’t, notice that the tabs are different).
442
Chapter 10
WSE Security
Figure 10-5 WseConfigEditor tabs in WSE 2.0 and WSE 3.0
Message Filters Overview Message filters help to enhance security in Web services by controlling what is trans mitted in the message and what is allowed to be received. They serve, as their name implies, to filter data. The process of how they work is fairly straightforward and is illustrated in Figure 10-6. Output Filters
Input Filters WSE Enabled Web Service
Soap Message Sender Input Filters
Output Filters
Figure 10-6 Message filter flow
Because you can create your own filters, there is no reasonable limit on how much granularity you can employ with security. Essentially, if you want it to happen, it can. The more complex the rules, the more complex the code, but this correlation is cer tainly not uncommon and no more burdensome than anything else.
Lesson 3: Message Filters
443
Implementation and Migration If an application is being migrated from WSE 2.0 to WSE 3.0, a few steps must be fol lowed. If an application is starting from scratch, then the same steps need to be fol lowed with one exception: In the latter case, new classes are actually created, whereas in the former case, existing definitions need to be changed. In both cases, the process consists of the following: 1. If this is a new application, create a class that derives from SoapFilter, ReceiveSe curityFilter, or SendSecurityFilter. If the application is being ported, change the existing filter’s base class to one of the same three classes. Table 10-3 describes what purpose each of these base classes serves. Table 10-3 The SoapFilter, ReceiveSecurityFilter, and SendSecurityFilter Classes
Class Name
Usage
SoapFilter
Base class from which all WSE 3.0 filters must derive; replaces input and output filters in WSE 2.0; used pri marily for filters that do not secure SOAP messages.
ReceiveSecurityFilter
Base class from which all filters that handle receiving and securing SOAP messages should derive.
SendSecurityFilter
Base class from which all filters that handle the trans mission and securing of SOAP messages should derive.
2. In new applications, specify a particular method to handle processing. For migrations, change the return type of the ProcessMessage method, depending on what tasks the filter is to perform. Table 10-4 illustrates when each should be used. Table 10-4 Add Methods or Specify Return Types for ProcessMessage Method
Action
Usage
Filters that do not secure SOAP messages
Use SoapFilterResult as the return type. Use SoapFilterResult.Continue to ensure backward compatibility.
Output filters that secure SOAP messages
Add a SecureMessage method.
444
Chapter 10
WSE Security
Table 10-4 Add Methods or Specify Return Types for ProcessMessage Method
Action
Usage
Input filters that secure SOAP messages
Add a ValidateMessageSecurity method.
3. Create a custom security assertion. 4. In the assertion created in step 3, return a new instance of the custom filter in the corresponding method (CreateClientInputFilter, CreateClientOutputFilter, CreateServiceInputFilter, or CreateServiceOutputFilter).
Using the SoapFilter Class At the risk of being repetitive, please note that classes derived from SoapFilter are not to be used if securing a SOAP message is necessary (see Lesson 2 for more on filtering with a custom security policy assertion). Hence, the primary usage of such classes will be to take advantage of exisiting functionality—for instance, tracing. With that said, all that’s necessary to implement a class that derives from SoapFilter is the inheritance definition and the overriding of the ProcessMessage method. (The key areas mentioned in the preceding steps are shown in bold.) 'VB Imports Imports Imports Imports
Microsoft.Web.Services3 Microsoft.Web.Services3.Design Microsoft.Web.Services3.Security Microsoft.Web.Services3.Security.Tokens
Public Class DemoSoapFilter Inherits SoapFilter Public Overrides Function ProcessMessage( _
ByVal envelope As SoapEnvelope) As SoapFilterResult
Me.Trace("Message you want to trace") Return SoapFilterResult.Continue End Function End Class //C# using using using using
Microsoft.Web.Services3; Microsoft.Web.Services3.Design; Microsoft.Web.Services3.Security; Microsoft.Web.Services3.Security.Tokens;
public class DemoSoapFilter : SoapFilter { public DemoSoapFilter() {}
Lesson 3: Message Filters
445
public override SoapFilterResult ProcessMessage(
SoapEnvelope envelope)
{
this.Trace("Message I want to trace");
return SoapFilterResult.Continue;
} }
Using the ReceiveSecurityFilter Class For classes that will handle the reception of messages that are secured, the ReceiveSe curityFilter class must be used. The class that you derive from must provide additional constructor logic to match the base class’s definition. (The key areas mentioned in the preceding steps are shown in bold.) 'VB Imports Imports Imports Imports
Microsoft.Web.Services3 Microsoft.Web.Services3.Design Microsoft.Web.Services3.Security Microsoft.Web.Services3.Security.Tokens
Public Class DemoReceiveSecurityFilter Inherits ReceiveSecurityFilter Public Overrides Sub ValidateMessageSecurity( _
ByVal envelope As SoapEnvelope, ByVal security As Security)
'Implement validation here End Sub Public Sub New(ByVal serviceActor As String, ByVal isClient As Boolean)
MyBase.New(serviceActor, isClient)
End Sub
End Class //C# using System;
using Microsoft.Web.Services3;
using Microsoft.Web.Services3.Design;
using Microsoft.Web.Services3.Security;
using Microsoft.Web.Services3.Security.Tokens;
public class DemoReceiveSecurityFilter : ReceiveSecurityFilter
{
public DemoReceiveSecurityFilter(String serviceActor, Boolean isClient) : base(serviceActor, isClient)
{
}
public override void ValidateMessageSecurity(SoapEnvelope envelope,
Security security)
{
//Implement validation here } }
446
Chapter 10
WSE Security
Using the SendSecurityFilter Class For classes that will handle transmission of messages that are secured, the SendSecu rityFilter class must be used. The class that you derive from must provide additional constructor logic to match the base class’s definition. (The key areas mentioned in the preceding steps are shown in bold.) 'VB Imports Imports Imports Imports
Microsoft.Web.Services3
Microsoft.Web.Services3.Design
Microsoft.Web.Services3.Security
Microsoft.Web.Services3.Security.Tokens
Public Class DemoSendSecurityFilter Inherits SendSecurityFilter Public Overrides Sub SecureMessage(ByVal envelope As SoapEnvelope, _ ByVal security As Security) 'Process message here End Sub Public Sub New(ByVal serviceActor As String, ByVal isClient As Boolean) MyBase.New(serviceActor, isClient) End Sub End Class //C# using using using using using
System;
Microsoft.Web.Services3;
Microsoft.Web.Services3.Design;
Microsoft.Web.Services3.Security;
Microsoft.Web.Services3.Security.Tokens;
public class DemoSendSecurityFilter : SendSecurityFilter
{
public DemoSendSecurityFilter(String serviceActor, Boolean isClient) : base(serviceActor, isClient)
{
}
public override void SecureMessage(SoapEnvelope envelope, { //Implement validation here }
Security security)
}
Lab 3: Creating a Custom Filter In this lab, you create a custom message filter. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder.
Lesson 3: Message Filters
1. Open Visual Studio 2005 and create a blank solution named DemoFilter. 2. Add a new Visual Basic or C# Web service project named DemoFilter. 3. Add a reference to the Microsoft.Web.Services3.dll assembly. 4. Add a class named DemoSoapFilter to the project in the App_Code folder. 5. Modify the class so that it looks like the following: 'VB Imports Imports Imports Imports Imports Imports Imports Imports Imports Imports
Microsoft.VisualBasic
System
System.Collections.Generic
System.Text
System.Xml
System.Security.Cryptography.X509Certificates
Microsoft.Web.Services3
Microsoft.Web.Services3.Design
Microsoft.Web.Services3.Security
Microsoft.Web.Services3.Security.Tokens
Public Class DemoSoapFilter
Inherits SoapFilter
Public Overrides Function ProcessMessage( _
ByVal envelope As SoapEnvelope) _
As Microsoft.Web.Services3.SoapFilterResult
Me.Trace("Message you want to trace")
Return SoapFilterResult.Continue
End Function
End Class
//C# using using using using using using using using using using
System.Web;
System.Web.Services;
System.Web.Services.Protocols;
System.Collections.Generic;
System.Text;
System.Security.Cryptography.X509Certificates;
Microsoft.Web.Services3;
Microsoft.Web.Services3.Design;
Microsoft.Web.Services3.Security;
Microsoft.Web.Services3.Security.Tokens;
public class DemoSoapFilter : SoapFilter { public DemoSoapFilter() {} public override SoapFilterResult ProcessMessage(SoapEnvelope envelope) { this.Trace("Message I want to trace");
447
448
Chapter 10
WSE Security
return SoapFilterResult.Continue;
}
}
6. Compile the application and verify that it completes. At this point, you now should have a working filter that can be consumed in a Web service.
Lesson Summary ■
Message filters represent a major area of security functionality in WSE but intro duce breaking changes between versions 2.0 and 3.0.
■
Classes that are to be used as filters in WSE 3.0 must inherit from the SoapFilter, ReceiveSecurityFilter, or SendSecurityFilter classes.
■
The SoapFilter class should be used to process tasks that do not involve securing SOAP messages.
■
Classes derived from SoapFilter must implement the ProcessMessage method and return SoapFilterResult.Continue to ensure that pre-existing client code can still call it.
■
Classes derived from the SendSecurityFilter class should implement the SecureMessage method.
■
Classes derived from the ReceiveSecurityFilter class should implement the Vali dateMessageSecurity method.
■
Because filters are no longer just added, in order to work in WSE 3.0, a custom policy assertion must be created to handle their implementation.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 3, “Message Filters.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which of the following are valid base classes from which custom filters can be derived? (Select all that apply.) A. SoapFilter
Lesson 3: Message Filters
449
B. ReceiveSecurityFilter C. SendSecurityFilter D. ProcessMessage 2. Classes that derive from the SendSecurityFilter class should implement which of the following methods? (Choose the best answer.) A. ProcessMessage B. ValidateMessageSecurity C. SecureMessage D. CreateClientOutputFilter
450
Chapter 10 Review
Chapter Review To further practice and reinforce the skills you learned in this chapter, you can per form the following tasks: ■
Review the chapter summary.
■
Review the list of key terms introduced in this chapter.
■
Complete the case scenarios. These scenarios set up real-world situations involv ing the topics of this chapter and ask you to create a solution.
■
Complete the suggested practices.
■
Take a practice test.
Chapter Summary ■
The identity of a recipient or sender as well as verification that a transmission has not been tampered with can be accomplished through digital signatures.
■
The process of obscuring a message so that unauthorized parties cannot view it is known as encryption. Digital signatures and encrypted messages can be used separately, but used together they provide a very powerful framework for secur ing data.
■
The process of taking an encrypted message and making it readable is known as decryption.
■
To prevent unauthorized parties from viewing the contents of a message, a mes sage must be encrypted.
■
By default, WSE 3.0 encryption only encrypts the body part of the message. If sensitive data is contained in the message outside of the body, it will be accessi ble to unauthorized third parties unless it too is encrypted.
■
Symmetric encryption algorithms, also known as private key algorithms, neces sitate both the sender and the recipient having access to the same private key. Because this key must be distributed to at least two parties to be useful, symmet ric algorithms are generally considered less secure than their asymmetric coun terparts.
■
Asymmetric algorithms, also known as public key algorithms, only require the sender to have access to a public key. The sender uses this public key to encrypt messages that can only be decrypted by someone with access to its matching pri vate key.
Chapter 10 Review
451
■
The IssueSecurityContextTokenRequest method of the SecurityContextTokenService class can be used to address both the and the elements of a request.
■
Configuration files can be used to control and manipulate virtually every aspect of custom policies.
■
The element can be used to specify endpoints of the application.
■
At a minimum, a policy expression file must contain a element, an element, and a element (which must contain , , and elements). In addition, each custom policy must be added.
■
The fileName attribute of the element of a Web.config file can be used to point the application to a custom policy expression file.
■
Three components can be configured under the element: the , the , and the .
■
Multiple policies can be added to an application by specifying additional elements.
■
If multiple policies are configured, the WseConfigEditor3 can be used to edit the configuration file manually.
■
If a policy is added manually, great care must be taken to ensure that element names are spelled correctly.
■
If multiple policies are configured, each must contain a unique name.
■
Assertions must be added to policy files to be of any use.
■
The WseConfigEditor3 can be used to manage most elements of WSE configu ration.
■
In previous versions of WSE, the WseConfigEditor was only available through individual projects. Starting with WSE 3.0, the tool can be used within Visual Studio 2005 or as a stand-alone application.
■
The WseConfigEditor3 can be used to manage general settings, security settings, policy settings, routing, token issuing, diagnostics, and messaging.
■
If a custom policy was implemented for an application, the Policy tab can be used to manage it. This feature provides access to a Security Settings Wizard and has the abilty to provide very intricate policy configuration.
■
There are four authentication modes available with custom policy: Anonymous, Windows, Certificate, and Username.
452
Chapter 10 Review
■
Anonymous authentication is the simplest mode to use but is considered by many to be the least secure of the available modes.
■
Windows authentication uses Kerberos (a proprietary Microsoft format) and provides both a high level of security and reduced administration complexity.
■
By default, two types of applications can be configured: client application and services.
Key Terms Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book. ■
asymmetric algorithm
■
digital signature
■
integrity
■
policy assertion
■
policy expression
■
policy framework
■
symmetric algorithm
■
timestamp
Case Scenario In the following case scenario you apply what you’ve learned about how to use secu rity features provided by WSE 3.0. You can find answers to these questions in the “Answers” section at the end of this book.
Case Scenario 1: How Secure Is Secure Enough? Having strong and flexibile security policies in place is often critical in Web services. After all, the Web service is an entry point into your corporation and, in most instances, one that needs to be secure. In the following scenario, making these deter minations is addressed.
Chapter 10 Review
453
Interviews Following is a list of company personnel interviewed and their statements: “What we’ve accomplished with respect to adding func tionality for users has been nothing short of amazing. Business has been great and not only is it increasing quickly, but our support calls are down and our cus tomers have never been happier. The main two risks we face, from my perspec tive, are service disruptions and a security breach. I want to do whatever is necessary to ensure neither happens. My main objective right now is to deter mine how good we’re doing in terms of security and start providing documenta tion for our customers about our security story.”
■ IT Department Head
“We’ve gotten through most of our technical challenges and our customers are delighted. Because we’re ahead of the curve, we really need to focus on product documentation so we can show our customers our infrastructure and hopefully use what we’re doing right to our advantage. I want to start developing a series of white papers with an emphasis on our security configuration. Is WSE 3.0 something that is recognized in the industry? Are the algorithms held in high regard in the academic and professional communities? What specifics can we highlight?”
■ Lead Business Analyst
“Financially, things couldn’t be better and we really need to take advantage of that. Sales are up, costs are down, and our existing customers are very pleased. Like my IT Department Head mentioned, the two things we have nightmares about are service interruptions and security breaches. I’ve directed a large share of resources toward documenting our service infrastructure. We need to do the same with respect to security.”
■ CIO
Questions Answer the following questions for your manager: 1. Are the security features provided by WSE held in high regard in the software community? 2. Within the parameters of what we do, how does WSE contribute to our security story? 3. Can white papers be developed about the security story that highlight the fea tures of WSE and our specific implementation?
454
Chapter 10 Review
Suggested Practices To successfully master the objectives covered in this chapter, complete the following tasks: For this task, you should complete at least Practices 1 and 2. You can do Practice 3 for a more in-depth understanding of WSE 3.0. ■ Practice 1
Create a Web service that uses WSE 3.0 that can decrypt encrypted
messages.
Create a new Web service consumer that uses WSE 3.0. Design this application
so that it can encrypt a message body using either an asymmetric or symmetric
algorithm.
Write down three or more desired objectives that you might have for a custom security policy. After analyzing these objectives, create a custom policy that implements these features. After the policy is created, build a Web service consumer and a Web service client that can use the policy you just built. To ensure that you understand how this task is accomplished, create the policy manually, and then use the WseConfigEditor3 tool to re-create the policy.
■ Practice 2
Create a custom policy and add it to a Web Service. From there, spec ify a filter or filters for the service to use via the policy file.
■ Practice 3
Take a Practice Test The practice tests on this book’s companion CD offer many options. For example, you can test yourself on just one exam objective, or you can test yourself on all the 70-529 certification exam content. You can set up the test so that it closely simulates the expe rience of taking a certification exam, or you can set it up in study mode so that you can look at the correct answers and explanations after you answer each question. MORE INFO
Practice tests
For details about all the practice test options available, see the “How to Use the Practice Tests” sec tion in this book’s Introduction.
Chapter 11
Messaging and Routing
This chapter focuses on the ways you can expand what you do with Web Services Enhancements (WSE) through messaging and routing. WSE Messaging allows you to add capabilities to traditional Web services such as one-way or two-way (bidirec tional) messaging and attachments. You can also use WSE messaging to specify that the Web service use an alternative communication protocol and increase the effi ciency of message transfers. A WSE router can be implemented for the purpose of directing requests to the appro priate servers, Web services, or both, including different versions of these. Once the router application is created, it can be configured to route requests for one or more Web services. Instead of accessing the Web services directly, outside clients make requests to the WSE router. Lesson 3 focuses on how to add and verify security credentials. In Chapter 9, “Web Services Enhancements 3.0 in Client and Server Applications,” you were introduced to using digital signatures to ensure message integrity. When using an intermediary such as a WSE router, you need to add credentials using WSE instead of assigning them to the proxy as you did in Chapter 9. In this chapter you learn how to add the security credentials and then verify those credentials.
Exam objectives in this chapter: ■
■
Implement WSE SOAP messaging. ❑
Select a protocol.
❑
Implement one-way SOAP messaging.
❑
Implement bidirectional SOAP messaging.
❑
Send the attachments from a Web service application.
❑
Receive attachments in a client application.
Route SOAP messages by using a WSE router. ❑
Create a WSE router application.
❑
Configure a WSE router application.
❑
Configure a referral cache for routing. 455
456
Chapter 11
■
Messaging and Routing
Add and verify security credentials. ❑
Add security credentials to a SOAP message.
❑
Verify security credentials.
Lessons in this chapter: ■
Lesson 1: How to Configure WSE Messaging . . . . . . . . . . . . . . . . . . . . . . . . . . 457
■
Lesson 2: How to Create a WSE Router . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
■
Lesson 3: How to Add and Verify Security Credentials . . . . . . . . . . . . . . . . . . 494
Before You Begin To complete the lessons in this chapter, you must have ■
A computer that meets or exceeds the minimum hardware requirements listed in the Introduction of the book.
■
Microsoft Visual Studio 2005 and the .NET Framework 2.0.
■
Knowledge of programming in Microsoft Visual Basic or C# and XML.
■
Downloaded and installed the Web Services Enhancements (WSE) 3.0 Service Pack 2 for Microsoft .NET.
■
Read and completed the labs from Chapter 1, “Creating an XML Web Service,” regarding creating an Extensible Markup Language (XML) Web service.
■
Read and completed the labs from Chapter 9 regarding WSE in client and server applications.
Lesson 1: How to Configure WSE Messaging
457
Lesson 1: How to Configure WSE Messaging This lesson explores how to use WSE Messaging to extend the functionality of your Web services. Specifically, this lesson will first cover how you can specify a communi cation protocol. It will also cover how you can implement both one-way and two-way (bidirectional) messaging. Finally, it will cover how to use Message Transmission Optimization Mechanism (MTOM) to send and receive attachments. After this lesson, you will be able to: ■
Select the protocol.
■
Implement one-way SOAP messaging.
■
Implement two-way SOAP messaging.
■
Send attachments from the XML Web service application.
■
Receive attachments in the client application.
Estimated lesson time: 45 minutes
WSE Soap Messaging One of the cornerstones of Service Oriented Architecture (SOA) is the notion of mes sages. Messaging has become increasingly popular with developers recently and is a critical component of Microsoft’s future direction with respect to how applications and services communicate with each other. Messaging in WSE 3.0 has some major enhancements over previous versions in that it can be accomplished exclusively through the Transmission Control Protocol (TCP) negating the need for an HTTP server. In addition, data being transmitted does not have to be sent as XML and can instead be sent in a binary format. Because XML greatly increases the data size and because TCP is considered a more efficient protocol, by combining the two, a tremen dous amount of efficiency can be achieved. MORE INFO
WSE Hands On Lab for messaging
MSDN offers a Hands On Lab that directly concerns WSE Messaging. It covers both basic and advanced scenarios and would be an excellent aid in preparation for the exam. The Hands On Lab is available for download at http://www.microsoft.com/downloads/details.aspx?familyid=0e5491c1 8bde-4fff-88c4-8e3dc102fad6&displaylang=en.
458
Chapter 11
Messaging and Routing
Selecting a Communication Protocol WSE 3.0–enabled Web services can make use of both the traditional HTTP protocol as well as the faster TCP protocol. Which one you select is really a matter of where and how the Web service should be accessed. HTTP traffic travels through most firewalls, whereas TCP traffic generally requires one or more specific ports to be open. As a gen eral rule of thumb, you should consider HTTP in Internet scenarios, whereas closed intranet scenarios could make use of TCP, especially if performance is important. If you intend to use TCP as the communication protocol, you should make sure you evaluate and mitigate potential security risks. Whichever protocol you choose, you need to register a SOAP receiver. To register a receiver with HTTP, you will need to add an element in the sec tion of the web.config for your Web service. First, though, make sure your Web service project is WSE 3.0 enabled. The following code is an example of what this entry would look like, if you register a class derived from the SoapReceiver class, named DemoReceiver in the SoapReceivers namespace located in the assembly DemoRe ceiver.dll:
NOTE
What is an .ashx file?
A file with an .ashx extension is called an ASP.NET Web handler file, and it is used to handle raw HTTP requests received by ASP.NET.
If you prefer to utilize TCP as the communication protocol, then you will need to cre ate an endpoint for the receiver and use the soap:tcp prefix in the Uniform Resource Indicator (URI) that is specified. You will then register the endpoint with the instance of the custom receiver class. The code for this will go inside the application that is receiving the request, and we will see another example of this code later in this lesson. For now you just need to know that an instance of a class derived from SoapReceiver will need to be created and then added to the SoapReceivers collection. The following is an example of what this code would look like: 'VB Dim receiver As New DemoReceiver()
Dim destination As New Uri("soap.tcp://" & _
System.Net.Dns.GetHostName() & "/DemoReceiver")
Lesson 1: How to Configure WSE Messaging
459
Dim demoEndPoint As New EndpointReference(destination)
SoapReceivers.Add(demoEndPoint, receiver)
//C# DemoReceiver receiver = new DemoReceiver();
Uri destination = new Uri(
"soap.tcp://" + System.Net.Dns.GetHostName() + "/DemoReceiver" ); EndpointReference demoEndPoint = new EndpointReference(destination); SoapReceivers.Add(demoEndPoint, receiver);
The first parameter of the Uri class has been set to soap.tcp.//, because you’re using TCP in this case. Replace with http:// if you want to use HTTP as the communication protocol.
Implementing One-Way Messaging This section will cover how to implement one-way messaging, in which a message is sent from one machine to another, but communication can only occur in one direc tion. A similar concept was covered in Chapter 2, “Extending XML Services with SOAP Formatting, Custom Headers, and Extensions,” but in this case we are imple menting one-way messaging using WSE. In WSE 3.0, this is facilitated through the SoapSender (see Table 11-1) and SoapReceiver (see Table 11-2) classes. Table 11-1 Properties of the SoapSender Class
Name
Description
Channel
Retrieves the output channel for the sender.
Destination
Specifies the SOAP message receiver endpoint, which is where you indicate the communication protocol and specify the URI to be accessed.
Pipeline
Retrieves or sets the object that allows custom filtering of messages.
RequireMtom
Retrieves or sets a Boolean value that indicates whether the message must be encoded with MTOM. This is used when adding an attachment.
SessionState
Retrieves or sets an object that represents state information for a SOAP message.
460
Chapter 11
Messaging and Routing
Specifying a Sender To implement an application that can send a message, you will need to create a new SoapSender object. To do this, you will first import the required namespaces, such as in the following code: 'VB Imports Imports Imports Imports //C# using using using using using using using
System.Xml
Microsoft.Web.Services3
Microsoft.Web.Services3.Addressing
Microsoft.Web.Services3.Messaging
System;
System.Collections.Generic;
System.Text;
System.Xml;
Microsoft.Web.Services3;
Microsoft.Web.Services3.Addressing;
Microsoft.Web.Services3.Messaging;
You will then need to specify a destination URI, which represents where the message will be sent. You will also need to specify an endpoint that corresponds to the desti nation URI, such as in the following code: 'VB Dim destinationUri As New Uri( _
"soap.tcp://" & _
System.Net.Dns.GetHostName() & "/DemoReceiver")
Dim destination As New EndpointReference(destinationUri) //C# Uri destinationUri = new Uri("soap.tcp://" +
System.Net.Dns.GetHostName() & "/DemoReceiver");
EndpointReference destination = new EndpointReference(destinationUri);
Finally, you will need to create new SoapSender and SoapEnvelope objects that are used to send the message. The following code demonstrates what this should look like: 'VB Dim sender As New SoapSender(destination)
Dim demoEnvelope As New SoapEnvelope()
demoEnvelope.Context.Addressing.Action = _
New Action("soap.tcp://" & _ System.Net.Dns.GetHostName() & "/DemoReceiver")
demoEnvelope.SetBodyObject("This is a test")
sender.Send(demoEnvelope)
//C# SoapSender sender = new SoapSender(destination);
Lesson 1: How to Configure WSE Messaging
461
SoapEnvelope demoEnvelope = new SoapEnvelope(); demoEnvelope.Context.Addressing.Action = new Action("soap.tcp://" + System.Net.Dns.GetHostName() & "/DemoReceiver"); demoEnvelope.SetBodyObject("This is a test"); sender.Send(demoEnvelope);
Specifying a Receiver Both the TCP and HTTP protocols can be used to receive messages. In both cases, a class needs to be created to handle the received message, and, in the latter case, a con figuration file entry must be made to process HTTP requests. Table 11-2 Properties of the SoapReceiver Class
Name
Description
Actor
Retrieves the actor name for the SoapReceiver, which is returned as a URI.
DispatchModel
Retrieves the dispatch model for the SoapReceiver.
Pipeline
Retrieves or sets the object that allows custom filtering of messages.
To create a class that can handle receiving the messages, use the following steps: 1. Create a new handler class that inherits from SoapReceiver (keep in mind that the System.Web, System.Web.Services, and Microsoft.Web.Services3 namespaces need to be referenced). 2. Override the Receive method, and process the message parameter according to your requirements.
Depending on the exact approach you are using to create a given remote object,
the invocation might look something like the following:
'VB Imports Imports Imports Imports
System.Xml
Microsoft.Web.Services3
Microsoft.Web.Services3.Addressing
Microsoft.Web.Services3.Messaging
Namespace DemoSoapReceivers
Public Class DemoReceiver
Inherits SoapReceiver
Protected Overloads Overrides Sub Receive( _
462
Chapter 11
Messaging and Routing
ByVal message As SoapEnvelope) Console.WriteLine("Message Received")
End Sub
End Class
End Namespace
//C# using using using using
System.Xml;
Microsoft.Web.Services3;
Microsoft.Web.Services3.Addressing;
Microsoft.Web.Services3.Messaging;
namespace DemoSoapReceivers {
class DemoReceiver : SoapReceiver
{
protected override void Receive(SoapEnvelope message) { Console.WriteLine("Message Received"); } }
}
This additional statement must be added to the configuration file for the Web service to process HTTP requests:
With the SoapReceiver object in place, one last piece of legwork needs to be performed for an application to consume the class: An instance of the class needs to be declared and added to the Receivers collection of the application. 'VB Dim destination As Uri = New Uri("soap.tcp://" & _ System.Net.Dns.GetHostName() & "/DemoReceiver")
Dim demoEndPoint As New EndpointReference(destination)
SoapReceivers.Add(demoEndPoint, GetType(DemoSoapReceivers.DemoReceiver))
//C# Uri destination = new Uri( "soap.tcp://" + System.Net.Dns.GetHostName() + "/DemoReceiver");
EndpointReference demoEndPoint = new EndpointReference(destination);
SoapReceivers.Add(demoEndPoint, typeof(DemoSoapReceivers.DemoReceiver));
Lesson 1: How to Configure WSE Messaging
463
Implementing Two-Way (Bidirectional) Messaging As was the case with one-way messaging, two-way messaging is very straightforward. However, in this case, you will use the SoapClient (see Table 11-3) and SoapService (see Table 11-4) classes to derive from in the same way the SoapSender and SoapReceiver were used. Keep in mind that these two classes can also be used for one-way messag ing as well as two-way messaging, which is covered. Table 11-3 Properties of the SoapClient Class
Name
Description
Channel
Retrieves the output channel for the sender.
DefaultTimeout
Specifies a time in milliseconds to wait for a response from a SendRequestResponse operation for all clients. The default value is 120000, or 2 minutes.
Destination
Specifies the SOAP message receiver endpoint, which is where you indicate the communication protocol and spec ify the URI to be accessed.
Pipeline
Retrieves or sets the object that allows custom filtering of messages.
RequireMtom
Retrieves or sets a Boolean value that indicates whether the message must be encoded with MTOM. This is used when adding an attachment.
SessionState
Retrieves or sets an object that represents state informa tion for a SOAP message.
SoapVersion
Specifies a time in milliseconds to wait for a response from a SendRequestResponse operation for this specific client request. The default value is 120000, or 2 minutes.
Specifying a Sender The process for creating a sender in a two-way scenario is very similar to the one-way scenario. The following steps are all that’s necessary: 1. Create a class that inherits from SoapClient.
464
Chapter 11
Messaging and Routing
2. Add a method that accepts a SoapEnvelope as a parameter and calls the base class’s SendRequestResponse method passing in that envelope. 3. Add a constructor that accepts an EndpointReference as a parameter and pass this parameter to the base class’s constructor. 'VB Namespace Sender
Public Class TwoWaySender
Inherits SoapClient
_ Public Function RequestResponseMethod( _ ByVal envelope As SoapEnvelope) As SoapEnvelope Return MyBase.SendRequestResponse("RequestResponseMethod", _ envelope) End Function Public Sub New(ByVal destination As EndpointReference) MyBase.New(destination) End Sub End Class End Namespace //C# namespace Sender {
class TwoWaySender : SoapClient
{
[SoapMethod("RequestResponseMethod")]
public SoapEnvelope RequestResponseMethod(SoapEnvelope envelope)
{
return base.SendRequestResponse("RequestResponseMethod", envelope); } public TwoWaySender(EndpointReference destination) : base(destination) { } }
}
Specifying a Receiver To create a receiver class, a virtually identical approach to the one used in the SoapRe ceiver example in the one-way section will be used. The following steps are all that’s necessary:
Lesson 1: How to Configure WSE Messaging
465
1. Create a class that inherits from the SoapService class (see Table 11-4). Table 11-4 Properties of the SoapService Class
Name
Description
Actor
Retrieves the actor name for the inherited instance of SoapReceiver, which is returned as a URI.
DispatchModel
Retrieves the dispatch model for the inherited instance of SoapReceiver.
Pipeline
Retrieves or sets the object that allows custom filter ing of messages.
2. Add a RequestResponseMethod to handle incoming responses. 3. Decorate this method with the SoapMethod attribute. 'VB Imports Imports Imports Imports
System.Xml
Microsoft.Web.Services3
Microsoft.Web.Services3.Addressing
Microsoft.Web.Services3.Messaging
Namespace DemoReceiver
Public Class DemoService
Inherits SoapService
_ Public Function RequestResponseMethod( _ ByVal envelope As SoapEnvelope) As SoapEnvelope Dim response As SoapEnvelope = New SoapEnvelope response.SetBodyObject("Response") Return response
End Function
End Class
End Namespace
//C# using using using using
System.Xml;
Microsoft.Web.Services3;
Microsoft.Web.Services3.Addressing;
Microsoft.Web.Services3.Messaging;
namespace DemoReceiver {
class DemoService : SoapService
{
466
Chapter 11
Messaging and Routing
[SoapMethod("RequestResponseMethod")]
public SoapEnvelope RequestResponseMethod(SoapEnvelope envelope)
{
SoapEnvelope response = new SoapEnvelope(); response.SetBodyObject("Response"); return response;
}
}
}
The approach with respect to TCP and HTTP follows the same procedures that are used in one-way calls.
Sending Attachments One of the other major changes in WSE 3.0 is the way attachments are handled. In previous versions, Dynamic Internet Message Encapsulation (DIME) was the primary vehicle used for handling attachments. In 3.0, MTOM is the primary vehicle for send ing binary data. Before selling the benefits, it’s worth taking a step back to address attachments in gen eral. Consider a situation where you wanted to send the string literal “WSE 3.0”. If you used XML as a wrapper to send this message, at a minimum you’d have the overhead associated with “WSE 3.0” and you’d have the tag that encapsulated this. Depending on the name used, the opening and closing tags could easily consume more resources than the message itself. Considering that one of the major benefits of using XML is the human-readable form it can take, it should be obvious that most descriptive names will also be resource-intensive. Now picture the same situation where something non trivial were being sent, say, a directory of Excel files. Obviously, the size of the extra data due to XML serialization could easily get quite large. So instead of encoding the request, attachments that have a smaller footprint can be used, which is the primary benefit of MTOM. However, performance isn’t the only benefit. The binary data that is being sent as an attachment can be digitally signed, which enables message verification to take place. If attachments weren’t signable, then their usefulness would be limited. After all, how much value would you assign to an attachment you couldn’t verify? In some cases, you might have no problem with this risk, but in others, it may be a deal-breaker. For those situations, digital signatures can be used. To enable a Web service to use MTOM, perform the following steps: 1. Right-click on the project, and choose the WSE Settings 3.0 menu item.
Lesson 1: How to Configure WSE Messaging
467
2. When the WSE 3.0 dialog box (see Figure 11-1) appears, on the General tab, select the Enable This Project for Web Services Enhancements and Enable Microsoft Web Services Enhancement Soap Protocol Factory check boxes. 3. Select the Messaging tab and set the Server Mode to Optional, Always, or Never.
Figure 11-1 The General tab from the WSE Settings 3.0 context menu
This will add a element to the configuration file and will include an element inside of it. Assuming you selected Always as the Server Mode, the web.config file should look like the following (the important sections are shown in bold): q21
468
Chapter 11
Messaging and Routing
The differences between the three modes are shown in the following table: Mode Name
Behavior
Optional
WSE Messages will be processed whether or not they are MTOM encoded. If any SOAP faults are encountered, they are MTOM encoded.
Always
All incoming and outgoing messages are MTOM encoded. If an incoming message is received that is not MTOM encoded, a SOAP fault will be returned.
Never
All incoming and outgoing messages are not MTOM encoded and if one comes in, a SOAP fault will be returned.
Once this is in place, the only thing that needs to be done to return binary data is to create a method with a return type of byte array. The following code demonstrates how this can be accomplished: 'VB _ Public Function GetFile(ByVal filename As String) As Byte() ' Return file here End Function //C# [WebMethod]
public byte[] GetFile(string fileName)
{
// Return file here }
Now, to build a consumer for this service, all of the work has already been performed. All that needs to be done to consume this service is to create a client application that references the Web service and calls the method that is returning the MTOM object. 'VB Sub Main()
Lesson 1: How to Configure WSE Messaging
469
Dim DemoService As New Service()
Dim ArrayValue As Byte() = DemoService.GetFile("FileName.extension")
End Sub
//C# static void Main(string[] args) {
Service DemoService = new Service();
Byte[] DemoArray = DemoService.GetFile("Filename.extension");
}
Lab 1: Data Transfer with MTOM In this lab, you’ll enable a Web service to use MTOM to send a binary file. Addition ally, you’ll create a MTOM-enabled client to receive the binary file. For the sake of sim plicity, a small image file will be transferred, but to fully test the benefits of MTOM, you can substitute the image file provided on the accompanying CD with a larger file of your choice. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. �
Exercise 1: Creating an MTOM enabled Web service
In this exercise, you’ll create a Web service by enabling MTOM; the service will return a JPEG image as an attachment. 1. Open Visual Studio 2005. 2. Select File, New, Web Site. 3. In the New Web Site dialog box, select ASP.NET Web Service as the template, and name the project file MTOMDemoService. 4. In Solution Explorer, right-click the MTOMDemoService project and choose Add Reference. On the .NET tab, add references to the System.Web.Services.dll assembly and click OK. 5. In Solution Explorer, right-click the MTOMDemoService project, and click WSE Settings 3.0. 6. In the WSE Settings dialog box, on the General tab, select the Enable This Project For Web Services Enhancements and Enable Microsoft Web Services Enhancement SOAP Protocol Factory check boxes. 7. On the Messaging tab (see Figure 11-2), click Optional in the Server Mode dropdown list box. Click OK to finish the configuration process.
470
Chapter 11
Messaging and Routing
Figure 11-2 Messaging tab, which is visible when enabling the project for Web service enhancement
8. Open the application’s web.config file and verify that the and elements have been added. Further ver ify that the Messaging element was added and that it contains an element with the serverMode attribute set to Optional. If the settings are configured cor rectly, the web.config file should resemble the following (the critical fragments are shown in bold, and the default comment sections have been removed for the sake of readability): 'VB
Lesson 1: How to Configure WSE Messaging
//C#
NOTE
Setting the Server Mode option
The default value for Server Mode is optional. Unless this setting is changed to a value other than the default, it will not appear in the web.config file. In order for the tag to appear, you must first set the value to something other than optional.
9. Open the App_Code folder and open the Service.vb or Service.cs file. 10. Import the System.IO namespace. 11. Delete the HelloWorld method, which is created by default when you create a Web service. 12. Create a method named GetVistaImage with a return type of byte array as shown here: 'VB _ Public Function GetVistaImage() As Byte()
Dim ResponseImage As Byte()
Dim ImagePath As String = AppDomain.CurrentDomain.BaseDirectory & _
"App_Data\VistaImage.jpg"
ResponseImage = File.ReadAllBytes(ImagePath)
Return ResponseImage
End Function
//C# [WebMethod(Description = "Get Demo Image")]
public Byte[] GetVistaImage()
Lesson 1: How to Configure WSE Messaging
473
{
Byte[] ResponseImage;
String ImagePath = AppDomain.CurrentDomain.BaseDirectory +
@"App_Data\VistaImage.jpg";
ResponseImage = File.ReadAllBytes(ImagePath);
return ResponseImage;
}
13. In Solution Explorer, right-click the project’s App_Data folder, and click Add Existing Item. 14. Navigate to the Images subdirectory of the Chapter 11 folder on the accompany ing CD, select the VistaImage.jpg file, and click OK to add the image to the project. 15. Rename the Web service to MtomDemoService. Change the Web service’s namespace to http://contoso.com and add the namespaces shown below. The full file definition should look like the following (again, the critical portions are shown in bold): 'VB Imports Imports Imports Imports Imports Imports Imports
System.IO
System.Web
System.Web.Services
System.Web.Services.Protocols
Microsoft.Web.Services3
Microsoft.Web.Services3.Addressing
Microsoft.Web.Services3.Messaging
_
_
_
Public Class MtomDemoService
Inherits System.Web.Services.WebService
_ Public Function GetVistaImage() As Byte() Dim responseImage As Byte() Dim imagePath As String = AppDomain.CurrentDomain.BaseDirectory & _ "App_Data\VistaImage.jpg"
responseImage = File.ReadAllBytes(imagePath)
Return responseImage
End Function
End Class //C#
using using using using
System;
System.IO;
System.Web;
System.Web.Services;
474
Chapter 11
using using using using
Messaging and Routing
System.Web.Services.Protocols;
Microsoft.Web.Services3;
Microsoft.Web.Services3.Addressing;
Microsoft.Web.Services3.Messaging;
[WebService(Namespace = "http://contoso.com/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class MtomDemoService : System.Web.Services.WebService { public MtomDemoService ()
{
}
[WebMethod(Description = "Get Demo Image")] public Byte[ ] GetVistaImage(){ Byte[ ] responseImage; String imagePath = AppDomain.CurrentDomain.BaseDirectory + @"App_Data\VistaImage.jpg"; responseImage = File.ReadAllBytes(imagePath); return responseImage;
} }
16. Compile the application, run it, and select the GetVistaImage hyperlink. You’ll be directed to the GetVistaImage demo page. Click Invoke and verify that the namespace is http://contoso.com and that the return value is enclosed in a element. If done correctly, the screen should resemble Figure 11-3.
Figure 11-3 Debug Confirmation of call to the GetVistaImage method
Lesson 1: How to Configure WSE Messaging
�
475
Exercise 2: Create a Winforms client to consume the MTOM attachment
In this exercise, you’ll create a Web service client that displays a JPEG image returned as an MTOM attachment. 1. Add a new C# or Visual Basic 2005 Windows Application project to the MTOMDemoService solution, and name it MTOMDemoClient. 2. In Solution Explorer, right-click the MTOMDemoClient project and click Add Reference. On the .NET tab, add a reference to the System.Web.Services.dll assem bly and click OK. 3. In Solution Explorer, right-click the MTOMDemoClient project and click Add Web Reference. 4. Click Web Services In This Solution. 5. Select the MTOMDemoService Web service. 6. Name the reference MtomDemoService, and click Add Reference to close the dialog box. 7. In Solution Explorer, right-click the MTOMDemoClient project and click WSE Settings 3.0. 8. On the General tab, select the Enable This Project for Web Services Enhance ments check box. 9. On the Messaging tab, click Always in the Server Mode drop-down list box, click On in the Client Mode drop-down list box, and then click OK to close the dialog box. 10. Open the app.config file that was created by the WseConfigEditor tool and ver ify that each of the elements mentioned in steps 3 and 4 have been added. If con figured correctly, the app.config file should look like the following: 'VB
http://localhost:2520/MTOMDemoService/MtomDemoService.asmx
//C#
Lesson 1: How to Configure WSE Messaging
477
http://localhost:2517/MTOMDemoService/MtomDemoService.asmx
11. Select Form1 in Design view and drag a PictureBox control onto the designer. Name the PictureBox vistaImagePictureBox. Set the size to 150 x 113. 12. Drag a button onto the designer. Name the button getImageButton and set its Text property to Get Image. 13. Double-click getImageButton and insert the following code (the full class defini tion is shown): 'VB Imports Imports Imports Imports Imports Imports Imports
System.IO
System.Web
System.Web.Services
System.Web.Services.Protocols
Microsoft.Web.Services3
Microsoft.Web.Services3.Addressing
Microsoft.Web.Services3.Messaging
Public Class Form1 Private Sub getImageButton_Click(ByVal sender As System.Object, _
478
Chapter 11
Messaging and Routing
ByVal e As EventArgs) Handles getImageButton.Click Dim mtomProxy As New MtomDemoService
Dim imageStream As Byte()
imageStream = mtomProxy.GetVistaImage
Dim memStream As New MemoryStream(imageStream)
vistaImagePictureBox.Image = _
System.Drawing.Image.FromStream(memStream)
End Sub
End Class //C# using using using using using using using using using using using using using using
System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Text;
System.IO;
System.Windows.Forms;
System.Web;
System.Web.Services;
System.Web.Services.Protocols;
Microsoft.Web.Services3;
Microsoft.Web.Services3.Addressing;
Microsoft.Web.Services3.Messaging;
namespace MTOMDemoClient {
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void getImageButton_Click(object sender, EventArgs e)
{
MtomDemoService.MtomDemoService mtomProxy =
new MtomDemoService.MtomDemoService();
Byte[] imageStream = mtomProxy.GetVistaImage();
MemoryStream
memStream = new MemoryStream(imageStream);
vistaImagePictureBox.Image =
System.Drawing.Image.FromStream(memStream);
} }
}
14. In Solution Explorer, right-click the MTOMDemoClient project, and click Set as Startup Project. 15. Compile and run the application. When the form appears, click Get Image to invoke the Web service and retrieve the attachment.
Lesson 1: How to Configure WSE Messaging
479
16. If the request is processed correctly, the form should display VistaImage.jpg in the vistaImagePicture control. The form should now look like Figure 11-4.
Figure 11-4 Form appearance after call to the GetVistaImage Web method
Lesson Summary ■
WSE Messaging, available with WSE 3.0, can be used to extend the functionality available with traditional Web services.
■
WSE 3.0 allows you to specify the communication protocol. You can specify that your Web service use TCP as opposed to HTTP.
■
WSE Messaging can be used to implement one-way messaging, in which the communication occurs in both directions and uses the SoapSender and SoapReceiver classes.
■
Two-way messaging, in which the communication is allowed in both directions, uses the SoapClient and SoapService classes. These classes are similar to the SoapSender and SoapReceiver classes used for one-way messaging.
■
Attachments can be sent with SOAP messages through the use of the MTOM, which is the primary vehicle for sending binary data.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “How to Configure WSE Messaging.” The questions are also available on the companion CD if you prefer to review them in electronic form.
480
Chapter 11
Messaging and Routing
NOTE Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. What two classes are needed to facilitate one-way messaging? A. SoapClient and SoapReceiver B. SoapClient and SoapService C. SoapSender and SoapReceiver D. SoapSender and SoapService 2. What steps are needed to implement a sender for a two-way messaging scenario? (Select all that apply.) A. Create a class that inherits from SoapClient. B. Create a class that inherits from SoapReceiver. C. Add a constructor that accepts a URI as a parameter. D. Add a constructor that accepts an EndpointReference object as a parameter. 3. Identify the XML that would appear in the configuration file of a project in which the Server Mode was set to Always. A.
B.
C.
D.
Lesson 2: How to Create a WSE Router
481
Lesson 2: How to Create a WSE Router This lesson explores how to create and configure a WSE router application. You first learn what a WSE router is and then step through how to create an application using Visual Studio 2005. You then configure the WSE router application by creating a con figuration file and adding handlers for each Web service that needs to be routed. Finally, you create a referral cache, which is an XML document that contains the Uni form Resource Locators (URLs) used by the WSE router application. After this lesson, you will be able to: ■
Create a WSE router application.
■
Configure the WSE router application.
■
Configure a referral cache for routing.
Estimated lesson time: 45 minutes
What Is a WSE Router? The WSE router serves as an intermediary between the outside world and your Web services. It is used to direct requests to the appropriate server and confirm that the requester is valid. Imagine a security guard who protects a building. The security guard stands at the door and checks the credentials of any people trying to enter the building. Once it is determined that the person is allowed to enter, the security guard might also act as a guide and direct the person to the right office within that building. A WSE router acts as a security guard for your Web services. It represents a gate between your Web services and the outside world. Your Web services can reside on a private network and be inaccessible to external clients. Any clients that want to use your Web services access the WSE router, which is also a Web service. Once validated, the clients are directed to the destination Web service on the private network. One of the benefits to using a WSE router is the ability to hide the details about your Web service. You can assign a virtual name to the router and the clients use this name instead of the server name on which the actual Web service resides. The client is unaware of your network’s topology. The biggest benefit to using a WSE router, how ever, is the ability to perform maintenance on servers hosting Web services without experiencing any downtime. When maintenance needs to be performed, a change is made to the configuration that routes all requests to an alternative server until the original becomes available again.
482
Chapter 11
Messaging and Routing
Real World Sara Morgan A fellow developer recently shared an experience he had working with a WSE router. He was doing consulting work for a large financial firm that hosted sev eral Web services. External clients used the Web services to access financial ser vices through handheld devices. The company decided to implement a WSE router so that they could routinely take servers down for maintenance while still providing 24/7 access to the Web services. The company made many considerations regarding security, but they failed to consider performance. The extra layer of the WSE router immediately caused a performance reduction and angered many of their customers. At periods of high demand, the servers were so overwhelmed that most customers just gave up try ing to get what they needed. Eventually, the company was able to recover, but this is just one example of how important it is to consider performance before implementing a WSE router.
Creating a WSE Router Application A WSE router application is actually just a Web service. The sole job of this Web service is to route requests for other Web services. To create a WSE router, you need to create a project using the Web service template in Visual Studio 2005. Figure 11-5 shows the New Web Site dialog box that is displayed when selecting this type of project.
Figure 11-5 Use the New Web Site dialog box to create a new ASP.NET Web service project, which is the basis for your WSE router
Lesson 2: How to Create a WSE Router
NOTE
483
Which version of WSE?
The latest version of WSE available is version 3.0. Refer to the Introduction for instructions on how to download and install this service pack add-on.
After the new project has been created, you need to add references to the following two .NET Framework components: This namespace contains the base classes needed to access WSE 3.0, which is needed to create a WSE router application.
■ Microsoft.Web.Services3
This namespace contains the base classes needed to create XML Web services. This namespace includes the WebService and WebMethodAttribute classes, which are needed when creating methods exposed within your Web service application.
■ System.Web.Services
You also need to add a new class file to your project that contains the code for the router handler. In addition to the namespaces added by default, the class file should contain Imports or using directives for the following namespaces: 'VB Imports Microsoft.Web.Services3 Imports Microsoft.Web.Services3.Messaging Imports System.Xml //C# using Microsoft.Web.Services3; using Microsoft.Web.Services3.Messaging; using System.Xml;
The class file that serves as your router handler needs to derive from the Microsoft.Web.Services3.Messaging.SoapHttpRouter class. This is accomplished by add ing the following code to the newly created class file: 'VB Public Class RouterHandler Inherits Microsoft.Web.Services3.Messaging.SoapHttpRouter //C# public class RouterHandler: Microsoft.Web.Services3.Messaging.SoapHttpRouter
Now you need to add logic to the router handler class that tells it how to route mes sages. This is a process known as content-based routing because you are routing mes sages based on their content. To accomplish this you need to create a method that overrides the ProcessRequestMessage method. The overloaded method declaration accepts the incoming Simple Object Access Protocol (SOAP) message as an input
484
Chapter 11
Messaging and Routing
parameter and return a Universal Resource Indicator (URI). The method would appear as follows: 'VB Protected Overrides Function ProcessRequestMessage(ByVal message As SoapEnvelope) As Uri ' Add some logic here to process the SOAP Message Return base.ProcessRequestMessage(message) End Function //C# protected override Uri ProcessRequestMessage(SoapEnvelope message) {
//Add some logic to process the SOAP message
return base.ProcessRequestMessage(message);
}
The logic you add inside the ProcessRequestMessage method depends on what your router needs to accomplish. If you just want to direct requests to the appropriate Web service, then some simple if..then logic is all that is needed. However, if you would like to add additional functionality in which you inspect the SOAP message and then route the message based on some complex criteria, the logic could get quite complicated. Because the WSE router is essentially a door to the outside world, make sure that door is secure, accessible, and functional. NOTE
Avoid adding too much functionality to your WSE router
If too much logic is added to the router handler, clients accessing the router could experience delays during peak usage times. To avoid this, minimize the amount of logic you add to the router handler and perform stress testing with tools such as the Microsoft Web Application Stress Tool (available as a free download from MSDN through the following URL: http://www.microsoft.com/ downloads/details.aspx?familyid=E2C0585A-062A-439E-A67D-75A89AA36495&displaylang=en).
Typically, a WSE router inspects the SOAP header to look for something that indicates how the request should be handled. It then returns a URI as a string that indicates where the request should be directed.
Configuring the WSE Router Application To configure the WSE router, you need to add one or more elements for (refer to Table 11-5). This is added to the Web.config file for your WSE router Web service. This element is used to specify that the Web service is using WSE. By adding this element you allow the WebServicesExtension class to intercept any SOAP messages sent to the Web service and process the SOAP headers within those
Lesson 2: How to Create a WSE Router
485
messages. To add the element, open your Web.config file in Solution Explorer by dou ble-clicking the file. You can then add a handler such as the following to handle all requests with an .asmx extension:
CAUTION
Warning about copying code
When following the examples in this section, make sure you remove the line breaks for the element. The line breaks were included here for readability, but if you leave them in the actual application, an error is generated.
Table 11-5 Attributes of the Element
Name
Description
Verb
Specifies the Hypertext Transfer Protocol (HTTP) operation to per form in the form of a comma-separated list or an asterisk to indi cate a wildcard value. The HTTP verbs can be values such as GET, PUT, and POST.
Path
Specifies either a path to a specific file or a wildcard reference (using an asterisk). You can use a single asterisk to represent all files, or you can use an asterisk with an extension to indicate all files with a certain extension.
Type
Specifies the class and assembly for the WSE router. The class and assembly names are comma-separated values, and you can also append additional information such as the assembly version.
In cases where you want the WSE router to handle more than one Web service, you need to add multiple elements. The following example is the configuration for a router that routes messages for two Web services named WebServiceA.asmx and WebServiceB.asmx:
Configuring a Referral Cache for Routing A referral cache is an XML-based file that contains information about where the SOAP requests need to be routed. This is a required file and you must ensure that the user account under which WSE runs has access to this file. The account that WSE uses is specified in the Machine.config file for your Web server. Look for the username attribute in the element of this file. Ensure that this account has access to the file, but restrict access to all others except the administrator. You do not want anyone else to have the ability to update this file. Name the file ReferralCache.config and place it in the root folder for your WSE router application. NOTE
Referral cache should always have a .config extension
Microsoft Internet Information Services (IIS) ensures that files with a .config extension cannot be opened in an Internet browser. Make sure that your referral cache uses the .config extension to pre vent outside sources from reading this file and learning the actual locations of your Web services.
Each time a request comes in to the WSE router, it refers to the referral cache file for routing instructions. The file itself contains URL references that point to where requests are routed. Table 11-6 shows the elements included in this file. Table 11-6 Elements Contained in the Referral Cache File
Name
Description
This is the root element for the referral cache.
This is the top element used to specify the location where a request will eventually be routed. You can have more than one of these elements or none at all.
There is one of these elements for each element. It contains an or element to specify the loca tion of the destination Web service.
Lesson 2: How to Create a WSE Router
487
Table 11-6 Elements Contained in the Referral Cache File
Name
Description
This is used when you need the URL to match exactly and the entire path for the Web service is included. This is a caseinsensitive match.
This is used when specifying only a portion of the Web ser vice name.
This is used to specify conditions for how to route requests.
This is used to specify a routing instruction that is no longer valid when the instruction being referenced is valid.
This is used in conjunction with the element and it specifies the routing instruction that will be invali dated.
This is used to specify where the request will be rerouted.
This is used in conjunction with the element and spec ifies a URI to which the request will be routed. There can be multiple elements.
This is used to specify a unique identifier that represents the routing instruction. Typically this is a globally unique identi fier (GUID).
More than likely, your referral cache file contains two URLs, which specify the loca tion exposed to the outside world and the location known only by your company. The following is a sample referral cache file that directs requests made to a Web service of Company A to the destination service located on the Company B server: http://www.companya.com/MyWebService/MyWebService.asmx
http://www.companyb.com/MyWebService/MyWebService.asmx
488
Chapter 11
Messaging and Routing
uuid:fa469956-0057-4e77-962a-81c5e292f2ae
Once the ReferralCache.config file has been created, you need to add a configuration section to the Web.config file for your WSE router. The configuration section indi cates that the router will be using WSE 3.0, and a element is used to specify the name of the referral cache file. The following is an example of what this addition to the Web.config file would look like:
Real World Sara Morgan The lab for this lesson includes code that writes a message to the Application Event Log. Typically you would not want to go through the trouble of creating a WSE router if its only function was to log the request to the Event Log. Generally a WSE router is used only if there is a specific reason you need to route messages based on something contained in the SOAP message. Otherwise the WSE router creates a lot of unnecessary overhead. Always consider alternatives before making a decision to implement a WSE router. For instance, if you want a way to log requests to your Web services, you can use a SOAP extension instead (such as what was covered in Chapter 2). Just keep in mind that by using a WSE router, you are adding one extra layer through which requests have to go, which could reduce the performance of your Web services.
Lesson 2: How to Create a WSE Router
489
Lab 2: Creating a WSE Router This lab steps you through how to create a WSE router using Visual Studio. You begin by creating a new Web service project that represents the WSE router. The lab also demonstrates how to configure the WSE router by adding an entry to the Web.config file and then creating a referral cache file that contains the locations of where requests will be routed from and to. You also add a class to the router project that overrides the ProcessRequestMessage method, intercepts the SOAP message coming from the client, and writes an entry to the Application Event Log. If you encounter a problem com pleting this lab, the completed projects are available on the companion CD in the Code folder. 1. Open Visual Studio 2005. 2. Click File, New, Web Site. 3. In the New Web Site dialog box (see Figure 11-5), select ASP.NET Web Service as the template and name the project file MyWSERouter. By default, Visual Studio saves the project in the My Documents\Visual Studio 2005\WebSites\ directory. 4. Set the language in the Language drop-down list box by selecting Visual Basic or Visual C#. By default, Visual Studio selects the language specified when you con figured the development environment. 5. A new solution named MyWSERouter is created. By default, the solution file cor responding to the project is stored in your My Documents\Visual Studio 2005\WebSites directory. The Web service project files themselves are stored in the directory specified in step 3. 6. In Solution Explorer, right-click the MyWSERouter project and click Add Refer ence. On the .NET tab, add references to the Microsoft.Web.Services3 and System .Web.Services namespaces. 7. In Solution Explorer, right-click the project and click Add New Item. Add a new Web configuration file named ReferralCache.config and click Add to add the new file to the project root. 8. Replace the code that appears in the code window with the following: http://localhost/MyWSERouter/Service.asmx
490
Chapter 11
Messaging and Routing
http://localhost/MyWSERouterWS/Service.asmx
uuid:fa469956-0057-4e77-962a-81c5e292f2ae
9. Delete the eb.config file and then right-click the project in Solution Explorer and click Add New Item. Add a new Web configuration file, and this time leave the name as the default name, Web.config. Click Add to add the new file to the project. 10. Replace the code in the code window with the following:
11. Right-click the App_Code folder and click Add New Item. Select Class as the template and name the file RouterHandler.cs or RouterHandler.vb, and then click Add. 12. In the code window, replace the entire contents of the file with the following: 'VB Imports System.Web.Services
Imports Microsoft.Web.Services3
Imports Microsoft.Web.Services3.Messaging
Lesson 2: How to Create a WSE Router
491
Imports System.Diagnostics Imports System.Xml Public Class RouterHandler Inherits Microsoft.Web.Services3.Messaging.SoapHttpRouter Protected Overrides Function ProcessRequestMessage(ByVal message As SoapEnvelope) As Uri Dim EventLog1 As EventLog = New EventLog If EventLog.SourceExists("WSERouter") Nothing Then
EventLog.CreateEventSource("WSERouter", "Application")
End If
EventLog1.Source = "WSERouter"
EventLog1.WriteEntry("Routing a request for MyWSERouterWS web service")
'Add code here that will read the SOAP message and make ' necessary changes
Return MyBase.ProcessRequestMessage(message) End Function End Class //C# using using using using using using using using using using using
System; System.Data; System.Configuration; System.Web; System.Web.Security; System.Web.UI; System.Web.UI.WebControls; System.Web.UI.WebControls.WebParts; System.Web.UI.HtmlControls; Microsoft.Web.Services3; Microsoft.Web.Services3.Messaging;
/// /// Summary description for RouterHandler /// public class RouterHandler : Microsoft.Web.Services3.Messaging.SoapHttpRouter { public RouterHandler()
{
} protected override Uri ProcessRequestMessage( SoapEnvelope message) { System.Diagnostics.EventLog EventLog1 = new System.Diagnostics.EventLog(); if (!System.Diagnostics.EventLog.SourceExists("WSERouter"))
492
Chapter 11
Messaging and Routing
{ System.Diagnostics.EventLog.CreateEventSource("WSERouter", "Application"); } EventLog1.Source = "WSERouter";
EventLog1.WriteEntry ("Routing a request for MyWSERouterWS web service");
//Add code here that will read the SOAP message and make // necessary changes
return base.ProcessRequestMessage (message);
} }
13. In Solution Explorer, right-click the solution and click Build Solution. Ensure that the message Build Succeeded appears in the bottom left-hand task bar.
Lesson Summary A WSE router serves as an intermediary between your Web services and the outside world. A router is just a Web service that receives requests and routes them to the appropriate destination Web service. A router handler class is added to this project, which contains an overridden method named ProcessRequestMessage. This method reads the incoming SOAP message, determines where it should be routed, and returns the URI. ■
The router is configured by adding a configuration file named ReferralCache .config that includes the URLs for the router and the target Web services.
■
You also add an element to the element in the Web.config file. This element is used to instruct the router which requests to handle.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 2, “How to Create a WSE Router.” The questions are also available on the com panion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
Lesson 2: How to Create a WSE Router
493
1. What namespaces should be included with your WSE router project? (Select all that apply.) A. System.Web.Services B. System.Web.Services2 C. System.Web.Services3 D. Microsoft.Web.Services3 2. Identify the correct code to configure a WSE router to accept requests for all Web service files. (Choose the best answer.) A.
B.
C.
D.
3. Which element in the referral cache file is used to specify the exact location to which a request will eventually be routed? (Choose the best answer.) A. B. C. D.
494
Chapter 11
Messaging and Routing
Lesson 3: How to Add and Verify Security Credentials This lesson explores how to add security credentials to a SOAP message. Rather than adding the security credentials using the proxy’s credentials property, you use WSE to add credentials so that the request can still pass through an intermediary. You also learn how to verify the digital signatures of the SOAP message sent by the client. After this lesson, you will be able to: ■
Add security credentials to a SOAP message.
■
Verify the security credentials sent by the client.
Estimated lesson time: 30 minutes
Why Add Security Credentials? You might be wondering why security tokens are covered here when they were dis cussed in Chapter 9. The reason is that this chapter discusses WSE routers, which serve as an intermediary. Whenever you go through an intermediary, you cannot just set the credentials of the proxy class as you saw in Chapter 9. The credentials are not passed through the intermediary and the client is not authenticated. Instead, you must use WSE to add the security credentials, which allows them to be passed through the WSE router.
Adding Security Credentials Using an X.509 Certificate To add security using WSE, you need to add a class to your WSE router that defines at least one policy. You can create two policies that allow for security between the cli ent and the router and also between the router and the Web service. In this lesson, we examine a class that uses a single policy assertion. It is concerned only about security between the client and the router. It then passes the message on to the Web service with no security. MORE INFO
Passing security through to the Web service
If you want to learn how to secure the message between the router and the Web service, refer to the sample named SecureRoutingToUltimateReceiver that is included in the sample folders for WSE 3.0. By default, this project is located at \Program Files\Microsoft WSE\v3.0\Samples\\QuickStart\Routing\SecureRoutingToUltimateReceiver.
Lesson 3: How to Add and Verify Security Credentials
495
The policy creates a custom policy assertion that uses an X.509 security token. In this case, security is applied using a set of packaged operations known as security assertions. The X.509 security token is applied using the X509SecurityToken class (refer to Table 11-7), which is used to represent an X.509 version 3 certificate. The certificate is the digital document that is used to verify that the person sending the request is legitimate. Table 11-7 Public Methods of the X509SecurityToken Class
Name
Description
Equals
Retrieves a value that indicates whether two objects are equal.
GetHashcode
Retrieves the hash code for the X.509 certificate.
GetSignedTokenXML
Retrieves XML that represents a signed token.
GetSTRTransformXML
Included only for use by WSE and is not used in your code.
GetXML
Retrieves an XML document that represents the secu rity token.
LoadXML
Used to load XML for the current security token.
Verify
Used to check the signatures of all signed parties and make sure the certificate is current.
VerifySignedTokenXML
Used to verify the signature of a token in its XML form.
The first thing you need to do is add a new class file that contains your router policies. If you add the class to the same project that was created in Lesson 2, you should already have references set to the System.Web.Services and Microsoft.Web.Services3 dynamic link libraries (DLLs), but you need to add directives for the following namespaces to this class: 'VB Imports Imports Imports Imports
Microsoft.Web.Services3 Microsoft.Web.Services3.Messaging Microsoft.Web.Services3.Design System.Security.Cryptography.X509Certificates
496
Chapter 11
//C# using using using using
Messaging and Routing
Microsoft.Web.Services3;
Microsoft.Web.Services3.Messaging;
Microsoft.Web.Services3.Design;
System.Security.Cryptography.X509Certificates;
Just like the RouterHandling class that was created in Lesson 2, the RouterPolicy class needs to derive from the HttpSoapRouter class. The following code shows what this would look like: 'VB Public Class RouterPolicy
Inherits Microsoft.Web.Services3.Messaging.SoapHttpRouter
//C# public class RouterPolicy : Microsoft.Web.Services3.Messaging.SoapHttpRouter
The next step is to add another class to the same class file you just created. This class contains the actual policy assertion, and the class inherits from Microsoft.Web .Services3.Design.Policy as in the following code: 'VB Public Class PolicyAssertion
Inherits Microsoft.Web.Services3.Design.Policy
//C# public class PolicyAssertion : Microsoft.Web.Services3.Design.Policy
You now add the code used to define the policy for messages passed between the cli ent and the WSE router. The following code creates a new assertion that is used to set what the client and service actor will be. You might recall from Chapter 2 that the actor/role attribute is included in the header element of your SOAP message. For requests exchanged between the router (service) and the client, the actor attribute must be set with a value other than an empty string. The following code also creates a token for both the router (service) and client and resides in the constructor for the PolicyAssertion class: 'VB Public Sub New()
Dim assertion As New MutualCertificate11Assertion()
assertion.ClientActor = "http://microsoft.web.services3.samples.securerouting/client" assertion.ServiceActor = "http://schemas.xmlsoap.org/soap/actor/next" assertion.ClientX509TokenProvider = New X509TokenProvider(StoreLocation.CurrentUser, StoreName.My, "CN=WSClient") assertion.ServiceX509TokenProvider = New X509TokenProvider(StoreLocation.LocalMachine,
Lesson 3: How to Add and Verify Security Credentials
497
StoreName.My, "CN=WSERouter") Me.Assertions.Add(assertion)
End Sub
//C# public PolicyAssertion() : base() { MutualCertificate11Assertion assertion = new MutualCertificate11Assertion(); assertion.ClientActor =
"http://microsoft.web.services3.samples.securerouting/client";
assertion.ServiceActor =
"http://schemas.xmlsoap.org/soap/actor/next";
assertion.ClientX509TokenProvider = new
X509TokenProvider(StoreLocation.CurrentUser,
StoreName.My, "CN=WSClient");
assertion.ServiceX509TokenProvider = new
X509TokenProvider(StoreLocation.LocalMachine,
StoreName.My, "CN=WSERouter");
this.Assertions.Add(assertion); }
In the previous code example, we defined both the serviceActor and clientActor. The serviceActor is used when messages are passed between the client and the router. The clientActor is required when additionally securing messages between the router and the Web service. NOTE
Creating a policy file
This lesson features an example in which the policy was defined in code, but you could have defined the policy in an XML-based policy file instead. You would have then returned that policy in the GetRequestPolicy method instead of returning the overridden PolicyAssertion class. The obvious benefit to using a policy file is that if the values used in your policy are likely to change, a policy file is preferred because changes will not require a code recompile.
Now you override the GetRequestPolicy method, which is used to get the policy asso ciated with the SOAP request. This code is placed in the RouterPolicy class. The method you create returns the policy assertion defined in the previous code example and looks like the following: 'VB Protected Overrides Function GetRequestPolicy() As Policy Return New PolicyAssertion() End Function
498
Chapter 11
Messaging and Routing
//C# protected override Policy GetRequestPolicy()
{
return new PolicyAssertion();
}
Other Security Tokens The previous section demonstrated how you can secure the router using an X.509 security token. This is just one security token supported by WSE 3.0. This type of security is known as message layer security because the security credentials are passed with the SOAP message. You can also use one of the following security tokens: Uses a username and password combination that is typically stored in an external database. You can also authenticate against Microsoft Active Directory directory service. Usually the password is stored as a hash of the actual password to provide extra security. It is important that the password be protected and not sent as clear text to prevent it from being compromised. To create a Username token, you use the UsernameToken class, which is part of the Microsoft.Web.Services3.Security.Tokens namespace to sign or encrypt the SOAP message. This is also message layer security.
■ UsernameToken
MORE INFO
Implementing message layer security
For more information about how to implement this type of security, refer to the Patterns and Practices document in MSDN titled “Implementing Direct Authentication with UsernameToken in WSE 3.0” and available through the following URL: http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/dnpag2/html/WSS_Ch3_ImpDirectAuth_WSE30.asp
Uses a user login and password to perform authentica tion, but the security is handled by Secure Sockets Layer (SSL) instead of a signed certificate. For many people, using SSL is a preferred method of handling security. It is known as transport layer security, and in this case the authentication credentials are passed on by the transport layer, which is SSL. You need to be aware that when you go through a WSE router using SSL, you need to create a new SSL connection between each access point. There is one SSL connection from the client to the router and another one between the router and the Web service. You also need to consider that the overhead associated with this method might cause your Web services not to scale well.
■ UsernameOverTransport
Lesson 3: How to Add and Verify Security Credentials
499
Also known as AnonymousForCertificate, this method allows any user with the server’s public key to authenticate, but the security is still handled by the server’s X.509 certificate. The client is unknown because it authenticates anonymously, so you would only want to apply this security level to noncritical services or those where the public key is only provided to a limited number of outside parties (such as company vendors or partner companies).
■ AnonymousOverX509
Verifying the Security Credential You might recall from Lesson 2 that the ReferralCache.config file was referenced by including a element in the Web.config file for the WSE router. This is just one of the elements used to configure WSE 3.0. You can also include a ele ment that includes the security settings for your WSE application. This lesson showed you how to implement a WSE router that handled security with an X.509 certificate. To verify these types of certificates, you need to include an element (refer to Table 11-8) in your WSE router’s Web.config file. Table 11-8 Attributes of the Element
Name
Description
allowTestRoot
Boolean value that indicates whether WSE allows the X.509 certificate signed by a test root to pass verification. This is used in conjunction with the verifyTrust attribute and is valid only when the attribute is set to true.
revocationMode
This attribute was included to allow for applications upgraded from WSE 2.0. It is equivalent to the AllowRevocationUrlRe trival attribute used with that version and is used to specify the algorithm used to compute the subject key for the X.509 certif icate. This attribute can be set with a value of NoCheck, Offline, or Online.
skiMode
Used to specify the algorithm that WSE 3.0 uses to compute the value of the subject key. It can be set with a value of RIC3280, IssueSerial, or ThumbprinSHA1 (default).
storeLocation
Used to specify the location where WSE stores the X.509 cer tificate. It also specifies the certificate store used during the verification process. It can be set with a value of CurrentUser or LocalMachine.
500
Chapter 11 Messaging and Routing
Table 11-8 Attributes of the Element
Name
Description
verificationMode
Indicates whether WSE looks in the Trusted People certificate store during verification. Set with a value of ChainOnly, TrustedPeopleOnly, or TrustedPeopleOrChain.
verifyTrust
Boolean value that indicates whether WSE verifies that the cer tificate was issued by a valid authority.
A typical entry in the Web.config file of a WSE router that uses an X.509 certificate would appear as follows:
Lab 3: Adding Security to Your WSE Router This lab steps you through how to add security to the WSE router created in Lab 2. You add a class to this project that is used to configure a policy file using an X.509 cer tificate. If you encounter a problem completing this lab, the completed projects are available on the companion CD in the Code folder. 1. Open Visual Studio 2005 and load the MyWSERouter project that was created in Lab 2 of this chapter. 2. Right-click the App_Code folder and click Add New Item. Select Class as the template, name the file RouterPolicy.cs or RouterPolicy.vb and then click Add. 3. Replace all the code in this class with the following: 'VB Imports Imports Imports Imports Imports
Microsoft.VisualBasic
Microsoft.Web.Services3
Microsoft.Web.Services3.Messaging
Microsoft.Web.Services3.Design
System.Security.Cryptography.X509Certificates
Public Class RouterPolicy Inherits Microsoft.Web.Services3.Messaging.SoapHttpRouter Protected Overrides Function GetRequestPolicy() As Policy Return New PolicyAssertion() End Function
Lesson 3: How to Add and Verify Security Credentials
501
End Class Public Class PolicyAssertion Inherits Microsoft.Web.Services3.Design.Policy Public Sub New() Dim assertion As New MutualCertificate11Assertion() assertion.ClientActor = "http://microsoft.web.services3.samples.securerouting/ client" assertion.ServiceActor = "http://schemas.xmlsoap.org/soap/actor/next" assertion.ClientX509TokenProvider = New X509TokenProvider(StoreLocation.CurrentUser, StoreName.My, "CN=WSE2QuickStartClient") assertion.ServiceX509TokenProvider = New X509TokenProvider(StoreLocation.LocalMachine, StoreName.My, "CN=WSE2QuickStartServer") Me.Assertions.Add(assertion) End Sub End Class //C# using using using using using
System.Web.UI.HtmlControls;
Microsoft.Web.Services3;
Microsoft.Web.Services3.Messaging;
Microsoft.Web.Services3.Design;
System.Security.Cryptography.X509Certificates;
///
/// Summary description for RouterPolicy
///
public class RouterPolicy : Microsoft.Web.Services3.Messaging.SoapHttpRouter
{ protected override Policy GetRequestPolicy() { return new PolicyAssertion();
}
} public class PolicyAssertion : Microsoft.Web.Services3.Design.Policy { public PolicyAssertion() : base() {
502
Chapter 11
Messaging and Routing
MutualCertificate11Assertion assertion = new MutualCertificate11Assertion(); assertion.ClientActor = "http://microsoft.web.services3.samples.securerouting/client"; assertion.ServiceActor = "http://schemas.xmlsoap.org/soap/actor/next"; assertion.ClientX509TokenProvider = new X509TokenProvider(StoreLocation.CurrentUser, StoreName.My, "CN=WSClient"); assertion.ServiceX509TokenProvider = new X509TokenProvider(StoreLocation.LocalMachine, StoreName.My, "CN=WSERouter"); this.Assertions.Add(assertion);
}
}
4. Right-click the Web.config file and click Open. In the code window, add the fol lowing code right below the element:
5. In Solution Explorer, right-click the solution and click Build Solution. Ensure that the message Build Succeeded appears in the bottom left-hand task bar.
Lesson Summary ■
Security credentials are handled differently for requests that pass through a WSE router. To allow for this, you need to use WSE to add the credentials. You can ver ify the security between the client and the router and then optionally between the router and the Web service.
■
If you add security using an X.509 certificate, you need to create a custom policy assertion using either code in a class file or an XML-based policy file.
■
For messages passed between the client and the router, the serviceActor attribute must be set with a value other than an empty string. Messages passed between the router and the Web service must also set the clientActor to a value other than the empty string.
■
In addition to the X.509 certificate, you can implement security using either UsernameToken, UsernameOverTransport, or AnonymousOverX509.
■
Verifying the security credentials for an X.509 certificate is done by including an element in your router’s Web.config file.
Lesson 3: How to Add and Verify Security Credentials
503
Lesson Review You can use the following questions to test your knowledge of the information in Les son 3, “How to Add and Verify Security Credentials.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. What are the two alternatives for creating a custom policy assertion? (Select two answers.) A. Add code to the constructor for the WSE router that verifies the signer’s credentials. B. Create an XML-based policy file and return it in the overridden GetRequestPolicy method. C. Add code to the GetRequestPolicy method to verify the signer’s credentials. D. Add code to a class file that adds an assertion that is returned by the over ridden GetRequestPolicy method. 2. Which security method utilizes a username and password that is stored in an external source such as Microsoft SQL Server or Active Directory? (Choose the best answer.) A. X.509 B. UsernameToken C. UsernameOverTransport D. AnonymousOverX509
504
Chapter 11
Messaging and Routing
3. Identify the code used to configure the router to allow certificates signed by a test root to pass verification. (Choose the best answer.) A.
B.
C.
D.
Chapter 11 Review
505
Chapter Review To further practice and reinforce the skills you learned in this chapter, you can per form the following tasks: ■
Review the chapter summary.
■
Review the list of key terms introduced in this chapter.
■
Complete the case scenarios. These scenarios set up real-world situations involv ing the topics of this chapter and ask you to create a solution.
■
Complete the suggested practices.
■
Take a practice test.
Chapter Summary ■
A WSE router serves as an intermediary between your Web services and the out side world. It is actually just another Web service that is configured to route requests to the appropriate Web services.
■
You configure the WSE router by adding an element for to the router Web.config file. You also create a ReferralCache.config file that contains the locations of your router and the Web services that will be the final destination.
■
When using a WSE router, you have to use WSE to add security credentials rather than adding them to the proxy. This is necessary for them to pass through the router.
■
When configuring security for a WSE router, you must consider two steps: secu rity between the client and the router, and security between the router and the Web service.
■
You can create a custom policy assertion using an XML-based policy file or code added to a class file. Either way, the policy is returned from the overridden GetRequestPolicy method.
■
Security is configured for verification by adding an element to the ele ment in the WSE router’s Web.config file.
506
Chapter 11 Review
Key Terms Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book. ■
assertion
■
certificate
■
message layer security
■
transport layer security
Case Scenarios In the following case scenarios, you apply what you’ve learned about how to imple ment and secure a WSE router. You can find answers to these questions in the “Answers” section at the end of this book.
Case Scenario 1: Performing a Server Upgrade You work for a medical services company that hosts several Web services on a large network. The network administrator has informed your department that all of the servers running these services need to be taken offline for several hours to perform a server upgrade. Fortunately, all of the Web services are routed through a WSE router and you know that all you need to do is devise a strategy for using the router to tem porarily reroute the requests. 1. What strategy do you suggest?
Case Scenario 2: Selecting a Security Method for Your WSE Router You have implemented a WSE router for the medical services company mentioned in Case Scenario 1. The Web services provided are only accessed by ancillary branches of your company. Although the branches do not reside on your public network and access the Web services through the public Internet, they are trusted members of your organization. You have been asked to implement security for the WSE router such that performance of the Web services is a primary consideration. 1. What security method might you recommend?
Chapter 11 Review
507
Suggested Practices To help you successfully master the exam objectives presented in this chapter, com plete the following tasks.
Implement WSE SOAP Messaging You only need to complete practice 1 to understand the WSE SOAP Messaging objective. For further understanding, download and install the MSDN Hands On Lab concerning WSE Messaging available through the following URL: http:// www.microsoft.com/downloads/details.aspx?familyid=0e5491c1-8bde-4fff-88c4 8e3dc102fad6&displaylang=en. This will allow you to complete practice 2. Create a Web service project and leave the HelloWorld Web method created by default. Enable WSE for the project and use WSE Messaging to change the Web method to use TCP as the communication protocol. You will then add a Windows application project to the solution and set a Web reference to the Web service in this solution. Add a label to the default form for this project and add code to the Page_load method that calls the HelloWorld Web method and displays the string result in text of the label control.
■ Practice 1
After downloading and installing the MSDN Hands On Lab, browse to the WSE Messaging folder that was created during the installation. Drill down to the folder for the language you prefer (VB or C#) and open the Basic folder. Open the Manual.doc file and review the instructions for completing the Hands On Lab. Walk through part 1 and part 2 of the basic lab and review the code sup plied with the lab.
■ Practice 2:
Route SOAP Messages by using a WSE Router To help master this objective, you should complete practice 1. Practice 2 is optional since it deals with more advanced concepts such as passing in a SOAP header so that message can be routed based on their content. Create a Web service project that will act as a WSE router. Configure the WSE Router to accept requests for all .asmx files and create a referralCache.config file that routes messages no matter what the content is. You can route messages for the Web service project you created in practice 1 for WSE SOAP Messaging. You will then add a Web application project that acts as a cli ent and makes a request to the Web service.
■ Practice 1
508
Chapter 11 Review
Modify the WSE Router project created in practice 1 to create a method that overrides the ProcessRequestMessage method and thus routes a mes sage based on a value passed in the SOAP header. You will then have to modify the client project to pass in the SOAP header.
■ Practice 2
Add and Verify Security Credentials To complete the practices in this section, you need to first complete practice 1 in the Route SOAP Messages section. Add a class to the WSE Router project created in practice 1 of the pre vious section. This class will be used to pass the username token through to the Web service. In this case, you will be using a username and password that can be retrieved from a database. However, you can just have the username and pass word passed in as static text. Refer to the MSDN document, http:// msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag2/html/ WSS_Ch3_ImpDirectAuth_WSE30.asp, for more information about implement ing a username token.
■ Practice 1
Verify the security credentials added in practice 1 by adding a element to the web.config file for the WSE Router project.
■ Practice 2
Take a Practice Test The practice tests on this book’s companion CD offer many options. For example, you can test yourself on just the content covered in this chapter, or you can test yourself on all the 70-529 certification exam content. You can set up the test so that it closely sim ulates the experience of taking a certification exam, or you can set it up in study mode so that you can look at the correct answers and explanations after you answer each question. MORE INFO
Practice tests
For details about all the practice test options available, see the section titled “How to Use the Prac tice Tests” in this book’s Introduction.
Chapter 12
Creating Serviced Components
Enterprise Services are part of the toolbox that any developer needs to use when developing complex transactional systems. Distributed transactions, high-volume systems, object pooling, and just-in-time (JIT) activation are but a few of the services that you need, but implementing code at the infrastructure level is not very produc tive. Enterprise Services and the underlying COM+ framework offer these services. Serviced components are the heart of the solution when it comes to accessing these services from a managed environment.
Exam objectives in this chapter: ■
Create, configure, and access a serviced component. ❑
Create a serviced component.
❑
Add attributes to a serviced component.
❑
Register a serviced component.
❑
Implement security.
❑
Add a reference to a serviced component in an application.
❑
Create an instance of a serviced component.
❑
Call the methods of a serviced component.
Lessons in this chapter: ■
Lesson 1: Serviced Component Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511
■
Lesson 2: Consuming the Serviced Component . . . . . . . . . . . . . . . . . . . . . . . . 555
Before You Begin To complete the lessons in this chapter, you should be familiar with Microsoft Visual Basic or C# and be comfortable with the following tasks: ■
Creating a library and console application in Microsoft Visual Studio using Visual Basic or C#.
■
Adding references to system class libraries to a project. 509
510
Chapter 12
Creating Serviced Components
■
Adding references to projects within a solution.
■
A basic understanding of remoting methodologies (XML Web services, DCOM, .NET Remoting, CORBA, and so on).
■
A basic understanding of COM+.
■
A basic understanding of queuing technologies such as Microsoft Message Queue Server (MSMQ).
■
A basic understanding of distributed transactions.
■
A basic understanding of ADO.NET.
Real World Mark Blomsma Serviced components are indispensable in the enterprise environment. I’ve worked on projects where transactional integrity across multiple data sources was an absolute must. We inserted data in two and sometimes more databases and pushed multiple messages on a queue, all in a high-transaction environ ment. Using serviced components to interact with the Microsoft Distributed Transaction Coordinator (DTC) was the only way to get the job done reliably.
Lesson 1: Serviced Component Overview
511
Lesson 1: Serviced Component Overview In this lesson we look at how COM, COM+, MTS, Enterprise Services, and serviced components are all related. You gain an understanding of what a serviced component is, how to make one, and how to use the Enterprise Services such as transaction man agement, object pooling, and JIT activation.
Interconnected Technologies There are a couple of technologies that are interconnected when it comes to ser viced components: COM, COM+, Microsoft Transaction Server, and Enterprise Services. The Common Language Runtime (CLR) is the replacement for COM but not for COM+. COM+ is a run-time environment that provides a set of ser vices intended to make building scalable distributed systems easier. COM+ is the name given to Microsoft Transaction Server 2.0 (MTS 2.0). Both COM+ and MTS had poorly chosen names, which do not cover the underlying technology. This has been corrected. The .NET Framework introduces Enterprise Services, the next generation of COM+. Enterprise Services is a run-time environment that revolves around serviced components. To use the services offered by the Enterprise Services run-time environment, you need to create a serviced component. The serviced component provides access to Enterprise Services, and Enterprise Services give you access to services such as JIT activation, queuing, role-based security, object pooling, synchronization, and transactions. Using the classes in the System.EnterpriseServices namespace, Visual Studio enables you to easily create serviced components, and essentially create a class that you’ll use to extend the ServicedComponent class. Let your class inherit from ServicedComponent and mark the class and its methods with attributes, providing metadata about how the component should be registered in the COM+ catalog. We’ll see how all this works in Lesson 1. An assembly for a serviced component must be strongly named. A serviced com ponent can be consumed from a .NET assembly in the same way as you would consume a regular component: simply add an assembly reference to the dynamic link library (DLL) defining the serviced component.
512
Chapter 12
Creating Serviced Components
Serviced components can also be made available as Web services by using the COM+ Simple Object Access Protocol (SOAP) service. Achieving this is merely a matter of applying the right attributes to your class. To be precise, use the SoapVRoot property of the serviced component’s ApplicationActivation attribute. One of the most frequently used COM+ services is the Microsoft DTC. By enlist ing the .NET transaction in the DTC you can create transactions that run across multiple data sources and can even use a compensating resource manager (CRM) for resources that are not transactional by nature. You can for example, create a transaction that updates a record in the database, inserts a message in a message queue, and writes an entry to a file, all within the scope of one transac tion. The database and the message queue are both transactional, but the file sys tem is not. In this example a CRM would need to be created to reverse the changes made to the file in case the transaction needs to be rolled back. COM+ gives developers more control over their applications by allowing config urable transaction isolation levels. Versions of COM+ prior to COM+ 1.5 always used the highest level of isolation for transactions. Although this level guaran tees that data integrity is always preserved, it can lead to performance issues, such as timeouts, when many transactions need to be performed on a large data base. With configurable isolation levels, experienced developers can increase concurrency to improve performance and scalability. Serviced components are most powerful in a server-side environment and can be used to enhance both the functionality and performance of your .NET application.
After this lesson, you will be able to: ■
Describe the background and uses of COM+ Services.
■
Create a serviced component.
■
Register a serviced component.
■
Add attributes to a serviced component.
■
Implement security on a serviced component.
Estimated lesson time: 45 minutes
Lesson 1: Serviced Component Overview
513
COM+ Services COM+ builds on and extends applications that have been written using COM, MTS, and other COM-based technologies. COM+ handles many of the resource manage ment tasks that developers previously had to program, such as thread allocation and security. Some COM+ services can be used within the .NET Framework to enhance functionality and performance. The .NET Framework attributes cause COM+ config uration to be stored in the COM+ catalog when the assembly is registered. The following COM+ functionality can be added to your managed application by implementing a serviced component: ■
Transaction support ❑
Automatic transaction processing
❑
Bring your own transaction (BYOT)
❑
CRMs
■
Object pooling
■
Just-in-time activation
■
Loosely coupled events
■
Object construction
■
Private components
■
Queued components
■
Role-based security
■
SOAP service
■
Synchronization
Transaction Support A transaction is a set of operations that are considered to be one unit. Either all oper ations succeed, or all fail. When a failure occurs, no operations are committed. The .NET Framework does have other kinds of transaction support within ADO.NET, but the COM+ transaction model supports a declarative coding style and the ability to perform distributed transactions.
514
Chapter 12
Creating Serviced Components
Automatic Transaction Processing Using the AutoCompleteAttribute you can have a transactional method rollback auto matically if an unhandled exception terminates the regular flow of the application. When not using the AutoCompleteAttribute the ContextUtil class can be used to inter act with the COM+ application and inform it of success or failure using the methods SetComplete() and SetAbort().
Bring Your Own Transaction (BYOT) In a distributed transaction scenario it might be necessary to manually manage the enlistment of code into a running transaction. The solution of connecting to the DTC to enlist in a running transaction is known as BYOT.
CRMs CRMs are used for resources that are not transactional by nature. You might, for exam ple, create a transaction that updates a record in the database, inserts a message in a message queue, and writes an entry to a file, all within the scope of one transaction. The database and the message queue are both transactional, but the file system is not. In this example a CRM would be needed to to reverse the changes made to the file in case the transaction needs to be rolled back.
Object Pooling Object pooling means that a limited supply of objects are made available for client use. When clients request a new object instance, they either receive a new object instance or an instance that already exists in the pool. After clients have finished using objects, they are returned to the pool rather than destroyed, so subsequent clients can reuse these pooled instances.
Just-in-Time Activation JIT activation is a process whereby COM+ components are only created when they are required and are destroyed as soon as they are no longer required. This activation and deactivation occurs when methods are called (although context information is saved between calls). When clients create instances of COM+ components, they receive a proxy class. However, the COM+ component that is accessed by the proxy class does not actually exist until the client uses component methods to perform operations.
Lesson 1: Serviced Component Overview
515
Loosely Coupled Events Loosely coupled events, or the LCE system as it often referred to, allows events to be sent across the network. See Chapter 8, “Method Invocations and Event Management with .NET Remoting,” for more information on how .NET Remoting is used to lever age this functionality.
Object Construction COM+ offers a mechanism for string-based object construction where, after creating the object using the default constructor, the Construct method is called, passing a string containing information about the state of the object. This feature needs to be enabled on the Activation tab of the component’s Properties dialog box or by decorat ing your class with a ConstructionEnabled attribute. This automatically sets the option the moment the component is imported in a COM+ application.
Private Components COM+ 1.0 marks all components in an application as public. A public component can be activated from any other application. An application typically has a number of com ponents that are internal to the application. COM+ 1.5, as implemented by Microsoft Windows Server 2003, allows you to mark these components as private. This means that the components are only visible and accessible by other components in the same application. The ability for developers to mark components as private gives developers more con trol over what functionality to expose. It is also possible to mark a component as pri vate but still take advantage of all the features of Enterprise Services.
Queued Components Very often, asynchronous messaging is accomplished using MSMQ. When using MSMQ, rather than updating data synchronously, we send a message to MSMQ with information about the task at hand (typically, but not always, in Extensible Markup Language [XML] format). Later on, we retrieve that message from MSMQ and perform the actual task. MSMQ works very well for this kind of operation. However, using MSMQ directly requires a significant amount of custom source code. The application needs to be specifically designed to post information to the queue, and the compo nents need to manually read information from the queue. For more information on message queuing, see Chapter 13, “Serviced Component Management.”
516
Chapter 12
Creating Serviced Components
Queued components are different. They make use of MSMQ but hide all of the com plexity. Setting up queued components is a purely administrative task. This makes it easy to convert a regular synchronous operation to an asynchronous operation.
Role-Based Security .NET has basic role-based security, but the .NET definition of a role is a Windows user group. This is a severe limitation because often you do not have control over your cus tomer’s information technology (IT) department. If you deploy your application in an environment where the user groups in your domain don’t match the roles expected by your application, nobody will have access to your application. There are two solu tions to this problem: (a) define a custom principle that redefines how a role needs to be verified, or (b) use Enterprise Services role-based security. Enterprise Services role-based security is unrelated to Windows user groups, allowing you to define roles directly from the application’s business domain, even if no corre sponding user groups exist.
SOAP Service The .NET Framework allows any COM+ component to be accessible through a SOAP service. Using the Component Services Microsoft Management Console (MMC) snapin you can configure the COM+ component in such a way that the interface will be available as a SOAP service. Microsoft Internet Information Services (IIS) will be used to host the service. Notice the use of the term SOAP service. The service uses SOAP over Hypertext Transfer Protocol (HTTP), but the SOAP spoken here is that of .NET Remoting. You’ll need the soapsuds command-line utility to create a .NET remoting proxy for this service.
Synchronization The last service offered through Enterprise Services is synchronization. When you use synchronization, COM+ creates a proxy for you that runs all instances of your object in its context. COM+ marshals all calls across this proxy where a performance penalty occurs. The service guarantees that only one thread is available to run each object at a time. COM+ synchronization provides another easy way to provide synchronization, but be careful when using it. Many calls to a COM+ synchronized object degrade your application greatly because of all the marshaling across the proxy. Be sure to test responsiveness when using it.
Lesson 1: Serviced Component Overview
517
Creating a Serviced Component The following steps need to be completed to create a serviced component: 1. Inherit from ServicedComponent. 2. Add a default constructor. 3. Make your class ComVisible. 4. Choose the COM+ activation type. 5. Assign a strong name.
Inheriting from ServicedComponent You create a serviced component by implementing a .NET Framework class that inherits, directly or indirectly, from the System.EnterpriseServices.ServicedComponent class. Serviced component classes are hosted in a COM+ application and can use COM+ services by using the System.EnterpriseServices namespace. To access the System.Enter priseServices namespace you have to add an assembly reference to System.EnterpriseServices.dll, which is part of the .NET Framework Class Library. 'VB
Imports System.EnterpriseServices
Public Class OrderService Inherits ServicedComponent End Class //C#
using System.EnterpriseServices;
namespace MSLearning.Chapter12.Services
{
public class OrderService : ServicedComponent
{
}
}
Adding a Default Constructor For COM+ to be able to manage your component, it needs to be able to create an instance of your class. COM+ will not be able to provide any default parameters when creating your class, so you need to implement a default public constructor. 'VB Imports System.EnterpriseServices Public Class OrderService
518
Chapter 12
Creating Serviced Components
Inherits ServicedComponent '''
''' Default constructor is required for COM+ registration.
'''
'''
Public Sub OrderService() End Sub End Class //C# using System.EnterpriseServices;
namespace MSLearning.Chapter12.Services
{
public class OrderService : ServicedComponent { /// /// Default constructor is required for COM+ registration. /// public OrderService() {
}
}
}
Making Your Class ComVisible Types in your managed code assembly are by default not visible to COM+. This is because your assembly info file contains the following entry: 'VB Imports System.Runtime.InteropServices
' Setting ComVisible to false makes the types in this assembly not visible
' to COM components. If you need to access a type in this assembly from
' COM, set the ComVisible attribute to true on that type.
//C# using System.Runtime.InteropServices;
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
You need to select Show All Files in Solution Explorer to be able to see the assemblyinfo in Visual Basic.
Lesson 1: Serviced Component Overview
519
It is considered poor design to make all your types visible to the COM+ environment. To make your custom type visible to COM+ you need to add the ComVisibleAttribute to your class. The ComVisibleAttribute can be found in the System.Runtime.InteropServices namespace. 'VB Imports System.EnterpriseServices Imports System.Runtime.InteropServices Public Class OrderService Inherits ServicedComponent '''
''' Default constructor is required for COM+ registration.
'''
'''
_
Public Sub OrderService()
End Sub
End Class //C# using System.EnterpriseServices; using System.Runtime.InteropServices; namespace MSLearning.Chapter12.Services { [ComVisible(true)] public class OrderService : ServicedComponent { /// /// Default constructor is required for COM+ registration. /// public OrderService() { } } }
Choosing a COM+ Activation Type In COM+ you can choose between the following two types of application activation: A library application runs in the process of the client that creates it and can be called only by clients located on the same computer. This is the default setting.
■ Library
■ Server
Server applications run in their own dedicated server process.
520
Chapter 12
Creating Serviced Components
It is possible to specify whether components in the assembly run in the creator’s pro cess or in a system process using the ApplicationActivationAttribute in the Assemblyinfo.cs file: 'VB Imports System.EnterpriseServices //C# using System.EnterpriseServices; [assembly: ApplicationActivation(ActivationOption.Library)]
Assigning a Strong Name A serviced component needs a strong name to be registered. A strong name consists of information about the assembly’s identity: the string that makes up the name of the assembly, a version number, culture information (if provided), a public key, and a dig ital signature. The information is generated from an assembly file that uses the corre sponding private key. The assembly file contains the assembly manifest, which contains the names and hashes of all the files that make up the assembly. This signa ture provides name uniqueness for the assembly and prevents people from taking over the name of your assembly (called name spoofing). Note that all assemblies that reference an assembly with strong names must also have strong names themselves. You can create a strong name using the Sn.exe command-line utility or by using the property pages of your project in Visual Studio 2005. When using the Sn.exe com mand-line utility, first create the keyfile. Sn.exe –k mykey.snk
Now use the property pages of your project to refer to the file you have just created. In the property pages you’ll find a tab named Signing. Select the Sign The Assembly check box and navigate to the Mykey.snk file. In .NET Framework 1.1 the reference to the keyfile was stored in the Assemblyinfo.cs file, but .NET Framework 2.0 stores the reference in the project file. true mykey.snk
Although not necessarily required by the runtime, providing your assembly with a name is a good practice. Using the ApplicationNameAttribute in the System.Enterprise
Lesson 1: Serviced Component Overview
521
Services namespace allows you to set a default application name at assembly level: 'VB Imports System.EnterpriseServices //C# using System.EnterpriseServices; [assembly: ApplicationName("AdventureWorks")]
Register a Serviced Component After completing the steps in the preceding section, you are ready to register your ser viced component in the COM+ catalog. There are four ways to register your serviced component: ■
MMC
■
Service Installation Tool
■
Dynamic registration
■
Windows Installer (MSI) package
Microsoft Management Console Component Services is a snap-in for the MMC. Use this snap-in to create a COM+ application, add components to the application, and set the attributes for the applica tion and the components. Start the Component Services management console by clicking Start, Run. Type c:\windows\system32\com\comexp.msc, and then click OK. Alternatively, click Start, Control Panel, Administrative Tools, Component Services. Then click Com ponent Services, Computers, My Computer, COM+ Applications. Your screen should now look similar to Figure 12-1.
522
Chapter 12
Creating Serviced Components
Figure 12-1 Default view of Component Services management console
Right-click COM+ Application and click New, Application. This starts the COM+ Application Install Wizard. Now follow these steps: 1. On the Welcome page, click Next. 2. On the Install Or Create A New Application page, click Create An Emtpy Application. 3. On the Create An Empty Application page, enter AdventureWorks as the name for the application. Click Library Application, and then click Next. 4. On the Thank You page, click Finish. You’ve now added the initial COM+ application. Of course it is still empty because we have not yet added our application. This can be done using drag-and-drop. In Com ponent Services Explorer, in the AdventureWorks folder, select the Components folder. As you can see, it is empty. Next, using Windows Explorer, locate your assem bly (DLL). Drag the assembly onto the Components folder. Your screen should now look like Figure 12-2.
Lesson 1: Serviced Component Overview
523
Figure 12-2 You have successfully installed your COM+ application
Congratulations! You’ve just successfully installed your COM+ application. To uninstall the COM+ application, just right-click AdventureWorks and click Delete.
Services Installation Tool The .NET Framework Software Development Kit provides the .NET Framework Ser vices Installation Tool (Regsvcs.exe), which can load and register an assembly, install a type library into a specified COM+ application, and configure COM+ services. Start the Visual Studio 2005 command prompt by clicking Start, All Programs, Microsoft Visual Studio 2005, Visual Studio Tools, Visual Studio 2005 Command Prompt. Change your current directory to the directory where your assembly is located and use Regsvcs.exe to register your COM+ application: regsvcs chapter12.dll AdventureWorks
Notice that because we’re using the Visual Studio 2005 command prompt there is automatically a path setting to the folder C:\Windows\Microsoft.NET\Frame work\v2.0.50727 where Regsvcs.exe is located. Another thing to take note of is that Regsvcs.exe does not use the application name from Assemblyinfo.cs. Instead it needs to be provided as one of the command-line arguments.
524
Chapter 12
Creating Serviced Components
Use the –u switch to unregister the COM+ application: regsvcs –u chapter12.dll AdventureWorks
Dynamic Registration Dynamic registration copies an assembly containing the serviced components to the folder that contains the client application. These are not placed in the global assembly cache (GAC). When a client tries to create an instance of the serviced component, the CLR registers the assembly and configures the COM+ catalog. This occurs only once for each version of an assembly. The technique only works if the calling code is man aged code, and the process that performs dynamic registration must have local administrator rights. We use dynamic registration when we consume our serviced component in Lesson 2 of this chapter.
Windows Installer (MSI) Package After a serviced component application has been installed on one system, it can be exported from the Component Services MMC snap-in in the form of a Microsoft Win dows Installer (MSI) package. Start the Component Services management console and browse to your COM+ appli cation (see the section “Microsoft Management Console” earlier in this lesson). Rightclick your application and click Export. You’ve now entered the COM+ Application Export Wizard. Next, follow these steps: 1. On the Welcome page, click Next. 2. On the Application Export Information page (see Figure 12-3), enter a folder and name for your MSI file, then click Next. 3. On the last page, click Finish.
Lesson 1: Serviced Component Overview
525
Figure 12-3 Export your COM+ application
Two files are created when exporting your COM+ Application, an .msi file and a .cab file. Copy both files to your target machine and double-click the .msi file to install your COM+ application on this machine. After installation your files are located in the following folder: C:\Program Files\ComPlus Applications\{GUID of your application}
You can uninstall your application in Control Panel using Add/Remove Programs.
Adding Attributes to a Serviced Component So far we know what serviced components can do, how to make one, and how to reg ister one. In this section we get into how to leverage the functionality offered by Enter prise Services. Most of the serviced component features are applied by using .NET Framework attributes. When a service is called, the associated attribute is set to inform the run time about the required COM+ service. The .NET Framework attributes cause COM+ configuration to be stored in the COM+ catalog when the assembly is regis tered. These attributes inform the COM+ hosting environment how to interact with
526
Chapter 12
Creating Serviced Components
the serviced components through the services. When we added a default applica tion name to our assembly in the previous section we were actually using an attribute, the ApplicationNameAttribute. Note that these attributed configuration set tings can be changed in the Component Services snap-in of the MMC after the com ponent has been installed. We’ll look at how to use attributes to utilize the following: ■
Transactions
■
Object pooling
■
JIT activation
■
Object construction
■
Private components
■
Queued components
Transactions Let’s take another look at our OrderService. We have two methods: SubmitOrder and GetOrders. Let’s assume that in our application when we place an order we want to store the order in our AdventureWorks database, but we also want to inform a separate application, our Billing Engine, of this order. We have direct access to our own data base, but we need to send a message via a message queue to our Billing Engine (see Chapter 13 for more information on message queues). For our database access we’re following the Data Access Layer pattern (see the Patterns & Practices Web site at http:/ /msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpatterns/html/ArcThreeLayeredSvcsApp.asp for more information) and have implemented a class named Adven tureWorksDB. To encapsulate the complexities of MSMQ we’ve also added a class named BillingEngineQueue. The code would look like this: 'VB Imports System.EnterpriseServices Imports System.Runtime.InteropServices Namespace MSLearning.Chapter12.Services Public Class OrderService
Inherits ServicedComponent
''' ''' ''' '''
Default constructor is required for COM+ registration.
Lesson 1: Serviced Component Overview
_
Public Sub OrderService()
End Sub
Public Sub SubmitOrder(ByVal customerId As Integer, ByVal articleId As Integer,
ByVal quantity As Integer)
Dim db As New AdventureWorksDB()
Dim orderId As Integer
orderId = db.InsertOrder(customerId, articleId, quantity)
Dim q As New BillingEngineQueue()
q.InformOfNewOrder(customerId, orderId)
End Sub
Public Function GetOrders(ByVal customerId As Integer) As DataSet
Dim db As New AdventureWorksDB()
Return db.GetOrders(customerId)
End Function End Class End Namespace //C# using System;
using System.Data;
using System.EnterpriseServices;
using System.Runtime.InteropServices;
namespace MSLearning.Chapter12.Services
{
[ComVisible(true)]
public class OrderService : ServicedComponent
{
///
/// Default constructor is required for COM+ registration.
///
public OrderService()
{
}
public void SubmitOrder(int customerId, int articleId,
int quantity)
{
AdventureWorksDB db = new AdventureWorksDB();
int orderId = db.InsertOrder(customerId, articleId, quantity);
BillingEngineQueue q = new BillingEngineQueue();
q.InformOfNewOrder(customerId, orderId);
}
public DataSet GetOrders(int customerId)
{
527
528
Chapter 12
Creating Serviced Components
AdventureWorksDB db = new AdventureWorksDB();
return db.GetOrders(customerId);
}
}
}
If implemented as shown here, the SubmitOrder method would work. So all is well, you say. However, it is not. An exception is thrown if, for some reason, the InformOfNewOrder method fails. The InsertOrder, however, will have already committed its changes to the database, leaving the application in an inconsistent state. We need to add transaction support, and, because we’re running our transaction over more than just a database, we need the DTC. The easiest way to do this is to add two attributes to the class: one at the class level and one at the method level. 'VB _
Public Class OrderService
Inherits ServicedComponent
'... _ Public Sub SubmitOrder(ByVal customerId As Integer, ByVal articleId As Integer, ByVal quantity As Integer) Dim db As New AdventureWorksDB()
Dim orderId As Integer
orderId = db.InsertOrder(customerId, articleId, quantity)
Dim q As New BillingEngineQueue()
q.InformOfNewOrder(customerId, orderId)
End Sub
'... End Class //C# [ComVisible(true)] [Transaction(TransactionOption.Required,
Isolation=IsolationLevel.Serializable, Timeout=30)]
public class OrderService : ServicedComponent
{
///...
Lesson 1: Serviced Component Overview
529
[AutoComplete] public void SubmitOrder(int customerId, int articleId,
int quantity)
{
AdventureWorksDB db = new AdventureWorksDB();
int orderId = db.InsertOrder(customerId, articleId, quantity);
BillingEngineQueue q = new BillingEngineQueue();
q.InformOfNewOrder(customerId, orderId);
}
///...
}
At class level we have used the TransactionAttribute to specify that for this class a trans action is Required, the transaction isolation level is set to Serializable, and the timeout for the transaction is set at 30 seconds. Note that a timeout value of 30 seconds is very short when you’re debugging, so you might consider setting this value higher while debugging. The following options are allowed when specifying how this class will use the COM+ transaction: ■ Disabled
Ignores any transaction in the current context.
■ NotSupported ■ Required
Creates the component in a context with no governing transaction.
Shares a transaction, if one exists, and creates a new transaction if
necessary. Creates the component with a new transaction, regardless of the state of the current context.
■ RequiresNew
■ Supported
Shares a transaction, if one exists.
The isolation level can be set to the following values: The pending changes from more highly isolated transactions cannot be overwritten.
■ Chaos
Shared locks are held while the data is being read to avoid dirty reads, but the data can be changed before the end of the transaction, resulting in nonrepeatable reads or phantom data.
■ ReadCommitted
A dirty read is possible, meaning that no shared locks are issued and no exclusive locks are honored.
■ ReadUncommitted
530
Chapter 12
Creating Serviced Components
Locks are placed on all data that is used in a query, preventing other users from updating the data. This prevents nonrepeatable reads but phantom rows are still possible.
■ RepeatableRead
A range lock is placed on the dataset, preventing other users from updating or inserting rows in the dataset until the transaction is complete.
■ Serializable
Reduces blocking by storing a version of data that one application can read while another is modifying the same data. It indicates that from one trans action you cannot see changes made in other transactions, even if you re-query.
■ Snapshot
A different isolation level than the one specified is being used, but the level cannot be determined. When using OdbcTransaction, if you do not set IsolationLevel or you set IsolationLevel to Unspecified, the transaction executes according to the default isolation level of the underlying Open Database Con nectivity (ODBC) driver.
■ Unspecified
What is great about .NET 2.0 is that when using transactions this way the DTC doesn’t actually become involved until it becomes absolutely necessary. This is good, because managing a transaction across multiple data sources creates a fair amount of overhead. Delaying the involvement of the DTC dramatically improves performance. What happens in SubmitOrder? A great way to find out is to set a debugging break point at the first line in SubmitOrder and add a watch to System.Transactions.Transac tion.Current. To do this you need to add a project reference to System.Transactions. Also open the Component Services management console and select the Distributed Trans action Coordinator node and click Transaction Statistics. Now start debugging the application. You see that there is no transaction until the connection to the AdventureWorks database is opened. At this point you see that System.Transactions.Transac tion.Current holds a reference to a transaction object and that this transaction only has a LocalIdentifier (see Figure 12-4).
Lesson 1: Serviced Component Overview
531
Figure 12-4 Watch System.Transactions.Transaction.Current
The DistributedIdentifier has not yet been initialized because at this point in time the transaction has not yet been handed over to the DTC. We confirm this by looking at the Transaction Statistics pane (see Figure 12-5).
Figure 12-5 No currently active transactions
The statistics show that there are currently no active transactions. Now we continue stepping through our code. Enter the InformOfNewOrder method. At the very moment that you send the message, you see a change in the Watch window. The DistributedIdentifier gets initialized (see Figure 12-6) and when you check the transaction statistics you see that there is one currently active distributed transaction (see Figure 12-7).
532
Chapter 12
Creating Serviced Components
Figure 12-6 The Watch dialog box now shows a distributed identifier
Figure 12-7 Transaction statistics show an active distributed transaction
The System.Transactions namespace that is used to monitor what happens during the course of SubmitOrder is new to the .NET Framework 2.0. The System.EnterpriseSer vices namespace in the .NET Framework 2.0 has been partially rewritten to make use of this new namespace. System.EnterpriseServices makes use of the Lightweight Transac tion Manager (LTM). The LTM has been created as part of the .NET Framework 2.0 to improve performance of the transaction mechanism. LTM is part of the System.Trans actions namespace, which is discussed later. Note that you can only create applica tions using this namespace in the Microsoft Windows 2000, Windows XP, and Windows 2003 platforms. Creating a transaction in the Microsoft Windows 98 and Windows ME platforms throws a PlatformNotSupportedException.
Lesson 1: Serviced Component Overview
533
Object Pooling Object pooling means that a limited number of objects are made available for client use, and when clients have finished using objects, they are returned to the pool rather than destroyed, so subsequent clients can reuse these pooled instances. Use the ObjectPoolAttribute to specify the minimum (MinPoolSize) and maximum (MaxPoolSize) number of instances and the CreationTimeout. 'VB Namespace MSLearning.Chapter12.Services _
Public Class OrderService
Inherits ServicedComponent
'... End Class End Namespace //C# namespace MSLearning.Chapter12.Services { [ObjectPooling(MinPoolSize = 1, MaxPoolSize = 10,
CreationTimeout = 1000)]
public class OrderService : ServicedComponent { ///... } }
■ MinPoolSize
The minimum number of objects in the pool.
■ MaxPoolSize
The maximum number of objects in the pool.
The timeout value in milliseconds. Specifies how long a client will wait, in milliseconds, for an object to be returned from the pool. An excep tion is thrown if an object does not become available within the specified time.
■ CreationTimeout
JIT Activation JIT activation is a process whereby COM+ components are only created when they are required and are destroyed as soon as they are no longer required. JIT is often used in conjunction with object pooling. As the name says, the JustInTimeActivationAttribute is used to specify that a component needs to be activated JIT.
534
Chapter 12
Creating Serviced Components
'VB Namespace MSLearning.Chapter12.Services _ Public Class OrderService
Inherits ServicedComponent
'... End Class
End Namespace
//C# namespace MSLearning.Chapter12.Services
{
[JustInTimeActivation] public class OrderService : ServicedComponent
{
///...
}
}
Object Construction As mentioned earlier, COM+ offers a mechanism for string-based object construction where, after creating the object using the default constructor, the Construct method is called, passing a string containing information about the state of the object. Use the ConstructionEnabledAttribute to enable this behavior on registration of your COM+ application. 'VB Imports System.EnterpriseServices
Imports System.Runtime.InteropServices
Namespace MSLearning.Chapter12.Services _ Public Class OrderService
Inherits ServicedComponent
'... End Class
End Namespace
//C# namespace MSLearning.Chapter12.Services
{
[ConstructionEnabled(true, Default="place your default string here")] public class OrderService : ServicedComponent
Lesson 1: Serviced Component Overview
535
{
///
/// Default constructor is required for COM+ registration.
///
public OrderService()
{
}
///
/// Override the Construct method to get access
/// to the construction string.
///
/// Construction String
protected override void Construct(string s)
{
base.Construct(s); } } }
Note that the string placed in the constructor of the ConstructionEnabledAttribute is placed in the COM+ configuration at registration time. If the setting is changed, on the Activation tab, the default in your code becomes obsolete.
Private Components An application typically has a number of components that are internal to the applica tion. This means that the components are only visible and accessible by other compo nents in the same application. The PrivateComponentAttribute allows you to mark a component as internal. The fol lowing code sample shows how this is done: 'VB Imports System.EnterpriseServices Namespace MSLearning.Chapter12.Services _ Public Class AdventureWorksDB
Inherits ServicedComponent
Public Sub AdventureWorksDB()
End Sub
'... End Class End Namespace
536
Chapter 12
Creating Serviced Components
//C# using System.EnterpriseServices; namespace MSLearning.Chapter12.Services
{
[PrivateComponent] public class AdventureWorksDB {
public AdventureWorksDB()
{
}
///...
}
}
Queued Components Queued components make use of MSMQ but hide all of the complexity. Setting up queued components is a purely administrative task. This makes it easy to convert a regular synchronous operation to an asynchronous operation. Queued components are sometimes referred to as queued invocation or even asynchronous component invocation. Use both InterfaceQueuingAttribute at class level and the ApplicationQueuingAttribute at assembly level to configure this feature. A component needs to implement a public interface to be set up as a queued component. This can be implicit by decorating a class with the InterfaceQueuingAttribute, but the recommended method is to define an interface. In the following code sample, the Visual Studio 2005 refactoring Extract Interface is used to create the required interface definition: 'VB Namespace MSLearning.Chapter12.Services Public Class OrderService
Inherits ServicedComponent
Implements IOrderServiceQueued
'...
End Class
_ Public Interface IOrderServiceQueued '...
End Interface
End Namespace
Lesson 1: Serviced Component Overview
537
//C# namespace MSLearning.Chapter12.Services {
public class OrderService : ServicedComponent, IOrderServiceQueued
{
///...
}
[InterfaceQueuing(true)] public interface IOrderServiceQueued
{
///...
}
}
The Assemblyinfo.cs file needs to be changed to include the following: 'VB ' Mark the COM+ application as queued at compile time by using the ' ApplicationQueuing attribute. Enable the COM+ listener by ' setting the QueueListenerEnabled to true //C# // Mark the COM+ application as queued at compile time by using the // ApplicationQueuing attribute. Enable the COM+ listener by // setting the QueueListenerEnabled to true [assembly: ApplicationQueuing(Enabled = true, QueueListenerEnabled = true)]
Note that the initial SubmitOrder returns an integer containing the order ID. For obvi ous reasons, this return value is not available when calling the interface through a queued component. This is why IOrderServiceQueued implements an overloaded method that returns no value. In Lesson 2 of this chapter, we look at how to consume a serviced component. The underlying technology for queued components is message queuing. We look at con suming a queued component in Chapter 13.
Implementing Security on a Serviced Component The Enterprise Services role-based security is unrelated to Windows user groups, allowing you to define roles directly from the application’s business domain, even if no corresponding user groups exist. Management of which domain users belong to a specific group in your application can be done using the Component Services man agement console, but first you need to set up your component to make use of the secu rity features of Enterprise Services.
538
Chapter 12
Creating Serviced Components
You start at the assembly level. In the AssemblyInfo file, add the ApplicationAccessCon trolAttribute, which specifies access controls to an assembly containing ServicedCom ponent classes. 'VB Imports System.EnterpriseServices //C# using System.EnterpriseServices; [assembly: ApplicationAccessControl(true)]
At class level, specify that the class should be enabled for security checking and which roles you’re using in the class. The ComponentAccessControlAttribute enables security checking on calls to a component. The SecurityRoleAttribute configures a role for an application or component. The SecurityRoleAttribute allows for an extra Boolean parameter specifying whether domain group Everyone should be linked to the role. This is especially useful when allowing for a Guest account. 'VB Imports System.EnterpriseServices Namespace MSLearning.Chapter12.Services _ _
_
_
Public Class OrderService
Inherits ServicedComponent
Implements IOrderServiceQueued
'... End Class End Namespace //C# using System.EnterpriseServices; namespace MSLearning.Chapter12.Services { [ComVisible(true)] [ComponentAccessControl(true)]
[SecurityRole("Manager")]
[SecurityRole("Guest",true)]
public class OrderService : ServicedComponent, IOrderServiceQueued
{
Lesson 1: Serviced Component Overview
539
///... } }
Now all that remains is to perform the actual security check. Using COM+ security, use the ContextUtil or SecurityCallContext classes to retrieve information about the user. In this example, we want to change the SubmitOrder method in such a way that only managers are allowed to submit orders. To do this, add a private Boolean prop erty CurrentUserIsManager. To make the exception thrown more informative, include the account name of the user in the exception. 'VB Imports System.EnterpriseServices; Namespace MSLearning.Chapter12.Services _
_
_
_
Public Class OrderService
Inherits ServicedComponent
Implements IOrderServiceQueued
'... _ Public Sub SubmitOrder(ByVal customerId As Integer, ByVal articleId As Integer,
ByVal quantity As Integer)
If CurrentUserIsManager Then Dim db As New AdventureWorksDB()
Dim orderId As Integer
orderId = db.InsertOrder(customerId, articleId, quantity)
Dim q As New BillingEngineQueue()
q.InformOfNewOrder(customerId, orderId)
Else
Dim msg As String
msg = "Only Managers can submit orders. " _
& GetAccountName() _
& " is not a Manager."
Throw New Exception(msg)
End If
End Sub Private ReadOnly Property CurrentUserIsManager() As Boolean
540
Chapter 12
Creating Serviced Components
Get If (ContextUtil.IsSecurityEnabled) Then Return ContextUtil.IsCallerInRole("Manager") Else Return (False) End If End Get End Property Private Function GetAccountName() As String If (ContextUtil.IsSecurityEnabled) Then Return (SecurityCallContext.CurrentCall.OriginalCaller.AccountName) Else
Return String.Empty
End If
End Function
End Class
End Namespace
//C# using System.EnterpriseServices; namespace MSLearning.Chapter12.Services { [ComVisible(true)] [ComponentAccessControl(true)] [SecurityRole("Manager")] [SecurityRole("Guest",true)] public class OrderService : ServicedComponent, IOrderService { ///... [AutoComplete] public void SubmitOrder(int customerId, int articleId, int quantity) {
if (CurrentUserIsManager)
{
AdventureWorksDB db = new AdventureWorksDB(); int orderId = db.InsertOrder(customerId, articleId, quantity); BillingEngineQueue q = new BillingEngineQueue(); q.InformOfNewOrder(customerId, orderId); } else { string msg = "Only Managers can submit orders. " + GetAccountName() + " is not a Manager."; throw new Exception(msg); }
Lesson 1: Serviced Component Overview
541
} public DataSet GetOrders(int customerId) {
AdventureWorksDB db = new AdventureWorksDB();
return db.GetOrders(customerId);
}
///
/// Use ContextUtil to determine if current user is a Manager.
///
///
private bool CurrentUserIsManager
{
get {
if (ContextUtil.IsSecurityEnabled)
{
return ContextUtil.IsCallerInRole("Manager"); } else { return (false);
}
}
}
///
/// Use SecurityCallContext to determine account name of the
caller. /// /// public string GetAccountName() { if (ContextUtil.IsSecurityEnabled) return (SecurityCallContext.CurrentCall.OriginalCaller.AccountName); else return String.Empty; ; } } }
The roles for the component look as shown in Figure 12-8 after registering the com ponent in the COM+ catalog.
542
Chapter 12
Creating Serviced Components
Figure 12-8 The registered component is visible in Component Services
Services Without Components In the previous sections we looked at how to create and register a serviced compo nent. Doing this creates a situation where the serviced component can be configured through the Component Services management console. COM+ 1.5 offers an alterna tive way of gaining access to a subset of Enterprise Services, using the ServiceConfig class. This class has been available since the release of the CLR version 1.1. COM+ 1.5 is available on the Windows XP, Windows 2000, and Windows 2003 platforms. 'VB Public Sub DeleteCustomer(ByVal customerId As Integer) Try ' Create ServiceConfig and set properties to define the behavior ' of the services. Dim svc As New ServiceConfig() svc.Transaction = TransactionOption.RequiresNew svc.TransactionTimeout = 30 svc.TransactionDescription = "Service without Component" svc.TrackingAppName = "AdventureWorks advanced" svc.TrackingComponentName = "Chapter12" svc.TrackingEnabled = True ServiceDomain.Enter(svc) ' Use data access layer for database interaction
Dim db As New AdventureWorksDB()
'db.DeleteCustomer(id)
Lesson 1: Serviced Component Overview
' Use queue proxy for informing remote system
Dim q As New BillingEngineQueue()
'q.InformOfDeletedCustomer(id)
' Commit transaction
ContextUtil.SetComplete()
Catch ex As Exception
' Log error and abort transaction
Trace.WriteLine("ERROR: " & ex.Message)
ContextUtil.SetAbort()
Finally
' Always leave the service domain.
ServiceDomain.Leave()
End Try End Sub //C# public void DeleteCustomer(int id) { try { // Create ServiceConfig and set properties to define the behavior
// of the services.
ServiceConfig svc = new ServiceConfig();
svc.Transaction = TransactionOption.RequiresNew;
svc.TransactionTimeout = 30;
svc.TransactionDescription = "Service without Component";
svc.TrackingAppName = "AdventureWorks advanced";
svc.TrackingComponentName = "Chapter12";
svc.TrackingEnabled = true;
ServiceDomain.Enter(svc);
// Use data access layer for database interaction
AdventureWorksDB db = new AdventureWorksDB();
db.DeleteCustomer(id);
// Use queue proxy for informing remote system
BillingEngineQueue q = new BillingEngineQueue();
q.InformOfDeletedCustomer(id);
// Commit transaction ContextUtil.SetComplete();
}
catch (Exception ex)
{
// Log error and abort transaction
Trace.WriteLine("ERROR: " + ex.Message);
ContextUtil.SetAbort();
}
finally
{
543
544
Chapter 12
Creating Serviced Components
// Always leave the service domain.
ServiceDomain.Leave();
}
}
Not all Enterprise Services are available when using ServiceConfig. Some very useful services such as object pooling, JIT activation, queued components, and loosely cou pled events are not available. Also note that this only works on platforms that run COM+ 1.5 (currently Windows XP, Windows 2000, and Windows 2003).
System.Transactions System.Transactions builds on top of services without components. The most impor tant features offered by System.Transactions are the following: ■
An easy-to-use programming model.
■
Support for transactional code blocks using the TransactionScope class.
■
A fast LTM for appdomain transactions that involve at most a single durable resource manager.
■
Automatic promotion from “lightweight” to distributed transactions on an asneeded basis (as shown earlier in this chapter).
■
Protection against early commits in an asynchronous environment using IDepen dentTransaction.
System.Transactions is built using ServiceConfig. This results in not having to register the serviced component in the COM+ catalog, but the use of COM+ 1.5 has as a con sequence that the namespace is only supported on Windows XP, Windows 2000, and Windows 2003. The following code shows the easiest way to use the classes in the System.Transactions namespace to manage a transaction: 'VB Imports System.Transactions
Imports System.Data.SqlClient
Public Class CustomerService ''' ''' ''' ''' '''
This method demonstrates how to use
System.Transactions.TransactionScope
to manage a transaction.
Lesson 1: Serviced Component Overview
'''
'''
Public Sub AddCustomer(ByVal name As String)
Using ts As New TransactionScope() Dim conn As New SqlConnection("your connection string") conn.Open() Dim cmd As New SqlCommand("insert into Customer ...", conn) cmd.ExecuteNonQuery() conn.Close() ts.Complete() End Using End Sub End Class //C# using using using using using
System; System.EnterpriseServices; System.Diagnostics; System.Transactions; System.Data.SqlClient;
namespace MSLearning.Chapter12.Services { public class CustomerService { ///
/// This method demonstrates how to use
/// System.Transactions.TransactionScope
/// to manage a transaction.
///
///
public void AddCustomer(string name)
{
using (TransactionScope ts = new TransactionScope()) { SqlConnection conn = new SqlConnection("your connection string"); conn.Open(); SqlCommand cmd = new SqlCommand("insert into Customer ...", conn); cmd.ExecuteNonQuery(); conn.Close(); ts.Complete(); } } } }
545
546
Chapter 12
Creating Serviced Components
Lab 1: Building a Simple Serviced Component In this lab, you create a simple serviced component and register it in the COM+ cata log. �
Exercise 1: Creating a Serviced Component
In this exercise, you create a class library named SalesService that offers methods for reading and inserting Sales Reasons values in the AdventureWorks database. 1. Open Visual Studio 2005 and create a blank solution named MSLearning.Exercises. 2. Add a new C# or Visual Basic Class Library project. 3. Name the project AdventureWorksServices. 4. In Solution Explorer, right-click the project and click Properties. In the Default Namespace text box, type MSLearning.Chapter12.Services. 5. In the project’s Properties window, click the Signing tab. Select the Sign The Assembly check box. Create a new key using the drop-down list box. 6. Delete the Class1 file. 7. Select Project, Add New Item, and click Class. Name the class SalesService and define it as illustrated in the following code example: 'VB Namespace MSLearning.Chapter12.Services {
Public Class SalesService
{
}
} //C# using System; namespace MSLearning.Chapter12.Services {
public class SalesService
{
}
}
8. Click Project, Add Reference, click System.EnterpriseServices on the .NET tab, and click OK. 9. Click Project, Add New Item, and click Class. Name the class AdventureWorksDB and define it as illustrated in the following code example, adjusting
Lesson 1: Serviced Component Overview
547
the connection string to point to the right location for the AdventureWorks data base: 'VB Imports System.Data.SqlClient Imports System.EnterpriseServices Namespace MSLearning.Chapter12.Services
_
Friend Class AdventureWorksDB
' Change string to match where your AdventureWorks database is
' located
Private Const cConnectionString As String = _
"DataSource=.\SQLEXPRESS;" & _
"AttachDbFilename='C:\Program Files\Microsoft SQL" & _
"Server\MSSQL.1\MSSQL\Data\AdventureWorks_Data.mdf';" & _
"IntegratedSecurity=True;Connect Timeout=30;User Instance=True"
Friend Sub AdventureWorksDB()
End Sub
Friend Function InsertSalesReason(ByVal name As String, _
ByVal reason As String) As Integer Dim sql As String sql = "INSERT INTO Sales.SalesReason (Name, ReasonType," & _ "ModifiedDate) VALUES ('" & name & "', '" & reason & _ "', GETDATE()); select COPE_IDENTITY()"
Dim conn As SqlConnection
conn = New SqlConnection(cConnectionString)
Dim cmd As New SqlCommand(sql, conn)
conn.Open()
Dim id As Object
id = cmd.ExecuteScalar()
conn.Close()
Return Convert.ToInt32(id)
End Function End Class End Namespace //C# using System; using System.Data.SqlClient; using System.EnterpriseServices; namespace MSLearning.Chapter12.Services {
548
Chapter 12
Creating Serviced Components
[PrivateComponent]
internal class AdventureWorksDB
{
internal AdventureWorksDB()
{
}
private const string cConnectionString = @"DataSource=.\SQLEXPRESS;" + @"AttachDbFilename='C:\Program Files\Microsoft SQL" + @"Server\MSSQL.1\MSSQL\Data\AdventureWorks_Data.mdf';" + @"IntegratedSecurity=True;Connect Timeout=30;" + @"User Instance=True";
internal int InsertSalesReason(string name, string reason) { string sql = "INSERT INTO Sales.SalesReason (Name, ReasonType," + "ModifiedDate) VALUES ('" + name + "', '" + reason + "', " + "GETDATE()); selectSCOPE_IDENTITY()"; SqlConnection conn = new SqlConnection(cConnectionString);
SqlCommand cmd = new SqlCommand(sql, conn);
conn.Open();
object id = cmd.ExecuteScalar();
conn.Close();
return Convert.ToInt32(id);
}
}
}
10. Open the SalesService file and add a public constructor, add System.EnterpriseServices and System.Runtime.InteropServices to the using statements, add an attribute to make the class ComVisible, inherit from ServicedComponent, add attributes to manage transactions, and define the security roles Manager and Guest. Only managers can add a SalesReason. Your code should now look like the following: 'VB Imports System
Imports System.EnterpriseServices
Imports System.Runtime.InteropServices
Namespace MSLearning.Chapter12.Services
_
_
_
_
_
Public Class SalesService
Inherits ServicedComponent
Lesson 1: Serviced Component Overview
Public Sub SalesService()
End Sub
_
Public Sub InsertSalesReason(ByVal name As String, _
ByVal reason As String)
If (ContextUtil.IsSecurityEnabled AndAlso _
ContextUtil.IsCallerInRole("Manager")) Then
Dim db As New AdventureWorksDB()
db.InsertSalesReason(name, reason)
End If
End Sub
End Class End Namespace //C# using System; using System.EnterpriseServices; using System.Runtime.InteropServices; namespace MSLearning.Chapter12.Services { [ComVisible(true)] [Transaction(TransactionOption.Required)] [ComponentAccessControl(true)] [SecurityRole("Manager")] [SecurityRole("Guest", true)] public class SalesService : ServicedComponent { public SalesService()
{
}
[AutoComplete]
public void InsertSalesReason(string name, string reason)
{
if (ContextUtil.IsSecurityEnabled &&
ContextUtil.IsCallerInRole("Manager"))
{
AdventureWorksDB db = new AdventureWorksDB();
db.InsertSalesReason(name, reason);
} } } }
549
550
Chapter 12
Creating Serviced Components
11. Open the AssemblyInfo file. In C# it is visible under the Properties node in Solu tion Explorer. In Visual Basic you have to click Show All Files in Solution Explorer before it becomes visible. 12. Set the COM+ application name to AdventureWorks, enable security checking, and mark the COM+ application as a library component. Your code should look like the following: 'VB Imports System.EnterpriseServices
//C# using System.EnterpriseServices; [assembly: ApplicationName("AdventureWorks")]
[assembly: ApplicationActivation(ActivationOption.Library)]
[assembly: ApplicationAccessControl(true)]
13. On the Visual Studio Project menu, click Rebuild Solution. �
Exercise 2: Registering a Serviced Component
1. Start the Component Services management console by clicking Start, Control Panel, Administrative Tools, Component Services. 2. Browse the Component Services node. On a clean machine your system will look like Figure 12-9. Depending on what applications are installed you might see additional COM+ applications. Close the Component Services management console.
Lesson 1: Serviced Component Overview
551
Figure 12-9 A clean view of the COM+ applications on your system
3. Open the Visual Studio 2005 command prompt by clicking Start, All Programs, Microsoft Visual Studio 2005, Visual Studio Tools, Visual Studio 2005 Com mand Prompt. 4. Change your current directory to the location where you’ve just built the project in Lesson 1. By default, it will be a subfolder in My Documents\Visual Studio 2005\Projects. 5. Type the following: regsvcs AdventureWorksServices.dll AdventureWorks
6. On the Start menu, start the Component Services management console by click ing Start, Control Panel, Administrative Tools, Component Services.
552
Chapter 12
Creating Serviced Components
7. Browse the Component Services node. Your system should look like Figure 12-10 (which is the same as we saw earlier in this chapter).
8. The registered component is visible in Component Services
Lesson Summary ■
Serviced components are part of Enterprise Services, the next generation of COM+.
■
A serviced component is created by inheriting from System.EnterpriseServices.Ser vicedComponent.
■
Interaction from managed code to Enterprise Services is done using a variety of attributes, the ContextUtil class, and the SecurityCallContext class.
■
There are four ways of registering your serviced component in the COM+ cata log: MMC, Service Installation Tool (Regsvcs.exe), dynamic registration, and Windows Installer (MSI) package.
■
Transactions in Enterprise Services use System.Transactions if possible but is backward-compatible with older platforms.
■
The Transaction attribute can be used to declare a class part of a transaction.
Lesson 1: Serviced Component Overview
553
■
The AutoComplete attribute can be used to let a method automatically notify the transaction of success or failure, leading to a vote to commit or abort.
■
The .NET Framework 2.0 offers the LTM, a component that promotes a transac tion only to the DTC if necessary.
■
Services without Component allow for the use of a subset of Enterprise Services with the benefit of not having to preregister the component in the COM+ catalog.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “Serviced Component Overview.” The questions are also available on the com panion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which of the following statements are true? (Choose all that apply.) A. A serviced component needs to implement the IServicedComponent inter face. B. The ServicedComponent base class is located in the System.EnterpriseServices namespace. C. The ServicedComponent base class is located in the System.EnterpriseCompo nents namespace. D. A serviced component needs to inherit from the ServicedComponent class. 2. Which attributes are relevant when implementing transactions using Enterprise Services? (Choose all that apply.) A. System.EnterpriseServices.AutoCompleteAttribute B. System.Runtime.InteropServices.ComVisibleAttribute C. System.EnterpriseServices.TransactionAttribute D. System.EnterpriseServices.JustInTimeActivationAttribute
554
Chapter 12
Creating Serviced Components
3. Which options are valid ways to register a serviced component? (Choose all that apply.) A. Command line: sn.exe –k myassembly.dll
B. Command line: regsvcs.exe myassembly.dll AppName
C. Create an application folder in C:\Program Files\ComPlus and drag MyAs sembly.dll into this folder. D. Create an application node in the Component Services management con sole and drag MyAssembly.dll onto this node.
Lesson 2: Consuming the Serviced Component
555
Lesson 2: Consuming the Serviced Component In this lesson you learn how to consume a serviced component by creating an appli cation that references an assembly that contains a serviced component and accessing methods and properties on the serviced component. You continue where Lesson 1 finished. After this lesson, you will be able to: ■
Create an application that consumes a serviced component.
■
Access methods, properties, and events on a serviced component.
Estimated lesson time: 25 minutes
Referencing the Assembly that Contains the Component In Lesson 1 you learned how to create and register a serviced component. Moving for ward, we now want to create an application that consumes the services on the ser viced component. The first step in this process in creating a reference to the serviced component. This is actually a pretty straightforward process. You create the reference as you would any other reference to an external assembly. Let’s assume you’ve just added a console application named MSLearning.Chapter12.Con soleClient to our existing solution and from this application we want to consume the AdventureWorks OrderService created in Lesson 1. Click Project, Add Reference, and then click MSLearning.Chapter12.Services.dll from the Projects tab. Alternatively, if you created a new solution, you would browse for MSLearning.Chapter12.Services.dll. Now because MSLearning.Chapter12.Services.dll references System.EnterpriseServices.dll, and this is not a default reference, we also need to add a reference to System.EnterpriseServices.dll from our console application. A serviced component reference is created just as you would create any other reference. Now it is important to note that because MSLearning.Chapter12.Services.dll is a strongnamed assembly, the reference you create to this assembly is also very specific. Chang ing the version number of the assembly and redeploying MSLearning.Chapter12.Ser vices.dll means that you break the reference. This broken reference can be solved by
556
Chapter 12
Creating Serviced Components
recompiling the console application against the new version of MSLearn ing.Chapter12.Services.dll or by using a policy that redirects the binding. The imple mentation of policies is outside the scope of this chapter, but you should be aware that it is an option.
Queued Components Referencing queued components works a little differently. The underlying technology of message queues is not covered until Chapter 13, when we return to how to con sume a queued component.
Declaring and Instantiating the Component Having created a reference to the assembly, you are now ready to declare and instan tiate the serviced component. Again, as the following code shows, there is little magic involved. Create an instance of the serviced component as if it were any other class, call SubmitOrder, telling it customer 10 wants to order 5 articles with the article ID 255. If successful, your display shows the orderId; if an exception occurs, you display the exception. 'VB Imports System Imports MSLearning.Chapter12.Services Namespace MSLearning.Chapter12.ConsoleClient
Public Class Program
Sub Main() Dim os As OrderService = New OrderService() Console.WriteLine("Serviced Component is created.") Console.ReadKey() End Sub
End Class
End Namespace
//C# using System; using MSLearning.Chapter12.Services; namespace MSLearning.Chapter12.ConsoleClient {
class Program
{
static void Main(string[] args) {
OrderService os = new OrderService();
Lesson 2: Consuming the Serviced Component
557
Console.WriteLine(“Serviced Component is created.”);
Console.ReadKey();
}
}
}
Access Properties, Methods, and Events of the Component After creating an instance of the OrderService, you want to add code to call SubmitOrder, telling it customer 10 wants to order 5 articles with the article ID 255. If suc cessful, your display shows the orderId; if an exception occurs, you display the exception. 'VB Imports System Imports MSLearning.Chapter12.Services Namespace MSLearning.Chapter12.ConsoleClient
Public Class Program
Sub Main()
Try Dim os As OrderService = New OrderService() Console.WriteLine("Serviced Component is created.") Console.ReadKey() Catch ex As Exception
Console.WriteLine("An error occured: " + ex.Message)
End Try
End Sub
End Class
End Namespace
//C# using System; using MSLearning.Chapter12.Services; namespace MSLearning.Chapter12.ConsoleClient {
class Program
{
static void Main(string[] args) {
try
{
OrderService os = new OrderService(); int orderid = os.SubmitOrder(10, 255, 5); Console.WriteLine("Order " + orderid.ToString() + " has been submitted."); } catch (Exception ex)
558
Chapter 12
Creating Serviced Components
{ Console.WriteLine("An error occured: " + ex.Message); } Console.ReadKey(); }
}
}
Lab 2: Using the Serviced Component in a Separate Application In this exercise, you create a console application named ConsoleClient that creates and consumes the service for inserting SalesReasons that you created in Lesson 1. If you chose to skip Lesson 1, look on the CD for the results of Lesson 1 and continue from there. 1. Open Visual Studio 2005 and open the solution created in Lesson 1, named MSLearning.Exercises. 2. Add a new C# or Visual Basic 2005 Console Application project. 3. Name the project ConsoleClient. 4. In Solution Explorer, right-click the project and click Properties. In the Default Namespace text box, type MSLearning.Chapter12.ConsoleClient. 5. Right-click the project in Solution Explorer and add a project reference to the AdventureWorksServices project. 6. Open the file Program.cs. 7. Modify the code to include an MSLearning.Chapter12.Lesson1 statement, make the Main method create an instance of the SalesService, and call the InsertSalesReason method. You can make up your own name and reason, write to the console that the sales reason has been added, and wait for a keystroke before terminating the application. Your code should look like this: 'VB Imports MSLearning.Chapter12.Services Module Module1 Sub Main() Using service As New SalesService() service.InsertSalesReason("MCP discount", _ "Certified Professionals are our friends.") Console.WriteLine("Sales reason has been added.") Console.ReadLine() End Using
Lesson 2: Consuming the Serviced Component
559
End Sub End Module //C# using System;
using MSLearning.Chapter12.Services;
namespace MSLearning.Chapter12.ConsoleClient {
class Program
{
static void Main(string[] args) { using (SalesService service = new SalesService()) { service.InsertSalesReason("MCP discount", "Certified Professionals are our friends."); Console.WriteLine("Sales reason has been added."); Console.ReadLine(); }
}
}
}
Lesson Summary ■
Referencing a serviced component is no harder than referencing any other man aged code assembly.
■
Calling a method or accessing a property on a serviced component from man aged code is like calling a regular method or property.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 2, “Consuming the Serviced Component.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
560
Chapter 12
Creating Serviced Components
1. Which are true of types that inherit from ServicedComponent? (Choose all that apply.) A. You can use Add Web Reference to consume the service. B. The accessibility of the class needs to be public. C. They have to explicitly implement an interface. D. You can use Add Reference to consume the service. 2. Which methods can you use to create an instance of a serviced component? (Choose all that apply.) A. Use Activator.CreateInstanceFrom(...). B. Add a reference to the assembly and use the new keyword. C. Add a reference to the object registered in the COM+ catalog and use the new keyword. 3. Which of the following statements are true? (Choose all that apply.) A. A serviced component needs to have an application name. B. A serviced component needs to have a strong name. C. A serviced component can only call other serviced components. D. A serviced component can only call components with a strong name.
Chapter 12 Review
561
Chapter Review To further practice and reinforce the skills you learned in this chapter, you can per form the following tasks: ■
Review the chapter summary.
■
Review the list of key terms introduced in this chapter.
■
Complete the case scenarios. These scenarios set up real-world situations involv ing the topics of this chapter and ask you to create a solution.
■
Complete the suggested practices.
■
Take a practice test.
Chapter Summary ■
Serviced components are created by inheriting from System.EnterpriseServices.Ser vicedComponent.
■
Interaction with Enterprise Services is managed by adorning classes and meth ods with attributes that contain metadata.
■
Distributed transaction management is the most frequently used service offered by Enterprise Services.
■
System.Transactions offers a separate programming model for managing transac tions. The LTM escalates the transaction to a distributed transaction if and when required.
■
Consuming a serviced component is no different than calling a method, prop erty, or event on a regular external type.
■
Security through Enterprise Services can be managed externally, separate from the domain roles, using the Component Services management console.
Key Terms ■
Component Services management console
■
Lightweight Transaction Manager
■
Microsoft Distributed Transaction Coordinator
■
transaction isolation levels
562
Chapter 12 Review
Case Scenario In the following case scenarios you apply what you’ve learned about how to use ser viced components. You can find answers to these questions in the “Answers” section at the end of this book.
Case Scenario 1: Reliability Through Transactions Interviews Following is a list of company personnel interviewed and their statements. We have recently seen a big increase in the load on our application server and suddenly we find that orders seem to go missing. Our sales system shows that the order was accepted from the Web site, but the order to ship never entered the shipping system. Current code that seemed to work fine under low server load appears to be less reliable as load increases. I think we don’t manage our transactions right, the original code was completed under severe time constraints, and I don’t believe we’re using distributed transactions. We need to evaluate our code and fix our transaction problem.”
■ IT Department Head
We need to be able to rely on data integrity across the var ious systems we have in play. Right now we are sending invoices for orders in our sales system, but we never ship. We’re losing customers. This is unacceptable!”
■ Lead Business Analyst
“Every customer lost is loss of face, and, on the Internet, the bad experi ences of customers spread like wildfire. What might look like only two or three unhappy customers can turn into the downfall of our online store at the drop of a hat. Fix it!”
■ CIO
Questions Answer the following questions for your manager: 1. Do our data stores support transactions? 2. Do we have the application logic for the online store running on a separate machine from the actual Web pages? 3. How can changes be made to the application logic that will make it more reliable?
Chapter 12 Review
563
Case Scenario 2: Optimizing Performance Interviews Following is a list of company personnel interviewed and their statements. “Our application runs great in the test environment, with a limited amount of users. The server loads seem a little high, but we never wor ried about it too much. In a production environment with many more users, the application grinds to a halt.”
■ IT Department Head
■ Lead Business Analyst
“We need to support more users.”
“We spend all this money on developing this system. Surely it can’t be that hard to improve the performance.”
■ CIO
Questions Answer the following questions for your manager: 1. Are you using object pooling? 2. Is the component designed to be stateless? If so, are you using JIT activation?
Suggested Practices To successfully master the objectives covered in this chapter, complete the following tasks.
Create, Configure, and Access a Serviced Component The following two practices let you implement a serviced component without the aid of a step-by-step tutorial. Try it to test yourself on how well you’ve understood the material. Create a class that inherits from ServicedComponent. Make use of object pooling service in Enterprise Services. Create a console client that tries to create more objects than are configured in your pool. Use the Component Ser vices management console to monitor the result.
■ Practice 1
564
Chapter 12 Review
Create a class that inherits from ServicedComponent. Set the class to require a transaction using the Transaction attribute. Implement a method that inserts data in two separate databases. Use the AutoComplete attribute on the method. Create a console application to run the code. Now adjust the code to make the method throw an exception after inserting data. Look at the transac tion statistics in the Component Services management console.
■ Practice 2
Implement Security in a Serviced Component The following is a little more advanced than the previous test and tests your overview on security. Create a class that inherits from ServicedComponent. Create a method that adds orders to a database. Create a check to see that only users that are in the manager role can add orders for more than $2,000. Add yourself to the man ager role using the Component Services management console.
■ Practice 1
Using Automatic Transactions This practice tests your understanding of automatic transactions and requires a little more time to implement because multiple databases are involved. Create a class library with a serviced component that implements a TransferFunds method. You need to create two databases, each with an account table. An account should have a number of fields, one of which is balance. Your method should accept an amount of money as a parameter and subtract the amount from an account in Database 1 and add it to an account in Database 2. Throw an exception if there are insufficient funds.
■ Practice 1
Hint: Use the AutoComplete attribute on the TransferFunds method. Building on Practice 1, create a Winform application that allows you to select two accounts, one for source and one for destination, and the amount of money that needs to be transfered. Add a reference to the library created ear lier and call the TransferFunds method.
■ Practice 2
Chapter 12 Review
565
Take a Practice Test The practice tests on this book’s companion CD offer many options. For example, you can test yourself on just the content covered in this chapter, or you can test yourself on all the 70-529 certification exam content. You can set up the test so that it closely sim ulates the experience of taking a certification exam, or you can set it up in study mode so that you can look at the correct answers and explanations after you answer each question. MORE INFO
Practice tests
For details about all the practice test options available, see the section titled “How to Use the Prac tice Tests” in this book’s Introduction.
Chapter 13
Serviced Component Management
Having learned how to create and deploy a serviced component in Chapter 12, “Cre ating Serviced Components,” it is now time for you to take matters a step further. In this chapter we take a look at message queuing and securing messages. Microsoft Mes sage Queuing (MSMQ) enables applications running at different times to communicate across networks and systems that might be temporarily offline. MSMQ provides guar anteed message delivery, efficient routing, security, and priority-based messaging. Message queuing can be used to implement scenarios in which messages are sent asynchronously, but synchronous scenarios are also within the realm of possibility. This chapter assumes the use of the latest version of MSMQ, which is MSMQ 3.0. MSMQ 3.0 is available for Microsoft Windows Server 2003 and Windows XP. If you are running older versions of Windows, note that MSMQ 1.0 is part of the Microsoft Windows NT 4.0 option pack. This version requires Microsoft SQL Server for storing the names of the queues. MSMQ 2.0 is part of Microsoft Windows 2000 and uses Microsoft Active Directory directory service for storing queue names. If Active Directory is unavailable, you can only use message queuing in workgroup mode.
Exam objectives in this chapter: ■
■
Create, delete, and set permissions on a message queue. ❑
Create a message queue manually.
❑
Create a message queue programmatically.
❑
Delete a message queue.
❑
Set permissions for a message queue programmatically.
Send messages to a message queue and delete messages from a message queue. ❑
Create a message.
❑
Post a message.
❑
Receive a message synchronously.
❑
Decide which formatter to use.
567
568
Chapter 13
■
■
■
Serviced Component Management
❑
Read a message body.
❑
Delete queued messages.
Receive messages. ❑
Peek at messages.
❑
Enumerate messages.
❑
Receive a message asynchronously.
❑
Use BeginReceive/BeginPeek methods.
❑
Respond to a ReceiveCompleted or PeekCompleted event.
Sign and encrypt messages. ❑
Sign a message.
❑
Encrypt a message by using UseEncryption and EncryptionAlgorithm prop erties.
❑
Handle acknowledgments.
Process messages. ❑
Verify a message source by using a certificate.
❑
Decrypt a message.
❑
Extract response queue details from a message.
❑
Compose and send a response to a response queue.
❑
Apply a rule to a message queue.
Lessons in this chapter: ■
Lesson 1: Message Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 570
■
Lesson 2: Securing Messaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620
Before You Begin To complete the lessons in this chapter, you should be familiar with Microsoft Visual Basic or C# and be comfortable with the following tasks: ■
Creating a library and console application in Microsoft Visual Studio using Visual Basic or C#.
■
Adding references to system class libraries to a project.
Before You Begin
569
■
Adding references to projects within a solution.
■
A basic understanding of remoting methodologies (XML Web services, DCOM, .NET Remoting, CORBA, and so on).
■
A basic understanding of COM+.
■
A basic understanding of queuing technologies like MSMQ.
■
A basic understanding of distributed transactions.
■
A basic understanding of ADO.NET.
■
A basic understanding of serviced components.
■
Must have read and understood Chapter 12 in this book.
Real World Mark Blomsma Message queuing solutions are ideal for building applications that need to be available 24/7. Asynchronous communication is the key to keeping systems online, even when systems in the vicinity of the application go down. I worked on a huge project for a customer where the system we were developing ended up being split into several subsystems. The goal was to be able to keep system A up and running, even when system B was unavailable. The solution was to use mes sage queuing. All data going from A to B was sent using asynchronous messages. System A never did a Web service call or Remote Procedure Call (RPC), it only sent messages. If system B went offline, the messages would collect in a queue, waiting to get processed as soon as system B came back online. Advantages were realized in both availability and performance. Putting a message on the queue is a fire-and-forget action, costing very little in terms of performance. It is impor tant to note that asynchronous processing does impact the way the user inter acts with the system. If processing in system B fails, how do you notify the user? This is part of the design challenge.
570
Chapter 13
Serviced Component Management
Lesson 1: Message Queues In this lesson we look at how to create, use, and monitor message queues using MSMQ technology. You gain an understanding of what a message queue is, how to cre ate one, and how to use Enterprise Services to use distributed transaction manage ment to enlist the usage of a queue in a transaction. You read and write to the queue and use the Microsoft Management Console to monitor the queues. We look at both synchronous and asynchronous use of message queues. After this lesson, you will be able to: ■
Describe the basics of message queuing.
■
Set up a message queue.
■
Send a message to a message queue.
■
Read a message from a message queue.
■
Send and receive a message asynchronously.
■
Enumerate messages.
■
Peek at messages.
■
Correlate messages.
Estimated lesson time: 45 minutes
Message Queuing Basics Before we go into creating and using message queuing, let’s take a step back and have a quick look at the basics of message queuing. Sending messages in a message queuing scenario is much like sending a letter through the mail. With message queuing there is a sending and receiving application, just as with a letter, where there is a sender and recipient. With message queuing you drop your message in a message queue and expect the message to be delivered, much as with a letter that you drop in a mailbox and wait for its delivery. In a message queu ing scenario, if your network is overloaded your message takes a bit longer to get deliv ered, just as if the mailman gets stuck in traffic, your message might arrive a bit later. If the receiving application does not become available within a preset timeout period, the message is rerouted to a dead-letter queue. With the letter, if the recipient is unavailable, perhaps because he or she moved or is on an extended vacation, the let ter remains at the post office; it is returned to the sender after a 30-day period. Perhaps the only difference between message queuing and the mail system is that MSQM guar
Lesson 1: Message Queues
571
antees 100 percent delivery, whereas the postal system has a lower service level. There are two basic kinds of queues: public and private. A public queue is registered in Active Directory and as such can be discovered by browsing the network. A private queue is created on a machine and can only be accessed from another machine if you know the exact name of the machine and queue. Private queues are usually used if operating in Windows workgroup mode or if the queue is meant to connect two appli cations running on the same machine. In workgroup mode, private queues are the only option, but even in domain mode, consider the following advantages of using private queues: ■
Private queues have all the features of public queues except that private queues are not listed in Active Directory and are not discoverable. If you know the queue is available, you can still connect remotely.
■
Private queues are faster to create than public queues because they require no extra time for Active Directory activity.
■
Private queues do not cause Active Directory replication problems.
Aside from queues there are also a number of queues for administrative purposes. Table 13-1 below shows the queue type, description, and syntax of the pathname. Note that you can use “.” if you want to refer to your local machine, for example, “.\Pri vate$\MyQueue”. Table 13-1 Queue Types, Description, and Syntax of the Pathname
Queue Type
Description
Syntax
Public queue
Registered in directory services, can be located by any message queuing applications
MachineName \QueueName
Private queue
Registered on local machine, typically cannot be located by other applications
MachineName\Private$ \QueueName
Journal queue
Contains removed mes sages, queue specific (if enabled)
MachineName \QueueName\Journal$
572
Chapter 13
Serviced Component Management
Table 13-1 Queue Types, Description, and Syntax of the Pathname
Queue Type
Description
Syntax
Machine journal queue
Contains removed mes sages, machine wide (if enabled)
MachineName\Journal$
Machine dead-letter queue
Contains undeliverable messages (if enabled)
MachineName \Deadletter$
Machine transactional dead-letter queue
Contains undeliverable transactional messages (if enabled)
MachineName \XactDeadletter$
Queues can be transactional. When a queue is transactional, the retrieval of a message from the queue triggers the Distributed Transaction Coordinator (DTC) into manag ing a distributed transaction. This process is particularly useful when you want to guarantee that any message removed from the queue has actually been successfully processed. Consider the following scenario: Our online store has accepted an order and sends a message to the shipping system. The shipping system receives the mes sage on the queue, reads it, and wants to insert a shipping order in the database. The insert fails with a message indicating that the transaction log is full. Now what? The message has been retrieved from the queue but has not been processed, and writing a status to the database is impossible because database access is impossible at this time. If the queue has been marked as transactional, by having a distributed transac tion we ensure that when the database insert fails, the removal of the message from the queue is actually rolled back, to be processed when the database becomes avail able again. A proper design choice would be to automatically alert a system adminis trator of the technical difficulties with the system.
Setting Up a Message Queue A queue can be set up manually, using the Microsoft Management Console (MMC), or through code.
Manually Setting Up a Message Queue The following steps show how to set up a message queue manually: 1. Click Start, Control Panel, Administrative Tools, Computer Management, or click Start, Run, and type compmgmt.msc to start the Computer Management console.
Lesson 1: Message Queues
573
2. Expand the Services And Applications node. 3. Expand the Message Queuing node. If you do not see the Message Queuing node, you most likely have not installed MSMQ. Use Add/Remove Windows Compo nents (part of Add/Remove Programs in Control Panel) to install this feature of Windows XP or Windows Server 2003. Your window should resemble Figure 13-1.
Figure 13-1 Default view of Message Queuing in the Computer Management console
4. Next, create a private queue. Right-click Private Queues, click New, click Private Queue, type MyQueue as a name for your queue, and click the Transactional check box (see Figure 13-2).
Figure 13-2 Create a private, transactional, message queue
574
Chapter 13
Serviced Component Management
5. Click OK. The queue has been created, and your screen should resemble Figure 13-3.
Figure 13-3 MyQueue has been created
Setting Up a Message Queue Programmatically The Framework Class Library contains a namespace named System.Messaging that contains classes that encapsulate the queue functionality. To be able to use this namespace, you need to add a reference to System.Messaging.dll. The two most signif icant classes are MessageQueue and Message. Use the static methods on the MessageQueue class to check whether your queue already exists. If it doesn’t, create the queue and set the label to match the name of the queue. The following code shows a console application named QueueSender implementing the check and creation of a queue named ShippingInbox (bold is used to emphasize the relevant portions). Notice how the private$ in the name of the queue is used to indicate that the queue should be pri vate. Special priviliges are required to register a queue in Active Directory so a try/ catch is warranted when creating a queue. On Windows XP, any member of the Users group can create a private queue. 'VB Imports System.Messaging Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox" Sub Main()
Lesson 1: Message Queues
Dim queue As MessageQueue ' Check to see if queue exists using static method. If MessageQueue.Exists(cQueueName) Then
Console.WriteLine("Queue '{0}' already exists.", cQueueName)
queue = New MessageQueue(cQueueName)
Else
Console.WriteLine("Queue '{0}' does not exist.", cQueueName)
Try
' Create the queue and set the label. queue = MessageQueue.Create(cQueueName, True) queue.Label = cQueueName
Console.WriteLine("Queue created.")
Catch exception As Exception
Console.WriteLine("Queue not created. Error message: {0}", _
exception.Message)
End Try
End If
Console.WriteLine("Hit enter...")
Console.ReadLine()
End Sub
End Module //C#
using System; using System.Messaging;
namespace QueueSender { class Program { private const string cQueueName = @".\private$\ShippingInbox"; static void Main(string[] args) {
MessageQueue queue;
// Check to see if queue exists using static method.
if (MessageQueue.Exists(cQueueName))
{
Console.WriteLine("Queue '{0}' already exists.", cQueueName); queue = new MessageQueue(cQueueName); }
else
{
Console.WriteLine("Queue '{0}' does not exist.", cQueueName); try { // Create the queue and set the label. queue = MessageQueue.Create(cQueueName, true); queue.Label = cQueueName; Console.WriteLine("Queue created.");
}
catch (Exception exception)
575
576
Chapter 13
Serviced Component Management
{ Console.WriteLine("Queue not created. Error message: {0}", exception.Message); }
}
Console.WriteLine("Hit enter...");
Console.ReadLine();
} } }
After running the application, use the Computer Management console to check and see that the queue has been created. The Computer Management console should resem ble Figure 13-4.
Figure 13-4 ShippingInbox has been created
Deleting a Message Queue There are two ways to delete a message queue. The first method uses the Computer Management console. Simply right-click the appropriate queue and click Delete. Beware: Any messages in the queue are permanently deleted. The second method uses Delete on the MessageQueue class. The following code shows how this is done (bold is used to emphasize the relevant portions): 'VB Imports System.Messaging
Lesson 1: Message Queues
577
Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox" Sub Main()
If MessageQueue.Exists(cQueueName) Then
MessageQueue.Delete(cQueueName) Console.WriteLine("Queue '{0}' has been deleted.", cQueueName) Else Console.WriteLine("Queue '{0}' does not exist.", cQueueName) End If Console.WriteLine("Hit enter...") Console.ReadLine() End Sub End Module //C# using System;
using System.Messaging;
namespace QueueDelete {
class Program
{
private const string cQueueName = @".\private$\ShippingInbox"; static void Main(string[] args) {
if (MessageQueue.Exists(cQueueName))
{
MessageQueue.Delete(cQueueName); Console.WriteLine("Queue '{0}' has been deleted.", cQueueName); } else { Console.WriteLine("Queue '{0}' does not exist.", cQueueName); } Console.WriteLine("Hit enter..."); Console.ReadLine(); }
}
}
Sending a Message to a Message Queue After creating the message queue, it is now time to send a message. Sending a message is as easy as creating an instance of a Message object and sending it using the Send method on the message queue. There are two types of message delivery: express mes saging and recoverable messaging. Note that the choice for the type of delivery is
578
Chapter 13
Serviced Component Management
made at the message level and not at the queue level. Use the Recoverable option on the message to specify that a message needs to be recoverable.
Express Messaging Using express messaging means that messages are stored in random access memory (RAM) during routing and delivery, providing extremely fast performance but no recoverability if any computer through which the message passes should fail. Notably, express messages are lost whenever the Message Queuing service is stopped. Too many express messages accumulating in RAM can cause paging out to disk, lead ing to performance degradation. Thus, you should not store more express messages than physical memory can hold. Express messages can, however, survive a network failure. For example, if a message queuing application sends express messages through a Message Queuing server and the network link between the Message Queuing server and the destination computer fails, the Message Queuing server continues to store the messages in memory. How ever, if the Message Queuing server fails before the network link is restored, the express messages are lost.
Recoverable Messaging Using recoverable messaging means that messages are written to disk during routing and delivery, making delivery somewhat slower than the use of express messaging, but ideal when failures cannot be tolerated or when computers shut down while mes sages remain in queues (as for mobile clients on laptop computers). If a computer fails or is shut down while sending a message, the message is stored on disk. When the computer is restarted and the Message Queuing service restarts, the sending process is automatically resumed. All recoverable messages are stored on disks in memory-mapped files. Performance gains can be realized by employing several physical disks to store recoverable messages.
Message Aging When sending a message, you need to consider that processing of the message does not occur synchronously. There will be a delay, meaning that the message has a life span of seconds, minutes, hours, or maybe even days, if you let them live that long. As a matter of design you need to consider how long a message should remain valid. For
Lesson 1: Message Queues
579
example, our online shop has an option for next-day delivery. To be able to meet the delivery deadlines, an order needs to be entered into the shipping system before 4:00 p.m. If an express order is entered at 3:30 p.m., this leaves the order valid for 30 min utes. After that the order is not delivered in time. Your system should probably pick up on this and take corrective action; for instance, an e-mail could be sent declaring that due to technical difficulties, the order is to be shipped tomorrow; does the client accept this change, yes or no? Technically this means that the shipping order needs to be redirected from the shipping system back to the online shop. The message offers three properties that help you control how to deal with messages that outlive their usefulness: TimeToReachQueue, TimeToBeReceived, and UseDeadLetterQueue. This property gets or sets the maximum amount of time for the message to reach the queue. If the interval specified by TimeToReachQueue expires before the message reaches its destination, the message is discarded in one of two ways. If UseDeadLetterQueue is set to true, the message is sent to the dead-letter queue. If UseDeadLetterQueue is false, the message is ignored. TimeToReachQueue
Note that you can set the message’s AcknowledgeType property to request that a nega tive acknowledgment message is returned to the sending application if the message does not arrive before the timer expires. If TimeToReachQueue is set to 0 seconds, MessageQueue.Send tries once to send the message to its destination—if the queue is waiting for the message. If the queue is local, the message always reaches it. TimeToBeReceived This property gets or sets the maximum amount of time for the message to be received from the destination queue. If the interval specified by TimeToBeReceived expires before the message is removed from the queue, the message is discarded.
This property gets or sets a value that indicates whether a copy of the message that could not be delivered should be sent to a dead-letter queue. If UseDeadLetterQueue is true, delivery failure (of a nontransactional message) causes the message to be sent to the nontransactional dead-letter queue on the computer that could not deliver the message. UseDeadLetterQueue
In the case of delivery failure for a transactional message, the message is sent to the transactional dead-letter queue on the source machine.
580
Chapter 13
Serviced Component Management
Transactions Let’s take a look at an example where you send a simple string to the queue. The code from before has been refactored so that the check and creation of the queue is moved to a method named GetQueue (bold is used to emphasize the relevant portions). The example shows that you take a string from the command line, put it in the body of a newly created Message object, and send the message. Note that because you designed your message queue to be transactional, you need to either supply the Send method with a reference to a MessageQueueTransaction or specify the MessageQueueTransactionType for defining how the transaction should be handled. 'VB Imports System.Messaging
Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox" Sub Main() Dim queue As MessageQueue queue = GetQueue() ' Enter a text for the body of the message
Console.Write("Enter body: ")
Dim body As String = Console.ReadLine()
' Create message and set properties Dim msg As Message = New Message() msg.Body = body msg.Label = "Message from webapplication to shipping system." msg.Priority = MessagePriority.Normal msg.Recoverable = True msg.TimeToBeReceived = New TimeSpan(1, 0, 0, 0, 0) ' 1 day msg.UseDeadLetterQueue = True msg.UseJournalQueue = True ' Send the message using a single MSMQ transaction queue.Send(msg, MessageQueueTransactionType.Single) Console.WriteLine("Message is sent to queue.")
Console.WriteLine("Hit enter...")
Console.ReadLine()
End Sub Private Function GetQueue() As MessageQueue
'...
Lesson 1: Message Queues
End Function End Module //C# using System; using System.Messaging; namespace QueueSender { class Program { private const string cQueueName = @".\private$\ShippingInbox"; static void Main(string[] args)
{
MessageQueue queue;
queue = GetQueue();
// Enter a text for the body of the message
Console.Write("Enter body: ");
string body = Console.ReadLine();
// Create message and set properties
Message msg = new Message(); msg.Body = body;
msg.Label = "Message from webapplication to shipping system.";
msg.Priority = MessagePriority.Normal;
msg.Recoverable = true;
msg.TimeToBeReceived = new TimeSpan(1, 0, 0, 0, 0); // 1 day
msg.UseDeadLetterQueue = true;
msg.UseJournalQueue = true;
// Send the message using a single MSMQ transaction
queue.Send(msg, MessageQueueTransactionType.Single); Console.WriteLine("Message is sent to queue."); Console.WriteLine("Hit enter..."); Console.ReadLine(); }
///
/// Get queue, create the queue if it does not exist.
///
///
private static MessageQueue GetQueue()
{
//... } } }
581
582
Chapter 13
Serviced Component Management
MessageQueueTransactionType can take one of the following options: A transaction type used for Microsoft Transaction Server (MTS). If there is already an MTS transaction context, it is used when sending or receiving the message. Use this in conjuction with the serviced component transactions you saw in Chapter 12.
■ Automatic
The Send operation is not transactional. In our case this results in a failure to send.
■ None
A transaction type used for single internal transactions. The Send opera tion creates, begins, and commits an internal transaction for sending this single message.
■ Single
Figure 13-5 shows the result when we run the code and enter as the body the text “Ship new camera to Mark Blomsma.”
Figure 13-5 Run the application to send a message
After running the application you can check your results by opening the Computer Management console and checking the ShippingInbox queue to see that the message has been placed in the queue. Double-click the message to inspect its properties. On the Body tab, you can actually inspect the contents of the message, and as you can see in Figure 13-6, the string has been serialized as Extensible Markup Language (XML).
Lesson 1: Message Queues
583
Figure 13-6 The body of the message is serialized as XML
When sending a message, the body of the message is by default serialized as XML. It is also possible to send a binary representation of an object as a message. To do so, you need to utilize the BinaryMessageFormatter, which is part of the System.Messaging namespace. In the following code, notice that instead of putting a string in the body of the message we create an instance of a custom type named ShippingOrder and set the properties of the object (bold is used to emphasize the relevant portions). Set the body of message by using the Write method on the BinaryMessageFormatter. Note that the ShippingOrder class needs to be marked as serializable using the Serial izableAttribute; otherwise, the BinaryMessageFormatter won’t be able serialize the order. 'VB Imports System.Messaging
Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox" Sub Main() Dim queue As MessageQueue queue = GetQueue()
584
Chapter 13
Serviced Component Management
' Enter a text for the body of the message
Console.Write("Enter to: ")
Dim shipTo As String = Console.ReadLine()
Dim order As QueueData.ShippingOrder = New QueueData.ShippingOrder() order.Id = 1
order.ArticleId = 555
order.Quantity = 1
order.ShipTo = shipTo
' Create message and set properties Dim msg As Message = New Message() SetMessageProperties(msg) ' Use the binary formatter for filling the body of the message Dim formatter As BinaryMessageFormatter = New BinaryMessageFormatter() formatter.Write(msg, order) ' Send the message using a single MSMQ transaction queue.Send(msg, MessageQueueTransactionType.Single) Console.WriteLine("Message is sent to queue.") Console.WriteLine("Hit enter...") Console.ReadLine() End Sub Private Sub SetMessageProperties(ByVal msg As Message) '... End Sub Private Function GetQueue() As MessageQueue
'...
End Function
End Module
//C# using System;
using System.Messaging;
namespace QueueSender {
class Program
{
private const string cQueueName = @".\private$\ShippingInbox"; static void Main(string[] args)
{
MessageQueue queue;
queue = GetQueue();
Lesson 1: Message Queues
585
// Enter a text for the body of the message
Console.Write("Enter to: ");
string to = Console.ReadLine();
ShippingOrder order = new ShippingOrder(); order.Id = 1; order.ArticleId = 555; order.Quantity = 1; order.To = to; // Create message and set properties Message msg = new Message(); SetMessageProperties(msg); // Use the binary formatter for filling the body of the message BinaryMessageFormatter formatter = new BinaryMessageFormatter(); formatter.Write(msg, order); // Send the message using a single MSMQ transaction queue.Send(msg, MessageQueueTransactionType.Single); Console.WriteLine("Message is sent to queue."); Console.WriteLine("Hit enter..."); Console.ReadLine(); }
///
/// Set the message properties.
///
///
private static void SetMessageProperties(Message msg)
{
//...
}
///
/// Get queue, create the queue if it does not exist.
///
///
private static MessageQueue GetQueue()
{
//... } } }
If in the preceding sample you replace BinaryMessageFormatter with XmlMessageFormatter you would actually implement the same serialization as the implicit serial ization implemented by the Send method.
586
Chapter 13
Serviced Component Management
Reading a Message from a Queue Reading a message from a queue is very much the reverse of the earlier code. Create a new console application named QueueReceiver, and instead of using Send, use Receive to read from the queue; rather than using Write on the BinaryMessageFormat ter, use Read to format the body back to the original object. Notice how we now use a MessageQueueTransaction when reading from the queue. If anything fails during the formatting of the object, you don’t want the message to be removed from the queue. Because you’re reverting the serialized object back to its original state, your new project needs to have a reference to the assembly that defines the ShippingOrder type. Because you’re deploying this type to both the sending and receiving application, it makes sense to move the ShippingOrder class to a separate assembly, in this case named QueueData.dll. 'VB Imports System.Messaging
Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox" Sub Main()
Dim queue As MessageQueue = GetQueue()
Dim tran As MessageQueueTransaction = New MessageQueueTransaction() tran.Begin() ' Receive will wait for a message to appear in the queue Dim msg As Message = queue.Receive(tran) Dim formatter As BinaryMessageFormatter = New BinaryMessageFormatter() Dim body As Object = formatter.Read(msg) Console.WriteLine("Type: " + body.GetType().ToString()) tran.Commit() Console.WriteLine("Hit enter...") Console.ReadLine() End Sub Private Function GetQueue() As MessageQueue
'...
End Function
End Module
//C#
Lesson 1: Message Queues
587
using System; using System.Messaging; namespace QueueRecipient { class Program { private const string cQueueName = @".\private$\ShippingInbox"; static void Main(string[] args)
{
MessageQueue queue = GetQueue();
using (MessageQueueTransaction tran =
new MessageQueueTransaction())
{ // Receive will wait for a message to appear in the queue Message msg = queue.Receive(tran); BinaryMessageFormatter formatter = new BinaryMessageFormatter(); object body = formatter.Read(msg); Console.WriteLine("Type: " + body.GetType().ToString()); } Console.WriteLine("Hit enter...");
Console.ReadLine();
}
///
/// Get queue, create the queue if it does not exist.
///
///
private static MessageQueue GetQueue()
{
//... } } }
The preceding code shows that the object in the queue is of type QueueData.ShippingOrder. Start the QueueReceive application. It runs and waits for a message to appear in the queue. Subsequently start the QueueSender application from the previous paragraph. Try to open the console applications next to each other. As soon as the QueueSender application puts the message in the queue you’ll see the QueueRecipi ent application retrieve the message from the queue and print the type.
588
Chapter 13
Serviced Component Management
Deleting a Message It is important to note that it is not possible to delete a message from the queue pro grammatically. There is no Delete on a queue. By receiving a message it is automati cally removed from the queue.
Sending and Receiving a Message Asynchronously Figure 13-7 shows a message being delivered instantaneously. It is important to note that even though delivery of the message took very little time, the actual sending and receiving of the message is an asynchronous act. You can test this by running the QueueSender application without running the QueueRecipient. The application starts and you can send a message. However, because the QueueRecipient application is not running, the message has nowhere to go and sits in the queue until it either gets retrieved from the queue, or the the time span you set for TimeToBeReceived has expired. In the latter case the message is moved to the queue named Dead-letter mes sages, also known as the dead-letter queue.
Figure 13-7 The message is delivered instantaneously
Sending and Receiving a Message Synchronously You’ve seen how we can use queues to send messages in an asynchronous manner. Now in some very unique scenarios it might be interesting to implement a synchro nous scenario using MSMQ. This would in effect create an RPC using the MSMQ pro tocol, and the advantage would be that it is possible to have the RPC return a value. MSQM does support this behavior. Because it is considered a highly unlikely sce
Lesson 1: Message Queues
589
nario, the .NET Framework Class Library classes that are available in the System.Mes saging namespace do not support RPC Message Queuing. If you’re interested in pursuing this scenario, search the MSDN Web site (http://msdn.microsoft.com) for RPC in conjunction with MSQM; you’ll find a number of interesting items under the header of “RPC Message Queuing” (http://msdn.microsoft.com/librar y/ default.asp?url=/library/en-us/rpc/rpc/rpc_message_queuing.asp).
Listening to a Queue Asynchronously The Peek and Receive methods will examine the queue and wait for a message to become available. You can specify how long to listen using a TimeSpan as one of the parameters. The code will either halt until a message arrives or wait for the amount of time specified. If you wish to run code while listening to the queue then one option is to use the methods BeginPeek, BeginReceive, EndPeek, and EndReceive to listen to the queue in an asynchronous manner. Or even use the PeekCompleted or ReceiveCompleted events. If such a scenario fits with your needs, it is recommended that you look further into how this part of the MSQM API works. NOTE
Using BackgroundWorkerProcess
If the scenario you’re considering involves asynchronous listening to a queue where a WinForm application wants to monitor a queue in the background while the user continues using the user interface, then using the BackgroundWorkerProcess would be more appropriate.
Sending Objects Using Messages By using generics, it becomes relatively easy to encapsulate a message queue and cre ate a typed representation of the message queue. There are numerous ways to do this, one of which is through inheritance. In the following code you see a class that inherits from MessageQueue and adds a generic type argument. The type parameter is passed on as a type parameter to the IMessageQueue interface, and used in the Send and Receive methods of the generic MessageQueue type of T. The static GetQueue method from the earlier example has also been moved to this new class and now returns a object of type T as well. 'VB Imports System
Imports System.Messaging
'/
'/ Simple interface for sending and receiving messages on a typed queue.
590
Chapter 13
Serviced Component Management
'/
'/
Public Interface IMessageQueue(Of T)
Sub Send(ByVal obj As T)
Function Receive() As T
End Interface
'/
'/ Generic message queue, using binary formatting for serializing objects.
'/
'/
Public Class MessageQueue(Of T)
Inherits System.Messaging.MessageQueue
Implements IMessageQueue(Of T)
Sub New()
MyBase.New()
End Sub
Sub New(ByVal path As String)
MyBase.New(path)
End Sub
Public Shadows Sub Send(ByVal obj As T) Implements IMessageQueue(Of T).Send Dim msg As Message = New Message() ' Use the binary formatter for filling the body of the message Dim formatter As BinaryMessageFormatter = New BinaryMessageFormatter() formatter.Write(msg, obj) MyBase.Send(msg, MessageQueueTransactionType.Single) End Sub Public Shadows Function Receive() As T _
Implements IMessageQueue(Of T).Receive
Dim obj As T
Dim tran As MessageQueueTransaction
tran = New MessageQueueTransaction()
tran.Begin()
' Receive will wait for a message to appear in the queue
Dim msg As Message = MyBase.Receive(tran)
Dim formatter As BinaryMessageFormatter = New BinaryMessageFormatter()
Dim body As Object = formatter.Read(msg)
obj = CType(body, T)
tran.Commit()
Return obj
End Function
'/
Lesson 1: Message Queues
'/ Get queue, create the queue if it does not exist.
'/
'/
Public Shared Function GetQueue(ByVal path As String) As MessageQueue(Of T)
Dim queue As MessageQueue(Of T) = Nothing
' Check to see if queue exists using static method.
If MessageQueue.Exists(path) Then
queue = New MessageQueue(Of T)(path)
Else
Try
' Create the queue and set the label.
MessageQueue.Create(path, True)
queue = New MessageQueue(Of T)(path)
queue.Label = path
Catch exception As Exception
'Add error logging.
Throw
End Try
End If
Return queue
End Function End Class //C# using System; using System.Messaging; namespace QueueSendAndReceive { ///
/// Simple interface for sending and receiving messages on a typed queue.
///
///
public interface IMessageQueue
{
void Send(T obj);
T Receive();
}
///
/// Generic message queue, using binary formatting for serializing objects.
///
///
public class MessageQueue : System.Messaging.MessageQueue,
IMessageQueue
{
public MessageQueue()
: base()
{
}
public MessageQueue(string path) : base(path)
591
592
Chapter 13
Serviced Component Management
{
}
public void Send(T obj)
{
Message msg = new Message();
// Use the binary formatter for filling the body of the message BinaryMessageFormatter formatter = new BinaryMessageFormatter(); formatter.Write(msg, obj); base.Send(msg, MessageQueueTransactionType.Single); } public new T Receive() { T obj; using (MessageQueueTransaction tran = new MessageQueueTransaction()) { // Receive will wait for a message to appear in the queue Message msg = base.Receive(tran); BinaryMessageFormatter formatter = new BinaryMessageFormatter(); object body = formatter.Read(msg); obj = (T)body; }
return obj;
}
///
/// Get queue, create the queue if it does not exist.
///
///
public static MessageQueue GetQueue(string path)
{
MessageQueue queue = null;
// Check to see if queue exists using static method.
if (MessageQueue.Exists(path))
{
queue = new MessageQueue(path); } else { try { // Create the queue and set the label. MessageQueue.Create(path, true); queue = new MessageQueue(path); queue.Label = path; } catch (Exception exception) {
Lesson 1: Message Queues
593
//Add error logging.
throw;
}
}
return queue;
} } }
Implementing a generic interface allows you to use the queue in a type-safe manner. It also simplifies the typed message queue by reducing the set of relevant operations to just Send and Receive. In a more real-world piece of code you would want to add a number of overloads to these two methods to facilitate transaction management in a more comprehensive manner, as follows: 'VB Imports System.Messaging Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox" Sub Main(ByVal args() As String)
Dim queue As IMessageQueue(Of QueueData.ShippingOrder)
queue = MessageQueue(Of QueueData.ShippingOrder).GetQueue(cQueueName) ' Enter text for the body of the message
Console.Write("Enter to: ")
Dim shipTo As String = Console.ReadLine()
Dim order1 As QueueData.ShippingOrder = CreateOrder(shipTo) ' Send the message using typed queue
queue.Send(order1)
Console.WriteLine("Message is sent to queue.")
Console.WriteLine("Hit enter...")
Console.ReadLine()
' Receive an object of type QueueData.ShippingOrder
Dim order2 As QueueData.ShippingOrder = queue.Receive()
Console.WriteLine("Received message to: '{0}'", order2.ShipTo)
Console.WriteLine("Hit enter...")
Console.ReadLine()
End Sub Private Function CreateOrder(ByVal shipTo As String) _
As QueueData.ShippingOrder
Dim order As QueueData.ShippingOrder = New QueueData.ShippingOrder()
order.Id = 1
order.ArticleId = 555
594
Chapter 13
Serviced Component Management
order.Quantity = 1
order.ShipTo = shipTo
Return order
End Function End Module //C# using System;
using System.Messaging;
namespace QueueSendAndReceive {
class Program
{
private const string cQueueName = @".\private$\ShippingInbox"; static void Main(string[] args) { IMessageQueue queue; queue = MessageQueue.GetQueue(cQueueName);
// Enter a text for the body of the message
Console.Write("Enter to: ");
string to = Console.ReadLine();
QueueData.ShippingOrder order1 = CreateOrder(to);
// Send the message using typed queue
queue.Send(order1);
Console.WriteLine("Message is sent to queue.");
Console.WriteLine("Hit enter...");
Console.ReadLine();
// Receive an object of type QueueData.ShippingOrder
QueueData.ShippingOrder order2 = queue.Receive();
Console.WriteLine("Received message to: '{0}'", order2.To);
Console.WriteLine("Hit enter...");
Console.ReadLine();
} private static QueueData.ShippingOrder CreateOrder(string to) { QueueData.ShippingOrder order = new QueueData.ShippingOrder(); order.Id = 1; order.ArticleId = 555; order.Quantity = 1; order.To = to; return order; }
}
}
Lesson 1: Message Queues
595
Sending a Message Using Queued Components In Chapter 12 we saw that it is possible to call a method on a serviced component using MSMQ. This is referred to as queued components. Instead of putting a message on a queue in a rather explicit manner, as we have seen in the previous sections, we can use a queued component to encapsulate the particulars of the message. When using queued components, it is recommended that the service you want to make available is specified using an interface. The following code shows what needs to be done. The application needs to be registered in the COM+ catalog with ApplicationActivation set to Server. ApplicationQueueing needs to be enabled using the correspond ing application attribute. The interface you want to expose needs to be marked with InterfaceQueuing and, of course, both the class and the interface need to be ComVisible. The client application doesn’t actually need access to the implementation of IShippingSystem, but it does need to reference the assembly defining the interface. Now instead of using the new statement, use the Marshal class, which can be found in the System.Runtime.InteropServices namespace to create a moniker that references the queue that the ShippingSystem will be listening to. The moniker can be cast to the ISh ippingSystem interface, so in your code and Visual Studio you have full benefit of type checking and intellisense. If you call SendOrder, instead of actually directly calling the method, the moniker creates a message and puts it on the queue. The COM+ compo nent runs in the background, picks up the message from the queue and, in the case of this example, writes an entry in the event log. After you are done with your moni ker, release the COM+ resources by calling Marshal.ReleaseComObject. 'VB Imports Imports Imports Imports
System
System.Reflection
System.EnterpriseServices
System.Runtime.InteropServices
Namespace QueuedComponent
_
_
Public Interface IShippingSystem
Sub SendOrder(ByVal shipTo As String)
End Interface
596
Chapter 13
Serviced Component Management
_ Public Class ShippingSystem
Inherits ServicedComponent
Implements IShippingSystem
Public Sub New()
End Sub
Public Sub SendOrder(ByVal shipTo As String) _ Implements IShippingSystem.SendOrder System.Diagnostics.EventLog.WriteEntry("ShippingOrder", _ "Ship order to: " + shipTo) End Sub End Class Class Program Shared Sub Main(ByVal args() As String) 'ShippingSystem s = new ShippingSystem(); Dim iss As IShippingSystem = Nothing Try iss = CType(Marshal.BindToMoniker( _ "queue:/new:QueuedComponent.ShippingSystem"), IShippingSystem) Catch exception As Exception Console.WriteLine("Error: {0}", exception.Message) End Try iss.SendOrder("MSLearning") Marshal.ReleaseComObject(iss) Console.WriteLine("Order send.") Console.WriteLine("Hit enter...") Console.ReadLine() End Sub
End Class
End Namespace
//C# using using using using
System;
System.Reflection;
System.EnterpriseServices;
System.Runtime.InteropServices;
[assembly: ApplicationName("ShippingSystem")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationQueuing(Enabled = true, QueueListenerEnabled = true)]
namespace QueuedComponent
{
[ComVisible(true)]
[InterfaceQueuing]
public interface IShippingSystem
{
Lesson 1: Message Queues
597
void SendOrder(string to);
}
[ComVisible(true)] public class ShippingSystem : ServicedComponent, IShippingSystem { public ShippingSystem()
{
}
public void SendOrder(string to) { System.Diagnostics.EventLog.WriteEntry("ShippingOrder", "Ship order to: " + to); } } class Program {
static void Main(string[] args)
{
//ShippingSystem s = new ShippingSystem(); IShippingSystem iss = null;
try
{
iss = (IShippingSystem) Marshal.BindToMoniker( "queue:/new:QueuedComponent.ShippingSystem"); }
catch( Exception exception)
{
Console.WriteLine("Error: {0}", exception.Message); } iss.SendOrder("MSLearning"); Marshal.ReleaseComObject(iss); Console.WriteLine("Order send."); Console.WriteLine("Hit enter..."); Console.ReadLine(); }
}
}
As shown by the example, queued components almost completely hide the queuing that is behind the queued components solution. Except for the BindToMoniker state ment, queues remain invisible.
Setting Permissions on a Message Queue So far we’ve looked at and used queues using the default security settings for message queues, and the default is that Everyone has full access. Although this is the most use
598
Chapter 13
Serviced Component Management
ful default for a developer, you will most likely want to change the settings once you are deploying your application. Like most of the features we’ve looked at so far, setting permissions can be done manually or through code. Manually setting the permissions is done using the Computer Management console. Start the Computer Management console, right-click the queue, and click Properties. In the Properties dialog box, click the Security tab. Figure 13-8 shows the default set tings.
Figure 13-8 Default security settings for a message queue
The rights to a queue can be any combination of the following: ■
Full Control
■
Delete Message
■
Receive Message
■
Peek Message
■
Receive Journal Message
■
Get Queue Properties
■
Set Queue Properties
■
Get Permissions
Lesson 1: Message Queues
■
Set Permissions
■
Take Queue Ownership
■
Write Message
599
We look at what these permissions entail in more detail when looking at how to set permissions using code. Setting the permissions on a queue is done using the MessageQueue class SetPermissions method. It is very easy to make a mistake that makes the queue inaccessible from the Computer Management console. To correct any mistakes, you can revert to the default settings using ResetPermissions on the MessageQueue class. The following code demonstrates how to set the permissions on your queue to allow only the user Mark to have full control (bold is used to emphasize the relevant por tions): 'VB Imports System.Messaging Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox" Sub Main()
If MessageQueue.Exists(cQueueName) Then
Dim queue As MessageQueue = New MessageQueue(cQueueName)
queue.SetPermissions("Mark", MessageQueueAccessRights.FullControl) End If
Console.WriteLine("Hit enter...")
Console.ReadLine()
End Sub End Module //C# using System; using System.Messaging; namespace QueuePermissions { class Program { private const string cQueueName = @".\private$\ShippingInbox"; static void Main(string[] args) {
if (MessageQueue.Exists(cQueueName))
{
600
Chapter 13
Serviced Component Management
MessageQueue queue = new MessageQueue(cQueueName); queue.SetPermissions("Mark", MessageQueueAccessRights.FullControl); }
Console.WriteLine("Hit enter...");
Console.ReadLine();
} } }
MessageQueueAccessRights matches the options you have when setting the permissions
manually but extends the granularity of permissions to include messages in the jour
nal queue. Possible values are the following:
ChangeQueuePermissions The right to modify queue permissions.
DeleteJournalMessage The right to delete messages from the journal queue.
The right to delete messages from the queue.
DeleteMessage
DeleteQueue The right to delete the queue.
FullControl
Full rights to the queue. A union of all other rights in the enumeration.
A combination of GetQueueProperties, GetQueuePermissions, ReceiveMes
sage, and ReceiveJournalMessage.
GenericRead
GenericWrite
A combination of GetQueueProperties, GetQueuePermissions, and Write-
Message. The right to read queue permissions.
GetQueuePermissions GetQueueProperties PeekMessage
The right to read properties of the queue.
The right to peek messages from the queue.
The right to receive messages from the journal queue. This includes the rights to delete and peek messages from the journal queue.
ReceiveJournalMessage
The right to receive messages from the queue. This includes the rights to delete and peek messages.
ReceiveMessage
SetQueueProperties TakeQueueOwnership WriteMessage
The right to modify properties of the queue. The right to take ownership of the queue.
The right to send messages to the queue.
The SetPermissions method has a number of overloads, which can be useful when set ting up more complex security scenarios. The following code shows how an AccessControlList is created, two trustees are added, permissions are set, and the list is used to set the permissions on the queue.
Lesson 1: Message Queues
'VB Imports System.Messaging Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox" Sub Main() If MessageQueue.Exists(cQueueName) Then Dim queue As MessageQueue = New MessageQueue(cQueueName) Dim acl As AccessControlList = New AccessControlList()
Dim mark As Trustee = New Trustee("Mark")
Dim enTryMark As AccessControlEnTry = _
New AccessControlEnTry(mark, GenericAccessRights.All, _ StandardAccessRights.All, AccessControlEnTryType.Allow) acl.Add(enTryMark) Dim ken As Trustee = New Trustee("Ken") Dim enTryKen As AccessControlEnTry = _ New AccessControlEnTry(ken, GenericAccessRights.All, _ StandardAccessRights.All, AccessControlEnTryType.Allow) acl.Add(enTryKen) queue.SetPermissions(acl) End If
Console.WriteLine("Hit enter...")
Console.ReadLine()
End Sub End Module //C# using System; using System.Messaging; namespace QueuePermissions { class Program { private const string cQueueName = @".\private$\ShippingInbox"; static void Main(string[] args) {
if (MessageQueue.Exists(cQueueName))
{
MessageQueue queue = new MessageQueue(cQueueName);
AccessControlList acl = new AccessControlList();
Trustee mark = new Trustee("Mark");
AccessControlEntry entryMark = new AccessControlEntry(mark,
GenericAccessRights.All, StandardAccessRights.All, AccessControlEntryType.Allow); acl.Add(entryMark);
601
602
Chapter 13
Serviced Component Management
Trustee ken = new Trustee("Ken"); AccessControlEntry entryKen = new AccessControlEntry(ken, GenericAccessRights.All, StandardAccessRights.All, AccessControlEntryType.Allow); acl.Add(entryKen); queue.SetPermissions(acl);
}
Console.WriteLine("Hit enter...");
Console.ReadLine();
} } }
After running the code, the Computer Management console shows the security set tings for the queue as shown in Figure 13-9.
Figure 13-9 Adjusted security settings for a message queue
Peeking and Enumerating Messages The previous samples have involved receiving just one message at a time. More often than not you’ll be receiving multiple messages. To have a look at all the messages in a queue, you can use the GetAllMessages method on the MessageQueue. Now the GetAllMessages introduces the concept of peeking at a message. Peeking is the process of cre ating a copy of a message in the queue without removing the message from the queue. GetAllMessages loops through all the messages in the queue and creates copies of each one. Use the appropriately named Peek method if you want to just create a copy of the
Lesson 1: Message Queues
603
first message on the queue. The option to peek at messages is often used when there are multiple messages in the queue that are part of a single transaction. This is best illustrated by an example. The following code shows five messages sent to a queue, but rather than using a sep arate transaction for each message, you make all messages part of the same transac tion. Now when you read the messages from the queue, you of course want to process all messages that entered the queue in a write transaction as part of a new, read trans action. If any message fails to process, then all messages need to remain on the queue. Note that the use of GetAllMessages most likely creates unnecessary overhead, but for this demonstration it serves a purpose. 'VB Imports System.Messaging Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox" Sub Main()
' Create access to queue
Dim queue As MessageQueue = New MessageQueue(cQueueName)
queue.MessageReadPropertyFilter.TransactionId = True
Dim writeTransaction As MessageQueueTransaction = _
New MessageQueueTransaction()
writeTransaction.Begin()
Dim i As Integer For i = 0 To 5 - 1 Step i + 1
' Create message and set properties
Dim msg As Message = New Message()
msg.Formatter = New BinaryMessageFormatter()
msg.Body = "Test " + i.ToString()
msg.Label = "Test " + i.ToString()
' Send the message using MSMQ transaction
queue.Send(msg, writeTransaction)
Next
writeTransaction.Commit()
Console.WriteLine("Messages have been sent to queue.") Dim messages() As Message = queue.GetAllMessages()
Console.WriteLine("{0} messages in queue.", messages.Length)
Dim transactiondId As String = messages(0).TransactionId
Console.WriteLine("Retrieving messages part of transaction: {0}", _
transactiondId)
604
Chapter 13
Serviced Component Management
Dim readTransaction As MessageQueueTransaction = _ New MessageQueueTransaction() readTransaction.Begin()
Dim m As Message
For Each m In messages
If m.TransactionId = transactiondId Then ' message is part of read transaction ' retrieve and remove from queue Dim msg As Message = _ queue.ReceiveByLookupId( _ MessageLookupAction.Current, m.LookupId, readTransaction) msg.Formatter = New BinaryMessageFormatter()
Console.WriteLine("Message received: {0}", msg.Body.ToString())
' Use the lines below to see behavior when transaction fails.
' if (msg.Body.ToString().Equals("Test 3"))
' throw new Exception("Testing");
End If
Next
readTransaction.Commit()
Console.WriteLine("Hit enter...")
Console.ReadLine()
End Sub End Module //C# using System;
using System.Messaging;
namespace QueueEnumeration {
class Program
{
private const string cQueueName = @".\private$\ShippingInbox"; static void Main(string[] args)
{
// Create access to queue
MessageQueue queue = new MessageQueue(cQueueName);
queue.MessageReadPropertyFilter.TransactionId = true;
using (MessageQueueTransaction writeTransaction = new MessageQueueTransaction()) { writeTransaction.Begin(); for (int i = 0; i < 5; i++) { // Create message and set properties
Lesson 1: Message Queues
Message msg = new Message();
msg.Formatter = new BinaryMessageFormatter();
msg.Body = "Test " + i.ToString();
msg.Label = "Test " + i.ToString();
// Send the message using MSMQ transaction
queue.Send(msg, writeTransaction);
}
writeTransaction.Commit();
}
Console.WriteLine("Messages have been sent to queue."); Message[] messages = queue.GetAllMessages(); Console.WriteLine("{0} messages in queue.", messages.Length); string transactiondId = messages[0].TransactionId; Console.WriteLine("Retrieving messages part of transaction: {0}", transactiondId); using (MessageQueueTransaction readTransaction =
new MessageQueueTransaction())
{
readTransaction.Begin();
foreach (Message m in messages)
{
if (m.TransactionId == transactiondId) { // message is part of read transaction // retrieve and remove from queue Message msg = queue.ReceiveByLookupId( MessageLookupAction.Current, m.LookupId, readTransaction); msg.Formatter = new BinaryMessageFormatter(); Console.WriteLine("Message received: {0}", msg.Body.ToString()); // Use the lines below to see behavior when // transaction fails. // if (msg.Body.ToString().Equals("Test 3")) // throw new Exception("Testing"); }
}
readTransaction.Commit();
} Console.WriteLine("Hit enter..."); Console.ReadLine(); } } }
605
606
Chapter 13
Serviced Component Management
Note that to be able to retrieve the TransactionId you need to adjust the MessageReadPropertyFilter. MSMQ keeps track of a number of properties. To optimize queue per formance as much as possible, MSQM does not copy all of those properties to every message; instead you need to specify which properties you want to be made available. The preceding sample specified that you want to make the TransactionId available by setting MessageReadPropertyFilter.TransactionId to true.
Correlating Messages A common scenario is for an application to send a message and then wait for a reply, the most common reply being an acknowledgment that the message has been received and processed by the recipient. In message queuing, when the reply is received it needs to be matched to the original message. This process is called corre lating messages. To be able to correlate messages, you need an identifier that can be used by acknowledgment, report, and response messages to reference the original message. The identifier used for correlation of acknowledgments is actually the mes sage ID of the first message. The next code example shows that you use two queues, one for sending your message and a second for receiving acknowledgments. The queues are configured to provide information about acknowledgments, correlation identifiers, and the time a message was sent. When you post your original message to the queue, you add information to the message. The Acknowledgment property is used to specify which acknowledg ments you want to receive; in this case you want notification of the message arriving in the queue and also of being received, or being taken off the queue. The Acknowledg mentQueue property is used to tell the recipient in which queue you want the acknowl edgment. The next step in the code shows how you retrieve the message from the queue. As you can see, no extra code is needed to send the acknowledgments. This is all handled by the MSMQ. Note that at this point you’re still looking at the original message, so you can expect the CorrelationId to be empty. The last step in the code is to look at the acknowledgment queue and see if the mes sage has arrived and whether the recipient has acknowledged receiving the message. You can now use the ID of the original message to look for acknowledgment. Because you’ve requested two acknowledgments, there is a while loop to look for all the expected acknowledgments. 'VB Imports System.Messaging
Lesson 1: Message Queues
Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox"
Private Const cAdminQueueName As String = ".\private$\AdminQueue"
Sub Main() Dim queue As MessageQueue = New MessageQueue(cQueueName) Dim adminQueue As MessageQueue = New MessageQueue(cAdminQueueName) queue.MessageReadPropertyFilter.CorrelationId = True queue.MessageReadPropertyFilter.SentTime = True adminQueue.MessageReadPropertyFilter.CorrelationId = True adminQueue.MessageReadPropertyFilter.Acknowledgment = True adminQueue.MessageReadPropertyFilter.SentTime = True Dim sendMessage As Message = New Message()
sendMessage.Formatter = New BinaryMessageFormatter()
sendMessage.Label = "Test"
sendMessage.Body = "Test"
sendMessage.AcknowledgeType = AcknowledgeTypes.PositiveArrival Or _
AcknowledgeTypes.PositiveReceive sendMessage.AdministrationQueue = adminQueue queue.Send(sendMessage, MessageQueueTransactionType.Single) Dim messageId As String = sendMessage.Id Console.WriteLine("Send Message Information:") Console.WriteLine("Id: '{0}'", sendMessage.Id) Console.WriteLine("Sent at: {0}", System.DateTime.Now.Ticks) Console.WriteLine() Console.WriteLine("Hit enter...") Console.ReadLine() Dim receiveMessage As Message = _ queue.Receive(MessageQueueTransactionType.Single) receiveMessage.Formatter = New BinaryMessageFormatter() Console.WriteLine("Received Message Information:") Console.WriteLine("Id: '{0}'", receiveMessage.Id) Console.WriteLine("CorrelationId: '{0}'", receiveMessage.CorrelationId) Console.WriteLine("Body: '{0}'", receiveMessage.Body) Console.WriteLine("Sent at: {0}", receiveMessage.SentTime.Ticks) Console.WriteLine()
Dim ackReceived As Boolean = False Try While (Not (adminQueue.PeekByCorrelationId(messageId) Is Nothing)) Dim acknowledgmentMessage As Message = _ adminQueue.ReceiveByCorrelationId(messageId) ackReceived = True ' Output acknowledgment message information. The correlation Id ' is identical to the id of the original message. Console.WriteLine("Acknowledgment Message Information:") Console.WriteLine("Id: " + acknowledgmentMessage.Id.ToString())
607
608
Chapter 13
Serviced Component Management
Console.WriteLine("Correlation Id: {0}", _ acknowledgmentMessage.CorrelationId) Console.WriteLine("Acknowledgment Type: {0}", _ acknowledgmentMessage.Acknowledgment) Console.WriteLine("Sent at: {0}", _ acknowledgmentMessage.SentTime.Ticks) Console.WriteLine() End While Catch exception As Exception If Not ackReceived Then Console.WriteLine("Acknowledgement not received. (error: {0})", _ exception.Message) End If End Try Console.WriteLine("Hit enter...")
Console.ReadLine()
End Sub
End Module //C# using System; using System.Messaging; namespace QueueCorrelation { class Program { private const string cQueueName = @".\private$\ShippingInbox"; private const string cAdminQueueName = @".\private$\AdminQueue"; static void Main(string[] args) { MessageQueue queue = new MessageQueue(cQueueName); MessageQueue adminQueue = new MessageQueue(cAdminQueueName); queue.MessageReadPropertyFilter.CorrelationId = true; queue.MessageReadPropertyFilter.SentTime = true; adminQueue.MessageReadPropertyFilter.CorrelationId = true; adminQueue.MessageReadPropertyFilter.Acknowledgment = true; adminQueue.MessageReadPropertyFilter.SentTime = true; Message sendMessage = new Message();
sendMessage.Formatter = new BinaryMessageFormatter();
sendMessage.Label = "Test";
sendMessage.Body = "Test";
sendMessage.AcknowledgeType = AcknowledgeTypes.PositiveArrival | AcknowledgeTypes.PositiveReceive; sendMessage.AdministrationQueue = adminQueue; queue.Send(sendMessage, MessageQueueTransactionType.Single);
string messageId = sendMessage.Id;
Console.WriteLine("Send Message Information:");
Lesson 1: Message Queues
Console.WriteLine("Id: '{0}'", sendMessage.Id); Console.WriteLine("Sent at: {0}", System.DateTime.Now.Ticks); Console.WriteLine(); Console.WriteLine("Hit enter..."); Console.ReadLine(); Message receiveMessage = queue.Receive(MessageQueueTransactionType.Single); receiveMessage.Formatter = new BinaryMessageFormatter(); Console.WriteLine("Received Message Information:"); Console.WriteLine("Id: '{0}'", receiveMessage.Id); Console.WriteLine("CorrelationId: '{0}'", receiveMessage.CorrelationId); Console.WriteLine("Body: '{0}'", receiveMessage.Body); Console.WriteLine("Sent at: {0}", receiveMessage.SentTime.Ticks); Console.WriteLine();
bool ackReceived = false; try { while ((adminQueue.PeekByCorrelationId(messageId) != null))
{
Message acknowledgmentMessage =
adminQueue.ReceiveByCorrelationId(messageId); ackReceived = true; // Output acknowledgment message information. The // correlation Id is identical // to the id of the original message. Console.WriteLine("Acknowledgment Message Information:"); Console.WriteLine("Id: " + acknowledgmentMessage.Id.ToString());
Console.WriteLine("Correlation Id: {0}" +
acknowledgmentMessage.CorrelationId);
Console.WriteLine("Acknowledgment Type: {0}",
acknowledgmentMessage.Acknowledgment);
Console.WriteLine("Sent at: {0}",
acknowledgmentMessage.SentTime.Ticks);
Console.WriteLine();
} } catch (Exception exception) { if (!ackReceived)
{
Console.WriteLine(
"Acknowledgement not received. (error: {0})",
exception.Message);
} } Console.WriteLine("Hit enter..."); Console.ReadLine();
609
610
Chapter 13
Serviced Component Management
} }
}
The result of the preceding code is shown in Figure 13-10. Notice that the sent time is only accurate to the second and is therefore not too useful.
Figure 13-10 Two acknowledgments have been received
Rules and Triggers The .NET Framework 2.0 does not offer any support for creating rules or triggers on a queue. It is, however, possible to set up one or more rules using the Computer Man agement console. A trigger will allow you to associate a COM component or an exe cutable to an incoming message, passing properties of the message as parameters. If you wish to make your .NET application callable from a trigger, you will need to create a COM Callable Wrapper for your assembly or create a console application that accepts parameters as command line arguments.
Lab 1: Creating a Message and Sending It to a Queue In this lab, you create a queue, create a message, and send that message to the queue. If you encounter a problem completing an exercise, the completed projects are avail able on the companion CD in the Code folder. �
Exercise 1: Creating a Queue
In this exercise, you create an application that creates a transactional queue named InvoicingQueue.
Lesson 1: Message Queues
611
1. From the Start menu, launch the Computer Management console by clicking Start, Control Panel, Administrative Tools, Computer Management. 2. Check the private queue to see that there is no queue named InvoicingQueue (see Figure 13-11). Depending on what you have installed on your machine you might see more queues than shown in Figure 13-11.
Figure 13-11 Check that there is no InvoicingQueue
3. If there is an InvoicingQueue, right-click that queue and click Delete. 4. Open Visual Studio 2005 and create a blank solution named MSLearning .Exercises. 5. Add a new C# or Visual Basic 2005 Console Application project named OnlineStore. 6. Right-click the project’s folder and click Properties. In the Default Namespace text box, type MSLearning.Chapter13.Sender. 7. Click Project, Add Reference. Click the .NET tab, click System.Messaging, and click OK. 8. Open the Program.cs or Module1.vb file. 9. Add a private constant string named cQueueName and assign a value reflecting that you want to create a private queue with the name InvoicingQueue. Add a using/imports statement to indicate that you want to use classes from System .Messaging, and add a new method named GetQueue. Add code to the method to check and see whether the InvoicingQueue exists; if it does not exist, create a
612
Chapter 13
Serviced Component Management
nontransactional queue, set the label, return the InvoicingQueue, adjust Main to call GetQueue, and wait for the user to press Enter. 'VB Imports System.Messaging Module Module1 Private Const cQueueName As String = ".\private$\InvoicingQueue" Sub Main() Dim queue As MessageQueue = GetQueue(cQueueName) Console.WriteLine("Using queue: {0}", cQueueName) Console.WriteLine("Hit enter...") Console.ReadLine() End Sub Function GetQueue(ByVal path As String) As MessageQueue Dim queue As MessageQueue If MessageQueue.Exists(path) Then queue = New MessageQueue(path) Else queue = MessageQueue.Create(path) End If queue.Label = path Return queue End Function
End Module
//C# using System;
using System.Messaging;
namespace MSLearning.Chapter13.Queues {
class Program
{
private const string cQueueName = @".\private$\InvoicingQueue"; static void Main(string[] args) { MessageQueue queue = GetQueue(cQueueName); Console.WriteLine("Using queue: {0}", cQueueName); Console.WriteLine("Hit enter..."); Console.ReadLine(); } static private MessageQueue GetQueue(string path) { MessageQueue queue; if (MessageQueue.Exists(path)) {
Lesson 1: Message Queues
613
queue = new MessageQueue(path);
}
else
{
queue = MessageQueue.Create(path, false); } queue.Label = path; return queue; }
}
}
10. Start the console application. 11. Use the Computer Management console to check that the queue has been cre ated, as shown in Figure 13-12.
Figure 13-12 InvoicingQueue has been created �
Exercise 2: Creating and Sending a Message
In this exercise, you use the application created in Exercise 1 to create and send a mes sage containing some simple order information. 1. Add a new method named CreateMessage. Implement code that sets the body of the message to “CustomerId=888;OrderId=501;ArticleId=9900232;Quan tity=10,” use XML as formatter, and set the label of the message to read “Request to invoice customer 888 for order 501.” Make the message recoverable, and give the message a life span of 1 hour. 'VB Private Function CreateMessage() As Message
Dim body As String =
"CustomerId=888;OrderId=501;ArticleId=9900232;Quantity=10"
Dim message As Message = New Message(body)
614
Chapter 13
Serviced Component Management
message.Formatter = New XmlMessageFormatter(New Type() {Type.GetType("System.String")})
message.Label = "Request to invoice customer 888 for order 501."
message.Recoverable = True
message.TimeToBeReceived = New TimeSpan(1, 0, 0)
Return message
End Function //C# static private Message CreateMessage() { string body = "CustomerId=888;OrderId=501;ArticleId=9900232;Quantity=10"; Message message = new Message(body); message.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) }); message.Label = "Request to invoice customer 888 for order 501."; message.Recoverable = true; message.TimeToBeReceived = new TimeSpan(1, 0, 0); return message; }
2. Now add code to Main to call CreateMessage and use the queue object to send the newly created message: 'VB Sub Main()
Dim queue As MessageQueue = GetQueue(cQueueName)
Console.WriteLine("Using queue: {0}", cQueueName)
Dim message As Message = CreateMessage()
queue.Send(message)
Console.WriteLine("Send message to queue.")
Console.WriteLine("Hit enter...")
Console.ReadLine()
End Sub
//C# static void Main(string[] args) {
MessageQueue queue = GetQueue(cQueueName);
Console.WriteLine("Using queue: {0}", cQueueName);
Message message = CreateMessage();
queue.Send(message);
Console.WriteLine("Send message to queue.");
Console.WriteLine("Hit enter...");
Console.ReadLine();
}
3. Start the console application.
Lesson 1: Message Queues
615
4. Use the Computer Management console to check that a message is now visible in the queue. You might have to refresh the display for the message to become visible (see Figure 13-13).
Figure 13-13 One message in the queue
5. Inspect the body of the message in the message’s Properties dialog box on the Body tab. Notice how the string is wrapped in XML (see Figure 13-14).
Figure 13-14 Body of the message is wrapped in XML
616
Chapter 13
�
Serviced Component Management
Exercise 3: Receiving a Message
In this exercise, you use the application created in Exercises 1 and 2 to receive the information that was put into the queue. Note that in a real-world scenario you would not use the same application to both send and receive a message in this manner, but for educational purposes this is a good way to get started. 1. Add code to Main to wait for an incoming message. On receiving a message, write the label and body to the console, and do not forget to set the correct formatter on the message: 'VB Sub Main()
Dim queue As MessageQueue = GetQueue(cQueueName)
Console.WriteLine("Using queue: {0}", cQueueName)
Dim message As Message = CreateMessage()
queue.Send(message)
Console.WriteLine("Send message to queue.")
Dim incoming As Message = queue.Receive() incoming.Formatter = New XmlMessageFormatter(New Type() _ {Type.GetType("System.String")}) Console.WriteLine("Incoming message.label : {0}", incoming.Label) Console.WriteLine("Incoming message.body : {0}", incoming.Body) Console.WriteLine("Hit enter...")
Console.ReadLine()
End Sub //C# static void Main(string[] args) {
MessageQueue queue = GetQueue(cQueueName);
Console.WriteLine("Using queue: {0}", cQueueName);
Message message = CreateMessage(); queue.Send(message); Console.WriteLine("Send message to queue."); Message incoming = queue.Receive(); incoming.Formatter = new XmlMessageFormatter( new Type[] { typeof(string) }); Console.WriteLine("Incoming message.label : {0}", incoming.Label); Console.WriteLine("Incoming message.body : {0}", incoming.Body); Console.WriteLine("Hit enter...");
Console.ReadLine();
}
2. Purge the InvoicingQueue using the Computer Management console. Rightclick the Queue Messages node and click All Tasks, Purge (see Figure 13-15).
Lesson 1: Message Queues
617
Figure 13-15 Purge the InvoicingQueue
3. Start the console application. 4. The result should be the following: Using queue: .\private$\InvoicingQueue
Send message to queue.
Incoming message.label : Request to invoice customer 888 for order 501.
Incoming message.body : CustomerId=888;OrderId=501;ArticleId=9900232;
Quantity=10
Hit enter...
Lesson Summary ■
MSMQ enables applications running at different times to communicate across heterogeneous networks and systems that might be temporarily offline.
■
MSMQ provides guaranteed message delivery.
■
MSMQ can be used to implement solutions for both asynchronous and synchro nous messaging scenarios.
■
MSMQ is wrapped by the classes in System.Messaging, the two dominant classes being MessageQueue and Message.
■
A message queue can participate in a distributed transaction, and alternatively System.Messaging offers a MessageQueueTransaction.
■
There are two types of message delivery: express and recoverable.
618
Chapter 13
Serviced Component Management
■
The Computer Management console can be used to monitor activity and status of queues.
■
Permissions can be set using the MessageQueue class or using the Computer Management console.
■
When peeking at a message, a clone of the message is created. Receiving a mes sage, in contrast, removes the message from the queue.
■
Enumerating messages using GetAllMessages allows for peeking at all messages in the queue and iterates over them.
Lesson Review You can use the following questions to test your knowledge of the information in Les son 1, “Message Queues.” The questions are also available on the companion CD if you prefer to review them in electronic form. NOTE
Answers
Answers to these questions and explanations of why each answer choice is right or wrong are located in the “Answers” section at the end of the book.
1. Which of the following statements are true? (Select all that apply.) A. A message queue is always registered in Active Directory. B. The prefix public$\ needs to be used to mark a queue as public. C. Private queues can operate in workgroup mode. D. MessageQueue.Create(“.\\InvoicingQueue”) creates a public queue named InvoicingQueue. 2. Which of the following statements are true? (Select all that apply.) A. A queue with messages cannot be deleted. B. MessageQueue.Remove(“.\\private$\\MyQueue”) deletes a queue by the name of MyQueue. C. MessageQueue.Delete(“.\\private$\\MyQueue”) deletes a queue by the name of MyQueue. D. The Computer Management console can be used to delete a queue. 3. Which of the following statements are true? (Choose the best answer.) A. Only simple types can be used when using queued components.
Lesson 1: Message Queues
619
B. A message can only be sent using System.Messaging.Message. C. The body of a message is always transmitted as binary data. D. When creating a message you do not need to specify whether it is to be part of a transaction. 4. Which of the following statements are true? (Select all that apply.) A. Using the Journal option on a queue allows you to view a message even after it has been removed from the queue. B. If a computer crashes, all messages are lost because MSMQ keeps all the messages in memory. C. After receiving a message from the queue using MessageQueue.Receive(...) you need to call Message.Delete()to remove it from the queue. D. You can retrieve a message from the queue without deleting it using Mes sageQueue.Peek(...).
620
Chapter 13
Serviced Component Management
Lesson 2: Securing Messaging There are two parts to securing a message: signing a message and encrypting a mes sage. In this lesson we look at signing and encypting messages, and, of course, at receiving these messages. After this lesson, you will be able to: ■
Sign a message.
■
Encrypt a message.
■
Validate a message.
Estimated lesson time: 25 minutes
Signing a Message MSMQ authenticates messages using internal or external security certificates. A cer tificate is used to generate a digital signature that uniquely identifies a user who sends an authenticated message. Both the user’s certificate and digital signature are attached to a message when it is sent. The digital signature is encrypted by the MSMQ service on the sending computer and decrypted by the MSMQ service on the destina tion computer. Internal certificates, provided by MSMQ, authenticate the Windows security identi fier (SID) of the sender. When an internal certificate is used, MSMQ guarantees only that the SID attached to the message is valid. Internal certificates are created by MSMQ the first time a user runs MSMQ in Control Panel. Users must register their internal certificates in the Message Queue Information Service (MQIS) before sending authenticated messages. External certificates use information about a user, supplied by a certificate authority (CA) rather than the sender’s SID, to verify the sender’s identity. The information in the external certificate is guaranteed by the CA that created the certificate. External certificates are required for sending authenticated messages to operating environ ments other than Windows machines. If external certificates are used for sending authenticated messages to other computers running versions of Windows Worksta tion or Windows Server, users must register their certificates only if they also want their SIDs to be used in authenticating the message. Doing so provides an additional— although optional—measure of authentication.
Lesson 2: Securing Messaging
621
Authentication is the process of verifying that the message sender is authentic (that is, that the sender has not been impersonated by another user). MSMQ uses digital sig natures to provide message authentication. MSMQ authentication also guarantees message integrity (that the message has not been tampered with) and nonrepudiation (because no user can sign a message with another user’s identity, no user can refute that he or she sent a message if it contains his or her signature). You can configure a queue to accept messages from authenticated users only. When configured to accept only messages from authenticated users, MSMQ checks the user credentials to see if a user certificate is available. If so, MSMQ includes the user certif icate and the SenderId in the message. By default, authentication is only available when the machine that you’re sending your message from is in an Active Directory domain. This is because any user that has been authenticated against Active Directory gets an internal user certificate, thus satisfying MSMQ’s need for a certificate. This is reflected in the security settings of MSMQ. When running in workgroup mode, the Security tab is unavailable. As shown in Figure 13-16, when connected to a domain three tabs are available: User Certificate, Service Security, and Security.
Figure 13-16 Machine-level security settings for MSMQ
Actually, the default setting for any queue is to include SenderId, but when running in workgroup mode no SenderId is available. You can adjust the default behavior by set ting AttachSenderId to false.
622
Chapter 13
Serviced Component Management
The following code shows some additional message properties set, namely AttachSenderId, to indicate that you want MSMQ to retrieve your identity from the current thread when sending the message. Use the Authenticated property in conjunction with the SenderId property to verify the sender’s access rights. The UseAuthentication prop erty specifies whether the message needs to be authenticated. If the sending applica tion requests authentication, Message Queuing creates a digital signature and uses it to sign the message when it is sent and authenticate the message when it is received. Use the UseAuthentication property to indicate that authentication is required. If UseAuthentication is false and a message is sent to a queue that accepts only authenticated messages, the message is rejected when it reaches the queue. 'VB Imports System.Messaging Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox" Sub Main()
' Create queue
Dim queue As MessageQueue
queue = MessageQueue.Create(cQueueName, True)
queue.Label = cQueueName
queue.Authenticate = True
' Create message and set properties
Dim msg As Message = New Message()
msg.UseAuthentication = True
msg.AttachSenderId = True
' Use the binary formatter for filling the body of the message
msg.Formatter = New XmlMessageFormatter()
msg.Body = "Test"
' Send the message using a single MSMQ transaction
queue.Send(msg, MessageQueueTransactionType.Single)
Console.WriteLine("Message is sent to queue.")
Console.WriteLine("Hit enter...")
Console.ReadLine()
End Sub End Module //C# using System; using System.Messaging;
Lesson 2: Securing Messaging
623
namespace QueueSenderSecure { class Program { private const string cQueueName = @".\private$\ShippingInbox"; static void Main(string[] args)
{
// Create queue
MessageQueue queue;
queue = MessageQueue.Create(cQueueName, true);
queue.Label = cQueueName;
queue.Authenticate = true;
// Create message and set properties
Message msg = new Message();
msg.UseAuthentication = true;
msg.AttachSenderId = true;
// Use the binary formatter for filling the body of the message
msg.Formatter = new XmlMessageFormatter();
msg.Body = "Test";
// Send the message using a single MSMQ transaction
queue.Send(msg, MessageQueueTransactionType.Single);
Console.WriteLine("Message is sent to queue.");
Console.WriteLine("Hit enter...");
Console.ReadLine();
} } }
After running that code, you’ll be able to inspect the message using the Computer Management console. The Sender tab of the message should resemble Figure 13-17, although the User property should be the name that you use to log on to the system, and the SenderId will also be different.
624
Chapter 13
Serviced Component Management
Figure 13-17 The message is authenticated and the SenderId is included
It is also possible to not include the SenderId but still require authentication. Just change the code to msg.AttachSenderId = false. Your message will then show that it has been authenticated when posted to the message queue, but the identity of the sender is hidden from the recipient, as shown in Figure 13-18.
Figure 13-18 The message is authenticated, but the sender is anonymous
Lesson 2: Securing Messaging
625
If you want to use authentication in workgroup mode, you’ll have to create your own certificate and use the SenderCertificate property on the message to set the certificate. The drawback here is that any certificate is actually acceptable. The recipient of the message needs to inspect the certificate contained in the message and determine whether it wants to accept this certificate. To authenticate without Active Directory, you need to obtain an X.509 certificate. Included with Visual Studio 2005 and also with the .NET Framework 2.0 Software Development Kit is a command-line utility named makecert. Use this tool to create a personal certificate and store in your per sonal certificates: makecert –n "CN=MSMQCertificate" –ss My
The certificate should become visible in your personal certificate store. You can inspect your certificate store by starting Internet Explorer, and clicking Tools, Internet Options, Content, Certificates, Personal. You should see the certificate that you’ve just created, as shown in Figure 13-19.
Figure 13-19 Your personal certificate
The next step is to load the certificate from the personal store and attach it to the mes sage. This is shown in the following code: 'VB Imports System.Messaging Imports System.Security.Cryptography.X509Certificates Module Module1 Private Const cQueueName As String = ".\private$\ShippingInbox"
Private Const cCertificateName As String = "MSMQCertificate"
626
Chapter 1