Developer's Journal 2007-06
Developer's Journal 2007-06
Developer's Journal 2007-06
NET TECHNOLOGIES
Presented by
Using XML with Stored Procedures Effectively in SQL Server 2005 Indexed LINQ
Presorted Standard US Postage PAID St. Croix Press
A Roadmap
Register Today!
W W W . D O T N E T D E V E L O P E R S J O U R N A L . C O M
Visit us at www.dotnetdevelopersjournal.com
[email protected] Editor-in-Chief Patrick Hynds [email protected] Group Publisher Roger Strukhoff [email protected] Mobility Editor Jon Box [email protected] Contributing Editor Derek Ferguson [email protected] Open Source Editor Dennis Hayes [email protected] Product Review Editor Doug Holland [email protected] VB Editor Keith Franklin [email protected] Smart Client Editor Tim Huckaby [email protected] BizTalk Editor Brian Loesgen [email protected]
EDITORIAL BOARD
ADVISORY BOARD
For subscriptions and requests for bulk orders, please send your letters to Subscription Department Subscription Hotline: [email protected] Cover Price: $6.99/issue Domestic: $69.99/yr. (12 issues) Canada/Mexico: $99.99/yr. Overseas: $129.99/yr. (u.s. banks or money orders). Back issues: $12/ea., plus shipping and handling. SYS-CON Media 577 Chestnut Ridge Rd., Woodcliff Lake, NJ 07677 Telephone: 201 802-3000 Fax: 201 782-9601 .NET Developers Journal (ISSN#1541-2849) is published monthly (12 times a year) for $69.99 by SYS-CON Publications, Inc., 577 Chestnut Ridge Road, Woodcliff Lake, NJ 07677. Postmaster: Send address changes to: .NET Developers Journal, SYS-CON Publications, Inc., 577 Chestnut Ridge Road Woodcliff Lake, NJ 07677.
Copyright 2007 by SYS-CON Publications, Inc. All rights reserved. No part of this publication may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopy or any information storage and retrieval system, without written permission. For promotional reprints, contact Reprint Coordinator Megan Mussa, [email protected]. Worldwide Newsstand Distribution Curtis Circulation Company, New Milford, NJ Newsstand Distribution Consultant: Gregory Associates / W.R.D.S. 732 607-9941 - [email protected] For list rental information: Kevin Collopy: 845 731-2684, [email protected]; Frank Cipolla: 845 731-3832, [email protected] All brand and product names used on these pages are trade names, service marks, or trademarks of their respective companies. SYS-CON Publications, Inc., is not affiliated with the companies or products covered in .NET Developers Journal. .NET and .NET-based marks are trademarks or registered trademarks of Microsoft Corporation in the United States and other countries. SYS-CON Publications, Inc., reserves the right to revise, republish and authorize its readers to use the articles submitted for publication.
SUBSCRIPTIONS
EDITORIAL OFFICES
ista is getting some traction as a client OS now and that means developers are starting to see on the horizon that they should begin to support it. This is good and bad. Good because there are lots of cool things for developers in Vista, but bad because Vista changes the game quite a bit on developers relative to security. On the good side Vista has lots more features and functionality, such as the new rewall API and the introduction of gadgets; the bad focuses mostly on trying to do things as a developer on Vista. Let me back up a bit. Security is good, of course, but it is often the case that usability and security are in an inverse relationship. The more secure I make my house, the more likely I will end up calling a locksmith from my porch. Vista has User Access Control (also known as UAC), which prompts the user for permission to do anything that could be construed as above the norm security-wise. It turns out that pretty much everything a developer does is above the norm security-wise, and that is a problem in a world where developers who dont code on the target platform often get killed for writing incompatible apps. This has resulted in a legion of developers that I know who have turned back or stopped at the brink of using Vista for their development. You can turn off UAC and other features, but there is also the fact that older versions of Visual Studio (anything older than Visual Studio 2005, in fact) are not compatible or supported on Vista. Even Visual Studio 2005 has issues as of this writing that some argue make it effectively unsupported. While the latter will certainly resolve itself soon, the fact remains that if you target Visual Studio 2003 or earlier, then life is hard on Vista. Alternatives such as using virtual machines abound, but they all cost something in terms of productivity and sometimes sanity for the developer. Maybe this is just the price that we pay for notching up the security of an operating system. Still it seems the developer bears much more of the brunt than others...
Visit us at www.dotnetdevelopersjournal.com
Inside DNDJ
EDITORIAL
MONKEY BUSINESS
10
INDEXES
Indexed LINQ
28
PRODUCT REVIEW
TM
34
20
Visit us at www.dotnetdevelopersjournal.com
BPEL is the is the BPEL is the BPEL SOA SQL of SOA of SQL of SOA the SQL SOA
Get started building next-generation Get started building next-generation SOA applications with the leading vendor of SOA applications with the leading vendor of BPEL technologies Getnext-generation SOA applications started building next-generation Build BPEL technologies SOA applications with the leading vendor of
with the leader in BPEL technologies BPEL technologies Download BPEL tooling & server software today generation Download BPEL tooling & server software today ding vendor of es Download BPEL tooling & server software today
Copyright 2006 Active Endpoints, Inc. All Rights Reserved. g. All product names are trademarks or service marks of their respective companies. se, Apache Tomcat, JBoss, Visit us at www.dotnetdevelopersjournal.com June 2007 Volume: 5 Issue: 6 rosoft .NET.
4 4 4 4
Visit us at www.dotnetdevelopersjournal.com
Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business
icrosoft has a new set of technologies called Silverlight that are meant to bring rich multimedia to browsers and portable devices. They have released two versions: a full release of version 1.0 and a beta version of 1.1. Version 1.0 is not very interesting, but the 1.1 beta is totally different and is making a big splash. It is .NET based, and of course Mono is working furiously on it. In fact, after Miguel saw Silverlight for the rst time at the Microsoft MIX 07 conference in Las Vegas and was offered the chance to demo Moonlight (the Mono version of Silverlight) at Microsoft Re-MIX 07 in Paris in 21 days, the whole Mono team started on a 21-day death march to implement Moonlight in time to demo it in Paris. After which, Miguel said The past 21 days have been some of the most intense hacking days that I have ever had and the same goes for my team that worked 12 to 16 hours per day every single day including weekends to implement Silverlight for Linux in record time. I know the long hard days Miguel and the Mono team typically work, and when he starts talking about the most intense hacking days he has ever had, part of me wishes I had been part of it, and part of me is glad I wasnt. What can a bunch of monkeys banging on a bunch of typewriters for 21 days create? The complete works of Shakespeare? Nope. Check out Figure 1. Joshua Allen from Microsoft was impressed with the Mono hack-a-thon and blogged about it a bit at http://visitmix.com/ (see the June 21 entry). Note that Mono has not completed implementing the entire Silverlight suit, but the screenshots show how much progress was made in just 21 days. For more screenshots, see http://www. mono-project.com/MoonlightShots. If you go to the bottom of the page and scroll up, you can see how Moonlight progressed. The Moonlight homepage is at http://www.mono-project.com/
Moonlight, and the Silverlight homepages are at http://silverlight.net/ and http://www.microsoft. com/silverlight/. You can also see video of Moonlight animation and video capture on Miguels blog at http://tirania.org/blog/index.html (see the June 24 entries). Moonlight is being developed as part of the Mono Olive Project (the Mono implementation of .NET 3.0). The implementation is now being discussed at http://groups. google.com/group/mono-olive. There are a number of parts that combine to form Silverlight, including a new security model (see a good whitepaper at http://blogs.msdn.com/ shawnfa/archive/2007/05/09/the-silverlight-security-model.aspx), video codecs, and a new version of the .NET libraries labeled .NET 2.1 (aka WPF/E). .NET 2.1 is both a subset (similar to, but different from, the compact framework), and a super set (adds libraries to support the new functionality); dont even get me started on how it relates to .NET 3.0 and 3.5.
C#3.0
Mono continues to work on C#3.0 and this month brings full support for the specication for implicitly typed local variables and implicitly typed arrays. I added for the specication because the specication does not support implicitly typed multidimensional arrays, but the Microsoft C# 3.0 compiler does support them; it is not clear if this is an enhancement or a bug; once this is decided, the Mono team will code accordingly.
Visit us at www.dotnetdevelopersjournal.com
Book Review
President and CEO Fuat Kircaali [email protected] Group Publisher Roger Strukhoff [email protected]
ADVERTISING
Senior Vice President, Sales and Marketing Carmen Gonzalez [email protected]
MCAD/MCSD Training Guide (70-315): Developing and Implementing Web Applications with Visual C# and Visual Studio.NET
Kalani, Amit
MCAD/MCSD Self-Paced Training Kit: Developing Web Applications with Microsoft Visual Basic .NET and Microsoft Visual C# .NET, Second Edition
Webb, Jeff
Advertising Sales Director Megan Mussa [email protected] Associate Sales Manager Corinna Melcon [email protected] Events Manager Lauren Orsi [email protected] Events Associate Sharmonique Shade [email protected]
PRODUCTION
MCAD/MCSD Self-Paced Training Kit: Developing Windows-Based Applications with Microsoft Visual Basic.NET and Microsoft Visual C#.NET, Second Edition
Stoecker, Matthew A.
Art Director Alex Botero [email protected] Associate Art Director Louis F. Cuffari [email protected] Assistant Art Director Tami Lima [email protected]
WEB SERVICES
VP, Information Systems Bruno Decaudin [email protected] Information Systems Consultant Robert Diamond [email protected]
MCAD/MCSD Self-Paced Training Kit: Microsoft .NET Core Requirements, Exams 70-305, 70-315, 70-306, 70-316, 70-310, 70-320, and 70-300 box vol. set
Microsoft Corporation
ACCOUNTING
Financial Analyst Joan LaRose [email protected] Accounts Payable Betty White [email protected]
SUBSCRIPTIONS
201 802-3012 888 303-5282 [email protected]
Programming Microsoft Visual Basic .NET Version 2003 (Book & CDROM)
Balena, Francesco
CUSTOMER RELATIONS
Circulation Service Coordinators Edna Earle Russell [email protected] Alicia Nolan [email protected]
PROVIDED BY
Visit us at www.dotnetdevelopersjournal.com
Figure 1: Silverlight-airlines-demo.png
MonoDevelop
MonoDevelop, the SharpDevelop fork for Mono, has released version 0.14. The MonoDevelop team has made a lot of progress on this release, and they now plan to make two more big releases in the next three months, and then do a feature freeze to prepare for the big version 1.0 release. This release includes a subversion add-in, refactoring, smart C# indenting, and importing/exporting Visual Studio 2005. The next releases are planned to include key binding, improved ASP .NET support, and, from the Summer of Code students, C/C++ support and improved make le integration. Full release notes are at http://www. monodevelop.com/Release_ notes_for_MonoDevelop_0.14.
Facebook
Tyler has posted information on how to use Mono to create Facebook applications; see his Weather# application example at http://www.unethicalblogger.com/ posts/tyler/mono_meet_facebook.
Banshee, the music player, now runs on Windows (screenshot at http://bp3.blogger.com/_ vUUhoww_aGI/RnnZTn6WDSI/AAAAAAAAAOc/ G6lyyAmfS5Q/s1600-h/itsalive.jpg). Mono has a list of what is needed to have complete .NET 2.0 compatibility at http://www.monoproject.com/Completing2.0Prole; currently the list is about 214 items long. Autosize is coming to System.Windows.Forms. This is one of the biggest missing pieces left in Winforms; if you look through the Moma reports, it appears very often, because VisualStudio sets it for most controls (to true or false depending on the control). Also mojoPortal 2.2.2.8 has been released, and Mono is again being built from the same source code, but with a switch that turns off WebParts; see more at http://www.mojoportal.com/ download.aspx. Brian Nickels shows how to make any application a Web server at http://kerrick.wordpress.com/2007/06/12/make-any-net-app-a-webserver/. The last few columns have been so packed that I have neglected to mention that the March issue began my fth year of writing Monkey Business for .NET Developers Journal .
Microsoft has a new set of technologies called Silverlight that are meant to bring rich multimedia to browsers and portable devices
Visit us at www.dotnetdevelopersjournal.com June 2007 Volume: 5 Issue: 6
Feature
By Catalin Sandu
ou might be tempted to say that once you enter the .NET world, youll never look back. Nothing seems too easy for you at this moment, what with the brand-new .NET 3.0 thats just out, high tech and still unexplored in its entirety. However, there are situations in which the past catches up, raising the legitimate question of how you can modify your existing .NET projects to allow for interaction with the old Win32 programming model.
Why would a programmer do that? For one, there are those old legacy projects your customers like to use, and nothing can convince them (yet, you hope) to upgrade to the latest and fastest platform out there, thank you very much. Or if youre among those lucky few whose clients are always eager to take the extra step and keep up with the latest technologies, maybe you still have some in-house projects (Visual Basic 6, anyone?) that are too complicated and would take a lot of time and money to convert to your platform of choice.
10
Visit us at www.dotnetdevelopersjournal.com
Feature
An elegant solution to these problems is to expose your constructs as Component Object Model (COM) objects that can be used without any problem from any Win32 program that can play with COM. The .NET platform allows you to do just that via a fancy intermediate object called COM Callable Wrapper, or CCW, that sits between the COM client and your managed code. More on this later. This article will describe what it takes to expose .NET components as COM objects. Youll nd that the job is not so difcult; in fact, most of the time what youll do is play with some new attributes that would decorate your classes, class members, and methods. Youll even learn how to create an ActiveX control in .NET, something thats still hot off Microsofts hands. But until then, lets start with...
[assembly: ComVisible(true)]
The Basics
The rst thing we need for our purposes is, obviously, a normal, not COM-exposed .NET class, which Ill change into a COM-visible component. Whats more convenient than a hello-there-like component, like the one shown below:
public class HelloClass { public string SayHelloTo(string person) { _name = person; return Hello there, + _name; } private string _name; }
Before modifying this class for COM, I need to do a quick detour and explain some of the theories involved in exposing a .NET class to a COM client. The framework component that is of help here is called COM Callable Wrapper (CCW). This is the middleman that stays between your .NET component and the actual consumer. Every call to your class will pass through this proxy, which will take care of the usual COM behaviors for you, like reference counting. When the reference count reaches zero, the .NET object becomes a good candidate for the next round of the framework garbage-collection process. One more thing about CCW: unlike normal .NET objects, the CCW is allocated from an uncollected heap. This is, in fact, the reason why the wrapper can be used from COM clients in the rst place. The CCW object is created on the y, making everything transparent for you, the .NET class designer. Coming back to our hello-there class, the rst step in the COM-.NET dance is to mark your assembly as being COM visible. For this, you need to go to the AssemblyInfo.cs le for your project (assuming youre writing a C# solution) and change the ComVisible attribute from false (the default) to true, like this:
The effect is that everything thats written in the .NET project will be exposed to the COM world. Well, at least this is the short answer; the real story will get revealed in a moment. The same thing can be accomplished if you go to your projects properties, select the Application section, and then click on the Assembly Information button. The last option in the dialog box that appears is Make assembly COM-visible, and checking it will take care of changing the ComVisible attribute to true. Still, ComVisible has other interesting uses as well, which you need to understand if you have to master the COM game as seen from a .NET vantage point. Whats the point of getting everything for free if you dont get your feet wet, right? Once you mark an assembly as being COM-visible, everything inside can be accessed by COM clients for free, but this is only partly true. What will actually get exposed is the stuff in the assembly thats public. Thats right; all thats private, protected, or internal wont see the COM light, ever. In our hello-there sample, the private data member _name is not exposed at all. The same is true about all static methods and elds from your constructs. The list of things that wont be accessible includes abstract classes, too. Whats more, your public data members will become properties for the new COM object. Did I mention that all types that really need to be exposed should have a default constructor? The ComVisible attribute, which can be used on every possible level (assembly, class, structure, elds, methods, so on), comes in handy when deciding which public parts of your library will get exposed. When using ComVisible(false) on a public method, for example, it wont be visible from COM clients. However, you can still use it from other .NET applications. Now that you know how to play nice with COM, all that remains to be done is to... yes, you guessed it register the resulting assembly in the Windows Registry, so that other applications know about it and use it. The next stop will be...
Visit us at www.dotnetdevelopersjournal.com
11
Feature
necessary entries in the registry. This friendly tool is smart enough to do a little bit more than that, such as generating a type library for the exposed component, or creating a registry script containing the registration details. Neat, isnt it? You might want to play with the tool to see what other options are available. Assuming that our hello component compiles to a Hello.dll library, heres how you can register it:
RegAsm.exe Hello.dll
To verify that the component has been correctly added to the Windows Registry, open the OleView tool. Expand the Grouped by Component Category node, then go to the .NET Category node. Now, assuming that the main namespace where the HelloClass type has been dened is called Hello, you will nd your COM object here (search for Hello. HelloClass). To perform the reverse operation, all thats needed is to add the /unregister (or /u) switch to the above command line. If youre not happy with the default programmatic identier generated for this class, there is an alternative: just use ProgIdAttribute on your class. Normally, the programmatic identier that gets written in the registry is composed of the namespace where the class is dened and the name of the class. This might result in a not-tooeasy to remember ProgID if the namespace chain is too long. ProgIdAttibute takes care of this, giving you control over this aspect too. Note that ProgIDs are limited to 39 characters, including punctuation (only dots are accepted.) To use this attribute, refer to the following snippet:
Here, the default ProgID would have been This. Is.A.Very.Looong.Loooong.Namespace.MyClass. Now COM clients can consume this class using the My.Shorter.ProgID programmatic identier. After registration, the component can be used from any Win32 application. However, theres one more word to be added here. The recommended way of registering a .NET library as a COM component is to assign a strong name to your project, and then install the resulting DLL in the Global Assembly Cache (GAC). In fact, using the command line that Ive just shown assumes that Hello.dll will eventually be added to the GAC. If you really need to deploy the component in its own folder and not place it in the Global Assembly Cache, you have to add the /codebase switch to the command prompt when registering the assembly. This will result in an extra CodeBase entry in the registry, pointing to the correct location of the resulting DLL. You can use this switch for debugging purposes as well. Once the object has been correctly registered in the Windows Registry, you can use it from any COM client. For example, heres how you can consume the Hello.HelloClass component from Visual Basic 6:
Dim obj As Object Set obj = CreateObject(Hello.HelloClass) MsgBox obj.SayHelloTo(Jack) Set obj = Nothing
BUILT-IN .NET TYPE System.SByte (sbyte) System.String (string) System.Int32 (int) System.Int64 (long) System.Byte (byte) System.Int16 (short) System.UInt16 (ushort) System.UInt32 (uint) System.Char (char) System.UInt64 (ulong) System.Boolean (bool) System.Decimal (decimal) System.Single (oat) System.Double (double) System.Object (object)
IDL EQUIVALENT char BSTR long int64 unsigned char short unsigned short unsigned long unsigned short uint64 VARIANT_BOOL wchar_t single double VARIANT
C++ EQUIVALENT char _bstr_t long __int64 unsigned char short unsigned short unsigned long unsigned short unsigned __int64 VARIANT_BOOL DECIMAL oat double _variant_t
Pretty cool, right? While were at it, I can show how you can use the same .NET class from C++. Since theres a little bit more code involved in this case, I invite you to have a look at Listing 1 at the end of this article (to keep things simple, error checking was omitted from the code). You might wonder what customizations can be made at register/unregister time, such as writing additional registry entries or the like. The news is good in this matter, too .NET denes two wonderful method-level attributes: ComRegisterFunctionAttribute and its counterpart ComUnregisterFunctionAttribute. The methods on which they are used will be called whenever the assembly is registered or unregistered:
public class HelloClass { ...code omitted... [ComRegisterFunctionAttribute] public static void RegisterHelloClass(Type t) { MessageBox.Show(Registered type: +
Table 1
12
Visit us at www.dotnetdevelopersjournal.com
Feature
As shown above, the methods used with the two attributes must be static. You can apply both attributes to the same method, but I dont recommend it unless you have a good reason to do so. There can be more such methods in your code, one for each class that gets exposed to COM, but you cant have more than one registration (or deregistration) method for the same class. I have to add here that all the functionality described so far is also accessible in two other ways. One is to check the Register for COM Interop option in the project properties. Note that the component will be registered as if you were using the /codebase switch in the Assembly Registration Tool. The other method is to do it programmatically. The .NET Framework offers a special class for this
in the System.Runtime.InteropServices namespace called RegistrationServices. This class contains a few useful methods that you might nd interesting, such as the possibility to enumerate all registerable types from an assembly. Isnt it nice when you have such great exibility at hand? Still, theres a catch in all that I described so far. Things work as expected, thats for sure, but have you ever wondered why it works at all? The question seems silly at rst; in fact, this is exactly what those attributes are meant to do, right? Well, yes, but there is also a hidden face you didnt see. Each exposed type will automatically get a few other attributes applied to them. It might help a project if you know a little bit more about this and will eventually lead to better designs. Its time to discuss the more serious stuff. Its time to talk about...
/mo
29
/mo
Visit gate.com/ndj
Call 866.233.0602 and mention referral code 2402
*Offer valid for a limited time. Set-up fee may apply.
Visit us at www.dotnetdevelopersjournal.com
13
Feature
otherwise the .NET component wont be of any use for COM clients. This is accomplished via ClassInterfaceAttribute, an attribute dened in the System.Runtime. InteropServices namespace that can be applied to your classes. When used, ClassInterfaceAttribute accepts a ClassInterfaceType argument that will establish the type of interface implemented by the class. You can have a dual class interface (ClassInterfaceType.AutoDual), a dispatch-only interface (ClassInterfaceType.AutoDispatch), or a class for which youll implement your own interface (ClassInterfaceType.None). The default value used by .NET when the class is not marked with this attribute is AutoDispatch. This means that the class implements the IDispatch interface and supports late binding COM clients only thats right, type information will not be generated for the exposed class, and its functionality can be discovered by calling IDispatch:: GetIDsOfNames. Class methods will then be callable via IDispatch::Invoke. This is exactly what I did in Listing 1, and this is what happens behind the scenes in the Visual Basic 6 snippet shown earlier. The next enumeration value is AutoDual. This will generate a dual class interface for you. The resulting component can be used both by late binding clients and by early binding COM clients. This might be wonderful, but let me spoil your happiness before its too late: AutoDual has its own problems and its use is not recommended at all for good designs. Remember that the order of methods in a COM interface is very important. You cant reorder the methods inside an interface and live happily ever after, especially when that piece of code is already in production. A dual class interface needs its own dispatch identiers (DISPIDs) for the public stuff dened for that class. This is exactly what AutoDual will do: it will automatically assign unique DISPIDs to your exposed methods, properties, public elds, and so on. Why is this bad news? A project is a living thing, and as it evolves, youll nd yourself redening classes, moving stuff around, and the next thing you know, your DISPIDs are not the same anymore. Other .NET applications are quite happy with that, but this presents a real versioning problem for already existing early binding COM clients, or clients that have cached the initial dispatch identier values. The basic advice here is to avoid using AutoDual at all costs, or to rely on it only if you dont have any other option. However, there might be a solution for this after all: the ace up .NETs sleeve is called DispId (surprise!), which is a nice attribute that can be applied to methods, elds, and properties. Thus, even if you move things around, rest assured noth-
Voil! You have successfully created an AutoDual class with set dispatch identiers. Lets move on. Whats left for us is ClassInterfaceType.None. No class interface is generated if you use this value, and you need to dene your own interfaces that will then be implemented by the exposed class. This offers the greatest exibility from all values dened by ClassInterfaceType, and is in fact the recommended way of exposing .NET types to COM. When dening interfaces that will later be implemented by a COM-visible .NET component, you do it by giving it a Global Unique Identier (GUID) and specifying the interface type via yet another attribute (InterfaceTypeAttribute):
[Guid(place-a-newly-generated-GUID-here)] [InterfaceType(...a ComInterfaceType enumerated value goes here...)] public class ICoolInterface { ...add some methods... }
Note that the GUID attribute is optional all types in .NET have a GUID associated with them, but if you do want to control this aspect, this is the attribute you need. ComInterfaceType is an enumeration that lets you dene an IUnknown-derivative interface (ComInterfaceType.InterfaceIsIUnknown), an IDispatch-derivative interface (ComInterfaceType.InterfaceIsIDispatch), or a dual interface (ComInterfaceType.InterfaceIsDual), similar to ClassInterface Type. Of course, you can implement non-COM visible interfaces, too (just use ComVisible(false) when dening them, or make them non-public). Obviously, these wont be accessible to COM clients. See Listing 2 for a simple illustration of what Ive just described. It contains a class that implements an IUnknown-type interface, an IDispatch-type interface (I omitted the GUID values from this code), as well as the class that implements them. What if you need to implement a standard COM interface? Youre in luck here, too. All you have to do is rewrite that interface in your solution and add an extra attribute, ComImport; it informs the framework that youre bringing an outside interface into your program. See Listing 3 for a possible denition of the well-known IOleCommandTarget interface (I left the denition for OLECMD and OLECMDTEXT for you as an exercise). Note that here youre forced
14
Visit us at www.dotnetdevelopersjournal.com
Feature
to use a GUID attribute, one whose value must be the same as the one assigned by COM to the original interface. Thats all I need to say about interfaces and classes for now. Now, before moving to more interesting things, I think we have to take another detour and see how .NET types are mapping to COM data types. After all, you need to know what other legacy project will have to deal with when talking with your coolest application ever. I promise to make it short. Itll be only...
a TestTypes class. Now all I have to do is build and register the resulting library (remember our friend, RegAsm.exe?). Once the component is registered, you can use the OleView tool installed with Visual Studio to browse for and look at the generated type library. All I did was apply the methods described in this article.
Exposing Events
Sooner or later you would need to expose some events for your components. Youre already familiar with .NET delegates and events, and youre probably wondering if this knowledge cant be leveraged in the COM world. True enough, this is exactly how COM clients can respond to events happening in your .NET code plus a little help from an intermediate interface. Remember our hello-there class from the beginning of the article? I didnt; its time to add some event-exposing stuff to it. What if the powers that be decide that your class cant accept an empty string as parameter after all, you cant say hello to nobody. An empty string parameter will trigger some kind of error in the code, and this can be done in the form of an HelloError event. To do it right from the COM point of view, the
ISBN 0-9777622-0-3
Visit us at www.dotnetdevelopersjournal.com
15
Feature
event will be dened as a method in the intermediate interface that I told you about:
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] public interface IHelloEvents { [DispId(0x00000001)] void HelloError(string error); }
End Sub Private Sub HelloObject_HelloError(ByVal hello_error As String) MsgBox hello_error, vbOKOnly, Error End Sub
Thats it! Youre ready to build full-blown COM components from within Visual Studio and from your framework (and language) of choice.
Note that the interface derives from IDispatch (required when exposing events to COM), and that Ive thrown my own DISPID for the only interface method. HelloError accepts a string parameter this might be useful if the design requires that all errors from HelloClass need to be self-describing. To use this new interface, all I need to do now is to add it as a COM source interface to HelloClass. Heres the new implementation for the hello-there class:
[ComSourceInterfaces(typeof(IHelloEvents))] public class HelloClass : IHello { ...implementation goes here... }
Note again that the class has been changed a little bit from what I wrote at the beginning of the article. See Listing 5 for all the implementation details. Ive created an IHello interface, dropped the useless _name data member, and then implemented the IHello interface via HelloClass. Theres a reason why I derived my IHello interface from IDispatch in this listing; I want to show you how to use HelloClass from the old Visual Basic 6, and VB6 cant consume IUnknown objects. Of course, making IHello an AutoDual interface will do just ne, too. The VB6 demo program will look pretty much like the one shown below. I assume youre dealing with an application containing one single form with two buttons called HelloJack and HelloNobody. Clicking on either button will call the SayHelloTo method from HelloClass. Dont forget to add a reference to the COM object you have just created.
Dim WithEvents HelloObject As HelloClass Private Sub Form_Load() Set HelloObject = New HelloClass End Sub Private Sub Form_Unload(Cancel As Integer) Set HelloObject = Nothing End Sub Private Sub HelloJack_Click() MsgBox HelloObject.SayHelloTo Jack, vbOKOnly, Hello End Sub Private Sub HelloNobody_Click() HelloObject.SayHelloTo
16
Visit us at www.dotnetdevelopersjournal.com
This is a free add-in for Visual Studio (actually, for Visual Basic 2005) that will help you create window forms and user controls ready to be used from Visual Basic 6. You read it perfectly: ofcially, the toolkit supports only VB.NET-to-VB6 scenarios. In its rst release (September 2006), the Interop Toolkit allowed VB6 applications to display .NET Windows Forms easily. Exposing UserControls as ActiveX components was still in beta at that time, but the last release (May 2007) adds full support for creating such controls from VB.NET, too. Once the toolkit is installed, youll end up with two additional project templates for VB.NET: one for building user controls that are visible from VB6 (the InteropUserControl template) and one to allow VB6 programs to use a Windows Form (the VB6 InteropForm template). There is also a very good documentation with plenty of tutorials, as well as a series of sample projects. Since the toolkit already comes with all that, I wont insist on this subject more than is necessary. I leave it to you to explore the powerful toys contained in this kit. I have to say a few more things here. Although the toolkit is intended to work with VB6 projects, you can still use the resulting ActiveX controls from any COM client. An ActiveX control is an ActiveX
control no matter what; be advised though that the toolkit has been created with VB6 in mind, and you should test your project carefully before going live with it. Its been a long ride and now were going for the home stretch. Im sure there is enough here to make you interested in exploring other .NET-COM interoperability issues. For now, I have to summarize everything up in a nice...
Conclusion
Ive only touched the tip of the iceberg in this article about the .NET and COM playground. There are still the issues of deployment to discuss, or what happens with classes that inherit from ComVisible ones, and which are exposed themselves to COM. Also, there are some issues when having overloaded methods; for example, if you have SomeMethod defined twice, what you get is a COM class whose second method is renamed to SomeMethod_2; this is a real problem for VB6, which doesnt recognize methods with underscores in their names. Anyway, your toolbox now contains one more secret when dealing with legacy applications. This can be the beginning of a beautiful friendship between you and those old projects.
LISTING 1: CALLING A .NET COMPONENT FROM C++ // Initialize the COM system ::CoInitialize(NULL); // Retrieve the CLSID for Hello.HelloClass CLSID clsidHello; LPOLESTR szHello = LHello.HelloClass; ::CLSIDFromProgID(szHello, &clsidHello); // Create the IDispatch instance LPDISPATCH lpDispHello = NULL; ::CoCreateInstance( clsidHello, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch, (LPVOID*)&lpDispHello); // Retrive the DISPID for the SayHelloTo method LPOLESTR szHelloFn = LSayHelloTo; DISPID dispSayHelloTo; lpDispHello->GetIDsOfNames( IID_NULL, &szHelloFn, 1, LOCALE_SYSTEM_DEFAULT, &dispSayHelloTo); // Prepare the parameters for the SayHelloTo method DISPPARAMS dispparams = { NULL, NULL, 0, 0 }; dispparams.cArgs = 1; dispparams.rgvarg = new VARIANT[dispparams.cArgs]; dispparams.cNamedArgs = 0; dispparams.rgvarg[0].vt = VT_BSTR; dispparams.rgvarg[0]. bstrVal = ::SysAllocString(OLESTR(J ack));
// The result will be stored here VARIANT vtRes; ::VariantInit(&vtRes); // Invoke the method lpDispHello->Invoke( dispSayHelloTo, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &vtRes, NULL, NULL); // Here vtRes.bstrVal is equal to Hello there, Jack // Get rid of our component lpDispHello->Release(); // Dont forget to uninitialize COM ::CoUninitialize(); LISTING 2: A CLASSINTERFACETYPE.NONE CLASS IMPLEMENTING TWO INTERFACES using System.Runtime.InteropServices; namespace Test { /// <summary> /// Dene an interface derived from IUnknown /// </summary> [Guid(...), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IInterfaceOne { void TestMethodOne(); } /// <summary> /// Throw in another interface, derived from IDispatch...
Visit us at www.dotnetdevelopersjournal.com
17
Feature
/// </summary> [Guid(...), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface IInterfaceTwo { void TestMethodTwo(); } /// <summary> /// And yet another interface, this time an internal one. /// Even if it is dened with InterfaceIsIUnknown, it /// will not be exposed to COM, because it is internal. /// </summary> [Guid(...), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface INotExposedInterface { void SomeMethod(); } /// <summary> /// This class is dened as ClassInterfaceType.None, and /// implements our interfaces. All members dened by /// these interfaces (except INotExposedInterface, of course) /// are exposed to COM clients. All other methods dened in /// this class are NOT exposed at all, even if they are public. /// That is, ClassInterfaceType.None is used for a class /// that will expose only ComVisible interfaces to the outside /// world. All other methods and members are not exposed. /// </summary> [ClassInterface(ClassInterfaceType.None)] public class TestClass : IInterfaceOne, IInterfaceTwo, INotExposedInterface { // This method IS exposed, because it comes // from the IInterfaceOne interface public void TestMethodOne() {} // This method IS exposed, because it comes // from the IInterfaceTwo interface public int TestMethodTwo() {} // This method, even if implements the method // with the same name from INotExposedInterface // will not be exposed because that interface // is marked as internal. public void SomeMethod() {} // This method IS NOT exposed, because it is // dened by a class marked as ClassInterfaceType.None, // and doesnt come from a ComVisible interface. public void InvisibleMethod() {} } } LISTING 3: THE DEFINITION FOR THE IOLECOMMANDTARGET STANDARD INTERFACE [ComVisible(true), ComImport()] [Guid(B722BCCB-4E68-101B-A2BC-00AA00404770)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IOleCommandTarget { [PreserveSig()] int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] OLECMD[] prgCmds, ref OLECMDTEXT pCmdText); [PreserveSig()] int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdExecOpt, ref object pvaIn, ref object pvaOut); } LISTING 4: CLASS USED TO DISCOVER THE CORRESPONDENCE BETWEEN .NET TYPES AND COM TYPES using System.Runtime.InteropServices; namespace Test { [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface ITestTypes { string GetAString(); int GetAnInt(); long GetALong(); sbyte GetSByte(); byte GetByte(); short GetShort(); ushort GetUShort(); uint GetUInt(); char GetChar(); ulong GetULong(); bool GetBool(); decimal GetDecimal(); oat GetFloat(); double GetDouble(); object GetAnObject(); } [ClassInterface(ClassInterfaceType.None)] public class TestTypes : ITestTypes { public string GetAString() { return string.Empty; } public int GetAnInt() { return Int32.MaxValue; } public long GetALong() { return Int64.MaxValue; } public sbyte GetSByte() { return SByte.MaxValue; } public byte GetByte() { return Byte.MaxValue; } public short GetShort() { return short.MaxValue; } public ushort GetUShort() { return ushort.MaxValue; } public uint GetUInt() { return uint.MaxValue; } public char GetChar() { return char.MaxValue; } public ulong GetULong() { return ulong.MaxValue; } public bool GetBool() { return true; } public decimal GetDecimal() { return decimal.MaxValue; } public oat GetFloat() { return oat.MaxValue; } public double GetDouble() { return double.MaxValue; } public object GetAnObject() { return null; } } } LISTING 5: EXPOSE EVENTS TO COM
18
Visit us at www.dotnetdevelopersjournal.com
using System.Runtime.InteropServices; namespace Test { public delegate void HelloErrorHandler(string error); [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface IHello { string SayHelloTo(string name); } [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] public interface IHelloEvents { [DispId(0x00000001)] void HelloError(string error); } [ClassInterface(ClassInterfaceType.None)] [ComSourceInterfaces(typeof(IHelloEvents))] public class HelloClass : IHello { event HelloErrorHandler HelloError; public string SayHelloTo(string name) { string realName = name.Trim(); if (realName != string.Empty) { return Hello there, + realName; } if (HelloError != null) { HelloError(No one out there?); } return string.Empty; } } }
Advertiser Index
ADVERTISER
Active Endpoints
URL
active-endpoints.com/soa
PHONE
PG
5
ESRI
www.esri.com/develop
1-888-288-1277
Gate.com
www.gate.com/ndj
866-233-0602
13
Microsoft
defyallchallenges.com
36
www.realworldajaxbook.com
201-802-3020
15
www.realworldjava.com
201-802-3020
29
Software FX
www.softwarefx.com
561-999-8888
35
Verio
www.verio.com/dotnetdoneright
888-Verio33
General Conditions: The Publisher reserves the right to refuse any advertising not meeting the standards that are set to protect the high editorial quality of .Net Developers Journal. All advertising is subject to approval by the Publisher. The Publisher assumes no liability for any costs or damages incurred if for any reason the Publisher fails to publish an advertisement. In no event shall the Publisher be liable for any costs or damages in excess of the cost of the advertisement as a result of a mistake in the advertisement or for any other reason. The Advertiser is fully responsible for all financial liability and terms of the contract executed by the agents or agencies who are acting on behalf of the Advertiser. Conditions set in this document (except the rates) are subject to change by the Publisher without notice. No conditions other than those set forth in this General Conditions Document shall be binding upon the Publisher. Advertisers (and their agencies) are fully responsible for the content of their advertisements printed in .Net Developers Journal. Advertisements are to be printed at the discretion of the Publisher. This discretion includes the positioning of the advertisement, except for preferred positions described in the rate table. Cancellations and changes to advertisements must be made in writing before the closing date. Publisher in this General Conditions Document refers to SYS-CON Publications, Inc. This index is provided as an additional service to our readers. The publisher does not assume any liability for errors or omissions.
continued from 34
WebSphere capabilities. Security can be handled by the Tivoli Access manager or by legacy or third-party security solutions. ActiveBPEL also supports all of the WebSphere clustering capabilities including automatic failover and content-based routing. On JBoss, ActiveBPEL uses the Axis JAX-RPC for Web services invocations and message exchange, and also allows direct control by the JBoss WS4EE or EJB bindings. It also integrates with JBossSX security or LDAP servers. ActiveBPEL supports all of the JBoss clustering technology, including JGroups and the Tomcat HttpSession object to provide load balancing, failover, and single sign-on. Truly the people at Active Endpoints understand how important it is to use and integrate with the different platforms they run on.
Future
The difference between BPEL and workow is that BPEL focuses on automating Web services, where workow focuses on connecting people-oriented processes. Active Endpoints has recently released ActiveBPEL for People, which extends BPEL to include people-oriented tasks, including roles and Web-based user interfaces.
More Information
For more information, checkout Active Endpoints Web site at http://www.active-endpoints.com/, there is a nice slide show at http://www.bptrends.com/publicationles/BPEL4ProgArchies.pdf, and Wikipedia has a page on BPEL at http:// en.wikipedia.org/wiki/BPEL.
Visit us at www.dotnetdevelopersjournal.com
19
Feature
ET lets us easily serialize an object into XML and deserialize XML into its corresponding . object. This functionality has been available since .NET 1.0. The introduction of new data type called XML in SQL Server 2005 gives us even more advantages that come in handy with Stored Procedures that attempt to insert/update records in multiple but related tables. Usually this involves passing a huge number of parameters that make up the individual objects to the Stored Procedure; but with SQL Server 2005 we could potentially serialize the object(s) into an XML string and pass it as the input parameter, leaving us with cleaner code thats easy to read and maintain. Consider the case of having a Person object with list of Address and Phone objects. The corresponding database schema would have tables for Person, Address, and Phone. When a new Person is inserted into the database (along with the corresponding address and phone number), generally wed have to pass all the member variables of the Person object to a Stored Procedure and, if successful, call other Stored Procedures to insert the records into the Address and Phone tables. I propose an alternate way of doing the same thing thats a lot simpler. Suppose we serialize the Person object along with its list of Address and Phone objects into XML; this XML parameter would then be passed to the Stored Procedure which could handle the inserts to the Person, Address, and Phone tables all by itself; and within
the Stored Procedure, wed use XQuery against the passed XML to extract the desired data. This eliminates multiple parameters to the Stored Procedure and minimizes the Stored Procedure calls to the database.
20
Visit us at www.dotnetdevelopersjournal.com
compared to the XmlAttribute model. In this article well consider the XmlAttribute model. As youve seen, to get the output in Xml-Element format, we dont have to do anything since its the default behavior; on the other hand, for the XmlAttribute model, we have to annotate the public properties and public elds with XmlAttributes. To control the generated XML, we could apply special XML Attributes to the class or the public elds and public properties. Attributes provide additional information to the XmlSerializer class on how the data appears in XML. Here are a few important attributes to consider: XmlAttributeAttribute: XmlAttributeAttribute (or just XmlAttribute) should be applied to the public properties and elds of the class that you want to be serialized as attributes (as opposed to elements). This article recommends using attributes model for XML serialization. Listing 1 below already offers an idea of how one model compares to the other. It shows how XmlAttribute is declared for properties. The name given for the attribute is the same as we see in the XML output. Generally wed have the same name as the Property itself. However, if desired, we could change this as shown in Listing 5 for the class Patient for the property MRN that would be serialized as Medical_Record_Number. Note: When specifying an Attribute, the phrase Attribute could be omitted from the name of the attribute since .NET would recognize the attribute. This is true for all Attributes and not just XML attributes. This is the reason why XmlAttributeAttribute can be specied as simply XmlAttribute. XmlRootAttribute: XmlRootAttribute (or just XmlRoot) decorates the class itself. It species how the root element would be displayed in XML. If this isnt specied, the name of the class would appear as-is, as the root element. Usually we dont have to set this attribute. However, there may be a case where the class name may not be suitable as the root name in the serialized XML. This can happen when the class name differs from the name used in business parlance. For instance, lets say we have a class called Doctor; by default, this would appear as the root in the serialized XML. But, if users like to see the root name as Physician instead of Doctor we need to set the XmlRoot attribute for the class as Physician. This is shown in Listing 4. XmlArrayItemAttribute: When we have to serialize a collection XmlArrayItemAttribute comes in handy. Consider the case of a Doctor having a list of patients as a Property. Listing 5 shows the Patient class and a property in the Doctor class called Patients, which is decorated with the attribute XmlArrayItemAttribute. Note the XML output that lists the XML node as Patients (the same name as
that of the property) and each patient is listed with the XML node Patient that is the same as the class name for the Patient class. Weve seen the snippet of code that does serialization and deserialization in Listing 3. However this code snippet is specic to the Book class. To generalize the code for serialization and deserialization, we could dene these functions in a common base class. Lets dene a class called BusinessBase that could serve as a base class to all business objects. This class could then have two functions ToXML() for serialization and FromXML() for deserialization. This way, any business object like Person, Doctor, Patient, Phone, Address, etc. could automatically leverage this functionality. Code for BusinessBase is shown in Listing 6. Notice the Copy() function that serves as a Copy Constructor (deep copy). This is needed since the Deserialize() function of XmlSerializer returns an object that has to be copied into this again. Use of these functions is shown in Listing 7, assuming that weve derived the class Book from BusinessBase. Weve already dened the Person class. Lets add collections to this class, one for the address list and the other for the phone list. To do this, we need two classes called Address and Phone. Listing 8 below shows the Address and Phone classes along with the part of the Person class that reects the change. Note that we have dened enums AddressType and PhoneType to enumerate different types of addresses and phones respectively. These enums have to be kept in synch with the data dened in the database tables AddressType and PhoneType. XmlIgnoreAttribute: Weve already said that by default, all public properties and elds of a class are serialized; if any of these have to be omitted, we need to annotate the desired properties/elds with the attribute XmlIgnoreAttribute or in short, XmlIgnore. Omitting certain data from serialization could
Figure 1: Database schema for Person with Address and Phone information
Visit us at www.dotnetdevelopersjournal.com
21
Feature
prove handy if you want to serialize only a select set of properties. As seen in Listing 8, we have two properties for the eld _addrType: AddrType and AddressTypeId; the former an AddressType type and latter of int type. In the XML output, we need the property to be expressed as int since thats how its expressed in the database (AddressType table). The other property need not be serialized to XML, which is why its annotated with the attribute XmlIgnore. The corresponding database schema for these classes would be as shown in Figure 1. Now, to insert a Person record along with its Address records and Phone records in one Stored Procedure all we have to do is serialize the Person object and pass the XML to the Stored Procedure. In the Stored Procedure, wed use XQuery to parse the XML and extract the data as needed. Listing 9 shows the code snippet that instantiates a Person object along with its Address and Phone collections, and the corresponding XML representation of the Person instance. The Stored Procedure CreatePerson could be seen in Listing 10. Notice the use of TRY-CATCH blocks in the Stored Procedure. From SQL Server 2005 onwards, this is the preferred way to catch any errors during the execution of a Stored Procedure; in earlier versions, global parameter @@ERROR was used to retrieve the code of the last error when executing a SQL statement, and GO TO statements had to be used to modify the control ow. Using TRY-CATCH block eliminates this to write cleaner code; if an exception does get raised in a TRY block during the execution, control automatically jumps to the CATCH block where the appropriate error-handling code could be placed. If Stored Procedure executes
LISTING 1 DEFINITION OF A BOOK CLASS FOR BOTH FORMATS Book denition for Xml-Elements model Book denition for XmlAttributes model Notice how each property in the class is decorated with XmlAttribute. public class Book { /// <summary> /// title of the book /// </summary> private string _title; public string Title { get { return _title; } set { _title = value; } } /// <summary> /// ISBN of the book /// </summary> private string _isbn; public string ISBN { get { return _isbn; } set { _isbn = value; } } /// <summary>
without any exceptions then control doesnt enter the CATCH block at all. As seen in Listing 10, at the end of the TRY block, we commit the transaction and return the error code (0=success) and the ID of the inserted Person record; whereas, in the CATCH block, we roll back the transaction and return the error code and error message. XQuery or XML Query Language is a W3C specication on how XML could be queried. XQuery specication document could be found at http:// www.w3.org/TR/xquery/. The value() and nodes() are the XML data type methods in SQL Server 2005. Other methods available, but not used in this article are query(), exist(), and modify(). The nodes() method returns an unnamed rowset, which is why its aliased in CreatePerson SP as seen in Listing 10. The value() method extracts the value of the node from the rowset with the type specied. Note that it can extract multiple values if the rowset returned by the nodes() method contains multiple values. This is how multiple address or phone records could be inserted with just one INSERT statement. As youve seen in this article, using XML saves us from passing multiple parameters to the Stored Procedure, and it facilitates inserting records into multiple/linked tables cleanly. Later, if the object properties change or tables get modied, the input to the Stored Procedure doesnt have to change, and could continue to be XML; the Stored Procedure itself would have to be changed to reect the changes, but the caller wouldnt have to modify the input parameters to the Stored Procedure. So code maintenance is easier since minimal changes have to be made.
/// Author of the Book /// </summary> private string _author; public string Author { get { return _author; } set { _author = value; } } /// <summary> /// Default constructor: Needed for Xml serialization /// </summary> public Book() { } /// constructor public Book(string title, string isbn, string author) { Title = title; ISBN = isbn; Author = author; } } public class Book {
22
Visit us at www.dotnetdevelopersjournal.com
/// <summary> /// title of the book /// </summary> private string _title; [XmlAttribute(Title)] public string Title { get { return _title; } set { _title = value; } } /// <summary> /// ISBN of the book /// </summary> private string _isbn; [XmlAttribute(ISBN)] public string ISBN { get { return _isbn; } set { _isbn = value; } } /// <summary> /// Author of the Book /// </summary> private string _author; [XmlAttribute(Author)] public string Author { get { return _author; } set { _author = value; } } /// <summary> /// Default constructor: Needed for Xml serialization /// </summary> public Book() { } /// constructor public Book(string title, string isbn, string author) { Title = title; ISBN = isbn; Author = author; } } LISTING 2 CODE TO SERIALIZE AND DESERIALIZE Book book = new Book(Visual C# 2005 A Developers Notebook, 0-596-00799-X, Jess Liberty); XmlSerializer serializer = new XmlSerializer(book.GetType()); MemoryStream memoryStream = new MemoryStream(); XmlTextWriter writer = new XmlTextWriter(memoryS tream, Encoding.UTF8); serializer.Serialize(writer, book); // get the stream from the writer memoryStream = writer.BaseStream as MemoryStream; // apply encoding to the stream UTF8Encoding encoding = new UTF8Encoding(); string xml = encoding.GetString(memoryStream. ToArray()).Trim();
// Following code would deserialize xml into Book object // get the byte stream from the string in utf-8 encoding Byte[] bytes = encoding.GetBytes(xml); // create a stream from the bytes memoryStream = new MemoryStream(bytes); Book deserializedBook = serializer.Deserialize(m emoryStream) as Book; //Deserialization LISTING 3 SERIALIZED XML FOR AN INSTANCE OF A BOOK CLASS XML-Element format XML-Attribute format <?xml version=1.0 encoding=utf-8 ?> <Book xmlns:xsi=http://www.w3.org/2001/ XMLSchema-instance xmlns:xsd=http://www. w3.org/2001/XMLSchema> <Title>Visual C# 2005 A Developers Notebook</Title> <ISBN>0-596-00799-X</ISBN> <Author>Jesse Liberty</Author> </Book> <?xml version=1.0 encoding=utf-8 ?> <Book xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:xsd=http://www.w3.org/2001/XMLSchema Title=Visual C# 2005 A Developers Notebook ISBN=0-596-00799-X Author=Jesse Liberty /> LISTING 4 DEFINITION OF A DOCTOR CLASS AND A PATIENT CLASS Class denition for Doctor Class denition for Patient [XmlRoot(Physician)] public class Doctor : Person { private string _specialization; [XmlAttribute(Specialization)] public string Specialization { get { return _specialization; } set { _specialization = value;}} private List<Patient> _patients = new List<Patient>(); [XmlArrayItem(typeof(Patient))] public List<Patient> Patients { get { return _patients; } set { _patients = value;}} /// <summary> /// default constrcutor /// </summary> public Doctor(){} // constructor public Doctor ( string fName, string lName, string specialization ) : base(fName, lName) { Specialization = specialization; } // function to add a patient public void AddPatient(Patient patient)
Visit us at www.dotnetdevelopersjournal.com
23
Feature
{ Patients.Add(patient); } //function to remove a patient public void RemovePatient(Patient pat) { Patients.Remove(pat); } } public class Patient : Person { private string _mrn; [XmlAttribute(Medical_Record_Number)] public string MRN { get { return _mrn; } set { _mrn = value; } } private string _diagnosis; [XmlAttribute(Diagnosis)] public string Diagnosis { get { return _diagnosis; } set { _diagnosis = value; } } /// <summary> /// default constructor /// </summary> public Patient(){} //constructor public Patient ( string fName, string lName ) : base(fName, lName) { } } LISTING 5 SERIALIZED XML FOR AN INSTANCE OF A DOCTOR CLASS <?xml version=1.0 encoding=utf-8 ?> <Physician xmlns:xsi=http://www.w3.org/2001/ XMLSchema-instance xmlns: xsd=http://www.w3.org/2001/XMLSchema FirstName=Austin LastName=Szubryt Specializa tion=Paediatrics> <Patients> <Patient FirstName=Malcom LastName=Speed Medical_Record_Number=M555678999 Diagnosis=Burns /> <Patient FirstName=Amit LastName=Jain Medical_Record_Number=M333875452 Diagnosis=Shortness of breath /> </Patients> </Physician> LISTING 6 BUSINESSBASE CLASS DEFINITION using System; using System.IO; using System.Reection; using System.Text; using System.Xml;
using System.Xml.Serialization; namespace DNDJ_XmlSerialization { public class BusinessBase { /// <summary> /// function to copy all properties from bBaseToCopy into this(Copy Constructor) /// </summary> /// <param name=bBaseToCopy></param> public virtual void Copy(BusinessBase bBaseToCopy) { // get all the properties from this PropertyInfo[] properties = this.GetType(). GetProperties(); // loop through the properties foreach (PropertyInfo property in properties) { if (property.CanWrite && property.CanRead) // if there is a Set & Get Method { //Invoke the set on this and get on bBaseToCopy property.GetSetMethod().Invoke(this, new object[] { property.GetGetMethod().Invoke(bBaseToCopy, null) }); } } } /// <summary> /// Function to convert this into XML string. Could be overridden in derived class /// if desired. we use UTF-8 encoding since SqlServer would understand only UTF-8 /// </summary> /// <returns></returns> public virtual string ToXML() { // create a memory stream/text writer for UTF-8 encoding (1-char 1-byte) MemoryStream memoryStream = new MemoryStream(); XmlTextWriter writer = new XmlTextWriter(mem oryStream, Encoding.UTF8); // create the serializer and serialize this XmlSerializer serializer = new XmlSerializer(this.GetType()); serializer.Serialize(writer, this); // get the stream from the writer memoryStream = writer.BaseStream as MemoryStream; // apply encoding to the stream and return the string UTF8Encoding encoding = new UTF8Encoding(); string xml = encoding.GetString(memoryStream. ToArray()).Trim(); // strip out the rst line; we do not need it. xml = xml.Replace(<?xml version=\1.0\
24
Visit us at www.dotnetdevelopersjournal.com
encoding=\utf-8\?>, string.Empty); return xml; } /// De-serializer: Hydrates an object from the xml; We use UTF-8 encoding since /// SqlServer would understand UTF-8 format only. /// </summary> /// <param name=xml></param> /// <returns></returns> public void FromXML(string xml) { // get the byte stream from the string in utf-8 encoding UTF8Encoding encoding = new UTF8Encoding(); Byte[] bytes = encoding.GetBytes(xml); // create a stream from the bytes MemoryStream memoryStream = new MemoryStream(bytes); // create a serializer and de-serialize this XmlSerializer serializer = new XmlSerializer(this.GetType()); // nally call copy constructor to copy the deserialized output into this this.Copy(serializer.Deserialize(memoryStream) as BusinessBase); } } } LISTING 7 USING TOXML() AND FROMXML() FUNCTIONS Book book = new Book(Visual C# 2005 A Developers Notebook, 0-596-00799-X, Jesse Liberty); string xml = book.ToXML(); // Serialization Book deserializedBook = new Book(); book.FromXML(xml); //Deserialization
LISTING 8 DEFINITION OF THE ADDRESS AND PHONE CLASSES WITH A PARTIAL DEFINITION OF PERSON CLASS Denition of AddressType and Address Denition of PhoneType and Phone public enum AddressType { Home = 1, Work = 2 } public class Address : BusinessBase { private int _addressId = -1; // default [XmlAttribute(AddressId)] public int AddessId { get { return _addressId; } set { _addressId = value;}} private string _line1; [XmlAttribute(Line1)] public string Line1 { get { return _line1; } set { _line1 = value;}} private string _line2; [XmlAttribute(Line2)] public string Line2 { get { return _line2; }
REPRINT IT!
June 2007 Volume: 5 Issue: 6
Visit us at www.dotnetdevelopersjournal.com
25
Feature
set { _line2 = value;}} private string _city; [XmlAttribute(City)] public string City { get { return _city; } set { _city = value;}} private string _state; [XmlAttribute(State)] public string State { get { return _state; } set { _state = value;}} private string _zipCode; [XmlAttribute(ZipCode)] public string ZipCode { get { return _zipCode; } set { _zipCode = value;}} private AddressType _addrType; [XmlIgnore()] public AddressType AddrType { get { return _addrType; } set { _addrType = value;}} [XmlAttribute(AddressTypeId)] public int AddressTypeId { get { return Convert.ToInt32(_addrType); } set {_addrType = (AddressType)value;} } // default constructor public Address() { } } public enum PhoneType { Home = 1, Work = 2, Mobile = 3, } public class Phone : BusinessBase { private int _phoneId = -1; // default [XmlAttribute(PhoneId)] public int PhoneId { get { return _phoneId; } set { _phoneId = value;}} private int _areaCode; [XmlAttribute(AreaCode)] public int AreaCode { get { return _areaCode; } set { _areaCode = value;}} private int _phoneNumber; [XmlAttribute(PhoneNumber)] public int PhoneNumner { get { return _phoneNumber; } set { _phoneNumber = value;}} private PhoneType _phType; [XmlIgnore()] public PhoneType PhType { get { return _phType; } set { _phType = value;}} [XmlAttribute(PhoneTypeId)]
public PhoneType PhoneTypeId { get { return _phType; } set { _phType = value;}} // default constructor public Phone() {} } public class Person : BusinessBase { private int _personId = -1; // default [XmlAttribute(PersonId)] public int PersonId { get { return _personId; } set { _personId = value;}} private List<Address> _addresses = new List<Address>(); [XmlArrayItem(typeof(Address))] public List<Address> Addresses { get { return _addresses; } set { _addresses = value;}} private List<Phone> _phones = new List<Phone>(); [XmlArrayItem(typeof(Phone))] public List<Phone> Phones { get { return _phones; } set { _phones = value;}} ...} LISTING 9 SERIALIZING PERSON WITH ADDRESSES AND PHONES Person person = new Person(John, Doe); Address addr1 = new Address(); addr1.Line1 = 124 Highland Ave.; addr1.Line2 = Suite 234; addr1.City = Lombard; addr1.State = IL; addr1.ZipCode = 60148; addr1.AddrType = AddressType.Work; person.AddAddress(addr1); Address addr2 = new Address(); addr2.Line1 = 3456 Main St.; addr2.City = Naperville; addr2.State = IL; addr2.ZipCode = 60563; addr2.AddrType = AddressType.Home; person.AddAddress(addr2); Phone phone1 = new Phone(); phone1.AreaCode = 800; phone1.PhoneNumner = 1234567; phone1.PhType = PhoneType.Work; person.AddPhone(phone1); Phone phone2 = new Phone(); phone2.AreaCode = 630; phone2.PhoneNumner = 5481234; phone2.PhType = PhoneType.Home; person.AddPhone(phone2); string xml = person.ToXML(); <Person xmlns:xsi=http://www.w3.org/2001/ XMLSchema-instance xmlns:xsd=http://
26
Visit us at www.dotnetdevelopersjournal.com
www.w3.org/2001/XMLSchema PersonId=-1 FirstName=John LastName=Doe> <Addresses> <Address AddressId=-1 Line1=124 Highland Ave. Line2=Suite 234 City=Lombard State=IL ZipCode=60148 AddressTypeId=2 /> <Address AddressId=-1 Line1=3456 Main St. City=Naperville State=IL ZipCode=60563 AddressTypeId=1 /> </Addresses> <Phones> <Phone PhoneId=-1 AreaCode=800 PhoneNumber=1234567 PhoneTypeId=2 /> <Phone PhoneId=-1 AreaCode=630 PhoneNumber=5481234 PhoneTypeId=1 /> </Phones> </Person> LISTING 10 DEFINITION OF STORED PROCEDURE CREATEPERSON set ANSI_NULLS ON set QUOTED_IDENTIFIER ON go -- sample xml of Person object /*** <Person xmlns:xsi=http://www.w3.org/2001/ XMLSchema-instance xmlns:xsd=http://www. w3.org/2001/XMLSchema PersonId=-1 FirstName=John LastName=Doe> <Addresses> <Address AddressId=-1 Line1=124 Highland Ave. Line2=Suite 234 City=Lombard State=IL ZipCode=60148 AddressTypeId=2 /> <Address AddressId=-1 Line1=3456 Main St. City=Naperville State=IL ZipCode=60563 AddressTypeId=1 /> </Addresses> <Phones> <Phone PhoneId=-1 AreaCode=800 PhoneNumber=1234567 PhoneTypeId=2 /> <Phone PhoneId=-1 AreaCode=630 PhoneNumber=5481234 PhoneTypeId=1 /> </Phones> </Person> ***/ CREATE PROCEDURE [dbo].[CreatePerson] ( @personXML XML ) AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; DECLARE @personId INT -- saves the value of the inserted Person records Id BEGIN TRY --begin the TRY block -- wrap the multiple inserts in a Transaction BEGIN TRANSACTION CREATE_PERSON -- insert the new person record INSERT INTO dbo.Person(FirstName, LastName)
SELECT newPerson.value(@FirstName, varchar(50)) FirstName, newPerson.value(@LastName, varchar(50)) LastName FROM @personXML.nodes(/Person) node(newPerson) SET @personId = @@IDENTITY -- note down the Id of the inserted record -- insert records into Address table (multiple possible) INSERT INTO dbo.Address(PersonId, AddressTypeId, Line1, Line2, City, State, Zipcode) SELECT @personId, newAddr.value(@AddressTypeId, int) AddressTypeId, newAddr.value(@Line1, varchar(50)) Line1, newAddr.value(@Line2, varchar(50)) Line2, newAddr.value(@City, varchar(50)) City, newAddr.value(@State, varchar(50)) State, newAddr.value(@ZipCode, int) Zipcode FROM @personXML.nodes(/Person/Addresses/Address) node(newAddr) -- insert records into Phone table (multiple possible) INSERT INTO dbo.Phone(PersonId, PhoneTypeId, AreaCode, PhoneNumber) SELECT @personId, newPhone.value(@PhoneTypeId, int) PhoneTypeId, newPhone.value(@AreaCode, int) AreaCode, newPhone.value(@PhoneNumber, int) PhoneNumber FROM @personXML.nodes(/Person/Phones/ Phone) node(newPhone) -- now that all inserts have been completed, commit the transaction COMMIT TRANSACTION CREATE_PERSON -- return the error code followed by the Id of the inserted Person record SELECT ISNULL(ERROR_NUMBER(), 0) ErrorCode, @personId END TRY -- end of TRY block BEGIN CATCH -- control would come here only in case of an exception -- since an exception has ocurred, rollback the transaction IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION CREATE_PERSON -- error has ocurred; return error number and err_msg to the caller SELECT ISNULL(ERROR_NUMBER(), 0) ErrorCode, ERROR_MESSAGE() ErrorMessage END CATCH -- end of CATCH block END -- end of SP
Visit us at www.dotnetdevelopersjournal.com
27
Indexed LINQ
Indexed LINQ
By Aaron Erickson
anguage Integrated Query (LINQ) is a very powerful new technology coming to us with Visual Studio 2008. There is a great deal of innovation going on in the LINQ space, including innovative projects like LINQ-to-Flickr and LINQto-Amazon (among many others), in addition to the great things Microsoft is providing in LINQ-to-SQL and LINQ-to-XML. This article dives deeper into the more mundane details about how to optimally query data you already have in memory via LINQ-to-Objects. LINQ lets you do queries on any object (typically collections or arrays) that support IEnumerable<T>. For example, if you have an online bookstore, you might decide to cache your products in memory on the Web server to improve performance. In such a case, you might have a List<Books> bookCache that you query this way when someone goes to look up a book:
var searchResult = from b in bookCache where b.ISBN=978-1590596326 select b;
Now, imagine your cache of books for your Web site contains, say, a million books, and you need to make this scale in a serious way. In such a case, you might be interested in optimizing how LINQ works.
When you use LINQ syntax to do a query, what really happens is that the syntax is converted to a series of method calls, such as where, join, and others related to the query operators that LINQ supports. Of interest to us is, for purposes of a query, how Microsoft implements Where in LINQ to Objects. Given that Where has to work for any IEnumerable<T>, theres very little that the default implementation can do to optimize the query. As a result, the default search over IEnumerable<T> in LINQ is a sequential search. In other words, if you are searching for the book at the bottom of a million-book pile, your search may not be terribly fast.
28
Visit us at www.dotnetdevelopersjournal.com
Sponsored by
Produced by
The Java Coffee Cup logo and Sun logos are trademarks of Sun Microsystems June 2007 Volume: 5 Issue: 6
Visit us at www.dotnetdevelopersjournal.com
29
Indexed LINQ
associates them with the appropriate inner list. For example, the inner list might hold everything that starts with A. The outer list might hold 26 items, one for each letter (a list for A, a list for B, and so forth). When you look up an item, you would look up by its rst letter, nd the list to look in, and then search through the smaller list, rather than searching through everything. Our data structure for a particular index looks like this:
Dictionary<int, List<T>>
[Indexable()] public string ISBN { get { return _isbn; } set { _isbn = value; } } [Indexable()] public int Id { get { return _id; } set { _id = value; } } public string AuthorFirstName { get { return _authorFirstName; } set { _authorFirstName = value; } } [Indexable()] public string AuthorLastName { get { return _authorLastName; } set { _authorLastName = value; } } }
In our actual implementation, well look up by int rather than string for the index. We use int because int is what the GetHashCode() method returns from all objects in .NET. One of the keys to making this work is that all objects support GetHashCode() which lets us, while not uniquely identify an object, effectively partition it into very small groups. Of course, there may be more than one index in our collection. So in actuality, our implementation will use a Dictionary of indexes, which gives us the following structure in the end:
protected Dictionary<string, Dictionary<int, List<T>>> _indexes = new Dictionary<string, Dictionary<int, List<T>>>();
Implementation of IndexableCollection<T>
The next step is to have a collection that, while based on the collection functionality that .NET gives us, lets us intercept certain operations on it that are of interest to maintaining an index. Namely, we want, at the very least, to know when something is added or removed from the collection. To start, well derive from Collection<T>, from System.Collections.ObjectModel, as follows:
public class IndexableCollection<T> : Collection<T> { }
Building the indexes on the construction of the collection is the next step. When we build a new IndexableCollection<T>, we need to reect what were collecting, nd out which properties are going to be indexed (by looking for the presence of the IndexableAttribute), and create the index accordingly. The following routine, called from the constructor, accomplishes that goal:
protected void BuildIndexes() { PropertyInfo[] allProps = typeof(T).GetProperties(); foreach (PropertyInfo prop in allProps) { object[] attributes = prop.GetCustomAt tributes(true); foreach (object attribute in attributes) if (attribute is IndexableAttribute) _indexes.Add(prop.Name, new Dictionary<int, List<T>>()); } }
30
Visit us at www.dotnetdevelopersjournal.com
Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ
We need to make these public since anything that might want to implement an index on this is going to need to be able to see if a property has an index and retrieve the index if a given index exists.
The mechanics of Add are to iterate through the known indexes, and using reection, retrieve the hash code of the appropriate property value for each eld on the object were going to index. Once we have that value (hashCode above), we look in the index to see if we have an entry yet for that hash code. If we do, we simple add the item to the list of entries that have the given hash code. If not, we create a new entry in that index based on the hash code. Once were done, we call the Add routine in the base class so that we have the normal behavior of add implemented. We can now go ahead and add a couple of helper methods that would help anyone trying to implement an index:
public bool PropertyHasIndex(string propName) { return _indexes.ContainsKey(propName); } public Dictionary<int, List<T>> GetIndexByProp erty(string propName) { return _indexes[propName]; }
Extension methods extend the class you specify in the rst parameter, the one shown here with the modier on the parameter. The second parameter is the actual parameter that a Where method is always passed namely, the expression were going to evaluate to determine whether we do an index lookup or simply pass the call through to the
Figure 1:
Visit us at www.dotnetdevelopersjournal.com
31
Indexed LINQ
This routine performs two important tests. The rst is to see if its the kind of expression we can use on the left side something called a MemberExpression. MemberExpressions are expressions that relate to any property lookup that might occur in code. We need the expression to be a member expression because thats the only type that will let us determine which index, if any, to use. The second test, which only applies if it is, in fact, a MemberExpression, is to make sure that the property that the MemberExpression tries to access is, in fact, indexed by the collection were working with. To perform this test, we use the PropertyHasIndex method we wrote earlier, passing the name of the property the user passed into the query. If this works, we know we have a field for which an indexed lookup is possible.
As anyone who has ever done work with databases knows, indexes can provide dramatic performance improvements for large sets of data.
32
June 2007 Volume: 5 Issue: 6 Visit us at www.dotnetdevelopersjournal.com
Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ
gure out the value that well be looking up in the index. So we need to gure out what value the right side of the BinaryExpression (a k a the right side of the equals) evaluates to. Unlike the left side, were less picky about what might be on the right side of the expression. The right side could be a constant number, a string, or any other valid C# statement that evaluates to something. The following routine does the evaluation:
private static int? GetHashRight(Expression rightSide) { //rightside is where we get our hash... switch (rightSide.NodeType) { //shortcut constants, dont eval case ExpressionType.Constant: ConstantExpression constExp = (ConstantExpression)rightSide; return (constExp.Value.GetHashCode()); default: LambdaExpression evalRight = Ex.Lambda(rightSide, null); //Compile it, invoke it, and get the resulting hash return evalRight.Compile(). DynamicInvoke(null). GetHashCode(); } }
foreach (T item in result) yield return item; } noIndex = false; //we found an index
The process of doing an indexed lookup is a matter of calling Where again, this time, on a much smaller set the items within a given page of the index rather than the entire enumeration. The results of the where are then iterated through, using the yield return statement on each item as you would with any other iteration routine. In case no index is found, you simply do the following:
if (noIndex) //no index? just do it the normal slow way then... { IEnumerable<T> sourceEnum = sourceCollection. AsEnumerable<T>(); IEnumerable<T> result = sourceEnum. Where<T>(expr.Compile()); foreach (T resultItem in result) yield return resultItem; }
This does nothing more than return results just like the default where implementation does looking at each element of the collection and checking to see if it passed the test in the expression passed to the Where method.
For constant nodes which could be an int, string, or some other data structure, we go ahead and simply grab the value, get its hash, and return that. For other nodes, we convert the expression to a LambdaExpression, compile it, invoke the resulting method, and get our hash from the result. Either way, so long as the result isnt null, well get an int lookup value we can use for our index lookup.
Results
As anyone who has ever done work with databases knows, indexes can provide dramatic performance improvements for large sets of data. Over a large set of a million books, our lookup time would have been around 1,200ms for the unfortunate book at the end of the collection. Using an index, however, brings that time down to less than 10ms a 100x performance improvement. But its not without a cost. The code to maintain an index in cases where the values of the objects change more frequently is somewhat more complex, requiring you to implement a change notication in the class youll be indexing. And just like database indexes, adding indexes slows down your add, remove, and update operations making the developer who has domain knowledge of how the objects will be used the only person who can possibly know the best indexing strategy. The source code that ties all of this together is available at www.codeplex.com/i4o, an open source project designed to make indexed collections available to all developers on the .NET platform.
Do the Query
Once we have a property we know we have an index and a value to look up in the index, we can go ahead and process the query using the index:
hashRight = GetHashRight(rightSide); Dictionary<int, List<T>> myIndex = sourceCollection.GetIndexByProperty(prope rty); if (myIndex.ContainsKey(hashRight.Value)) { IEnumerable<T> sourceEnum = myIndex[hashRight. Value].AsEnumerable<T>(); IEnumerable<T> result = sourceEnum. Where<T>(expr.Compile());
Visit us at www.dotnetdevelopersjournal.com
33
Product Review
TM
What Is BPEL?
BPEL or Business Process Execution Language is an XML and Web standardsbased SOA (service-oriented architecture) standard that allows business people to combine services into automated processes. As described in this review, Active Endpoints ActiveBPEL product family includes a visual designer that works by allowing nonprogrammers to assemble Web services into processes by dragging and dropping graphical representations of components (Web services) and wiring them together in sequences and owcharts (see Figure 1). There are ve parts of a BPEL application. The true BPEL part is an XML le that conforms to the BPEL standard (BPEL4WS 1.1 and WS-BPEL 2.0). This le contains the denition of the activities that make up the BPEL process. Another part are the services that get integrated by BPEL; these are dened by standard WSDL les. The other three parts are the proprietary BPEL engine that runs the processes, the proprietary graphic designer that functions as the IDE or the development environment that allows drag n drop design of processes, and the nal part is the administrative tools for deploying and monitoring the BPEL process. BPEL is considered programming in the large because it deals with higher-level constructs such as ow control and looping, processes that are long running (for instance, a request for a loan approval might be routed to the accounting department and sit there for days before being approved or denied), fault tolerance (it can be set up to start an alarm process if accounting does not respond to the loan request within a week), and it uses compensation instead of transactions (a transaction cant be kept open for the length of time needed in a long-running process, so, if a stage of a process fails, it has to be xed, not just rolled back).
Figure 2:
Active Endpoints has three versions of their BPEL products: a standalone open source version, a standalone commercial version that can run under either .NET or Tomcat, and an integrated commercial version that can run under
Figure 1:
J2EE or .NET. Active Endpoints provides a complete set of administrative tools and a sophisticated development environment based on the Eclipse IDE (see Figure 2). Active Endpoints created the open source ActiveBPEL Engine to generate interest in the BPEL standard and to help educate the community about BPEL. The open source ActiveBPEL Engine is uses the same source code as used in the ActiveBPEL commercial products. The open source version is recommended for design and testing, but not for deployment because it lacks the system integration, clustering support, and security that are included in the commercial versions and typically needed in enterprise-level BPEL implementations. The open source version does contain everything needed to create and run BPEL applications without these enterprise-level features, including the designer and execution engine, management console, debugging, static BPEL analysis, multiple languages (XPath, XQuery, JavaScript, and custom languages), persistence, and all the documentation. The open source version is released under the GPL and is maintained by Active Endpoints. Active Endpoints also provides free forum-based support and premium support on a fee-based subscription basis. The commercial standalone (paid license and support) version runs on Tomcat or .NET, and adds support for WS-Security, WS-Reliable Messaging, advance process ltering and alerts, suspend and retry via both the console and APIs, enhanced process deployment and invocation, and process versioning (currently running processes use the old version, new instances of a process use the new version, with the old version being retired after all running processes end). continued on page 19
34
Visit us at www.dotnetdevelopersjournal.com
Visit us at www.dotnetdevelopersjournal.com
35
36
Your challenge: create rich, dynamic PC or mobile apps. Defy it: deliver value, not just TM data with Visual Studio and Windows Vista. More tips and tools at defyallchallenges.com
Visit us at www.dotnetdevelopersjournal.com