Except for the most basic applications, we often need to bundle one or more resources with our programs or libraries. By resources, we are referring to data such as strings, images, and even sound or video media content.
These resources are typically attached to a compiled execution image; they are embedded in the binary, if you will. Including these resources in your .NET project’s output is normally a very simple affair. This is because, for the most part, Visual Studio‘s managed resources editor does the job with no incident.
That job being: the visual creation of a .resx
file, which allows for the attachment to the executable of resources that are either linked at compile time or embedded in the resources file.
Unfortunately, embedding media content in a resources file results in runtime errors.
This article seeks to demonstrate some outstanding bugs with Visual Studio‘s resource editor, and how I got around them with a little dotnet
tool I made.
Link or Die: How Embedding Fails
It always gives me a sense of relief (and disbelief) when, after finding a significant bug and developing a workaround for it, said bug still exists over a year later when I get around to writing an article on it.
When adding resources to our .resx file via Visual Studio‘s managed resources editor, by default, each resource is configured to be linked at compile time.
Let’s say we want our application to play an audio file. Using the managed resources editor, we’ll see that adding a particular .wav file to our resources file will also add it to our project so that it’s linked at compile time.
The strangely named audio file shown above is something I used in my Vision application. Let’s just leave it at that.
If we peer inside the Resources.resx
file, we’ll see a reference to our audio file via a relative path.
Entry for Audio File in Resources.resx
<data name="MurderedVincentLaugh" type="System.Resources.ResXFileRef, System.Windows.Forms"> <value> ..\Resources\MurderedVincentLaugh.wav;System.IO.MemoryStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 </value> </data>
Get Out of My Project! Let’s Directly Embed It
You may have one or more reasons for not wanting a bunch of loose resource assets present in your project’s structure. Luckily for you, we can open up the .resx file via the managed resources editor, click on the resource, and then adjust its manner of persistence via the Properties window.
By selecting the option to embed the resource in our .resx
file, the resource no longer needs to be part of our project, as it now exists in the resources file.
Partial Entry for Embedded Audio File in Resources.resx
<data name="MurderedVincentLaugh" mimetype="application/x-microsoft.net.object.binary.base64"> <value> AAEAAAD/////AQAAAAAAAAAEAQAAABZTeXN0ZW0uSU8uTWVtb3J5U3RyZWFtCgAAAAdfYnVmZmVyB19v cmlnaW4JX3Bvc2l0aW9uB19sZW5ndGgJX2NhcGFjaXR5C19leHBhbmRhYmxlCV93cml0YWJsZQpfZXhw b3NhYmxlB19pc09wZW4dTWFyc2hhbEJ5UmVmT2JqZWN0K19faWRlbnRpdHkHAAAAAAAAAAACAggICAgB AQEBCQIAAAAAAAAAAAAAACBTHwAgUx8AAAEAAQoPAgAAACBTHwACUklGRhhTHwBXQVZFZm10IBAAAAAB AAIAgLsAAADuAgAEABAATElTVBoAAABJTkZPSVNGVA4AAABMYXZmNTguMjkuMTAwAGRhdGEgPR8AAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA <!--And on and on and on...--> </value> </data>
Neat. Functionally, the resource asset behaves the same in our code whether it is linked at compile time or embedded in the .resx
file ahead of time. Namely, accessing Resources.MurderedVincentLaugh
will return an UnmanagedMemoryStream
, with which we can feed the audio data through some playback mechanism.
But It Won’t Work!
It would’ve worked just fine if we left our resource configured to be linked at compile time, but changing it to be directly embedded in the .resx
file will result in an exception thrown when accessing the resource.
Functionally identical to using a resource that was linked at compile time; unfortunately, choosing to embed the resource directly in the .resx
file has left us with a dud.
Why Doesn’t It Work?
Knowing the exact cause isn’t all that important here. Clearly, there is a bug happening with Visual Studio‘s managed resources editor when embedding a resource into a .resx
file — a bug that persists even with .NET 7 and the newest version of Visual Studio 2022.
For some reason, perhaps due to how the managed resources editor baked in the resource, it’s actually trying to directly deserialize the resource as a MemoryStream
. You normally deserialize data into a Stream
, not as a Stream
.
We’re going to say “goodbye” to the managed resource editor and take matters into our own hands in order to embed this resource into our assembly.
Embedding Resources With ResourceCreator
It would be much simpler if we could embed the raw byte data of our media into our assembly. At runtime, we would then take the bytes and pipe that data through a Stream
.
We don’t (to my knowledge) have that level of control when working with .resx
files, so let’s skip them altogether and jump straight to working with .resources
files, which are what .resx
files compile into anyway.
To generate a .resources
file, you’ll have to write some code — or, you could make use of a dotnet
tool I created.
Installing ResourceCreator
dotnet tool install BadEcho.ResourceCreator --global
Whether or not you want it installed globally is up to you, of course. The tool is published on NuGet, so the above command should simply work.
The tool can be invoked with the command resource-creator
:
C:\src\resources>resource-creator --help
Description:
Create a .resources file from a directory containing files we wish to bundle as resources for a program.
Usage:
resource-creator <RESOURCES_PATH> [options]
Arguments:
<RESOURCES_PATH> The path to the directory containing the resources.
Options:
-o, --output <OUTPUT_PATH> The path to the output resources file. [default: out.resources]
-f, --filter <SEARCH_PATTERN> A filter comprising one or more search patterns matched against the names of
files to be included in the output resources file. Each pattern can contain a
combination valid literal and wildcard characters, with each pattern separated
by a comma. [default:*.*]
--version Show version information
-?, -h, --help Show help and usage information
Hopefully the usage is self-explanatory. We simply take all the media resources we want to embed, throw them into a directory, and then run our tool. Something like the following:
resource-creator C:\src\resources -o Sounds.resources
This will generate a Sounds.resources
file which we can then attach to our project, with its Build Action set to Embedded resource.
Creating the Accessor Class
We got our baked-in resources attached to the project. We now need a nice way of accessing them.
What I do is create a class with the same name as the .resources
file; so, in this case: Sounds.cs
.
In this class, we want to initialize a ResourceManager
for our .resources file, provide a property that returns the audio resource in question, and finally include a general helper method that we’ll be calling into for this and any other resource we have bundled into the particular .resources
file.
Sounds.cs
public static class Sounds { private static readonly ResourceManager _Manager = new( "BadEcho.Embedding.Stuff.Properties.Sounds", typeof(Sounds).Assembly); /// <summary> /// Gets a stream for the rather pleasant sound effect of Vincent Price laughing at us for /// being murdered. /// </summary> public static Stream MurderedVincentLaugh => GetStream(nameof(MurderedVincentLaugh)); private static Stream GetStream(string name) { UnmanagedMemoryStream? stream = _Manager.GetStream(name, CultureInfo.InvariantCulture); if (stream == null) throw new BadImageFormatException(Strings.SoundMissingResource.InvariantFormat(name)); return stream; } }
Adding another sound file is as simple as adding another property (after generating a new .resources
file).
Voilà! We have working fully-embedded media resources. Enjoy.