Search code examples
.net.net-assemblyassembly-binding-redirectassemblybindingfuslogvw

Where does .NET fetch an assembly when it is not found in the GAC


I am running into

FileLoadException: Could not load file or assembly 'Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

I found the Assembly Binding Log Viewer (Fuslogvw.exe) and I am struggling to understand the log that I got back from it.

*** Assembly Binder Log Entry  (10/26/2022 @ 7:23:21 PM) ***

The operation failed.
Bind result: hr = 0x80131040. No description available.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
Running under executable  C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\Extensions\TestPlatform\testhost.net472.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc
 (Fully-specified)
LOG: Appbase = file:///C:/git/company/Tests/WebDriver/regression.TestSuite/bin/Debug/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = Tests_812982921
Calling assembly : CompanyLib, Version=4.4.0.29278, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: C:\git\company\Tests\WebDriver\regression.TestSuite\bin\Debug\regression.TestSuite.dll.config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.
LOG: Post-policy reference: Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc
LOG: GAC Lookup was unsuccessful.
LOG: Attempting download of new URL file:///C:/git/company/Tests/WebDriver/regression.TestSuite/bin/Debug/Castle.Core.DLL.
LOG: Assembly download was successful. Attempting setup of file: C:\git\company\Tests\WebDriver\regression.TestSuite\bin\Debug\Castle.Core.dll
LOG: Entering run-from-source setup phase.
LOG: Assembly Name is: Castle.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc
WRN: Comparing the assembly name resulted in the mismatch: Major Version
ERR: The assembly reference did not match the assembly definition found.
ERR: Run-from-source setup phase failed with hr = 0x80131040.
ERR: Failed to complete setup of assembly (hr = 0x80131040). Probing terminated.

*** Assembly Binder Log Entry  (10/26/2022 @ 7:23:21 PM) ***

The operation failed.
Bind result: hr = 0x80131040. No description available.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
Running under executable  C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\Extensions\TestPlatform\testhost.net472.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc
 (Fully-specified)
LOG: Appbase = file:///C:/git/company/Tests/WebDriver/regression.TestSuite/bin/Debug/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = Tests_812982921
Calling assembly : CompanyLib, Version=4.4.0.29278, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: C:\git\company\Tests\WebDriver\regression.TestSuite\bin\Debug\regression.TestSuite.dll.config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.
LOG: Post-policy reference: Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc
LOG: GAC Lookup was unsuccessful.
LOG: Attempting download of new URL file:///C:/git/company/Tests/WebDriver/regression.TestSuite/bin/Debug/Castle.Core.DLL.
LOG: Assembly download was successful. Attempting setup of file: C:\git\company\Tests\WebDriver\regression.TestSuite\bin\Debug\Castle.Core.dll
LOG: Entering run-from-source setup phase.
LOG: Assembly Name is: Castle.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc
WRN: Comparing the assembly name resulted in the mismatch: Major Version
ERR: The assembly reference did not match the assembly definition found.
ERR: Run-from-source setup phase failed with hr = 0x80131040.
ERR: Failed to complete setup of assembly (hr = 0x80131040). Probing terminated.

I would appreciate if someone could point me to docs or explain from a high level the following concepts in this log; 'Pre binding', 'calling assembly'

and specifically my problem is

LOG: GAC Lookup was unsuccessful.
LOG: Attempting download of new URL file:///C:/git/company/Tests/WebDriver/regression.TestSuite/bin/Debug/Castle.Core.DLL.

Since the assembly was not in the GAC where does .NET look to find out where to download the assembly from? I dont see any reference to this assembly in regression.TestSuite.csproj or the app.config only an dependentAssembly element in the app.config with a bindingRedirect`.

I have tried updating the bindingRedirect to target the newer version 5.0.0.0 but was still running into this issue.


Solution

  • Ok I have sorted out my specific issue and have answers to my questions that I will post here for others new to .NET assembly binding.

    'Pre Binding' as I now understand it is what we know about the assembly before the runtime attempts to locate assemblies.

    'calling assembly' is the assembly that was invoked by the calling code that in turn requires the assembly to be fetched. ie: project A depends directly on project B which depends on project C. When A invokes code in B the runtime has to then fetch C. B in this example is the calling assembly.

    As for where .NET downloads a file that is not found in the GAC, the answer is nuanced depending on your setup but all the good details I found here.

    Really what made this click for me was reading the entire doc on How the Runtime Locates Assemblies. After reading this I could not make sense of how even after updating the bindingRedirect to point to 5.0.0.0 (this was the version of the assembly in the application bases' binpath) the FusionLog was still showing the 'Post-Policy' version of 4.0.0.0.

    This lead me to looking at the child elements of <assemblyBinding>. And there I found my answer...

    Use one dependentAssembly element for each assembly.

    So I had two issues.

    1. I was putting multiple assembly redirects in a single dependentAssembly elements (MS did not error on this and it just silently ignores any additional assembly redirects under the first one)
    2. the bindingRedirect was not targeting the correct new version.

    Borked app.config

        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <dependentAssembly>
            <assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
            <assemblyIdentity name="Castle.Core" publicKeyToken="407dd0808d44fbdc" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="5.0.0.0" />
          </dependentAssembly>
        </assemblyBinding>
    

    Fixed app.config

        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <dependentAssembly>
            <assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="Castle.Core" publicKeyToken="407dd0808d44fbdc" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="5.0.0.0" />
          </dependentAssembly>
        </assemblyBinding>