How to override dependency version of a nuget package

635 views Asked by At

I have a .netstandard2.0 project, let's say it is called common, which is using System.Configuration.ConfigurationManager 5.0.0 and log4net 2.0.15 from nuget.org. This common project is used by project A which is a .net5 project, and also used by project B, which is a .net framework 4.6.1 project. In Common project, I use System.Configuration.ConfigurationManager and log4net like this:

<ItemGroup>
  <PackageReference Include="log4net" Version="2.0.15" />
  <PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
</ItemGroup>

Now I got assembly loading issue of System.Configuration.ConfigurationManager.dll when log4net tries to initialize, because it needs 4.0.1.0 of System.Configuration.ConfigurationManager instead of 5.0.0.

The error message looks like this:

System.IO.FileNotFoundException: Could not load file or assembly 'System.Configuration.ConfigurationManager, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.

How can I force log4net.dll to use System.Configuration.ConfigurationManager 5.0.0.0? I tried to add a log4net.dll.config as below, but not working. log4net still tries to load the older version of System.Configuration.ConfigurationManager. Anyone knows how to solve this issue except rebuild log4net from source?

<configuration>
    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <assemblyIdentity name="System.Configuration.ConfigurationManager" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
                <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
            </dependentAssembly>
        </assemblyBinding>
    </runtime>
</configuration>

I checked metadata of log4net.dll, it does use 4.0.1.0 version System.Configuration.ConfigurationManager. I tried to rebinding with above syntax, but seems being ignored.

To reproduce this issue, you need to set output folder of A and B are the same:

<OutputPath>$(SolutionDir)$(Configuraiton)$(Platform)</OutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>

Also, I found that If I rebuild A only (or rebuild solution, three projects A, B and Common are in the same solution), then check log4net.dll properties in the output folder by right click -> Properties -> Details, the Product version shows "2.0.15.0-NET Standard 2.0". However, If I rebuild B only, it becomes "2.0.15.0-.NET 4.5", in this case the issue is gone. The assembly loading issue only happens when log4net.dll shows "2.0.15.0-NET Standard 2.0".

1

There are 1 answers

4
Hank On

Everything is finally coming together with the latest information you provided.

The fundamental problem is building two separate '.NET' implementations into the same folder. There's a reason AppendTargetFrameworkToOutputPath exists.

A .NET dll cannot rely on a .NETFramework class library and vice-versa is also true. And that essentially what is happening here. The outputs of the build are clobbering each other and what runs is basically the one you built most recently.

When I set up my projects the way you describe I can recreate it.
ProjectA: net5.0, ProjectB: net461, Common: netstandard2.0.

Build A -> Build B. B runs fine but A does not.
Build B -> Build A. A runs fine but B does not.

If I add this hand rolled assembly binding redirect to B's appconfig

<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <dependentAssembly>
            <assemblyIdentity name="System.Configuration.ConfigurationManager" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
        </dependentAssembly>
    </assemblyBinding>
</runtime>

Build B -> Build A. Both work, but this is very unreliable.

If the assemblies are started via reflection, then you need to be extra careful because you need to make sure the CLR is loading the config files in the same way as it naturally would when starting the EXEs. This is what sets up all the binding redirects (app.config for .NETFramework, deps.json for .NET).