Resource loading failures can be tricky to debug. However, once you have a basic overview of how the ResourceManager works, as well as a few debugging tools, you’ll be able to easily get to the bottom of most problems.
This post will cover the key elements of resource lookup and debugging with a simple code sample, addressing the following:
The code sample is posted in the BCL code gallery downloads as ResourceManagerSample.zip. The zip file contains:
After Step 1, the directory structure looks like the following:
FallbackTest.exe (main executable, contains FallbackStrings.resources) de\FallbackTest.resources.dll (de satellite assembly, contains FallbackStrings.de.resources) fr\FallbackTest.resources.dll (fr satellite assembly, contains FallbackStrings.fr.resources) fr-CA\FallbackTest.resources.dll (fr-CA satellite assembly, contains FallbackStrings.fr-CA.resources)
Before diving into the results, let’s look at the source of FallbackTest.cs to see what it’s doing.
The ResourceManager can perform two types of resource lookup, depending on how you construct it.
Assembly-based is by far the most commonly used; a satellite assembly has the same advantages as a regular assembly such as versioning and the ability to sign. That’s why we’re focusing on assembly-based lookup in this example.
The code sample creates a ResourceManager as follows:
ResourceManager rm = new ResourceManager("FallbackStrings", Assembly.GetExecutingAssembly());
The first argument is baseName — the name of the resources files we’re searching without the culture and “.resources” extension. In step 1a above, resgen created files named, for example, FallbackStrings.fr.resources and FallbackStrings.fr-CA.resources. So baseName in this example would be FallbackStrings.
The second argument is the main assembly for the resources. Since we’re calling from that assembly, we can just say Assembly.GetExecutingAssembly(). Note that this is the common case.
Let’s move to the resource lookups and sample code output. First, notice these lines:
CultureInfo ci = new CultureInfo(culture); Thread.CurrentThread.CurrentUICulture = ci;
This is done because the ResourceManager uses the current thread’s CurrentUICulture to search for resources by default. ResourceManager.GetString also has overloads that accept a CultureInfo. If provided, that culture will be searched instead.
After setting the UI Culture, fallback test searches for a couple of localized strings and prints them to the screen.
Current UI culture: fr-CA Greeting: bonjour End of week: la fin de semaine ------------------------------ Current UI culture: fr-FR Greeting: bonjour End of week: le week-end ------------------------------ Current UI culture: de-DE Greeting: Guten Tag End of week: weekend ------------------------------ Current UI culture: ja-JP Greeting: hello End of week: weekend ------------------------------
Let’s focus on fr-CA and fr-FR. fr-CA is the French Canadian locale, which has some different words than fr-FR, the French spoken in France.
In this code sample, we’ve included resources for fr-CA, but none for fr-FR. Instead, we’ve provided resources for fr, which is the neutral French culture. A neutral culture is not region-specific and is the parent of its corresponding region-specific cultures. For example, fr is the parent culture of both fr-CA and fr-FR.
A quick aside on this decision:
Resource divisions like this are fairly common in practice; the most common words for a language (across regions) are placed in the neutral culture’s resource file, and any deviations from that are placed in the specific culture’s resource file. This minimizes duplication in resource files. So in this example, the choice to put greeting in fr allowed us to share resources across French-speaking regions, but adding explicit resources for fr-CA allows French Canadian to differ from the most commonly-used French.
Now notice in the program output that fr-CA and fr-FR have the same greeting but different words for the end of the week. That’s because for greeting, fr-CA resources didn’t have a value, so it fell back to fr (same as fr-FR).
In general, a resource fallback chain looks like this.
Culture passed to GetString(, x) or ThreadUICulture (fr-CA) -> parent culture (fr) -> invariant culture (embedded in main assembly)
Compare the output for the other cultures above to see why the output makes sense.
The fallback chain can also vary as follows:
There are 3 common tools I use for debugging resource-related issues. These are:
Let’s look at these in-depth by seeing what can go wrong. Comment out the call to GoodTest(), then uncomment the call to BadTest(), and rerun make.bat. This code has a bug, which is that the baseName is incorrect (“Fallback_Strings” instead of “FallbackStrings”). Running the code, we get the following exception:
System.Resources.MissingManifestResourceException: Could not find any resources appropriate for the specified culture or the neutral culture. Make sure "Fallback_Strings.resources" was correctly embedded or linked into assembly "FallbackTest" at compile time, or that all the satellite assemblies required are loadable and fully signed.
Let’s investigate this failure showing how the different tools are used:
To enable fusion logs, launch the fusion log viewer, fuslogvw.exe, which is provided as a Framework sdk tool (you can find it under Framework\tools). Choose the option “Log all binds to disk”. I also select the “Enable custom log path” checkbox and specify a path such as C:\fuslog. (Since fusion logs are written as html files, I prefer to browse the files directly on disk than view the output in the fusion log viewer.)
Then run your program and look in C:\fuslog for the results.
In C:\fuslog\Default\FallbackTest.exe\, I see there were binding attempts for fr-CA and fr, because there are files named, e.g.:
“FallbackTest.resources, Version=0.0.0.0, Culture=fr-CA, PublicKeyToken=null.HTM”
Opening those files, you can see that the assembly did load correctly, so the problem isn’t a missing satellite assembly.
If the fusion log file did indicate a problem with the satellite assembly loading, then you know that either the satellite assembly is missing or has some other problem (version or signing problem), in which case you most likely have a build or install problem to correct.
Both of these allow you to see the contents of a .resources file embedded in an assembly, but resview (part of the Framework sdk) has additional diagnostic tools. However, Reflector provides a nice interface for browsing the resource file, and I most often use that.
Open up the assembly of interest (either satellite or main) in Reflector. Expand the “Resources” section of the assembly to verify that the baseName we specified is actually present. As expected, we have a resource file named FallbackStrings.X.resources, but not Fallback_Strings.X.resources, and the solution is to remove the underscore from the baseName.
While this file name problem seems basic, it turns out to be a very common problem, so keep it in mind.
Another simple common mistake is if the key isn’t actually in the file. You can double-click on the .resources file in reflector to see the contents.
PingBack from http://blog.a-foton.ru/index.php/2009/02/17/working-with-the-resourcemanager-kim-hamilton/
Kim Hamilton has a couple of excellent posts on the BCL Team blog . In the first post on Working with
In an earlier blog about resource fallback essentials , I said that the resource diagnostic tool resview
I was wrong above when I said resview is part of the Framework sdk. But you can get it on code gallery. I highly recommend trying out resview since it provides useful diagnostic info that's not available from the tools described above.
I blogged about resview -- how to use it and download it -- here:
http://blogs.msdn.com/kimhamil/archive/2009/03/06/advanced-resource-debugging-with-resview.aspx