VSIX Best Practices
This post is about a new way to install extensions to Visual Studio, introduced in VS 2010, called the VSIX file. The information it contains will be of most interest to readers who develop Visual Studio extensions, but I encourage users who download and install those extensions to read it as well.
A VSIX file conforms to the ECMA Open Packaging Conventions (OPC) standard. It’s created as part of a VSIX project build in Visual Studio, and you can view its contents with any zip file utility. If you upload your VSIX to the Visual Studio Gallery, your customer can install it right in Visual Studio, in the new Extension Manager:
It can also be installed by downloading and double clicking on the file, and uninstalled either in the Extension Manager, or by simply deleting the associated files. You can find introductory information about VSIX here and here.
The VSIX feature comes with a lot of options. In most cases you don’t have to understand them all. This post is a list of tips that will give you some guidance about how to use the new VSIX capabilities in the best way. Here’s what I’m going to talk about:
- How to package your extension into a VSIX in the simplest way
- How to install via MSI if you need to
- How to use VSIX versioning
- What to avoid
Packaging your extensions using VSIX
- Use strong names for all your assemblies. You don’t want your “util.dll” to collide with somebody else’s; if you don’t use strong names the system won’t distinguish between them, and somebody will get a run time error.
- Distribute your whole product in one independent VSIX if you can. The feature does allow one VSIX to depend on another. But, save that for situations where each one is developed and shipped separately, because shipping a single VSIX will reduce the amount of information you have to understand.
- If you ship more than one VSIX, and they share common assemblies, copy the common assemblies into each separate VSIX. This has the effect of shipping your whole product in one VSIX described above. There’s no runtime harm in shipping copies of common assemblies – in memory the CLR will only load one:
But be aware that it will load only one, and the first one loaded wins. So you should ship updates to all your VSIXs that contain common assemblies together.
- If your product is extending another extension, then your VSIX needs to take a dependency on the target VSIX using either the Select Installed Extension or Manual Reference choice in the dialog below (which you raise by clicking the Add Reference button in the VSIX Manifest Editor).
In this case
- Read the Versioning section below to understand the best way to specify the version of your target VSIX in this dialog.
- Stay aware of your target extension’s updates, and test to make sure your extension is still compatible with each update.
- If your VSIX publishes an API that another VSIX will use
- Read the versioning section below to understand how your users will expect your versioning to behave.
- Maintain binary compatibility between versions if you can; this just makes life simpler for your extenders.
- Keep your extenders aware of approaching updates so they can test against them. Make sure they know in advance if you plan to release a version that breaks compatibility.
Installing via MSI
Some extensions still need to be installed by MSI: for example some of your files might have to be in a specific, well-known loca
tion, you might have a component like an MSBuild task that VSIX install doesn’t support, you might need to use binding redirection – see more information here. There’s no problem with doing that. In fact, we provide a way for an MSI installed extension to make itself visible in the new Extension Manager, so that the customer can see all his extensions in one place.
To make your extension visible in the Extension Manager, your MSI install should create a subdirectory in the Extensions directory for the hosting product:
For a non-administrative, per-user install (recommended) in Visual Studio, the path will look like this:
Usersuser idAppDataLocalMicrosoftVisualStudio10.0Extensionsyour companyextension nameversion
and for a per-machine install:
Program FilesVS 10.0 install directoryCommon7IdeExtensionsyour companyyour extension nameversion
An Isolated Shell application will define its own Extension directory.
In that folder, put the extension.vsixmanifest file built by your VSIX project, with an added element that marks it as installed by MSI:
1 <Identifier Id="VSIXProject2.Microsoft IT.8532242f-afdc-44fa-82b2-0b6b5afc1c38"><br /> <Name>VSIXProject2</Name><br /> <strong><font color="#008040"><InstalledByMsi>true</InstalledByMsi></font></strong><br /><br /> …<br /> </Identifier>
Note that although the user will then see the extension in the Extension Manager, since it’s installed by MSI, he still needs to manage it through Windows Add/Remove Programs.
If your extension is self-contained (i.e. you distribute it in a single VSIX that doesn’t have any dependencies on other ones), and no other VSIXs will depend on yours (i.e. your VSIX doesn’t expose any APIs), you don’t need to read this section. If your VSIX does offer or consume APIs, or you distribute multiple VSIXs with common shared assemblies, read on for more information.
First, let’s do a quick review of how versioning works in the CLR. For an assembly with a strong name, its CLR identity comes from a combination of the file name on disk, Assembly Version string, an optional cultural attribute, and a digital signature. When one assembly references (i.e. consumes APIs from) another one, the consumer is targeted to a particular version of the referenced assembly at build time. (Binding redirection can change this at run time, but the VSIX installer doesn’t support that yet.) The version string contains four segments: <major version>.<minor version>.<build number>.<revision> (for example “188.8.131.52”). The recommended convention for using the version string is that when an assembly’s API breaks binary compatibility, the major version is incremented. (Note that I’m talking about Assembly Version, which is part of the strong name, not Assembly File Version, which is purely informational – you can use Assembly File Version any way you like. See more information here.)
That’s all background information. Now let’s talk about using versioning with VSIX files. The first thing I’m going to recommend, although it probably sounds a little unexpected, is that you not change the Assembly Version strings when you ship an update of your VSIX. This is because, as I mentioned above, the VSIX installer doesn’t support binding redirection yet, so if you do change any segment of an assembly version number, you may break downstream VSIXs that depend on the old version number of your assembly. The VSIX file has its own mechanism for version management, and I recommend that you use that one instead, because its added flexibility gets you around the binding redirection issue.
For a VSIX that uses an API from another VSIX:
The syntax of a VSIX version string is the same as the assembly one, and we will use the recommended convention to indicate that a new release breaks binary compatibility with the old one: incrementing the major version number. The big advantage of the VSIX versioning mechanism is that if you’re consuming an API from another VSIX, you can specify a range of version numbers of the target VSIX that you’re compatible with. Let’s see how this works. When you raise the Add VSIX Reference dialog in the VSIX Manifest Editor:
in the Version fields just above, you can specify a range between minimum and maximum version numbers that you’re compatible with. If the developer of the VSIX whose API you consume obeys the versioning conventions, you can specify a range like from Min 1.0 to Max 1.9999 to indicate that you will use any version of your dependency between those two. When the VSIX you depend on installs, for example, version 1.2, you will be compatible with it. When the user attempts to install 2.0, the installer will recognize the incompatibility:
and display a warning dialog:
If the user updates the extension you depend on anyway, your extension will be disabled because of the incompatibility, and he should look for an update from you that’s compatible with the new version of the API.
If for any reason you believe you are dependent on a specific version of the target extension, you can code that number as the Min and Max values to target only that version:
If you only code the Min value, you will bind to anything equal or higher. I don’t recommend that, because binary compatibility breakage (in the example below, from 1.x to 3.0) can lead to run time errors.
For a VSIX that offers an API to other VSIXs:
If you release an extension that offers an API, you should handle the versioning at the VSIX level. This means leaving the Assembly Version string unchanged across releases, and incrementing your VSIX version number (shown in the VSIX manifest editor below).
It’s great for your consumers if your API can maintain binary compatibility across releases. If you need to break compatibility, increment the major version number. But at that point, when the VSIX installer upgrades your extension, all the consumers of your API will have to ship releases that are compatible with the new API.
Things to avoid:
- Embedding one VSIX inside another (using the Add payload selection in the Add VSIX Reference dialog).
In the current version of Visual Studio, certain combinations of embedded VSIXs and version updates don’t install properly. This feature should be avoided for now; the safest practice is to install VSIXs that have a dependency relationship separately.
In this article we looked at a set of recommendations for using the new VSIX feature. What I want to leave you with is: minimize complexity. Take advantage of the new VSIX features as you need them, but keep your life as simple as possible by using only the features you need. That way the VSIX install experience will be simple for your customers, which is the reason VSIX was invented.
Please feel free to post comments and questions!
Program Manager, Visual Studio Extensibility