msbuild: GenerateResource task does not support non-string resources on .NET core

From @tarekgh on June 14, 2017 15:54

@Spongman commented on Tue Jun 13 2017

a (.csproj) project a with an embedded text file, added using the VS2017.2 project properties UI.

ConsoleApp2.zip

on windows, it works fine.

on linux, it just shows:

$ dotnet --version
1.0.4
$ uname -a
Linux mybox 3.16.0-4-amd64 #1 SMP Debian 3.16.43-2 (2017-04-30) x86_64 GNU/Linux
$ dotnet run
..\Resources\New Text Document.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252

@danmosemsft commented on Tue Jun 13 2017

@tarekgh are there any known issues here?


@tarekgh commented on Wed Jun 14 2017

Please take a look at the issue https://github.com/dotnet/cli/issues/3695 this was CLI issue and I believe it is already fixed but I am not sure which release include the fix. I’ll move this issue to CLI repo.

Copied from original issue: dotnet/cli#6866

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 22
  • Comments: 79 (46 by maintainers)

Commits related to this issue

Most upvoted comments

@danmosemfst @tarekgh, Thanks, I managed to work around it like this:


var assembly = Assembly.GetExecutingAssembly();
String resourceName = @"GradeTranslator.grades.json";
using (var stream = assembly.GetManifestResourceStream(resourceName))
{
    if (stream == null)
    {
        throw new Exception($"Resource {resourceName} not found in {assembly.FullName}.  Valid resources are: {String.Join(", ", assembly.GetManifestResourceNames())}.");
    }
    using (var reader = new StreamReader(stream))
    {
        return reader.ReadToEnd();
    }
}

This appears to work under both dotnet cli and msbuild

I’m pretty sure this is now a must-fix for the announced .NET Core 3.0 support for Windows Forms: https://blogs.msdn.microsoft.com/dotnet/2018/05/07/net-core-3-and-support-for-windows-desktop-applications/ 😃

And we can’t just error out and say only strings are supported. We have to actually make non-strings work.

Any ETA on fixing this?

MSBuild does not support currently support non-string resources on .NET Core. However, it is not checking the resx data’s type attribute, but instead blindly using the value as a string. This should either work or have a clear “not supported” error at build time.

Relevant snippet from repro project resx:

  <data name="New Text Document" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Resources\New Text Document.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
  </data>

Code that is not checking the type attribute: https://github.com/Microsoft/msbuild/blob/cc727f715651f305639ad8635fefc40165299f86/src/Tasks/GenerateResource.cs#L2909-L2920

It might be easy enough to actually handle this ResxFileRef-to-string case correctly. If we do that, we still need a clear error in other non-string data cases.

That’s correct, but with one wrinkle: we didn’t make it into 16.3p1, so there will be a difference in your assemblies between code compiled with MSBuild.exe/Visual Studio, and that compiled with dotnet {ms}build for 16.3p1/3.0.100-p7. Both should work, but only the dotnet build-produced binaries will fully adopt the new mechanisms. This will be fixed for p2/p8.

I’m going to close this since the initial implementation is complete. After trying your scenarios with dotnet build in p7, please file new issues about any problems you encounter with non-string resources!

@voltcode I’m afraid adding support for something that never worked in that combination would not be allowed for a servicing change. We keep a very high bar for that. Taking big features like this backwards adds too much risk. This was a very large change across three repositories. We have fixed this for .NET Core 3.0+ and MSBuild 16.3+, and that is all we can do without a time machine.

I’m also getting with issue with dotnet build / dotnet msbuild, but not with the msbuild.exe packaged with VS2017, which seems to correctly embed string resources.

What do you mean it never worked?

I mean .NET Core 2.x projects with .NET Core SDK 2.x behave as they always have. It is regrettable that they did not have the result of this work, but it is simply too late to change that.

.NET Core 2.x filled many gaps with .NET Framework vs. .NET Core 1.x, but not this one. We didn’t backport all of those gap closings to 1.x either. We literally cannot do that. We have to defend every backport rigorously, and the change will be rejected if it is closing a gap that was always there.

We will get tenfold more passionate feedback when we break something in a servicing update. We are holding this bar with the best of intentions. The risk is simply too high.

@tarekgh wrote:

you can just embed the file directly to the resources and read it with Assembly.GetManifestResourceStream. no need to miss with the resx at all.

Note that this doesn’t work very well if your resources are localized. You also need the logic to get the right resource for the current UI culture. ResourceManager is supposed to be the thing that handles that.

any ETA on this?

I’ve asked this before, but I’ll ask it again here. Why do we need reflection at all to go from one serialized format (resx) to another (resources)?

Ignoring format details, it seems theoretically possible to defer all instantiation until runtime and just encode the resx without loss of information into the resource.

Is there a limitation of the .resources format itself at play, or is it just the API we’ve been using to write it?

AppDomain dependency

Another angle to take on that is: why does ResGen need to load user code for execution/relfection? ResGen should be able to get everything it needs by just reading assembly metadata and never loading types. That likely means larger changes in either this task or in the WinForms implementation of ResXResourceReader but that’s the direction we’ve been heading with other build tools.

Just bumping this comment: https://github.com/Microsoft/msbuild/issues/2221#issuecomment-389000840

This is a must-fix for .NET Core 3. @AndyGerlicher, do we have a milestone or tag we can use for that?

@dsplaisted Can you put this on the agenda for next sync up with msbuild?

cc @dasMulli

As a stop-gap solution while we wait for resource embedding to become a properly-supported, cross-platform, I created a dotnet tool which does something vaguely similar: https://github.com/RendleLabs/Embedder/blob/master/README.md

(I forgot to git push, will do that soon)

Visual Studio uses MSBuild.exe to build. Using that will be closer to the VS build.

Yes, using MSBuild.exe followed by dotnet test --no-build sounds reasonable in your use case.

@voltcode What I’ve been doing is using Visual Studio’s .NET Framework msbuild which has always supported non-string resources rather than .NET Core’s msbuild which had never supported them. I used this starting with VS2017:

$visualStudioInstallation = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath
$msbuild = Join-Path $visualStudioInstallation 'MSBuild\15.0\Bin\MSBuild.exe'
& $msbuild path\to\project\folder /restore /p:Configuration=Release /v:minimal /nologo

See related https://github.com/dotnet/corefx/issues/37041 and https://github.com/microsoft/msbuild/pull/4420

initial deploy in 16.3p1/SDK 3.0.100-p7

There was live streamed discussion available at https://www.youtube.com/watch?v=5heMq4U2ek8

Is there an ETA for this?

We are currently working around this issue in our projects with the following helper function:

public static class ResourceHelper
{
    public static byte[] GetBytes<T>(string name)
    {
        var assembly = typeof(T).Assembly;
        using (var stream = assembly.GetManifestResourceStream(name))
        {
            if (stream == null)
            {
                throw new Exception(
                    $"Resource {name} not found in {assembly.FullName}.  Valid resources are: {string.Join(", ", assembly.GetManifestResourceNames())}.");
            }
            using (var ms = new MemoryStream())
            {
                stream.CopyTo(ms);
                return ms.ToArray();
            }
        }
    }
}

Then we add the files as <EmbeddedResource Include="Resources\some_file" /> and replace the old Resourceswith this:

namespace Some.Namespace.Resources
{
    public class Resources
    {
        public static byte[] SomeFile => ResourceHelper.GetBytes<Resources>("Some.Namespace.Resources.some_file");
    }
} 

I have a ResourceManager.GetObject call on an embedded resource which succeeds on windows but fails with the following error on using dotnetcore 2.0.3 (from the dotnet-sdk-2.0.3 docker image) on Linux:

System.InvalidCastException : Unable to cast object of type 'System.String' to type 'System.Byte[]'.

Is this a related issue?

I have an embedded text file “grades.json” which is accessed via the autogenerated Resources.Designer.cs file. That error message occurs here:

internal static byte[] grades {
    get {
        object obj = ResourceManager.GetObject("grades", resourceCulture);
        return ((byte[])(obj));
    }
}

EDIT: *** IF YOU ARE RUNNING INTO THIS ERROR WHEN THEN READ THIS - YOUR BUILD SERVER’S “BUILD TOOLS FOR VISUAL STUDIO 2019” MAY NOT BE UP TO DATE ***

I am able to successfully run my app from my command line on my local dev machine, but cannot run it after it’s being built in TeamCity.

Local Dev Machine

 & "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\M
sBuild.exe" --version
Microsoft (R) Build Engine version 16.2.37902+b5aaefc9f for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

MSBUILD : error MSB1001: Unknown switch.
Switch: --version

For switch syntax, type "MSBuild -help"

TeamCity Build Server

Visual Studio Build Tools 16.1.9 image

When I click “Launch”, I get:

**********************************************************************
** Visual Studio 2019 Developer Command Prompt v16.0
** Copyright (c) 2019 Microsoft Corporation
**********************************************************************

Attempt to fix

  1. Go to https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019
  2. Download “Build Tools for Visual Studio 2019” (vs_buildtools__1704816089.1574373004.exe)
  3. Get file SHA256 hash with PowerShell: 7DA4388648C92544C97407C6F052FD6BC0317DB407CADAB9FDCB328A34D3E317
PS C:\Users\john.zabroski> Get-FileHash "C:\Users\john.zabroski\Downloads\vs_buildtools__1704816089.1574373004.exe"

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          7DA4388648C92544C97407C6F052FD6BC0317DB407CADAB9FDCB328A34D3E317       C:\Users\john.zabroski\Downlo...
  1. Install the exe
  2. Running the installer indicates it is 16.3.10

It is not supported in VS 2017. There has been a lot of work in msbuild, NuGet, the project system, and other VS components for .NET Core 3.

This fix should be backported to MSBuild 15 at least and .NET Core 2.1/2.2, resource files are a very old feature, lack of support for them prevents people on .NET FX from migrating onto .NET Core.

Let’s hope it won’t be windows only then (as WinForms will be.)

Extremely sloppy notes to myself from things I’ve figured out recently:

  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
  <data name="logoPictureBox.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
  • So the task will probably need a custom resx parser that understands that, or get a ResXResourceReader that does type-name resolution but never loads a type (somehow?)

@maxrandolph definitely. If you look at all the commits I’ve been linking in here they are fixing problems on Windows. It’s a dotnet build issue.

@nguerrera okay to change the title?

@nguerrera gave me a work around here. Going to move to this declaration to work around it.

<EmbeddedResource Include="results\uncheckedctypeandimplicitconversionseven.txt" />

Note that this doesn’t work very well if your resources are localized. You also need the logic to get the right resource for the current UI culture. ResourceManager is supposed to be the thing that handles that.

That is right. who want language-specific resources will need this functionality fixed otherwise it will not be easy to workaround this except if you do a lot of manual workaround work.

@nguerrera I was looking at some other offline internal reported issue, and it end-up it is the same issue here but using binary files and calling ResourceManager.GetStream.

  <data name="LanguageDetection" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>AudioFiles\pt-br\LanguageDetection.wav;System.IO.MemoryStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </data>

of course, using mscorlib need to change anyway by our tools.

I have a couple of questions here:

  • does currently msbuild differ if building for Windows than building on Linux? I am asking because the claim from the user is what they are doing work on Windows but not on Linux. I’ll try to take a deeper look to know how they are building anyway.
  • Is there any possibility the fix of this embedded files issues be serviced to 2.1? the user is trying to ship with 2.1 and wants this functionality.

When is this planned to be fixed ?

you can just embed the file directly to the resources and read it with Assembly.GetManifestResourceStream. no need to miss with the resx at all.

This also applies to images even on Windows using dotnet build, msbuild 😭

.NET Command Line Tools (2.0.2)

Product Information:
 Version:            2.0.2
 Commit SHA-1 hash:  a04b4bf512

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.16299
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\2.0.2\

Microsoft .NET Core Shared Framework Host

  Version  : 2.0.0
  Build    : e8b8861ac7faf042c87a5c2f9f2d04c98b69f28d