Search code examples
.netvsixvspackageassembly-binding-redirectassembly-loading

Binding redirect not effective in VSPackage/VSIX


Main Question

I'm using VS2017 (15.9.25) to develop a VSIX VSPackage.

The package uses the MySqlConnector 1.0.0 NuGet package, which in turn depends on System.Memory 4.5.4 NuGet package which includes the System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 assembly.

When I run my VSPackage in debug mode using the Visual Studio Experimental Instance, my plugin loads, and I can see my custom options pages and the custom commands which I'm adding to the VS context menus. But when I actually run the custom command (which tries to retrieve some data using MySqlConnector), the database connection fails because the System.Memory assembly fails to load.

FileNotFoundException details

There is some more debugging information provided in the exception message which I don't fully understand:

additional assembly loading details

My app.config contains the following binding redirect:

  <dependentAssembly>
    <assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1" />
  </dependentAssembly>

The bin/Debug folder and the generated .vsix file both contain MySqlConnector.dll 1.0.0 and System.Memory.dll 4.0.1.1.

Looking at MySqlConnector.dll using ReSharper's Assembly Explorer, I see it references System.Memory 4.0.1.0, which is the assembly that's failing to load.

My questions are:

  1. Why does the FileNotFoundException mention 4.0.1.0? Shouldn't the binding redirect cause .NET to try and load 4.0.1.1, which is definitely present?
  2. Is there anything special I need to do to configure binding redirects in a VSIX/VSPackage context?
  3. What do the extra assembly binding/loading diagnostics (e.g. === Pre-bind state information === etc) actually mean?
  4. What do I need to change to get this assembly to load correctly?

More details

I started with the standard VS2017 VSIX package project template, which targets .NET 4.6, and later retargeted my project to .NET Framework 4.7.2.

I thought the issue might be caused by types which are included in .NET Fx 4.7.2 but shipped separately when targeting .NET Fx 4.6, so I tried reinstalling both the MySqlConnector and System.Memory NuGet packages after changing target to 4.7.2. It made no difference.

I am using traditional packages.config-style NuGet configuration.

The VSIX package uses the NuGet package MySqlConnector 1.0.0 (project home page). The package doesn't list any dependencies explicitly for .NET 4.7.2:

MySqlConnector dependency list from NuGet package manager

But it appears the dependencies for .NETFramework,Version=4.7.1 also apply to 4.7.2, since installing MySqlConnector results in the System.Memory 4.5.4 NuGet package also being installed. This adds a reference to the System.Memory 4.0.1.1 assembly to my VSIX VSPackage project, with Copy Local == True:

System.Memory reference properties

The checkbox in the project properties for "Auto-generate binding redirects" is checked: auto-generate binding redirects is checked

But, oddly, there is no <AutoGenerateBindingRedirects> element in the csproj file. Perhaps that element doesn't apply to VSIX projects for some reason or it became implicit behaviour at some point?

When I build my project, the System.Memory.dll file is being copied from the NuGet packages folder into the binary output folder, as I would expect:

Windows explorer screencap showing System.Memory.dll in output folder

Inspecting the file in the binary output folder with ILSpy proves that it's the latest version, and matches the assembly binding redirect in app.config:

// C:\git\[redacted]\bin\Debug\System.Memory.dll
// System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
// Global type: <Module>
// Architecture: AnyCPU (64-bit preferred)
// Runtime: v4.0.30319
// This assembly is signed with a strong name key.
// Hash algorithm: SHA1

Opening the .vsix file in the output folder using 7-zip shows that the VSIX also contains System.Memory.dll:

VSIX archive contents


Solution

  • It seems that the answer is that .NET was trying to load the older version because VSPackage/VSIX projects do not honor binding redirects configured in app.config (which is maintained by NuGet package install/uninstall actions), because they are plugins loaded within the context of Visual Studio's devenv.exe, which picks up its primary binding redirect configuration from its own devenv.exe.config file that the VSIX can't edit.

    Instead, VSIX infrastructure allows for binding redirects to be configured in .pkgdef files, which VS pays attention to when loading the VSPackage. The .pkgdef file is an output from the VSIX build process, so directly editing it would be rather fragile. The easiest way to maintain .pkgdef binding redirects is to add assembly-scoped instances of the Microsoft.VisualStudio.Shell.ProvideBindingRedirectionAttribute to the VSPackage project's AssemblyInfo.cs file. The VSIX build tools will pick up those attributes and generate binding redirects in the .pkgdef file when the VSIX is built.

    After adding various ProvideBindingRedirectionAttribute instances based on the contents of my app.config, I can now load the MySqlConnector DLL.