We’ve covered resources in a couple of our previous articles.

In Localization in ASP.NET Core, we delved into .NET localization, touching upon resources since XML files utilized for localization fall under .NET resources.

We also discussed resources in our article, How to Read a String From a .resx (Resource) File in C#, where we explored how developers can extract strings from RESX files, a type of resource.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

However, in this article, we’ll focus on embedded resources.

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

Let’s dive in.

What Are Embedded Resources?

In addition to data in our program, we often need outside resources. Sometimes, these resources are provided by the end-user, but there are also situations where we have all the resources at the time of development. We need an option to ship them with our program somehow.

Rather than merely attaching files to our program, it’s advantageous to include them within its assembly. This ensures they’re always readily available and eliminates the risk of overlooking them.

The encapsulation of resources within the program itself constitutes embedded resources.

Test Project With Embedded Resources

We’ll walk through the creation of a sample command-line application in several steps:

  • Start by crafting an empty command-line application.
  • Utilize Visual Studio to incorporate embedded resources.
  • Manually include embedded resources.
  • Learn how to retrieve the list of embedded resources within our application.
  • Construct a satellite assembly containing embedded resources.
  • Finally, read and use embedded resources in our program.

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 designate these files as embedded resources, 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

Reading the List of Embedded Resources in Our Application

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 all the embedded resources 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. If we take the example of the PDF file, ‘Embedded_Resources_in_NET.Resources.Pdf.pdf-file.pdf‘, and split it by dots (except the last one), we get:

Embedded_Resources_in_NET
Resources
Pdf
pdf-file.pdf

Embedded_Resources_in_NET‘ is our assembly 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 assembly name, 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 as embedded resources 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>

This XML fragment indicates which files are designated as embedded resources.

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="..\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.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.

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.

Embedded Resources in a Satellite Assembly

Now that we’ve learned how to retrieve a list of embedded resources in our main assembly let’s attempt to list the embedded resources in a satellite assembly that we’ll create.

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_Satellite

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 satellite assembly.

Reading a List of Embedded Resources in a Satellite Assembly

We can apply the same approach to any assembly, including satellite assemblies.

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

private static Assembly SatelliteAssembly =>
    Assembly.Load("Embedded_Resources_in_NET_Satellite");

Then, we can call the method:

public static void ListResourcesInOurSatelliteAssembly()
    => ListResourcesInAssembly(SatelliteAssembly);

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

Resources in Embedded_Resources_in_NET_Satellite, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Embedded_Resources_in_NET_Satellite.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 Assemblies of Our Project

To list all the embedded resources 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 approach allows us to comprehensively gather information about embedded resources across all assemblies within our solution.

Reading the Content of an Embedded Resource

Indeed, understanding how to access the content of an embedded resource is crucial for utilizing them.

Similar to retrieving a list of all embedded resources using 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;
}

Method iterates through each assembly and retrieves the list of embedded resources 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.

This utility method simplifies locating and accessing specific embedded resources within our solution’s assemblies.

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);
}

Method utilizes a StreamReader to read the content of the resource stream. 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.

Conclusion

In 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 assembly name with the folder path and file name, separated by commas. For example, ‘Embedded_Resources_in_NET.Resources.Pdf.pdf-file.pdf’.

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

This simple yet powerful mechanism allows us to seamlessly integrate various resources, such as text files, images, and even binary files like PDFs, directly into our .NET assemblies, streamlining deployment and ensuring that all necessary resources are readily available to our applications.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!