AutoGenerateBindingRedirects doesn't propagate from dependencies and ReferenceCopyLocalPaths copies wrong assembly versions
- Dominant language
- C#
- Stars
- 5.5k
- Forks
- 1.5k
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 141
Description
## Problem Summary
When building .NET Framework projects with `AutoGenerateBindingRedirects=true`, MSBuild has two critical issues that cause runtime failures:
1. **Binding redirects from referenced class library projects are not propagated** to the consuming executable project
2. **Multiple versions of the same assembly are copied to output**, with the wrong version often overwriting the correct one specified in binding redirects
This causes "Could not load file or assembly" runtime errors despite having correct binding redirects in app.config.
## Background
This issue relates to several long-standing problems:
- #1310 (2016) - `` doesn't work for class libraries
- dotnet/sdk#40284 (2023) - MSBuild missing automatic binding redirects with transitive dependencies
- #10821 (2024) - Binding redirects not generated for transitive packages with CPM (now closed)
## Reproduction Scenario
**Project Structure:**
```
Solution
├── ClassLibrary.csproj (references System.Runtime.CompilerServices.Unsafe 6.0.3.0)
│ └── ClassLibrary.dll.config (has binding redirect to 6.0.3.0)
└── ExecutableApp.csproj (references ClassLibrary)
└── Also indirectly needs System.Runtime.CompilerServices.Unsafe
```
**Current Behavior:**
1. ClassLibrary correctly generates binding redirect for `System.Runtime.CompilerServices.Unsafe` → 6.0.3.0
2. ExecutableApp's `AutoGenerateBindingRedirects` **ignores** ClassLibrary's binding redirects
3. MSBuild copies multiple versions (6.0.0.0, 6.0.1.0, 6.0.3.0) to output
4. **Wrong version wins** (e.g., 6.0.1.0 overwrites 6.0.3.0)
5. Runtime failure: binding redirect points to 6.0.3.0, but 6.0.1.0 is in output
**Expected Behavior:**
1. ExecutableApp should merge binding redirects from all dependencies
2. Only the highest version (6.0.3.0) should be copied to output
3. Runtime should work without errors
## Current Workarounds (All Manual)
Teams currently work around this by:
1. Adding direct `PackageReference` to every conflicting assembly in executable projects
2. Manually copying binding redirects from dependencies
3. Running `Add-BindingRedirect` PowerShell command after every package update
4. Maintaining large manual binding redirect sections
All of these are error-prone and don't scale.
## Working Solution (Pure MSBuild)
A complete solution has been implemented using pure MSBuild (no custom tasks, no inline code) that solves both problems.
### Architecture
Two targets working together:
**1. `MergeBindingRedirectsFromDependencies`** - Fixes binding redirect propagation
- Runs `AfterTargets="ResolveAssemblyReferences"` and `BeforeTargets="GenerateBindingRedirects"`
- Reads `.config` files from `@(ReferencePath)` items
- Extracts binding redirects using `XmlPeek` task
- Uses Cartesian product technique to find maximum versions
- Merges into `@(SuggestedBindingRedirects)` before MSBuild generates final redirects
**2. `FixReferenceCopyLocalPaths`** - Fixes assembly copy conflicts
- Runs after binding redirect merge, before file copy
- Uses `GetAssemblyIdentity` to read actual assembly versions from DLLs
- Detects duplicates in `@(ReferenceCopyLocalPaths)` by output path
- Compares with binding redirect versions
- Removes incorrect versions, keeping only the one matching binding redirects
- Handles related files (.pdb, .xml, .config)
### Key Implementation Techniques
**Cartesian Product for Max Version Selection:**
```xml
%(_DependencyBindingRedirect.MaxVersion)
```
**Assembly Deduplication:**
```xml
```
### Full Implementation
Complete working targets file will be attached as a follow-up comment.
## Testing Results
Tested successfully on:
- Solutions with 20+ projects and conflicting NuGet dependencies
- BCL packages: `System.Runtime.CompilerServices.Unsafe`, `System.Memory`, `Microsoft.Bcl.AsyncInterfaces`
- Mixed .NET Framework 4.6.1+ and .NET Standard 2.0 references
- Central Package Management with transitive pinning
- No detectable build time impact
## Proposed MSBuild Improvements
This solution demonstrates the problems are solvable within MSBuild's existing capabilities. Microsoft could incorporate similar logic into built-in targets:
### 1. Enhance `GenerateBindingRedirects` Target
- Read `.config` files from `@(ReferencePath)` assemblies
- Parse existing binding redirects using `XmlPeek`
- Merge with `@(SuggestedBindingRedirects)`, preferring higher versions
- Already has access to all necessary inputs
### 2. Fix `ResolveAssemblyReferences` Assembly Copy Logic
- Deduplicate `@(ReferenceCopyLocalPaths)` by assembly identity (not just filename)
- Use `GetAssemblyIdentity` to read actual versions
- When multiple versions exist:
- Prefer version matching binding redirect if available
- Otherwise prefer highest version
- Apply same logic to related files (.pdb, .xml, .config)
### 3. Add Diagnostic Warnings
- **MSB3XXX**: Multiple versions of assembly '{0}' are being copied to output
- **MSB3XXX**: Assembly '{0}' version {1} doesn't match binding redirect version {2}
## Benefits
- **Zero configuration** - Works automatically for all .NET Framework projects
- **Eliminates runtime failures** - Ensures copied assemblies match binding redirects
- **Reduces manual maintenance** - No more copying binding redirects between projects
- **Compatible** - Works with existing projects without breaking changes
- **Handles modern scenarios** - Works with PackageReference, CPM, transitive dependencies
## Request
Please consider incorporating this approach into MSBuild's built-in targets. The .NET Framework ecosystem would greatly benefit from automatic handling of these long-standing issues.
While the community can use this solution via `Directory.Build.targets`, having it built into MSBuild would:
- Reach all .NET Framework developers automatically
- Be maintained and optimized by the MSBuild team
- Provide consistent behavior across all projects
- Reduce the support burden from these common issues
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.