Designer shadow-copy leak when multi-targeting .NET Framework and modern frameworks
- Dominant language
- C#
- Stars
- 3.2k
- Forks
- 1.3k
- PR merge metrics
- PR metrics pending
Description
## Problem
A developer with a WinForms project multi-targeting both a modern and a legacy framework:
```xml
net8.0-windows;net48
```
...could not reliably use third-party WinForms controls in the designer. The control vendor (e.g., DevExpress) ships separate builds per TFM (`lib/net462` and `lib/net8.0-windows`). Opening the designer for the `net8.0-windows` side of the project would end up with the **`net462`-built control assemblies** shadow-copied alongside the correct `net8.0-windows` ones, mixing two different builds of the same libraries in one design surface and corrupting/crashing it. Removing `net48` from `TargetFrameworks` (going single-target) made the problem disappear.
## Root Cause
File: `src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets`
The target `_GatherDesignerShadowCopyFiles` populates `DesignerRuntimeImplementationProjectOutputGroupOutput`, which backs the shadow-copy folder (`UserAppData`) the out-of-process WinForms/WPF designer host (`DesignToolsServer.exe`) loads from. The target had **no target-level condition**, so it always ran for every inner build of a multi-targeted project. For a project's `net48` inner build (`.NETFramework`), this meant **every NuGet copy-local asset resolved for `net48`** (i.e. the vendor's `net462` build) went into the output group that is later merged by the IDE into the `net8.0-windows` designer session.
The `net48` inner build's result has nothing that legitimately consumes it: the classic **in-process** WinForms designer (the default for `.NET Framework`) loads assemblies directly from `bin\\net48\` and the NuGet global packages folder — it never reads this output group. So this data is unused-by-default noise for `net48` — except it doesn't stay unused. Whatever mechanism the IDE uses to select/merge per-TFM design-time-build results for a multi-targeted project ends up feeding this unused `net48` result into the actual `net8.0-windows` out-of-process designer session's shadow-copy folder.
## Repro Steps
See the new test cases in `test/Microsoft.NET.Build.Tests/GiventThatWeWantDesignerSupport.cs`:
1. `It_does_not_include_framework_assets_when_multitargeting_framework_and_core()` — multi-targets `net6.0-windows;net46` and verifies the net46 side produces no output group entries
2. `It_includes_nuget_assets_for_framework_when_out_of_proc_designer_is_opted_in()` — verifies the opt-in `.NET Framework` out-of-process designer scenario still works when `UseWinFormsOutOfProcDesigner=true`
## Expected vs Actual
**Expected:** Designer shadow-copy for the active `net8.0-windows` TFM contains only `net8.0-windows`-built assemblies
**Actual:** `net462`-built assemblies from the `net48` side leak in, corrupting the design surface
## Solution
Add a condition to the `_GatherDesignerShadowCopyFiles` target to only run when an out-of-process host actually needs it: `.NET Core` always, or `.NET Framework` when `UseWinFormsOutOfProcDesigner` is set.
Related: #2895 (original implementation for .NET Core designer), #2707 (original feature request)
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets and inspect _GatherDesignerShadowCopyFiles, then read the cases in test/Microsoft.NET.Build.Tests/GiventThatWeWantDesignerSupport.cs. Run the named tests and confirm framework assets are excluded when multi-targeting, while the opted-in .NET Framework out-of-process designer still receives its NuGet assets.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- build-system, desktop
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100