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.
However, in this article, we’ll focus on embedded resources.
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:
- Right-click each file.
- From the context menu, choose ‘Properties.’
- In the Properties window, select ‘Build Action.’
- Choose ‘Embedded resource’ from the dropdown menu.
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.
Streams
extensively on Code Maze, with articles such as StreamWriter and StreamReader Classes in C#, How to Use MemoryStream in C#, Using Streams with HttpClient to Improve Performance and Memory Usage, and more.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.