Search code examples
c#.netassembly-binding-redirectassemblybinding

Could not load file or assembly...even when 'correct' version is found


This seems like the standard issue of an incorrect version of a DLL is being found and to put an assembly binding redirect (or update the DLL version referenced) to resolve. However...

The full error message appearing is;

System.IO.FileLoadException: Could not load file or assembly 'Microsoft.SqlServer.Smo, Version=15.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
File name: 'Microsoft.SqlServer.Smo, Version=15.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91'
   at DatabaseManagement.clsDatabaseManagement.DeployDatabase(clsDatabaseDeploymentElement deployment, String scriptPath, String dataFilePath, String logFilePath)
   at DatabaseManagement.clsDatabaseManagement.DeployDatabase(clsDatabaseDeploymentElement deployment) in C:\agent\_work\4\s\src\Database\DatabaseManagement\DatabaseManagement.cs:line 920
   at DBAdmin.clsProgram.ProcessAction(Action action, clsDatabaseManagement manager) in C:\agent\_work\4\s\src\Database\DBAdmin\Program.cs:line 664

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].



Processing aborted!

Picking apart that message, the file it's looking for is 'Microsoft.SqlServer.Smo, Version=15.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91'.

The file it's finding is: 'Microsoft.SqlServer.Smo, Version=15.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91'

The strings are identical.

The assembly binding configuration;

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="Microsoft.SqlServer.Smo" publicKeyToken="89845dcd8080cc91" culture="neutral" />
      <bindingRedirect oldVersion="0.0.0.0-15.0.0.0" newVersion="15.0.0.0" />
    </dependentAssembly>
   </assemblyBinding>
</runtime>

A hindrance here is that .NET's assembly binding logging doesn't appear to be in effect (as per the error message). I've gone through the steps to enable it but it remains disabled. The registry settings are correctly set and persist after a restart. It...just doesn't work, and the text that it's not enabled still appears as part of the error message.

What I've tried/confirmed so far;

  • The correct version of the DLL is present in the exe's folder (v15)
  • The dependencies of the DLL are also present in the same folder (dependencies found via ilasm, and cross-checked with ilspy)
  • The specific version of .NET that is used by the DLL (v15 was build on .NET 4.5), is installed and present on the target machine
  • The assembly binding config is being used, as if I change the newVersion to 16.0.0.0, the message changes accordingly
  • Registering the correct version of the file into the GAC completed successfully but had no effect on the error message.
  • Putting v16 of the DLL into the exe's folder had no effect on the message. It still finds v15. This suggests the binding is not looking at the DLLs in the exe's folder (at least for that assembly).

The various tests point me at the conclusion that it is finding the correct version of the file (somewhere, I'm assuming the GAC), but that there is some other incompatibility that's triggering the error message.

Without the Fusion logs, I'm left with black-box testing to try and diagnose.


Solution

  • The solution was ultimately the usual one, putting in the appropriate assembly binding redirects, but getting the right redirect was a bit of a journey.

    The message displayed (assuming that .NET's binding logging is not enabled) is the assembly's display name. This string contains a version as part of it but this is distinct from the assembly's actual version or file version. This can make figuring out what's going on, as well as the correct binding, troublesome.

    The display name will get shown as part of the error message. In Windows Exporer, right-clicking and going to Properties and the Details tab will list the file version. Getting the actual version was a bit trickier but you can use ildasm or ILSpy to open the dll and look.

    In my case, the message was correct for the display name - they are indeed the same. What was actually happening is that the assembly being found was registered as 15.100.0.0, not 15.0.0.0, so there was a minor version mis-match. A further annoyance is that the assembly in the GAC was actually the correct version (I did a bit comparison to be sure), but had been registered incorrectly.

    I was able to find this out by getting the binding logging turned on. While I had set the registry settings manually, they weren't functional. I used FusionLog++ to capture the binding steps, and these clearly showed;

    • the assembly it was finding
    • the reason for the binding failure

    Some nuances to keep in mind when assessing the bindings/references;

    • there are some expected binding failures that are normal but will show as an error in the logs
      • some assembles will look for a *.resources.dll with the local culture setting (e.g., it may look for en-US, then en). This is normal and if those assembles don't exist, .NET will use the strings in the calling DLL.
      • some assembles look for msvcm90.dll (or there's no specified calling assembly in the log). This can be ignored.
      • if your application uses the XmlSerializer object, the .NET logic will look for an assembly YourApplication.XmlSerializers.dll. This is by design and can be ignored.
    • the binding will look in the GAC first for the referenced assembly. If found, this will ignore any DLL in the same folder as the application. It won't matter if you have the correct version deployed with your app if the target machine has a version in the GAC.
    • if your application is being built in a pipeline, the VM doing the build will need the appropriate 3rd party libraries installed if they are not specified by Nuget. Ensure they are the correct/same version as your development/target machine.
    • reference external packages via Nuget where possible rather than a direct reference