Generate Strongly-Typed Resources with .NET Core

dotnet, csharp, vs comments edit

UPDATE OCT 25 2022: I filed an issue about some of the challenges here and the weird <Compile Remove> solution I had to do to get around the CS2002 warning. I got a good comment that explained some of the things I didn’t catch from the original issue about strongly-typed resource generation (which is a very long issue). I’ve updated the code/article to include the fixes and have a complete example.


In the not-too-distant past I switched from using Visual Studio for my full-time .NET IDE to using VS Code. No, it doesn’t give me quite as much fancy stuff, but it feels a lot faster and it’s nice to not have to switch to different editors for different languages.

Something I noticed, though, was that if I updated my *.resx files in VS Code, the associated *.Designer.cs was not getting auto-generated. There is a GitHub issue for this and it includes some different solutions to the issue involving some .csproj hackery, but it’s sort of hard to parse through and find the thing that works.

Here’s how you can get this to work for both Visual Studio and VS Code.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <!--
        Target framework doesn't matter, but this solution is tested with
        .NET 6 SDK and above.
    -->
    <TargetFrameworks>net6.0</TargetFrameworks>

    <!--
        This is required because OmniSharp (VSCode) calls the build in a way
        that will skip resource generation. Without this line, OmniSharp won't
        find the generated .cs files and analysis will fail.
    -->
    <CoreCompileDependsOn>PrepareResources;$(CompileDependsOn)</CoreCompileDependsOn>
  </PropertyGroup>

  <ItemGroup>
    <!--
        Here's the magic. You need to specify everything for the generated
        designer file - the filename, the language, the namespace, and the
        class name.
    -->
    <EmbeddedResource Update="MyResources.resx">
      <!-- Tell Visual Studio that MSBuild will do the generation. -->
      <Generator>MSBuild:Compile</Generator>
      <LastGenOutput>MyResources.Designer.cs</LastGenOutput>
      <!-- Put generated files in the 'obj' folder. -->
      <StronglyTypedFileName>$(IntermediateOutputPath)\MyResources.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>Your.Project.Namespace</StronglyTypedNamespace>
      <StronglyTypedClassName>MyResources</StronglyTypedClassName>
    </EmbeddedResource>

    <!--
        If you have resources in a child folder it still works, but you need to
        make sure you update the StronglyTypedFileName AND the
        StronglyTypedNamespace.
    -->
    <EmbeddedResource Update="Some\Sub\Folder\OtherResources.resx">
      <Generator>MSBuild:Compile</Generator>
      <LastGenOutput>OtherResources.Designer.cs</LastGenOutput>
      <!-- Make sure this won't clash with other generated files! -->
      <StronglyTypedFileName>$(IntermediateOutputPath)\OtherResources.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>Your.Project.Namespace.Some.Sub.Folder</StronglyTypedNamespace>
      <StronglyTypedClassName>OtherResources</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>
</Project>

Additional tips:

Once you have this in place, you can .gitignore any *.Designer.cs files and remove them from source. They’ll be regenerated by the build, but if you leave them checked in then the version of the generator that Visual Studio uses will fight with the version of the generator that the CLI build uses and you’ll get constant changes. The substance of the generated code is the same, but file headers may be different.

You can use VS Code file nesting to nest localized *.resx files under the main *.resx files with this config. Note you won’t see the *.Designer.cs files in there because they’re going into the obj folder.

{
  "explorer.fileNesting.enabled": true,
  "explorer.fileNesting.patterns": {
    "*.resx": "$(capture).*.resx, $(capture).designer.cs, $(capture).designer.vb"
  }
}

Comments