Updated on 2020-01-07
Solve some common resource complications in .NET with this simple utility class
I recently needed to retrieve resources with a name not qualified by a namespace and the code to do so is surprisingly difficult. The reason I needed this was I was including a project at the source level using CSBrick and I needed the thing to be able to find its resources in the new assembly. Using CSBrick is a bit like transferring a potted plant. It's easy when you know how, but if you don't know what you're doing, you'll kill it.
Fortunately, this utility is very easy to use.
Each resource is stored by fully qualified name, which includes the default namespace of a project and the resource's original filename, usually suffixed by ".resource" It can be bloody difficult for code to know its own default namespace in the right assembly, depending on the situation, so it can be hard to find and load a resource in that case.
This utility provides several methods for retrieving streams and names from resources in a series of assemblies, defaulting to the calling assembly and entry assembly both. It will also use the executing assembly if you include ResourceUtility.cs in your own projects instead of referencing the library. You can retrieve resources by using an index, a "short name" or the fully qualified name.
We'll start with the demo code:
Console.WriteLine("Resource List:");
var fqnames = ResourceUtility.FillNames();
// could pass false above and then use GetFullyQualifiedName() but this is more efficient
foreach(var name in fqnames)
{
Console.WriteLine(string.Format
(" {0} ({1})",ResourceUtility.GetUnqualifiedName(name),name));
}
Console.WriteLine();
var names = ResourceUtility.FillNames(null,false);
for(int ic=names.Count,i=0;i<ic;++i)
{
Console.Write("Get resource by index " + i.ToString());
var stm = ResourceUtility.GetStream(i);
if(null!=stm)
{
Console.WriteLine(" succeeded.");
stm.Close();
} else
Console.WriteLine(" failed.");
Console.WriteLine("Fully qualified name of {0} is {1}",
names[i], ResourceUtility.GetFullyQualifiedName(names[i]));
Console.Write("Get resource by name " + names[i].ToString());
stm = ResourceUtility.GetStream(names[i]);
if (null != stm)
{
Console.WriteLine(" succeeded.");
stm.Close();
}
else
Console.WriteLine(" failed.");
}
The first method in the demo is FillNames(). I use a "fill" pattern instead of a "get" pattern because it's more flexible in that it allows you to pass in your own lists to fill, as well as allowing you to "get" by calling it without one. This method will optionally retrieve the "short name" / "friendly name" of the resources and duplicates are returned as well. You can handle that, or simply retrieve resources by the full name or the index to get the right one.
The next method is GetUnqualifiedName() which simply takes a resource name and strips it of its extension and namespace.
Next, we have GetFullyQualifiedName() which returns the fully qualified name given a resource name. In cases where the short name has duplicates, the first one found will be used.
Finally, we have GetStream() which retrieves a stream by index, short name, or fully qualified name. Again, in cases where the short name has duplicates, the first one found will be used. Make sure to dispose of any streams you get back from this method.
There is no way to distinguish between resource names with dots in them and the namespace part of the resource name, so resources with dots in the resource name (other than the namespace) or resources without file extensions will be returned weirdly. The results are "undefined" but honestly, just see what you get back in this case.
There you go!