Generated Access to .NET Resource Strings

Environment: Visual Studio .NET, C#

Overview

This article provides an alternative method of accessing string resources in a C# project. It provides a means of treating each resource identifier (associated with a string) as an object with a public interface for retrieval of the string itself. Although my original intent was simply to provide compile-time verification of string resource identifiers, the end result is truly a better way to get to a project’s string resources.

These are some additional benefits:

  1. String resource retrieval syntax is simple. Just call a method on the object named for the desired resource identifier
  2. IntelliSense supplies the retrieval method name and reminds you of any parameters for formatted strings
  3. ResourceManager operation is encapsulated
  4. Incorrect number of arguments when formatting resource strings is caught at compile time

Introduction

Recently I was given the task of moving string literals from the many files of a C# service project into a single resource file to allow for localization in the future. After describing what needed to be done, my manager handed me a book on .NET resources and said it would help.

As soon as he left, I said, “I don’t need no stinking book! I’ve done this dozens of times before during my years as a C++ Windows developer; this couldn’t be much different.” It didn’t take me long to realize I’d need that book, after all.

As it turns out, resources have been completely revamped for the .NET framework. This new model provides excellent support for both globalization and resource serialization, but it does require you to forget everything you know about Win32 resources.

Resource Identifiers in Win32

One of the aspects of the Win32 resource model that I didn’t appreciate until moving from C++ to C# was the compile-time checking of resource identifiers.

Under Win32, resource identifiers are numeric constants, which are usually defined in a header file and mapped to resources in a resource file. These resources can be accessed from anywhere within a project by referencing the corresponding identifier. Identifiers are first validated when the resource script is compiled, and then validated again when the application that uses the resources is compiled. If an undefined identifier is referenced in the resource script, the resources fail to compile. Likewise, an undefined identifier referenced in code will keep the application from compiling successfully. This provides a layer of protection against changes made to resource identifiers that are easily caught by the developer. Of course, this doesn’t solve the problems incurred when an existing resource identifier is changed and the previous version isn’t deleted, or the clutter of unreferenced identifiers in the resource header file, but that’s another story.

Here is an example of how to load the string resource IDS_RESOURCE1 under Win32:

TCHAR szString[256];
LoadString( GetModuleHandle(NULL), IDS_RESOURCE1,szString,
            sizeof(szString)/sizeof(TCHAR) );

Resource Identifiers in the .NET Framework

Under the .NET framework, resource identifiers are strings associated with a specific resource, both of which are defined in the same resource file. As far as the compiler is concerned, resource identifiers referenced in the code are simply string literals, so no compile-time verification of the relationship between these identifiers and their respective resources is performed. An incorrect identifier isn’t apparent until run time, when an exception is thrown while trying to load a resource.

Here is an example of how to load the string resource Resource1 under .NET:

ResouceManager rm = new ResourceManager( "AssemblyName.Resources",
                    Assembly.GetExecutingAssembly() );
string temp = rm.GetString( "Resource1" );

Generator Utility—Take 1

Given all this, I decided I needed to create a tool that would provide compile-time verification by generating classes that shared names with the resource identifiers. These classes would then be used to access resources from the code. To this end. I created a command-line utility that took as input the resource file name, output C# file name, and the namespace for the generated classes. A C# file is produced, which contains an access class corresponding to each resource identifier in the specified resource file. This utility can be run as a pre-build step, so the access classes are present when the project is compiled.

For example, say a resource file contains the following resource:

ResourceId1="This is ResourceId1"

The C# file that is generated looks like this:

namespace SpecifiedNamespaceGoesHere
{
  class ResourceFormatter
  {
    public static string GetString( ResourceManager rm,
                                    string resourceId )
    {
      return rm.GetString( resourceId );
    }
  }

  class ResourceId1
  {
    public static string GetString( ResourceManager rm )
    {
      return ResourceFormatter.GetString( rm, "ResourceId1" );
      }
  }
}

To access the resource “ResourceId1”, you would use the following code:

ResourceManager rm = new ResourceManger();
String result      = ResourceId1.GetString( rm );

Central to the design of this tool is the ResourceFormatter class, which performs the task of loading and formatting the string. All of the access classes simply return the GetString method of the ResourceFormatter class, thereby cutting down on code duplication.

Improvements

At this point, I had a working version that did what I had set out to do, but still left room for improvement.

The first item I wanted to change had to do with the fact that the caller is responsible for managing an instance of ResourceManager and passing it to the access classes every time a resource is requested. This just seemed to add an unnecessary layer of complexity to the seemingly simple task of retrieving a resource string. Although my original goal was to provide compile-time verification of resource identifiers, I wanted my methods to be as easy to use as possible.

I decided to address this by including a ResourceManager singleton as an inner class of the generated ResourceFormatter class that performs the actual work of loading the resource. This alleviates the user from having to create and manage instances of ResourceManager. Now, the generated .cs file includes this version of the ResourceFormatter class:

class ResourceFormatter
{
    /// <summary>
    /// Loads an unformatted string
    /// </summary>
    /// <PARAM name="resourceId">Identifier of string
    /// resource</PARAM>
    /// <returns>string</returns>
    public static string GetString( string resourceId )
    {
        return ResourceManagerSingleton.Instance.GetString(
               resourceId );
    }

    /// <summary>
    /// Singleton responsible for creating an instance of the
    /// ResourceManager class
    /// </summary>
    private class ResourceManagerSingleton
    {
        /// <summary>
        /// Class constructor
        /// </summary>
        /// <remarks>Private to keep class from being
        /// instantiated</remarks>
        private ResourceManagerSingleton() {}

        /// <summary>
        /// Static method that creates an instance of the
        /// ResourceManager
        /// </summary>
        /// <returns>ResourceManager</returns>
        protected static ResourceManager CreateInstance()
        {
            string assemblyName = Assembly.GetExecutingAssembly().
                                           GetName().Name;
            // Assembly name.Resource File
            string baseName = string.Format( "{0}.BaseResources",
                                             assemblyName );

            return new ResourceManager( baseName, Assembly.
                                        GetExecutingAssembly() );
        }

        /// <summary>
        /// Store for the Instance property
        /// </summary>
        private static ResourceManager _instance;

        /// <summary>
        /// Instance property
        /// </summary>
        /// <value>Read-only access to the ResourceManager
        /// instance</value>
        public static ResourceManager Instance
        {
            get
            {
                // Allow access from multiple threads
                lock( typeof(ResourceManagerSingleton) )
                {
                    if( _instance == null )
                    _instance = CreateInstance();
                }

                return _instance;
            }
        }
    }
}

This allows a desired string to be retrieved with a single, simple statement:

String result = ResourceIdentifier.GetString();

Instead of:

ResourceManager rm = new ResourceManger();
String result      = ResourceIdentifier.GetString( rm );

More Improvements

My original goal when adding support for string resources containing format specifiers was to alleviate the need to first retrieve the format string from the resource file before formatting it. Before I’d gotten very far, I realized this would be an excellent opportunity to go a step further and provide security against a problem I’d seen many times in the past when dealing with this type of string resource.

Regardless of whether Win32 or .NET is being used, one error that doesn’t appear until run time is the incorrect number of parameters used when formatting strings. If the number of format specifiers in an existing string resource is changed without a corresponding change in all the places it is used, problems will occur.

For example, let’s say we used to have a string resource that contained a single format specifier. Our application loads the string (using the access method implemented previously) and formats it, passing in a string to replace the placeholder with:

ResourceId2="The Customer's name is: {0}"

String format = ResourceId2.GetString();
String result = String.Format( format, "Corey" );

Later on down the road, I decide to update this to include individual format specifiers for both first and last names. I make the change to the resource file, but get sidetracked before updating the code that performs the formatting. When this is compiled, no error is generated until the formatting code is run with the incorrect number of arguments.

ResourceId2="The customer's name is: {0} {1}"

String format = ResourceId2.GetString();
String result = String.Format( format, "Corey" );    // Oops!

This is a fairly common problem, especially on large development teams and projects where string resources are localized by an outside agency.

What I really want is to have the GetString method require the correct number of arguments—you know the drill, compile time checking Using the .NET regular expression support. It was easy to tell the number of format specifiers in each string resource, and generate the GetString method based on this information.

For the following string resource:

ResourceId2="This is ResourceId2. It contains a replaceable
             parameter: {0}"

This access class will now be generated:

class ResourceId2
{
    public static string GetString( object arg0 )
    {
        return ResourceFormatter.GetString( "ResourceId2", arg0 );
    }
}

This update required the addition of a GetString method override to the ResourceFormatter class that could handle any number of parameters:

class ResourceFormatter
{
    ...

    /// <summary>
    /// Loads a formatted string
    /// </summary>
    /// <param name="resourceId">Identifier of string
    /// resource</param>
    /// <param name="objects">Array of objects to be passed
    /// to string.Format</param>
    /// <returns>string</returns>
    public static string GetString( string resourceId,
                                    params object[] objects )
    {
        string format = ResourceManagerSingleton.Instance.
                        GetString( resourceId );
        return string.Format( format, objects );
    }
}

This allows a desired resource string to be retrieved and formatted using a single statement:

String result = ResourceIdentifier.GetString( param1, param2 );

Instead of:

String format = ResourceIdentifier.GetString();
String result = String.Format( format, param1, param2 );

Additionally, IntelliSense will show how many parameters are expected while you are typing the statement (see Figure 1). If you ignore this and provide an incorrect list, the code won’t compile.

Figure 1: IntelliSense Parameter List

Sample Application

The sample solution consists of the following projects:

  • Demo Application—A simple Windows application that uses reflection to fill a listbox with the resource strings that have had a corresponding access class created by the generator utility.
  • GenResourceKeys—The generator utility.
  • PreBuildStep—Contains a batch file that runs the generator utility and creates the resource access classes during a build if one of the dependency files is out of date.

I’m not going to delve into the details of the Demo Application because its only purpose is to demonstrate the capabilities of the GenResourceKeys project.

I need to include a copyright notice for the CommandLineParser class developed by the Genghis Group and used by the generator to parse the command line arguments:

Portions copyright © 2002 The Genghis Group

It Can Still be Improved

To get rid of annoying source code control prompts because of the generated files, I created a version of this utility that runs as a Visual Studio .NET custom tool. Because custom tools aren’t the focus of the article, I have included this as a sample project, but leave it as an exercise for the reader to pursue.

When creating the custom tool, I referenced the MSDN article “Top Ten Cool Features of Visual Studio .NET Help You Go From Geek to Guru” by Jon Flanders and Chris Sells, along with the CollectionGen tool that the article discusses.

Downloads

Download source (includes demo) – 24 Kb

Download generator utility – 40 Kb

Download Custom Tool Version – 15 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read