.netassembly-binding-redirect

Eliminating assembly binding redirects when targeting the .Net framework


We have a solution with 100+ projects (part of a big ugly .Net Framework Monolith app). Now we are pushing to move as much code as possible as NuGet packages to our internal repo. But now we are in a dependency hell, because even though all of our code is unsigned, most of the 3rd party NuGets are signed. As well as all the Microsoft packages.

This hell got worse since we migrated from packages.config to PackageReference, because many dependencies became implicit (being transitive). On one hand we want to use PackageReference, because:

  1. It is a step closer to moving to SDK style projects, where possible.
  2. Precisely, because it shows what we use without cluttering the project with transitive deps.
  3. It is the future, right?

But on the other hand it is hellish to sort through all these binding redirects. And the worst thing - it is not consistent from msbuild to VS IDE, see Why does console build generate radically different project.assets.json than that generated with VS IDE build?

I want to get rid of them once and for all. My idea is:

  1. Suppress all the warnings related to binding redirects - MSB3277 and MSB3247
  2. Remove them from all the config files
  3. Resolve assemblies at runtime with a dedicated code

I wonder if anyone has tried this approach. Cannot be that we are the only ones that are struggling with the binding redirects, this device of torture inflicted upon us undoubtfully for the sin of programming .Net rather than Java.

I have a concrete question - has anyone succeeded in replacing all the config time assembly binding redirects with a logic at runtime? I want to suppress all of the binding redirect related warnings and forget about them once and for all while staying in .Net Framework (not Core) realm.

Edit 1

So, there is a desire to see a sample of concrete binding redirect warnings that we have. Alright:

MSB3247: Found conflicts between different versions of the same dependent assembly. In Visual Studio, double-click this warning (or select it and press Enter) to fix the conflicts; otherwise, add the following binding redirects to the "runtime" node in the application configuration file: <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Buffers" culture="neutral" publicKeyToken="cc7b13ffcd2ddd51" /><bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" /></dependentAssembly></assemblyBinding><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Diagnostics.DiagnosticSource" culture="neutral" publicKeyToken="cc7b13ffcd2ddd51" /><bindingRedirect oldVersion="0.0.0.0-4.0.3.1" newVersion="4.0.3.1" /></dependentAssembly></assemblyBinding><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Memory" culture="neutral" publicKeyToken="cc7b13ffcd2ddd51" /><bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1" /></dependentAssembly></assemblyBinding><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" culture="neutral" publicKeyToken="b03f5f7f11d50a3a" /><bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" /></dependentAssembly></assemblyBinding> [C:\xyz\tip\Services\Platform\WBDataSvc\TestServices\TestServices.csproj]
MSB3247: Found conflicts between different versions of the same dependent assembly. In Visual Studio, double-click this warning (or select it and press Enter) to fix the conflicts; otherwise, add the following binding redirects to the "runtime" node in the application configuration file: <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Buffers" culture="neutral" publicKeyToken="cc7b13ffcd2ddd51" /><bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" /></dependentAssembly></assemblyBinding><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Memory" culture="neutral" publicKeyToken="cc7b13ffcd2ddd51" /><bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1" /></dependentAssembly></assemblyBinding><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" culture="neutral" publicKeyToken="b03f5f7f11d50a3a" /><bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" /></dependentAssembly></assemblyBinding> [C:\xyz\tip\Services\Platform\DeviceServices\DeviceServices.csproj]
MSB3247: Found conflicts between different versions of the same dependent assembly. In Visual Studio, double-click this warning (or select it and press Enter) to fix the conflicts; otherwise, add the following binding redirects to the "runtime" node in the application configuration file: <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Buffers" culture="neutral" publicKeyToken="cc7b13ffcd2ddd51" /><bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" /></dependentAssembly></assemblyBinding><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Memory" culture="neutral" publicKeyToken="cc7b13ffcd2ddd51" /><bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1" /></dependentAssembly></assemblyBinding><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" culture="neutral" publicKeyToken="b03f5f7f11d50a3a" /><bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" /></dependentAssembly></assemblyBinding> [C:\xyz\tip\Services\Platform\WBDataSvc\DataSvc\DataSvc.csproj]
MSB3247: Found conflicts between different versions of the same dependent assembly. In Visual Studio, double-click this warning (or select it and press Enter) to fix the conflicts; otherwise, add the following binding redirects to the "runtime" node in the application configuration file: <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Buffers" culture="neutral" publicKeyToken="cc7b13ffcd2ddd51" /><bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" /></dependentAssembly></assemblyBinding><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Memory" culture="neutral" publicKeyToken="cc7b13ffcd2ddd51" /><bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1" /></dependentAssembly></assemblyBinding><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" culture="neutral" publicKeyToken="b03f5f7f11d50a3a" /><bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" /></dependentAssembly></assemblyBinding> [C:\xyz\tip\Services\Platform\WBDataSvc\MobileWebService\MobileWebService.csproj]
MSB3277: Found conflicts between different versions of "System.Diagnostics.DiagnosticSource" that could not be resolved.  These reference conflicts are listed in the build log when log verbosity is set to detailed. [C:\xyz\tip\ServerJobs\BackgroundJobTests\BackgroundJobTests.csproj]
MSB3277: Found conflicts between different versions of "System.Diagnostics.DiagnosticSource" that could not be resolved.  These reference conflicts are listed in the build log when log verbosity is set to detailed. [C:\xyz\tip\ServerJobs\ImportJobsTests\ImportJobsTests.csproj]
MSB3277: Found conflicts between different versions of "System.Diagnostics.DiagnosticSource" that could not be resolved.  These reference conflicts are listed in the build log when log verbosity is set to detailed. [C:\xyz\tip\Services\Platform\WBDataSvc\PayrollEngineDALTests\Payroll.Engine.DB.Tests.csproj]
MSB3277: Found conflicts between different versions of "System.Diagnostics.DiagnosticSource" that could not be resolved.  These reference conflicts are listed in the build log when log verbosity is set to detailed. [C:\xyz\tip\Services\Platform\WBDataSvc\PayrollEngineTests\Payroll.Engine.Tests.csproj]
MSB3277: Found conflicts between different versions of "System.Diagnostics.DiagnosticSource" that could not be resolved.  These reference conflicts are listed in the build log when log verbosity is set to detailed. [C:\xyz\tip\Services\Platform\WBDataSvc\ReportingEngineSupportTests\ReportingEngineSupportTests.csproj]
MSB3277: Found conflicts between different versions of "System.Diagnostics.DiagnosticSource" that could not be resolved.  These reference conflicts are listed in the build log when log verbosity is set to detailed. [C:\xyz\tip\Services\Platform\WBDataSvc\RuleEngineTests\RuleEngineTests.csproj]
MSB3277: Found conflicts between different versions of "System.Diagnostics.DiagnosticSource" that could not be resolved.  These reference conflicts are listed in the build log when log verbosity is set to detailed. [C:\xyz\tip\Services\Platform\WBDataSvc\WbDbTests\WbDbTests.csproj]
MSB3277: Found conflicts between different versions of "System.Diagnostics.DiagnosticSource" that could not be resolved.  These reference conflicts are listed in the build log when log verbosity is set to detailed. [C:\xyz\tip\Test\UnitTests\UnitTests.csproj]

Now sure enough, we can shuffle the missing binding redirects into the config files, but:

  1. In light of Why does console build generate radically different project.assets.json than that generated with VS IDE build? that is not good enough, because VS IDE produces a different set of dependencies in the project.assets.json (I know, a totally bogus thing. An issue in the DC was open) and we have witnessed that different set of redirects were required for devs building in VS IDE vs command line
  2. Binding redirects grow monotonically - we are told when to add, but not when to remove. As a result, with time we have gazillion different binding redirects and they need to be maintained, because some get out of date, some are no longer needed.

It is one big nuisance.


Solution

  • I decided against trying to replace the config time binding redirects with the runtime assembly resolution. The reasons - I do not know how to ensure it reliably given:

    Instead I decided to leverage the following aspects of our setup:

    This makes it possible to write a tool that could read project.assets.json for the given project (and recursively for all the other projects it depends on) and based on them do two things:

    1. Identify all the NuGet package dependencies which are mentioned with different versions. E.g. if NuGet package X depends on NuGet package Y v1, but NuGet package Z depends on Y v2, then Y is problematic. And we can recognize this condition and determine the file path of the highest version - v2.
    2. Update the binding redirects automatically.
    3. After the build copy the files identified in the first step to the published directory.

    This way the binaries in the bin folder would not depend on the build order of the projects in the solution and we would have a deterministic process to maintain the binding redirects.

    This is a work in progress, but it looks promising - https://github.com/MarkKharitonov/GenerateDotNetBindingRedirects