Updated on

An embedded resource is a file the compiler writes into the assembly itself, so the DLL or EXE carries it instead of shipping a loose file next to it. A text file, an image or a PDF goes in unchanged and comes back out through System.Reflection, not through a file path.

That makes deployment one file instead of several, and it makes a missing resource a build error rather than a support ticket. Resources come in other shapes too: we cover the localization side in Localization in ASP.NET Core and the string side in How to Read a String From a .resx (Resource) File in C#.

To download the source code for this article, you can visit our GitHub repository.

Let’s dive in.

What Are Embedded Resources?

An embedded resource is a file the compiler copies into the assembly’s manifest at build time. The DLL or EXE carries the bytes, so there is no separate file to deploy, no relative path to get wrong, and no way for the file to go missing between build and run.

The file itself does not change. Text stays text and a PDF stays a PDF. What changes is how we reach it: through System.Reflection rather than through System.IO.

Two things put a file there, and they write the same project-file item. In Visual Studio we set the file’s Build Action to Embedded resource. In the .csproj we add an <EmbeddedResource Include="..." /> entry ourselves.

Two calls on Assembly take it back out. GetManifestResourceNames() returns every name in the manifest, and GetManifestResourceStream(name) opens one of them as a Stream.

That name is not the file path, and that single fact is what most of this article is about.

How Do We Add an Embedded Resource to a Project?

Two mechanisms add a file to the assembly, and both end up writing the same line into the project file.

In Visual Studio, we select the file in Solution Explorer, open its Properties, and set Build Action to Embedded resource. Visual Studio writes the entry for us.

In the project file we write it by hand as <EmbeddedResource Include="Resources\text-file.txt" />. Editing it directly buys two things the dropdown cannot express. Include accepts MSBuild globs, so Resources\**\*.txt embeds a whole subtree and keeps embedding files added to it later. And a path can climb out of the project folder, which the Properties window has no way to say.

One category needs no entry at all. The .NET SDK already embeds every .resx file under the project, which is why a resource designer file works without anyone adding anything. Setting EnableDefaultEmbeddedResourceItems to false turns that behaviour off.

Let’s start by preparing a quick test program, a basic command-line app. We can either make a new command line app right from Visual Studio or utilize the dotnet command:

dotnet new console -n Embedded_Resources_in_NET

Once our command-line project is set up, we can promptly incorporate embedded resources into our app.

We’ll generate sample text (.txt) and PDF (.pdf) files for testing. We can grab files from our hard drive or extract them from the Code Maze sample application.

Creating Sample Folders and Files

Let’s set up the folders and files for our resources. First, we’ll create two subfolders within our project directory: Resources and Files. While the specific names aren’t crucial, Resources is commonly used.

Here’s how we can do it:

md Resources
cd Resources
md Pdf
cd ..
md Files

So, we have created a folder structure where the Resources and Files folders are on the same level, and the Pdf folder is under Resources.

Next, copy some text files from the disk into the Resources and Files folders, naming it text-file.txt. Similarly, let’s duplicate a sample PDF file, naming it pdf-file.pdf, and place it within the Pdf subfolder under Resources.

After completing these steps, our folder structure will look like this:

|
|-- Resources
    |-- text-file.txt
    |-- Pdf
        |-- pdf-file.pdf
|-- Files
    |-- text-file.txt

The actual content of the files is not significant for our purposes.

Adding Embedded Resources Using Visual Studio

Now that we’ve included all the folders and files in our .NET/C# project, they should be visible in the Solution Explorer.

To mark each of these files for embedding, follow these steps:

  1. Right-click each file.
  2. From the context menu, choose ‘Properties.’
  3. In the Properties window, select ‘Build Action.’
  4. Choose ‘Embedded resource’ from the dropdown menu.

embedded resource properties window

How Do We List the Embedded Resources in an Assembly?

Now that we’ve embedded three resources in our application, let’s write some C# code to retrieve a list of these files.

First, we’ll need a handle to the assembly containing the embedded resources. Since we’ve added all resources to our main assembly, we can easily reference it:

private static Assembly ThisAssembly
    => typeof(SampleResourceReader).Assembly;

Once we have the assembly handle, we can list all the resources within it using the GetManifestResourceNames() method:

private static void ListResourcesInAssembly(Assembly? assembly)
{
    if (assembly is null)
        return;

    var resources = assembly.GetManifestResourceNames();
    if (resources.Length == 0)
        return;

    Console.WriteLine($"Resources in {assembly.FullName}");
    foreach (var resource in resources)
    {
        Console.WriteLine(resource);
    }

    Console.WriteLine();
}

This is a general function, so we first check if we have a valid assembly handle. Then, we call the GetManifestResourceNames() method to get the list of everything embedded within it. If there are no embedded files, we exit the function. Otherwise, we display the names of the resources in the Console.

The Names of the Embedded Files

By running the current code, we will get the list of files:

Resources in Embedded_Resources_in_NET, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Embedded_Resources_in_NET.Files.text-file.txt
Embedded_Resources_in_NET.Resources.text-file.txt
Embedded_Resources_in_NET.Resources.Pdf.pdf-file.pdf

Dots separate each resource name. Reading that name backwards from the last dot gives us where it came from:

The manifest resource name Embedded_Resources_in_NET.Resources.Pdf.pdf-file.pdf split into root namespace, two folder names and the file name.

‘Embedded_Resources_in_NET‘ is our root namespace, which defaults to the project name, Resources and Pdf are the respective sub-folders, and pdf-file.pdf is the filename of the embedded resource. The dot (‘.’) is a separator between the root namespace, the entire folder structure, and the name of the embedded resource itself.

This structure allows us to have two identical files named text-file.txt in our assembly without conflict. One resides in the Files sub-folder, hence its name is Embedded_Resources_in_NET.Files.text-file.txt, while the other is in the Resources sub-folder, making its name Embedded_Resources_in_NET.Resources.text-file.txt. Since the names are distinct, there’s no issue.

Embedded Resources in the .csproj File

In the .csproj file, Visual Studio records the embedded file selection. Here’s the relevant portion of the XML:

<ItemGroup>
    <EmbeddedResource Include="Files\text-file.txt" />
    <EmbeddedResource Include="Resources\Pdf\pdf-file.pdf" />
    <EmbeddedResource Include="Resources\text-file.txt" />
</ItemGroup>

Visual Studio writes one literal path per file, but the item accepts a good deal more than that. The first line below is in our sample project, and the other three show what else the item can say:

<ItemGroup>
    <EmbeddedResource Include="my-folder\note.txt" />
    <EmbeddedResource Include="Resources\**\*" Exclude="Resources\skipme.txt" />
    <EmbeddedResource Include="..\..\README.md" Link="Docs\README.md" />
    <EmbeddedResource Include="Files\note.txt" LogicalName="note" />
</ItemGroup>

The glob keeps embedding files added to Resources later, Exclude takes one back out again, Link gives an outside file a virtual folder inside the assembly, and LogicalName replaces the computed name outright. Each of those changes the name the resource ends up with, and the table further down lists what each one produces. Our own my-folder\note.txt is the plainest case: it embeds as Embedded_Resources_in_NET.my_folder.note.txt, because a folder name that is not a valid identifier is rewritten into one.

Embedded Resources Outside Our Project

By modifying the .csproj file, we can embed files that aren’t within our project’s subfolders, a capability not directly available in Visual Studio. Editing the file allows us to include files from different locations, like so:

<ItemGroup>
    <EmbeddedResource Include="Files\text-file.txt" />
    <EmbeddedResource Include="Resources\Pdf\pdf-file.pdf" />
    <EmbeddedResource Include="Resources\text-file.txt" />
    <EmbeddedResource Include="my-folder\note.txt" />
    <EmbeddedResource Include="..\Embedded_Resources_in_NET.sln" />
    <EmbeddedResource Include="..\..\README.md" />
</ItemGroup>

With this setup, we’re embedding our solution file, one folder up, and even a README.md file that is two folders up. Executing this code will yield:

Resources in Embedded_Resources_in_NET, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Embedded_Resources_in_NET.Files.text-file.txt
Embedded_Resources_in_NET.Resources.text-file.txt
Embedded_Resources_in_NET.my_folder.note.txt
Embedded_Resources_in_NET.Resources.Pdf.pdf-file.pdf
Embedded_Resources_in_NET.Embedded_Resources_in_NET.sln
Embedded_Resources_in_NET.README.md

Notice how all such resources are named as if they were in the same folder as our assembly. Link metadata fixes that: <EmbeddedResource Include="..\..\README.md" Link="Docs\README.md" /> embeds it as MyApp.Docs.README.md instead.

However, this was just a test to explore the possibility. While feasible, embedding resources from outside our project is ill-advised. We should not embed resources outside our project, as we can’t guarantee their availability at build time. A sparse or partial checkout is enough to break it: the compiler stops with error CS1566: Error reading resource ... Could not find file.

How Do We Read Embedded Resources From Another Assembly?

Nothing about GetManifestResourceNames() is tied to the assembly we are running in. Given a handle to any loaded assembly, the same call lists that assembly’s resources.

Assembly.Load("Name") gets the handle from a simple assembly name, and typeof(SomeTypeInside).Assembly gets it without a string at all when we already reference the project.

AppDomain.CurrentDomain.GetAssemblies() looks like the way to sweep the whole application, and it is not. It returns the assemblies already loaded into this process, which is not the same set as the assemblies our solution builds. A referenced project whose types the code has not touched yet is absent from that array even though its DLL sits in the output folder beside ours, because the runtime loads assemblies on first use.

A satellite assembly is something else again: a code-free .resources.dll holding one culture’s resources, built from culture-suffixed .resx files and placed in a subfolder named after the culture.

Localization is where satellite assemblies come from, so if that is what brought us here, what a satellite assembly actually is, and how .resx files become one is covered separately, as is reading strings back out with ResourceManager.

For this experiment, let’s create a new project and embed a text file into that assembly:

dotnet new classlib -o Embedded_Resources_in_NET_Library

Next, open this new project in Visual Studio and embed a text file. Alternatively, we can edit the .csproj file:

<ItemGroup>
    <EmbeddedResource Include="Resources\text-file.txt" />
</ItemGroup>

This setup will embed the text-file.txt into our class library.

Reading a List of Embedded Resources From a Referenced Assembly

We can apply the same approach to any assembly, referenced class libraries included.

We already have the method ListResourcesInAssembly() for this purpose. All we need to do is specify the correct assembly:

private static Assembly ReferencedAssembly =>
    Assembly.Load("Embedded_Resources_in_NET_Library");

Then, we can call the method:

public static void ListResourcesInReferencedAssembly()
    => ListResourcesInAssembly(ReferencedAssembly);

Since our class library contains only one embedded resource, we’ll receive a list with just one element:

Resources in Embedded_Resources_in_NET_Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Embedded_Resources_in_NET_Library.Resources.text-file.txt

This demonstrates how we can read embedded resources from any assembly, providing flexibility in resource management.

Reading a List of Embedded Resources in All of the Loaded Assemblies

To list everything embedded in all the assemblies comprising our solution, we can first obtain a list of all assemblies of the current application domain:

private static Assembly[] AllAssembliesOfCurrentAppDomain
    => AppDomain.CurrentDomain.GetAssemblies();

Then, we can iterate over this list and call the ListResourcesInAssembly method for each assembly:

public static void ListResourcesInAllAssemblies()
    => AllAssembliesOfCurrentAppDomain.ToList().ForEach(ListResourcesInAssembly);

This covers every assembly the process has loaded so far, which is not the same as every assembly in our solution. Assembly.Load() on the ones we care about, before the sweep, is what makes the list complete.

How Do We Read the Content of an Embedded Resource?

GetManifestResourceStream() is the counterpart to GetManifestResourceNames(): pass it one of those names and it hands back a Stream over the embedded bytes.

Similar to retrieving the full list of names with the GetManifestResourceNames() method, we can obtain the content of a resource via the GetManifestResourceStream() method.

Once we have a Stream object, we can perform various operations, including reading, transforming, displaying, saving to disk, transmitting over the network, and more. Streams provide a flexible and powerful means of handling data in C#.

Finding an Embedded Resource in Assemblies

To streamline the process of locating a specific embedded resource across all assemblies within our solution, we can implement a utility method:

private static Stream? FindResource(Func<string[]?, string?> finder)
{
    foreach (var assembly in AllAssembliesOfCurrentAppDomain)
    {
        var resourceNames = assembly.GetManifestResourceNames();
        var resourceName = finder(resourceNames);

        if (resourceName is not null)
        {
            Console.WriteLine($"Resource {resourceName} found in {assembly.FullName}");
            return assembly.GetManifestResourceStream(resourceName);
        }
    }

    return null;
}

The method iterates through each assembly and retrieves the list of resource names within that assembly using GetManifestResourceNames(), and then passes this list to an external finder method.

If the finder method successfully locates the desired resource, we print a message indicating its discovery and return the corresponding Stream using GetManifestResourceStream(). If the resource is not found in any assembly, we return null.

Finding a Resource by Specifying the Whole Name

To find an embedded resource by its complete name, we can create a finder method that returns the name if it matches precisely:

FindResource(names => names?.FirstOrDefault(rn => rn == resourceName));

This method utilizes the FirstOrDefault() LINQ function to find the first resource name that matches the provided resourceName.

Finding a Resource by Specifying Part of a Name

Similarly, to find a resource by specifying only part of its name, such as ‘pdf-file.pdf’, we can modify the finder method to check for name containment:

FindResource(names => names?.FirstOrDefault(rn => rn.Contains(partialResourceName)));

Here, we use the Contains method to check if any resource name contains the specified partialResourceName. If a match is found, that resource name is returned.

Displaying the Content of an Embedded Resource

Once we have a Stream object representing our embedded resource, displaying its content on the screen is straightforward:

private static void DisplayResource(string resourceName, Stream resourceStream)
{
    using var reader = new StreamReader(resourceStream);
    var resourceContent = reader.ReadToEnd();
    Console.WriteLine($"Resource {resourceName} content:");
    Console.WriteLine(resourceContent);
}

The method reads the content of the resource stream with a StreamReader. It then prints the resource name and its content to the console.

Showing the PDF File

To display the embedded PDF file, we first need to save its content to disk, then open the saved file using a PDF application. We can accomplish this with two methods:

private static string SaveResourceToAFile(string partialResourceName, Stream resourceStream)
{
    var tempFileName = Path.Combine(Path.GetTempPath(), partialResourceName);
    using var fileStream = new FileStream(tempFileName, FileMode.Create, FileAccess.Write);
    resourceStream.CopyTo(fileStream);
    fileStream.Close();

    return tempFileName;
}

private static void ShowFile(string fileName)
    => Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true });

The SaveResourceToAFile() method accepts the partial file name and the Stream representing the embedded resource. It creates a temporary file in the system’s temporary folder and writes the contents of the resource stream to that file. It then returns the name of the newly created file.

The ShowFile() method starts a new process using the system’s default application for opening PDF files, passing the file name as an argument. This opens the PDF file using the default PDF viewer installed on the system.

Why Does GetManifestResourceStream() Return Null?

Because the string we passed is not a name in the manifest, and the method says so by returning null rather than by throwing.

Four differences account for nearly every miss. The prefix is the project’s root namespace, not its assembly name, and those two only look like the same thing until somebody changes one. Folder names are turned into identifiers, so a hyphen or a space becomes an underscore and a leading digit gains one in front. The separator is a dot, never a slash or a backslash. And the match is case-sensitive, so a single wrong capital returns nothing at all.

The file name at the end is left alone, hyphens and all, which is why half of a wrong name looks right.

The reliable move is to stop composing the string. Call GetManifestResourceNames() once, print what comes back, and copy the entry it gives us.

The null is documented rather than accidental. Microsoft’s reference for Assembly.GetManifestResourceStream says of the return value:

null if no resources were specified during compilation or if the resource is not visible to the caller.

Here is what the compiler produces from each kind of entry. Two MSBuild properties change the answer: EnableDefaultEmbeddedResourceItems, which turns off the automatic .resx glob, and EmbeddedResourceUseDependentUponConvention, which is what makes a .resx sitting beside a source file take its name from the type declared in that file.

The file in the projectIts manifest resource nameThe rule
Resources\text-file.txtMyApp.Resources.text-file.txtRoot namespace, then each folder, then the file name, joined with dots
my-folder\a.txtMyApp.my_folder.a.txtFolder names become identifiers: a hyphen or a space becomes _
two words\a.txtMyApp.two_words.a.txtSame rule, spaces included
2digits\a.txtMyApp._2digits.a.txtA folder starting with a digit gains a leading _
Files\my-file-name.txtMyApp.Files.my-file-name.txtThe file name is left alone; its hyphens survive
..\..\README.mdMyApp.README.mdFolders above the project are dropped
..\..\README.md with Link="Docs\README.md"MyApp.Docs.README.mdLink chooses the virtual folder for an outside file
Files\note.txt with LogicalName="note"noteLogicalName replaces the whole computed name
Auto.resx, with no project-file entry at allMyApp.Auto.resourcesThe SDK embeds every .resx by default and compiles it, so the extension changes
Forms\Form1.resx beside Forms\Form1.cs declaring Some.Ns.Form1Some.Ns.Form1.resourcesThe name comes from the co-located type, not from the folder path

Conclusion

Embedding resources into .NET assemblies is straightforward. We can select them in Visual Studio or manually add them as XML tags in the .csproj file.

Every embedded resource is named by concatenating the project’s root namespace with the folder path and file name, separated by dots. For example, Embedded_Resources_in_NET.Resources.Pdf.pdf-file.pdf.

Invoking the GetManifestResourceNames() method of the Assembly object retrieves the list of all resource names in an assembly. Similarly, the GetManifestResourceStream() method obtains the embedded resource stream.

One mechanism carries text files, images and binary files like PDFs inside the assembly itself. Deployment stays one file, and every resource the application needs travels with the code that reads it.

Tested with .NET 10.0.10.