Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OOP-Designer is unable to resolve assembly references when CopyLocal = false #12791

Open
furkane-3 opened this issue Jan 15, 2025 · 12 comments
Open
Labels
area-VSDesigner Windows Forms out-of-proc designer related issues blocking-migration An issue that is preventing the developer from migrating from .NET Framework or earlier .NET

Comments

@furkane-3
Copy link

Environment

Version 17.12.3

.NET version

.NET 8.0

Did this work in a previous version of Visual Studio and/or previous .NET release?

Yes, in .NET 4.7.2

Issue description

Hi,

We are currently migrating a WinForms solution from .NET Framework (472) to .NET Core (8.0), but we are having issues with the Out-of-Process Designer resolving assembly references to several DLL's that are the output of another solution.

We are opting to do it this way because we still want to maintain backwards compatibility with the Framework edition of our codebase. It is our understanding that the general recommendation is to use NuGet feeds and/or CopyLocal = True for the referenced assemblies, but this does not work for our use case since both methods are disruptive to our workflow.

We are wondering if there is a way to assist the Designer in resolving these assembly references by indicating to it to look for:
<MySharedOutputDir>\MyAssembly.dll
instead of
C:\Users\<user>\AppData\Local\Microsoft\VisualStudio\17.0_XXXX\WinFormsDesigner\UserAppData\MyAssembly.dll?

Thanks!

Steps to reproduce

I have attached a reproduction below that causes the error when I try to add a UserControl. which refers to a class in the external assembly, to a Form.
WinFormsDesignerIssueRepro.zip

Caveat

I understand this example is quite simple and we could probably work around it by putting the instantiation of Class1 behind a check for DesignMode, but this would not work in our actual project due to the sheer complexity of refactoring it as such.

Steps:

  1. Build ClassLibrary solution => OK
  2. Build WinFormsApp solution => OK
  3. In VS, open Form1.cs with the Designer and try to drag the UserControl1 from the Toolbox onto it
  4. Observe error indicating that it was unsuccessful.
  5. Clicking "OK" displays the empty form without the UserControl => NOK

Image

Some diagnostic output from procmon64.exe that confirms the issue has to to with resolving the assembly reference:

Image

Temporary Workaround

  1. In WinFormsApp.csproj do the following:
...
    <Reference Include="ClassLibrary">
      <HintPath>..\..\build\ClassLibrary.dll</HintPath>
	<CopyLocal>True</CopyLocal>
    </Reference>
...
  1. Close all designers
  2. Rebuild WinFormsApp solution
  3. Reopen Form1.cs in Designer and try to add the UserControl1 from the Toolbox onto it => OK
    Image

Diagnostics

[14:50:52.629663] trce: Sending request: DesignerHosts/CreateComponent
[14:50:52.629663] info: [WinFormsApp]: TypeResolution: type = WinFormsApp.UserControl1, TimeTaken = 0 ms.
[14:50:52.629663] trce: DesignerHosts/CreateComponent took 00:00:00.0053744.
[14:50:52.629663] fail: Request failures: DesignerHosts/CreateComponent.
                        Microsoft.DotNet.DesignTools.Client.DesignToolsServerException: Could not load file or assembly 'ClassLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.

                        For information on how to troubleshoot the designer refer to the guide at https://aka.ms/winforms/designer/troubleshooting.
[14:50:52.629663] fail: Exception: Microsoft.DotNet.DesignTools.Client.DesignToolsServerException: Could not load file or assembly 'ClassLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
                           at Microsoft.DotNet.DesignTools.Client.DesignToolsClient.<SendRequestAsync>d__49`1.MoveNext()
                        --- End of stack trace from previous location where exception was thrown ---
                           at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
                           at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
                           at Microsoft.VisualStudio.Threading.JoinableTask.CompleteOnCurrentThread()
                           at Microsoft.VisualStudio.Threading.JoinableTask`1.CompleteOnCurrentThread()
                           at Microsoft.DotNet.DesignTools.Protocol.Endpoints.DesignToolsEndpoints.DesignerHostsImpl.CreateComponent(SessionId sessionId, TypeIdentity type, String name, NameValuePairs defaultValues)
                           at Microsoft.WinForms.DesignTools.Client.Toolbox.WinFormsToolboxItem.CreateComponentsCore(IDesignerHost host, IDictionary defaultValues)
                           at System.Drawing.Design.ToolboxItem.CreateComponents(IDesignerHost host, IDictionary defaultValues)
                           at Microsoft.DotNet.DesignTools.Client.Designers.ComponentProxyDesigner.CreateTool(ToolboxItem tool, Nullable`1 location, Nullable`1 size, ObjectProxy toolboxSnapArgs)

                        For information on how to troubleshoot the designer refer to the guide at https://aka.ms/winforms/designer/troubleshooting.
[14:50:52.629663] fail: Exception message = Microsoft.DotNet.DesignTools.Client.DesignToolsServerException: Could not load file or assembly 'ClassLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
                           at Microsoft.DotNet.DesignTools.Client.DesignToolsClient.<SendRequestAsync>d__49`1.MoveNext()
                        --- End of stack trace from previous location where exception was thrown ---
                           at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
                           at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
                           at Microsoft.VisualStudio.Threading.JoinableTask.CompleteOnCurrentThread()
                           at Microsoft.VisualStudio.Threading.JoinableTask`1.CompleteOnCurrentThread()
                           at Microsoft.DotNet.DesignTools.Protocol.Endpoints.DesignToolsEndpoints.DesignerHostsImpl.CreateComponent(SessionId sessionId, TypeIdentity type, String name, NameValuePairs defaultValues)
                           at Microsoft.WinForms.DesignTools.Client.Toolbox.WinFormsToolboxItem.CreateComponentsCore(IDesignerHost host, IDictionary defaultValues)
                           at System.Drawing.Design.ToolboxItem.CreateComponents(IDesignerHost host, IDictionary defaultValues)
                           at Microsoft.DotNet.DesignTools.Client.Designers.ComponentProxyDesigner.CreateTool(ToolboxItem tool, Nullable`1 location, Nullable`1 size, ObjectProxy toolboxSnapArgs)

                        For information on how to troubleshoot the designer refer to the guide at https://aka.ms/winforms/designer/troubleshooting.
@furkane-3 furkane-3 added the untriaged The team needs to look at this issue in the next triage label Jan 15, 2025
@Zheng-Li01
Copy link
Member

@furkane-3, no exception pops up when add the usercontrol from toolbox to Form1.cs[Design] page as below screenshot,
Image

@furkane-3
Copy link
Author

Hi Zheng,

Thank you for the response.

It appears as though you have the projects under the same solution, which is different from the structure in the repro that I sent.

What you have:

WinFormsApp.sln
|
| --- ClassLibrary.csproj
|
| --- WinFormsApp.csproj

What I have:

ClassLibrary.sln
|
| --- ClassLibrary.csproj

...

WinFormsApp.sln
|
| --- WinFormsApp.csproj

Sorry for the confusion.

We are doing this because we have domain-specific code in this separate ClassLibrary solution, that we are referring to in our WinFormsApp solution to render our views. Therefore, trying to add a UserControl, which refers to something (i.e. a Class) in the external ClassLibrary.dll, to a Form in the WinFormsApp solution, results in an exception in the Designer.

Could you please try again with the project structure that I have suggested?

Thanks!

@Tanya-Solyanik
Copy link
Member

@Zheng-Li01 - please move this issue to the designer repo once you can repro it.

@Zheng-Li01
Copy link
Member

@furkane-3, thanks for your response. and the issue can reproduce based on your description. just there still have a question need to confirm with you. in my testing, the usercontrol can be added successfully when setting the CopyLocal = false & Private = True or uncommand the Private = False in the .csproj file as below screenshot. so this issue seems related the Private filed setting?
Image
Repo.zip

@furkane-3
Copy link
Author

Hi @Zheng-Li01 ,

Glad to hear that!

I think the Private and CopyLocal tags are related. Per the docs:

Private | Optional boolean. Specifies whether the reference should be copied to the output folder. This attribute matches the Copy Local property of the reference that's in the Visual Studio IDE.

So in the case where you are able to add the UserControl with CopyLocal = False and Private = True, the value assigned to Private is overriding the value assigned to CopyLocal, so it is still copying the external assembly, which is not intended.

Please correct me if I am wrong.

Thanks

@Zheng-Li01
Copy link
Member

@furkane-3, thanks for your response. and the issue can reproduce based on your description. just there still have a question need to confirm with you. in my testing, the usercontrol can be added successfully when setting the CopyLocal = false & Private = True or uncommand the Private = False in the .csproj file as below screenshot. so this issue seems related the Private filed setting? Image Repo.zip

@Tanya-Solyanik could you please have a check for above results. if still need to open a GH DT issue to track this?

@Tanya-Solyanik
Copy link
Member

Tanya-Solyanik commented Jan 19, 2025

@furkane-3

We are wondering if there is a way to assist the Designer in resolving these assembly references by indicating to it to look for:
<MySharedOutputDir>\MyAssembly.dll
instead of
C:\Users\<user>\AppData\Local\Microsoft\VisualStudio\17.0_XXXX\WinFormsDesigner\UserAppData\MyAssembly.dll?

Designer resolves assemblies only from this user profile location. This directory is populated from your project output folders and design time dependencies. THe later ones are collected by these targets - https://github.com/dotnet/sdk/blob/0dd73ce2e1855ff5470c76e8aa7dde82cc64eca1/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets. Note that list of your designer dependencies must be in sync with your designer deps file, generated by the same targets.

so it is still copying the external assembly, which is not intended.

Is it possible for your project to set Private to true only when assembly must be copied?

@Zheng-Li01 - does combination CopyLocal = False and Private = True work the same on SDK-style projects and the "old" style projects?

@Tanya-Solyanik Tanya-Solyanik added blocking-migration An issue that is preventing the developer from migrating from .NET Framework or earlier .NET area-VSDesigner Windows Forms out-of-proc designer related issues labels Jan 19, 2025
@Zheng-Li01
Copy link
Member

@furkane-3

We are wondering if there is a way to assist the Designer in resolving these assembly references by indicating to it to look for:
<MySharedOutputDir>\MyAssembly.dll
instead of
C:\Users\<user>\AppData\Local\Microsoft\VisualStudio\17.0_XXXX\WinFormsDesigner\UserAppData\MyAssembly.dll?

Designer resolves assemblies only from this user profile location. This directory is populated from your project output folders and design time dependencies. THe later ones are collected by these targets - https://github.com/dotnet/sdk/blob/0dd73ce2e1855ff5470c76e8aa7dde82cc64eca1/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets. Note that list of your designer dependencies must be in sync with your designer deps file, generated by the same targets.

so it is still copying the external assembly, which is not intended.
Is it possible for your project to set Private to true only when assembly must be copied?

@Zheng-Li01 - does combination CopyLocal = False and Private = True work the same on SDK-style projects and the "old" style projects?

CopyLocal = False and Private = True Not Work the same on SDK-style projects with exceptions pops up
Image

CopyLocal = False and Private = True working "old" style projects
Image

@merriemcgaw merriemcgaw removed the untriaged The team needs to look at this issue in the next triage label Jan 22, 2025
@Tanya-Solyanik
Copy link
Member

@Zheng-Li01 - by "working" I meant the binary was copied to the output folder.

@furkane-3 - does setting private to true unblock you?

@furkane-3
Copy link
Author

Hi @Tanya-Solyanik,

Setting Private = true appears to resolve the issue. But, as I told @Zheng-Li01 last week, the binary is still copied over to the directory, which we do not want. Are you suggesting we utilize/override some of the designer's targets in order to only copy over the assemblies at design-time?

@Tanya-Solyanik
Copy link
Member

Tanya-Solyanik commented Jan 23, 2025

@furkane-3 - You could use these targets and tasks as an inspiration to create your own custom ones to copy or not copy assemblies to the right places. But first I wonder if you tried to conditionally include different dependencies that targeting different frameworks and create package only for the .NET dependencies, so that you follow the recommended path for .NET and the "old" path for .NET Framework?

  <ItemGroup Label="include class library as a package" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
    <PackageReference Include="ClassLibraryPackage" Version="1.1.1" />
  </ItemGroup>
  <ItemGroup Label="include class library " Condition="'$(TargetFrameworkIdentifier)' == '.NETFRAMEWORK'">
    <PackageReference Include="ClassLibrary.dll" Version="1.0.0" />
  </ItemGroup>

@furkane-3
Copy link
Author

For the time being, we've opted to do:

	<Reference Include="MyAssembly">
		<SpecificVersion>False</SpecificVersion>
		<HintPath>$(PathToMyAssembly)\MyAssembly.dll</HintPath>
		<Private>False</Private>
		<CopyLocal>False</CopyLocal>
		<Private Condition="'$(DesignTimeBuild)|$(TargetFramework)' == 'true|net8.0-windows'">True</Private>
		<CopyLocal Condition="'$(DesignTimeBuild)|$(TargetFramework)' == 'true|net8.0-windows'">>True</CopyLocal>
	</Reference>

Per the recommendation here.

This seems to resolve the assembly file in design-time and avoid copying it over when building the solution, but we are observing some slowness and some panels becoming unresponsive in the Designer. Would you know of another, more efficient solution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-VSDesigner Windows Forms out-of-proc designer related issues blocking-migration An issue that is preventing the developer from migrating from .NET Framework or earlier .NET
Projects
None yet
Development

No branches or pull requests

4 participants