Update
[10/21/2010] The installer has been updated to support both Visual Studio 2008 and Visual Studio 2010!
Introduction
With the release of .NET 2.0, Microsoft provides a type-safe (code-behind) wrapper class that wraps resource files. This is a huge improvement over the old error-prone way of accessing resources. We can now access resources like images, icons, files and strings just by using the resource’s identifier as a property of this wrapper class.
For example, suppose we have an image file called MyCompanyLogo.png and a resource file called Images.resx. The resource file includes the image as follows:
name = "MyCompanyLogo" file = ".\MyCompanyLogo.png"
The code-behind file creates a class called Images
and a property in that class like so:
internal Image MyCompanyLogo { get; }
Now in our source code, we can access the image file MyCompanyLogo.png simply by accessing the type-safe property:
Image pic = Images.MyCompanyLogo;
The Problem
As it turns out, there is still one major problem area; runtime errors are possible when using formatted strings. A formatted string is a string
that contains special replacement parameters. For example, suppose we have a string
like “Welcome John
” but we want to use it to display a welcome message for any user that logs on to our application. We can create a formatted string like “Welcome {0}
”. The {0}
is an indexed replacement parameter that allows us to use the string
in the following way:
Console.WriteLine(string.Format("Welcome {0}", name));
At this point, we want to make the string
localizable. We place it in a resource file (let’s call the file LocalizedStrings.resx and the name of the resource WelcomeMessage
). Microsoft’s custom tool called ResXFileCodeGenerator
creates a property called WelcomeMessage
.
However, we now have a problem. Because the formatted string
is defined as a property, we can easily do the following:
Console.WriteLine(LocalizedStrings.WelcomeMessage);
And the output looks like this:
Welcome {0}
Clearly that isn't what we want and thus we have a runtime bug.
The correct usage is:
Console.WriteLine(string.Format(LocalizedStrings.WelcomeMessage, name));
Another problem with this approach occurs if we decide to add more parameterized content to the string
. Suppose we change the welcome message to “Welcome {0} {1}
”. What is the meaning of this new replacement string
? It is unclear; does it mean first name, last name, or does it mean salutation full name? In the end, it is difficult for others to discover our intent.
Also, another runtime bug occurs because we now have two replaceable parameters instead of one. We have to remember to search our code and fix every place we are using this string
. This is time consuming and error-prone.
The Solution
The solution is to provide a smarter layer over the existing structure provided by Visual Studio. Our new layer produces a code-behind file similar to that created by Visual Studio. Non-string resources (images, icons, etc.) and string
s that do not contain replacement parameters are not altered. String
s with replacement parameters are transformed into methods that have type-safe parameters representing each of the replacement parameters.
To access a non-formatted string
, no changes are required to your existing code. Simply reference the string
property:
Console.WriteLine(LocalizedStrings.MyNonFormattedString);
To access a string
containing format information, call the string
’s method and pass in the replacement parameters:
Console.WriteLine(LocalizedStrings.WelcomeMessage(firstName, lastName));
In the above case, the WelcomeMessage string
resource has the following value:
Welcome {0} {1}
In cases where access to the original string
s is required, we provide an inner class called Raw
. This allows developers to access any string
as follows:
string rawMessage = LocalizedStrings.Raw.WelcomeMessage;
Usage
Using ResXFileCodeGeneratorEx
is very straightforward. Simply follow these steps:
- Add replacement parameter information in the comment column of the
string
resource editor for any formatted string
s. - Select the resource file you want to update and change the Custom Tool property (located in the Properties window) to
ResXFileCodeGeneratorEx
. If you need public
access to your resources use PublicResXFileCodeGeneratorEx
. - Update your code accordingly.
Adding Replacement Parameter Information
Replacement parameters are used to build the method parameters for a formatted string
. They are defined in the comment column of the string
resource editor and are required for all formatted string
s. The content of the comment column is ignored if a string
does not contain formatting information.
Replacement Parameter Information Syntax
The syntax of the replacement parameter information is very simple yet flexible. It consists of the following rules:
- The
string
resource editor comment column is used to define the format types and parameter names. - The general format is:
<formatType> <paramName>[, <formatType> <paramName>]
- Supports XML parameter comments, i.e. “
///
” comments. - Supports C#-style comments, i.e. “
//
” comments. These comments aren't added to the code-behind file. They are normally used to help the translators during translation. - Supports the following format types:
string
, int
, long
, bool
, char
, byte
, float
, double
, decimal
, short
, sbyte
, ushort
, uint
, and ulong
. Note: Object is not a valid format type since everything derives from object. Instead, convert the object to a supported format type such as string
.
To exclude a string
from processing, simple place $exclude$
in the comment column. The string
will be represented as a property even if it contains formatting information.
Examples
Here is a typical string resource editor view:
Name | Value | Comment |
InvalidIdentifier | Invalid class name '{0} '. | string className |
Hi | Hi my name is {0} and I am {1} years old. | string name, int age |
UnformatedMessage | C# uses { and } to define beginning and ending blocks | $exclude$ |
Address | {0} {1} , {2} {3} | // Just a normal message8 <br /><br />string address /// Can't accept PO boxes8 <br /><br />string city, string state,8 <br /><br />string zip /// The zip must include the 4 digit extension. i.e. 43249-1234 |
Note: Comments are terminated by carriage returns (8
). This allows for inline comments for each replacement parameter.
Here is part of the code-behind file generated from the example resources:
public string InvalidIdentifier(string className) {…}
public string Hi(string name, int age) {…}
public string UnformatedMessage { get {…} }
public string Address(string address, string city, string state, string zip) { … }
The Enhanced Resource File Code Generator source and executable can be downloaded from the links at the top of this article.
Enjoy!
History
- 3rd July, 2008: Initial post
- 21st October, 2010: Installer updated to support both Visual Studio 2008 and Visual Studio 2010!