dotnet / dotnet/msbuild

AutoGenerateBindingRedirects doesn't propagate from dependencies and ReferenceCopyLocalPaths copies wrong assembly versions

Open
#13,329 1 comment 0 reactions 0 assignees View on GitHub
triaged
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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.