With the release of version 1.1 of the .NET Framework, there was an uproar among the .NET heavyweights on component versioning and the Framework. Now, developers have always dealt with these issues (read: DLL Hell). Unfortunately, they'll never completely go away (read: Assembly Hell). The .NET Framework, however, attempts to make it easier through methodologies such as Strong Name Signing, and systems such as the Global Assembly Cache. I'll show you a simple and concise method of dealing with these issues, building on the same techniques that Microsoft uses.
In conducting research for this article, I had a conversation with several individuals at Microsoft. They made some pretty important points:
- "You should not (and will not, in the future) be able to load an assembly in a CLR version older than the version in which it was compiled; you would need to deploy an assembly for each framework version you supported."
- "[Microsoft] recommends that you create a version of your control(s) for each Framework version, and then allow the user to choose which of them to install. This will require more user sophistication when they start targeting a new Framework version, but, on the other hand, users can continue using your 1.0 implementation with the 1.1 framework if they don't know how to add the redirect or reference the new assembly."
This is probably news to many of you, but if you think about it, it makes sense. Would you really try to open a VB7 application in VB6? So why would you expect the VB6 runtime to run a VB7 app? So many people got upset over the Framework compatibility issue, they forgot how dependent their old VB apps were on specific versions of the VB Runtime (read: .NET Framework).
So, before we go any further, I'd like to point out how Microsoft versions its Framework assemblies. Open any project in VS.NET, right-click on the project in the Solution Explorer, and click Add Reference. You should see a screen similar to Figure A, with the assembly name in the first column, the version in the second column, and the path in the third.
|Add Reference Dialog in VS.NET 2003|
If you're in VS.NET 2002, System.dll and all its Framework friends will display the version "1.0.3300.0". If you're in VS.NET 2003, they'll read "1.0.5000.0". This is a very important point, so remember it—it will come in handy in a minute.
One final point about this dialog: VS.NET 2003 will not display Framework assemblies for version 1.0. This is a departure from the Final Beta of VS.NET 2003 (codenamed Everett). If you were involved in that beta, you remember that it showed both versions by default. If you wish to reference older versions of the assemblies (WARNING: NOT RECOMMENDED), you have to browse for them manually.
Now, VS.NET always creates a file in every application that defines how the physical assembly is named, signed, and versioned. These attributes are defined in the AssemblyInfo.* file in the root directory of every project. Every AssemblyInfo file has the following guidelines regarding versioning built right into the comments:
' Version information for an assembly consists of the following four values:
' Major Version
' Minor Version
' Build Number
The attribute that the VS.NET-induced Reflection uses to display the assembly versioning information in the Add References dialog is formatted as follows:
Now, for all the products that my company sells, I've changed the default versioning policy to read as follows:
' Major Version
' Minor Version
' Framework Version
' Build Number
GenX.NET 3.0 is Interscape's first product to conform to this specification. Recalling the information about Framework versions we discovered earlier, the copy of GenX.NET built with VS.NET 2003 is versioned as follows:
The result is that the assembly will display the Framework compatibility in the Add References version column. Not only is this consistent with Microsoft's versioning policy for Framework assemblies, but it also allows you to release bug fixes without skewing the manifest files or assembly bindings of any projects that use your component. All you have to do to upgrade is XCOPY-deploy the new assembly to the /BIN folder of your application.
By now, you're probably asking, how do I uniquely identify my assembly between builds?
You can accomplish this by using an attribute that does not show up in the default AssemblyInfo file: the AssemblyFileVersion attribute. When using this attribute, be sure to follow the original versioning specification. For GenX.NET, the current build looks like this:
As you release bug fixes, you can increment the last number to reflect the number of iterations since the initial release. As you might have already guessed, the AssemblyFileVersion attribute is what the file system references when in Tiles display mode in Windows Explorer. This allows you to easily keep track of different assembly copies across your hard drive. I used to beta test components for Xheo.com, and, before it instituted this policy, dealing with version issues was a nightmare. After it was implemented, our versioning headaches were virtually eliminated.
Speaking of Xheo.com, I'd like to take a moment to thank Paul Alexander of Xheo for his assistance. He was instrumental in architecting a really solid methodology through lots of experimentation.
Taking it to the next level
The goal of every developer should be to make components idiot-proof. While 100-percent idiot-proof components are nearly impossible, there are steps that you can take to make it easier to implement. If you recall, the first statement I made was that you should compile a copy of your component to each version of the Framework. So if you install both versions on a user's machine, how do you idiot-proof it for your customers so they use the right assembly? The answer is simpler then you might think: Specify the Framework version in the assembly name.
Using our GenX.NET example once again, the first entry in the AssemblyInfo file reads as follows:
<Assembly: AssemblyTitle("GenX.NET 3.0 for .NET 1.0")>
<Assembly: AssemblyTitle("GenX.NET 3.0 for .NET 1.1")>
Now, when browsing through the Add References dialog, there should not be any question of which assembly to use. Another benefit to this technique is that Windows Explorer also displays the information in this attribute when browsing in Tiles mode. Consider the screenshot in Figure B. You get a simple view of the File Name, Assembly Name, and Assembly Version, leaving absolutely no question about what you're dealing with.
Tying it all together
So how can you ensure that this policy will achieve maximum effectiveness? Through customer education, that's how. Too many component developers just assume that the customers will understand what they are trying to accomplish with the product—.netSHIP and Aspose.Pdf are excellent examples of this. These products offer very little Web site content, poor documentation at best, and almost no technical support, leaving the customer clueless as to what to do.
The best thing you can do is use your marketing materials to guide the customer through a process in which you control what questions are raised and how they're answered. There is nothing more satisfying to a customer than reading a marketing piece, having a question come up as they go, and having it answered in the next sentence or paragraph.
I also believe that a vendor's responsibility is to gently push customers to use the best technology available. So, on my marketing site for GenX.NET, I added a new section to my Features page to educate my customers about my new versioning policy:
- Version 3.0.3300 requires Microsoft .NET Framework 1.0.
- Version 3.0.5000 is enhanced for Microsoft .NET Framework 1.1.
By using this specific terminology, I get the customer thinking about what I'm saying, and I open the door to either have them explore my site further or seek one-on-one interaction. Either way, it's a win-win situation. Whether or not my customer ends up switching to .NET 1.1 for development, I get the chance to interact with a customer, which is invaluable.
There you have it. By implementing this system, you get to take advantage of a single, solid, coherent strategy that makes sense, and you get the benefit of consistency with Microsoft's practices. All told, you'll have fewer headaches between builds and point upgrades. Next time I'll be taking these concepts one step further, and I'll apply them to namespace organization and source code management systems.
For some additional background reading and information, check out Robert McLaws' Weblog and Xheo's versioning policy: Blog entry 1 Blog entry 2 Xheo's versioning policy