Assembly Versioning in Extensible Applications |
This page expands on the assembly versioning information originally described by Schabse Laks. In addition to a thorough breakdown of the advantages and limitations created by different policies towards assembly versioning, this page includes best practices for developing and consuming different kinds of assemblies.
An assembly which, once released, never changes its public API or strong name. New releases of an application ship the original version of any immutable assemblies which are still in use.
An assembly which may change over time, and new versions of the assembly include only backwards-compatible changes. Extensible applications use assembly binding redirection to ensure only the newest version of the assembly is used at runtime.
Unversioned assemblies are associated with a particular version of an extensible application. Third-party assemblies may make use of APIs defined in unversioned assemblies; however, this will limit the use of the third-party assembly to the same specific version of the extensible application that the unversioned assembly was build for.
Immutable assemblies are the most straightforward assemblies to consume in an extension. These dependencies introduce only a minimal amount of constraints on assemblies which use them.
COM-Interop assemblies, such as EnvDTE.dll or Microsoft.VisualStudio.Shell.Interop.9.0.dll.
Expressly immutable assemblies, such as Microsoft.VisualStudio.Shell.Immutable.10.0.dll.
Interface assemblies for cross-version services, such as the Tvl.VisualStudio.OutputWindow.Interfaces.dll assembly used by the Output Window Service.
The following are some best practices for developers creating immutable assemblies for extensible applications.
DO limit the content of immutable assemblies only to the minimal amount of code required to ensure maximum portability. Bugs or limitations present in shipping versions of immutable assemblies cannot be corrected after release.
DO NOT ship two versions of an immutable assembly, i.e. the strong name of the assembly should never change. Remember that the strong name of an assembly is formed from both the AssemblyVersionAttribute and the key used for signing the assembly.
DO NOT make changes to the public API of an immutable assembly. This includes changes which are generally considered non-breaking, such as the introduction of an interface that did not previously exist.
AVOID making changes to the internal implementation of items in an immutable assembly, and do not assume that the changes will take effect. Since the strong name of the assembly will not be changed by the update, there is no way to ensure that the application will load the updated version of the assembly at runtime.
The source code for an immutable assembly may change, but generally only for the purpose of updating the documentation and/or distribution mechanism(s) for the assembly.
The following are some best practices for developers consuming immutable assemblies as part of extending an extensible application.
AVOID using APIs defined in immutable assemblies introduced later than the minimum version of the application you intend to support. Using an immutable assembly will limit your extension to working with versions of an application starting with the point when the assembly was first introduced. For example, using APIs defined in Microsoft.VisualStudio.Shell.Interop.11.0.dll will limit your extension to working with Visual Studio 2012 or newer. If you want to use these APIs when available, you can either develop your extension as a versioned or unversioned assembly, and distribute multiple versions of the extension according to the supported version of the application.
Versioned assemblies are straightforward to consume in an extension, and tend to have strict backward compatibility guarantees even across multiple versions of the extensible application. By taking advantage of assembly binding redirection, extensions developed for previous versions of the application often continue working with new versions of the application.
Versioned assemblies have a more dramatic impact on service or utility extensions that are intended for consumption by other extensions. Since assembly binding redirection is a runtime feature, assemblies shared between multiple extensions must pay particular attention to the types used in the exposed API of the assembly. If the exposed API includes a reference to a versioned assembly, the shared assembly must be built multiple times, once for each version of the versioned assembly that has shipped to date. For example, the Text Editor Utility Library includes separate versions referencing the multiple versions of Microsoft.VisualStudio.Text.Logic.dll that have shipped to date.
Public assemblies for extension development, such as Microsoft.VisualStudio.CoreUtility.dll and Microsoft.VisualStudio.Text.Logic.dll.
The following are some best practices for developers creating versioned assemblies for extensible applications.
DO provide assembly binding redirection for the assembly to make sure the runtime always only loads the most recent version of the assembly, even if one or more assemblies in the application (or extensions) were compiled with a reference to a previous release.
The following are some best practices for developers consuming versioned assemblies as part of extending an extensible application.
If your assembly will be used by other extensions and references members of the versioned assembly in its exposed API, DO create multiple versions of your assembly according to the versions of the versioned assembly that have been created to date. For example, the Text Editor Utility Library may be used by other Visual Studio extensions, and exposes the Microsoft.VisualStudio.Text.Data.dll versioned assembly in its API. For this reason, separate versions of this library are released for extensions targeting a minimum Visual Studio version of 2010, 2012, and 2013.
AVOID using APIs defined in versioned assemblies introduced later than the minimum version of the application you intend to support. Using a versioned assembly will limit your extension to working with versions of an application starting with the point when the assembly was first introduced. For example, using APIs defined in Microsoft.VisualStudio.Shell.11.0.dll will limit your extension to working with Visual Studio 2012 or newer. If you want to use these APIs when available, you can either develop your extension as a versioned or unversioned assembly, and distribute multiple versions of the extension according to the supported version of the application.
Unversioned assemblies are the easiest assemblies to develop and deploy for fixed versions of an extensible application, but pose unique challenges when attempting to share an assembly between multiple extensions all targeting the same application.
Private assemblies included with an extensible application, such as Microsoft.VisualStudio.Languages.CSharp.dll and Microsoft.VisualStudio.Language.CallHierarchy.dll.
Assemblies which reference an unversioned assembly, such as the Inheritance Margin extension which references the Microsoft.VisualStudio.Languages.CSharp.dll assembly.
Shared assemblies designed for side-by-side loading, such as Tvl.VisualStudio.Text.Utility.10.0.dll and Tvl.VisualStudio.Shell.Utility.10.0.dll.
The following are some best practices for developers creating unversioned assemblies for extensible applications.
DO design your assembly to support side-by-side loading scenarios, with unique strong names given to different shipped versions of your assembly.
DO NOT export MEF components from unversioned assemblies. If the runtime loads multiple versions of your assembly (or a different version than you expected), extensions could suddenly fail en-masse. This avoids the problem described by Jared Parsons as he developed a utility library for Visual Studio.
The following are some best practices for developers consuming unversioned assemblies as part of extending an extensible application.
DO create separate versions of your extension for each released version of the unversioned assembly that you intend to support, even if the use of the unversioned assembly is not exposed through the API.
Application developers and extension developers tend to face distinct challenges when creating assemblies. For the purposes of this discussion, we'll examine the Visual Studio 2010 and Visual Studio 2012 applications, and the Output Window Service and Inheritance Margin extensions. Visual Studio 2010 and Visual Studio 2012 are two consecutive major releases of an extensible application. The Inheritance Margin extension is a single extension developed to support both of these versions of Visual Studio, but is not intended for other extensions to these applications to reuse or extend the features provided by it. The Output Window extension is a single extension developed to support both versions of Visual Studio, but also provides service and support code intended for reuse in many other extensions created for either (or both) versions of the application.
Application developers have two primary advantages in writing assemblies. First, it is much easier to write versioned assemblies when the application has complete control over the assembly binding redirection configuration. While some applications offer third-party extensions a mechanism to update the binding redirection configuration (e.g. Visual Studio 2012 with the ProvideBindingRedirectionAttribute), extension for other applications (e.g. Visual Studio 2010 are restricted to the use of immutable and unversioned assemblies. Second, application developers are able to operate under a more "natural" release lifecycle. For example, the Microsoft.VisualStudio.Text.Logic.dll assembly which first shipped with Visual Studio 2010 was updated for a new release with Visual Studio 2012. Microsoft was never in a position to try making multiple versions of this assembly operate within the same release of Visual Studio, and also never needed to create a single common version of this assembly to operate in both Visual Studio 2010 and Visual Studio 2012. Updates to Visual Studio provide the opportunity to correct bugs in this assembly (and others), while at the same time ensuring that all dependencies are loading the most recent version of the assembly at runtime.
Application developers have additional responsibilities in regards to supporting extension developers that should be carefully considered prior to releasing the extensible application. When a single extension works seamlessly across multiple versions of an application, the extension developer has lower maintenance overhead to support the extension. In most cases, extensions relying only on immutable and versioned assemblies will be able to trivially support new releases of the application. Extensions which rely on unversioned applications require special consideration, and potentially a new build and distribution, in order to support an updated application. These advantages of creating an immutable or versioned assembly must be carefully weighed against the additional challenges imposed by the long-term restrictions implied by those kinds of assemblies.
Tip |
---|
Defining types for extending your application in immutable or versioned assemblies reduces the maintenance burden on extension developers when new versions of your application are released. This in turn increases the number of extensions available for many versions of the application, and reduces the chance that customers relying on extensions will be unable to upgrade to the latest version of your product. |
Most extensions written for extensible applications are isolated extensions. These extensions build on the support provided by the application, but generally do not make special considerations for other extensions which may be running, or provide any special manner of further extending their functionality. The Inheritance Margin extension is a prime example of an extension developed in this manner.
Shared extension development is one of the most challenging and least understood areas of extensible applications. However, there are many cases where an extension developer should consider developing a shared extension.
Providing New Features: Extensions which provide a highly-polished user interface for bold new features, such as the Peek Help feature provided in Productivity Power Tools for Visual Studio 2013, are prime candidates for development as a shared extension. Extensibility is especially important when users will not have another way to provide an extended version of this feature. For example, the Inheritance Margin adds a new feature to Visual Studio which could apply to many languages, but the current implementation is limited to C#. Installing this extension would not prevent a user from developing a second extension which independently provided the same feature for Visual Basic users, so creating a shared extension for this feature is likely not essential. On the other hand, the Peek Help feature exposes API documentation within the C# editor, but is limited to the documentation available on MSDN. Without a method to directly extend this feature, 3rd-party library developers will be unable to provide their documentation through this interface without changing to a new keyboard binding, or duplicating the ability to load documentation from MSDN.
Exposing Existing Features: The Output Window extension does not add any new features to Visual Studio. Instead, it provides extension developers using MEF components with a simple, reliable way to use a Visual Studio feature which tends to cause unnecessary complexity in extension code. When developers of multiple extensions find themselves facing the same challenges and duplicating their solutions, a shared extension which dramatically simplifies the solution is highly appealing.
The primary limitation shared extension developers are likely to face is an application which does not provide a method for extending the assembly binding redirection configuration. Visual Studio 2012 provides this feature by writing devenv.exe in native code, and using the Unmanaged Hosting API to configure and start the managed runtime environment. Assembly binding redirection is an essential feature supporting the creation of versioned assemblies; so extension developers for applications which do not provide this feature will always be forced to write either immutable or unversioned assemblies.
The other limitation governing shared extensions is imposed by the assembly binding rules for strong-named assemblies and along with the fact that MEF imports and exports components by contract names formed from the name of the type and/or member which is exported. This behavior means that the contract types, i.e. the type provided in the ExportAttribute for a component, must be defined in an immutable or versioned assembly.
Important |
---|
The MEF contract types for most 3rd-party extensions must be defined in immutable assemblies. |