dotnet / project-system

The .NET Project System for Visual Studio
MIT License
968 stars 387 forks source link

WPF .NET Core 3 (SDK styled) project: Images (.png, .jpg etc) don't get correct Build Action when added to project #4534

Open vsfeedback opened 5 years ago

vsfeedback commented 5 years ago

When you have a .NET Core 3 WPF project in Visual Studio 2019 and you add a .png or a .jpg file, these files have the Build Action "None". That means if you use the image with an Image element in XAML, the Image won't be displayed at runtime, as it is not available at runtime.

The Build Action should be set to "Resource" when adding such an image. This happens, if you have a WPF project with the old .csproj format.

This problem is directly related to this issue on Github: https://github.com/dotnet/wpf/issues/292

Thanks a lot, Thomas

This issue has been moved from https://developercommunity.visualstudio.com/content/problem/440436/wpf-net-core-3-sdk-styled-project-images-png-jpg-e.html VSTS ticketId: 781517 These are the original issue comments:

Thomas Claudius Huber on 30/01/2019, 09:16 PM (4 days ago): Note: This happens with SDK-styled projets. It is not directly related to .NET Core 3. When you create an SDK styled project with WPF that targets .NET Framework 4.7.2, you have the same issue that images added to the project have the Build Action "None" instead of Resource. These are the original issue solutions: (no solutions)

davidwengier commented 5 years ago

The WPF flavor currently does this in ModifyBuildAction called by OnAfterAddFilesEx but it fails with the simplified project system here.

davkean commented 5 years ago

Why is WPF flavor even involved in this?

Pilchie commented 5 years ago

@nguerrera @dsplaisted - should this be handled by globs in the WPF SDK in the new world?

davkean commented 5 years ago

The concern with this would be that switching globs by opting in "WPF" would be a fairly disruptive and likely breaking change of the project.

davidwengier commented 5 years ago

Discussed this at the CPS meeting and adding a new glob to the WPF SDK, but not removing the items from None, seems like the best solution. @davkean we can discuss offline if you still have concerns.

dsplaisted commented 5 years ago

I don't think having globs for different item types (ie Resource and None) cover the same files works well. I think Visual Studio ends up doing weird things to your project file.

tannergooding commented 5 years ago

Right, and some things in VS may break entirely (T4 templates fail to work if both None and Content for example).

davidwengier commented 5 years ago

The idea behind using globs was to keep the project file clean, as distinct from changing the item type automatically which would have no option but to specify each file in the project file.

T4 templates should work after this change in CPS I imagine.

davidwengier commented 5 years ago

I tested this and adding <Resource Include="**\*.xml;... more extensions ...;" /> seems to work fine in general, though CPS is adding an unnecessary <None Remove="..." /> when a file is added, which I will look into.

davidwengier commented 5 years ago

Current proposal is to implement something akin to this in the SDK:

  <ItemGroup Condition="'$(UseWPF)' == 'true' And $(UseWindowsForms) != 'true'">
    <None Exclude="**\*.xml;**\*.xsl;**\*.xslt;**\*.txt;**\*.bmp;**\*.ico;**\*.cur;**\*.gif;**\*.jpeg;**\*.jpe;**\*.jpg;**\*.png;**\*.dib;**\*.tiff;**\*.tif;**\*.inf;**\*.compositefont;**\*.otf;**\*.ttf;**\*.ttc;**\*.tte" />
    <Resource Include="**\*.xml;**\*.xsl;**\*.xslt;**\*.txt;**\*.bmp;**\*.ico;**\*.cur;**\*.gif;**\*.jpeg;**\*.jpe;**\*.jpg;**\*.png;**\*.dib;**\*.tiff;**\*.tif;**\*.inf;**\*.compositefont;**\*.otf;**\*.ttf;**\*.ttc;**\*.tte" />
  </ItemGroup>
dsplaisted commented 5 years ago

The list of file extensions comes from the legacy WPF flavor. If you add a file with one of these extensions (via an item template or "add existing item"), the WPF flavor would use the Resource item type / Build action.

dsplaisted commented 5 years ago
vatsan-madhavan commented 5 years ago

I'm not convinced that <Resource Include=... is a good default. <Content ...> is a common alternative.

In fact <Content might be preferable - it doesn't add a surprise embedded-resource into the assembly.

/cc @rladuca

dsplaisted commented 5 years ago

My comment above was written while we were discussing this in a meeting, and doesn't really capture what we concluded.

We concluded there's not a good default for a build action / item type to use for image, Xml, and other extensions listed here, because they might be Resource, EmbeddedResource, or Content/None with CopyToOutputFolder, even in a WPF project.

The consensus was that we should fix the experience issues with None items. IE if you set the build action of a file that is in the None glob to Resource, then VS should add an include for the Resource, but shouldn’t also add a <None Remove="foo.png" />.

Once the “None” issue is fixed, it may be fine to have the “resource-type” items listed explicitly in the project file with the appropriate build action / item type. We may also want to use a convention, such as that all files in the “resources” folder are included as resources, and all files in the "content" folder are included as content which is copied to the output folder. These conventions could either be implicit in the SDK, or explicitly part of the new project templates.

verelpode commented 3 years ago

"Build Action" = "Resource" is also for non-WPF projects

Sometimes people are surprised to hear that actually "Build Action" = "Resource" is not only for WPF projects!
@davkean wrote:

Why is WPF flavor even involved in this?

I think @davkean made an important point there. This issue is not really about WPF projects. The biggest problem is that if you make ANY .csproj that targets .NET 5 SDK (such as a non-WPF Console or ASP.NET 5 project) then "Build Action" = "Resource" fails to build correctly, whereas it did (and still does) build successfully in projects targeting .NETFW 4.8. (I tested this again today with Visual Studio 16.8.3.)

I use "Build Action" = "Resource" in multiple non-WPF .csproj's of these kinds:

In NETFW 4.8 Console .csproj's, when you use "Build Action" = "Resource", Visual Studio bundles all those resources into a .resources file named like "MyNamespace.g.resources" and stores it in an assembly manifest resource.

.resources files are not only readable in WPF apps. Any app, including Console programs, can use System.Resources.ResourceManager (alternatively System.Resources.ResourceReader) to read these resources at runtime.

System.Resources.ResourceManager and ResourceReader also exist in .NETFW 5.0 in the DLL System.Runtime.dll:

C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\5.0.0\ref\net5.0\System.Runtime.dll

Note System.Resources.ResourceManager/ResourceReader aren't located in WPF's PresentationFramework.dll, rather they're in System.Runtime.dll and they work without WPF.

So currently (VS 16.8.3) the situation is, although you can use ResourceManager and ResourceReader in NETFW 5 and NETFW 4.8 Console projects without WPF, the "Build Action" = "Resource" was broken for NET Core / 5. Even if you manually edit the XML inside a SDK/5 .csproj to make it correct, and try to build the .csproj, then the .resources file fails to be produced. Currently it only works correctly in NETFW 4.8 projects.

I would greatly appreciate this feature being restored. Currently I'm trying to update several non-WPF .csproj's to NETFW 5 (from NETFW 4.8) but these programs fail at runtime because the necessary resources are missing because Visual Studio 16.8.3 basically ignores files marked "Build Action" = "Resource" in SDK/5 projects.

Regarding the other option: "Build Action" = "Embedded Resource"

I suggest renaming "Build Action" = "Embedded Resource" to "Assembly Manifest Resource" or "Manifest Resource", because that's what it actually produces. Currently the name "Embedded Resource" is confusing because both of "Build Action" = "Resource" and "Embedded Resource" produce kinds of resources that are embedded in the output .exe or .dll.

Actually all resources in an app are usually embedded in the app -- "embedded" is the general meaning of any resource in an app. So it'd be great if "Embedded Resource" could be renamed to something clear such as "Assembly Manifest Resource" or "Manifest Resource".

Where the resources show up:

"Build Action" Where resource shows up
"Embedded Resource" This actually means "assembly manifest resource". Resources of this kind show up in System.Reflection.Assembly.GetManifestResourceNames and can be read using System.Reflection.Assembly.GetManifestResourceStream.
"Resource" In NETFW 4.8 projects (either Console or WPF), Visual Studio bundles all files marked "Build Action" = "Resource" into a file named like "MyNamespace.g.resources" and stores it in an assembly manifest resource. At runtime, the ".resources" can be read using System.Resources.ResourceManager (alternatively System.Resources.ResourceReader).
"Content" Keeps the file as a standalone file. In the case of WPF apps, "Content" makes a resource/association that refers to the file, without embedding/copying the file into the ".exe". Good for very large files, and optionally-installed files.
".resx" Resources File A ".resx" file with "Build Action" = "Embedded Resource" and "Custom Tool" = "ResXFileCodeGenerator". If the ".resx" file is named for example "Localizations.resx" then it is compiled/built to an assembly manifest resource named like "MyNamespace.Localizations.resources".

Currently "Build Action" = "Embedded Resource" builds correctly in SDK/5 .csproj's, whereas "Build Action" = "Resource" is broken.

A question may be raised: Can you workaround the problem by changing all files in a .csproj to use "Embedded Resource" instead of "Resource"? No, because "Embedded Resource" (meaning assembly manifest resource) provides less functionality than ".g.resources"/ResourceManager/ResourceReader. For example, manifest resources lack the content type string (such as "image/svg+xml") and culture/localization abilities provided by System.Resources.ResourceManager.

Regarding ".resx" Resources File

RESX is a separate-but-related issue to the issue of the broken "Build Action" = "Resource".

Make a new .csproj targeting NETFW 5, such as a Console project, and use Visual Studio (16.8.3) to open the Project Properties GUI, then click "Resources" on the left, then click "Click here to create a default resources file", and then VS creates a file named "Resources.resx" in a folder named "Properties" in the same folder as the .csproj.

Alternatively, in VS 16.8.3, you can also use the template named "Resources File" to add a new ".resx" file to a .csproj, including a SDK/NETFW/5 .csproj. Visual Studio has a nice GUI/Designer for .resx files. Inside the .resx file is XML.

Although resx does not require WinForms, unfortunately currently the resx Designer/GUI in VS 16.8.3 produces resx files that do contain references to WinForms. For example:

<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="DgmlGraph" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>Resources\DgmlGraph.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>

It would be great if sometime the Designer/GUI for .resx could be modernized to be independent of WinForms.

Re location in the output Assembly, if the ".resx" file is named for example "Localizations.resx" then it is compiled/built to an assembly manifest resource named like "MyNamespace.Localizations.resources".

Suggested plan

  1. Restore/unbreak the preexisting ability to use "Build Action" = "Resource" in non-WPF and WPF .csproj's. It should work in SDK/5 projects like how it works in NETFW 4.8 projects, regardless of whether WPF is involved or not. Note System.Resources.ResourceManager/ResourceReader are in System.Runtime.dll not in WPF's PresentationFramework.dll.
  2. Retain the necessary "Build Action" = "Embedded Resource" feature as-is but rename it to eliminate the confusion. I suggest renaming it to either "Assembly Manifest Resource" or "Manifest Resource". See System.Reflection.Assembly.GetManifestResourceNames.
  3. As a totally separate issue, it would be nice if sometime .resx could be updated/modernized. resx does not require WinForms, but currently the Designer/GUI for it is creating resx files that do contain references to "System.Windows.Forms.dll", although resx could also be used outside of WinForms. Ideally the resx Designer/GUI should only produce WinForms references when it is for a project targeting WinForms, not when it is used in a SDK/NETFW/5 project or a Console project.
  4. Regardless of whether resx is ever updated, the separate issue of "Build Action" = "Resource" needs to be fixed to restore the same behavior as in NETFW 4.8 projects.
dsplaisted commented 3 years ago

@verelpode It sounds like your main issue is that setting Build Action to Resource doesn't work in non-WPF SDK-style projects. I would suggest filing a new issue about that in the dotnet/sdk repo. This issue is about what the default build action should be, either when a new file is added via VS, or if a file isn't specified explicitly in the project but is in the project folder.

Thanks.