(Originally published in VBA Magazine, Jan. 2002)
To make an analogy to Fukuyama’s treatise on liberal democracies, has the evolution of Visual Studio finally ended? What began several years ago as a revision of COM, turned into a fundamental change to almost every aspect of software development in the Microsoft environment. To call the .NET Framework and the accompanying products a “next version” would not be giving enough credit to the fundamental changes that Microsoft has introduced to both the end-user experience and to component developers. Such a major change requires that software developers take a step back and evaluate their development strategies, skills, and general practices. For the corporate developers and consultants, this means learning new skills and adjusting to new rules, and for Visual Studio product developers this has meant a change to almost every aspect of development, including a re-evaluation of how software developers will use their products.
The biggest fundamental change on the consumer side is for the Visual Basic developer. Since the .NET Framework is language agnostic and compiles to an intermediary language, the different higher-level languages can work together, but to do this they must share datatypes and architecture. This means that VB.NET adheres to the same rules every .NET language must follow. Now a VB developer must follow strict object oriented guidelines, use traditional exception handling instead of OnError, understand Stream classes, multi-threading, garbage collection, and a huge assortment of foundation classes (the base level classes included with the Framework).
For the Visual Studio product developers, namely the component manufacturers, the change has been just as profound. Not only has the programming language changed from C++ with ATL to C# (at Microsoft’s recommendation), but some serious interface and usability decisions must now be made. Adding to the challenge is that Microsoft has included excellent interoperability support for ActiveX controls. This means that users that want to continue using their older ActiveX components and the traditional COM interface can do so with a very small performance penalty in .NET. Thus, component developers need to bring a compelling reason for purchasing their native .NET products beyond just a simple conversion from C++ to C# as the development language.
So what should the consumer expect from 3rd party .NET components? First and foremost, the release of the .NET Framework has brought a rare opportunity for component manufactures, which is the ability to re-evaluate every aspect of their products. Often, new product versions are based on a currently existing architecture and fundamental changes are too expensive or time-consuming to introduce. Witness the evolution of the Microsoft operating systems. Every product that is targeted for the .NET platform should be reviewed with respect to user feedback and architectural problems that were impossible to correct.
Next, the advantages and capabilities that .NET makes available to a component needs to be comprehensively reviewed:
- How will the object be delivered to the consumer, as a class, as a component, or as a control? What will that mean to the users and how will they want to use the product?
- Exception handling is now the standard and users can be expected to use the Try-Catch method in response to error codes.
- Visual Basic is no longer limited to a single threaded model so objects are expected to be used in a multi-threaded fashion. In addition, internal design should incorporate multiple threads so that users can have efficient mechanisms for synchronous and asynchronous operation.
- Microsoft has introduced the Help 2.0 engine and a new style. Help is now completely integrated with the IDE. Should a product completely integrate, use Help 1.3 with the new “look” or just stay with the old Help 1.3 in every aspect?
Finally, there are the user expectations from the interface. This is the most difficult aspect of creating a product for a development environment, especially if the environment has not been tested by the user community and feedback is limited to beta testers who are often advanced users themselves. One of the great advantages of the .NET Framework is that it is not COM, and that enables substantial design flexibility. Functions can now be overloaded and mutually exclusive interface options representing a variety of datatypes can be accepted. This means that a single function can serve the needs of both advanced users and beginners without muddling the interface with dozens of confusing functions. In addition, interface mistakes are more easily forgiven for two major reasons. The first is that objects are not bound to a unique identifier in the registry and in turn, a runtime can be bound to the DLL that was used during compilation, creating a one-to-one relationship. The result is that DLLs of the same name can co-exist on the same machine. Thus, interface changes will only break compatibility if a local (non-shared) version of a DLL is changed. New versions of the DLL will only be bound to runtimes that are compiled later. If a DLL will be shared using the Global Assembly Cache (GAC), the only limitation is interface compatibility, not all the “artificial” COM related interface limitations that were imposed by an ActiveX control. This does not mean problems will not arise. If versioning is not used and a local DLL is replaced, interface changes can break the code. If versioning is used, then changing the version will require that a configuration file be added identifying the original version and the new version of the DLL.
An appropriate case study in .NET product development and decision making process is a comparison between the PowerTCP FTP Tool, an ActiveX product developed by Dart Communications, and PowerTCP FTP for .NET. Both products serve to provide stable, high-performance FTP functionality while creating an excellent user experience, but in dramatically different ways. The FTP Tool is built using C++ and ATL. The design incorporates both synchronous and asynchronous functionality using complex internal blocking and non-blocking that requires state management in the control, raising the potential for problems as a result of the extra code. Another challenge was that socket level communications could occur to/from memory or a file, depending on the protocol and specific functionality. To properly take advantage of protocol-based communications, a technique is needed to prevent Visual Basic developers from having to write substantial amounts of code just to prepare the required data. In almost any other environment, developers would have access to a data stream and a very fast string collection. To circumvent this problem, Dart created proprietary DartStrings and DartStream objects. The highly optimized DartStrings collection would hold a collection of strings that could be easily manipulated and delimited, while the DartStream would enable stream-based data manipulation unavailable in Visual Basic, although present in most other development environments. Using a DartStream, data could be loaded as a file or written from memory, or written to a file and read to memory. While this added flexibility, a function still had to accept Variants so that simple data types such as strings could be accepted. Add some optional parameters to the function and suddenly you have a relatively complex interface that is not very self-descriptive.
The final aspect of the ActiveX design is more philosophical. There are two schools of thought in the industry when it comes to object oriented control design. One approach is that just by having a control, a design is “object-oriented.” This often translates into all the functionality of a control placed in a flat interface, resulting in dozens of functions, properties, and events. While this approach may make the interface initially easier to digest for a user, this also forces more code writing, can create confusion when the developer’s needs become more sophisticated, and does not provide any logical grouping of related functionality. The blame for this philosophy is not completely on the side of the control designer. Microsoft placed a number of barriers in COM that made creating true object oriented controls a technical ordeal. Moreover, Visual Basic developers were never encouraged to be good object-oriented developers as the language they used was so forgiving in virtually every respect. Those that went ahead with object-oriented designs for their controls often faced large hurdles in COM and from Visual Basic beginners.
The ActiveX FTP control design follows the object-oriented philosophy. Having to instantiate and populate a secondary object like a DartStream before it can be passed to the primary object is based in more traditional object oriented philosophy. In addition, the FTP control uses objects as properties such as the ListEntries object, which is a collection of entries with operating system formatting support, returned form a List method call.
The .NET Framework has changed everything.
Before getting into the technical details and design decisions, the first order of business was to identify what users liked about the ActiveX version and what they wanted added to the FTP for .NET. The best input came from the technical support department, and in any organization, that department should be the lead in usability and functionality requests. Requests, such as a user-selected data port and an expanded ListEntry object were easily added in the design stage.
PowerTCP FTP for .NET should start where the ActiveX discussion ended, the object-oriented philosophy. With .NET, the argument is over before it can even begin. Everything is an object. The basic tenants of the Framework, and thus the VB.NET language, are based on pure object-oriented philosophy. With this in mind, bringing a component to the market that is based on a solid object design is no longer the exception, but the rule. For those that have carried the burden, .NET is at least an endorsement, if not a vindication, of the object-oriented philosophy. A product that delivers a flat interface to such a rich environment does a disservice to software developers, especially when so much effort will be expended to help software developers become better object-oriented designers.
Not only is the Listing property an object, but an operation such as an FTP Get will return an FtpFile object that represents the transfer statistics, events return EventArgs populated with information, and best of all, streams are everywhere and native to .NET. Because Microsoft derived so many classes from the base Stream class, member functions can just look for a Stream interface, enabling the users to deliver or accept almost any kind of data. The object-oriented aspects of the Framework also allow overloading, which obsoletes the clunky approach to optional parameters. Now a function can have several overloads, making the function far more self-descriptive and usable.
For example, performing a Get operation on a single file or multiple files required two separate functions with numerous optional parameters in the ActiveX product, but here:
- Get a single file from the server synchronously (remoteFileName, localFileName).
- Public Function Get(String, String) As FtpFile
- Get a file from the FTP server synchronously, storing the retrieved data in a stream object (remoteFileName, localStream).
- Public Function Get(String, Stream) As FtpFile
- Get multiple files from the server synchronously (remoteRoot, searchPattern, localRoot, subDirectories) – please note the return value is an array of FtpFile objects.
- Public Function Get(String, String, String, Boolean) As FtpFile
- Synchronously Get a stream that can be used to read the FTP data connection (remoteFileName, restartMarker) – please note that the return value is a SegmentedStream object derived from the Stream base class which is used to read the data connection.
- Public Function Get(String, Long) As SegmentedStream
All of this capability is available in one function and every overload is self-descriptive.
Internally, 100% of the code was written in C# and was fully managed. After combining a thorough review of the internal ActiveX design with the breadth and power of the .NET architecture, the actual code for the component was far more efficient and maintainable. The best example is the handling of asynchronous and synchronous functionality. Instead of using the complex state management system from the ActiveX iteration, a pure multi-threaded design was a far better fit, partly from the fact that the Framework enabled multi-threaded techniques with so much ease. This not only resulted in far less code, but also made it more manageable so that the chance of potential problems has been significantly reduced. Another decision was to provide events rather than delegates. The reasoning for this approach was based on the expectation that most developers would be coming from Visual Basic and accustomed to using events, rather than callbacks. Because the delegate model is so sophisticated in .NET, advanced programmers could actually hook their own delegates if they had such a requirement.
Error reporting is now completely handled with Exception Handling. In .NET, good coding techniques require that a Try-Catch block is implemented and the component fully supports this paradigm, either providing product specific errors, or allowing system errors to be raised to the users.
You may have noticed that when the discussion switched from ActiveX to .NET, the product was no longer referred to as a control but a component. In the .NET Framework, everything is a class and those classes are objects. A class that uses the component interface is a component and a component that provides user-interface functionality, and thus uses the System.Windows.Forms.Control base class, is a control.
For the purpose of FTP, a component seemed to be the perfect solution. While slightly less efficient than a class, a component is designed for use in a rapid application development (RAD) environment. This means that usability is as simple as dragging the component from the toolbox onto a form. Properties and Events for a component are available through the Properties Window and to assist in the development process, Dart added a design-time Editor to the FTP component so that a connection could be established and tested while code was being written. Then, the settings used during the test are saved directly to code.
Not to forget one of the most important aspects of any commercial product, the help system in PowerTCP FTP for .NET was based on Help 2.0 rather than the older Help 1.3 engine. This was not an easy decision as the .NET Framework is able to launch an external .chm document while the Help 2.0 engine SDK is not complete. Still, the difference between complete documentation integration with .NET and launching an external help file that just gives some of the “look and feel” of Help 2.0 is substantial. Part of the focus in .NET is the User Experience and the documentation experience has equal importance with the code. Creating Help 2.0 integration required the dedication of a full time developer who spent weeks creating utility applications that dealt with all the holes in the currently available Help 2.0 implementation.
Another change is product distribution. Microsoft does not recommend sharing DLL files and when the Dart.PowerTCP.Ftp.dll is used with an application, the best approach is to localize the DLL with the runtime. Because communications are no longer handled through COM, multipl DLL files can now co-exist, and with the shackles of COM removed from component development, interfaces can change for components in the future. Thus, unless there is a compelling need to change a DLL for a compiled application, any updated DLL should only be used when an application is recompiled or a new project is started.
What should a Visual Basic developer do in preparation for the .NET experience and using components in .NET? First, become familiar with object-oriented principles. This will be at the root of virtually every aspect of development in a .NET language. Second, learn some of the primary base classes. They are the foundation of development in the Framework. Third, pay special attention to the base Stream class and how Microsoft uses it throughout the Framework. Fourth, consider your distribution options. Finally, prepare to be a better developer, because the .NET Framework is a major step in creating the ultimate development experience.
Visual Studio .NET has evolved for 10 years, virtual lifetimes in the technology industry, to meet the needs of modern Information Technology. Software developers are consistently faced with more complex problems that require flexible development techniques within a RAD environment. The .NET Framework is not the end, but the beginning of a new, and better framework for software developers. For this new paradigm to succeed, consumers of .NET products must be prepared to evolve their skills, and .NET product developers must leave behind their bad habits and limited interfaces to make sure consumers have an enjoyable and productive user experience.