.NET assemblies may contain embedded streams of arbitrary data. In an msbuild project this is done by adding an EmbeddedResource item to your project with the path to a file to be embedded. By default, MSBuild will compute a ‘manifest resource name’ to key the content of the file within your assembly.

This technique is useful for many use cases. One common case for me is to embed files that my automated tests require, so that the test assembly doesn’t have to go looking for them. One can easily embed all files under a particular directory by adding this xml to the project file:

  <ItemGroup>
    <EmbeddedResource Include="Assets\**" />
  </ItemGroup>

This produces an assembly with embedded resources that are all in a flat list, where the directory separaters are converted to periods:

This is usually good enough, but recently I realized a need to retain a clear directory structure in the embedded resource names so that my test can recreate the directory structure in a scratch directory at runtime. But given the above resource names, there is ambiguity between which periods should be taken literally and which should be replaced with slashes.

An MSBuild target called CreateManifestResourceNames is responsible for assigning the names of the resource streams based on the file path, and therein lies the policy to replace directory separator characters with periods. It turns out that neither the C# compiler nor the ECMA-335 spec forbids backslashes in resource names, so this substitution is in fact not necessary.

It turns out you can defeat the slash to period replacement with just an extra piece of item metadata:

  <ItemGroup>
    <EmbeddedResource Include="Assets\**" LogicalName="%(RelativeDir)%(Filename)%(Extension)" />
  </ItemGroup>

With this change, check out the new list of resource names. Note how periods and backslashes both appear, with no more ambiguity:

I should call out though that if you’re using this in a project that builds on multiple OSs, this may lead to inconsistent resource naming because Windows will use backslashes and Linux/Mac will use forward slashes. To ensure consistent compilation regardless of OS, you can normalize the directory separators by modifying the msbuild item to this:

<EmbeddedResource Include="Assets\**" LogicalName="$([System.String]::Copy('%(RelativeDir)').Replace('\','/'))%(Filename)%(Extension)" />

Now that’s some really fancy syntax for switching backslashes to forward slashes because it’s manipulating MSBuild metadata, which doesn’t work without a fancy hack involving copying it as a string to a new one so you can call Replace on it.

In case embedded resource streams are new to you, the next step to using these resources is to access their Stream at runtime via the Assembly.GetManifestResourceStream Method. You can even enumerate them via the Assembly.GetManifestResourceNames Method.