dotnet / dotnet/sdk

Designer shadow-copy leak when multi-targeting .NET Framework and modern frameworks

Open Beginner friendly
#55,954 1 comment 0 reactions 0 assignees View on GitHub
untriaged
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.