Click here to Skip to main content
Click here to Skip to main content
Go to top

Enhanced Resource File Code Generator - Type-safe Formatted Strings

, 23 Oct 2010
Rate this:
Please Sign up or sign in to vote.
An enhanced ResXFileCodeGenerator that handles formatted strings in a type-safe and natural way

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 strings that do not contain replacement parameters are not altered. Strings 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 strings 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:

  1. Add replacement parameter information in the comment column of the string resource editor for any formatted strings.
  2. 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.
  3. 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 strings. 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:

  1. The string resource editor comment column is used to define the format types and parameter names.
  2. The general format is: <formatType> <paramName>[, <formatType> <paramName>]
  3. Supports XML parameter comments, i.e. “///” comments.
  4. 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.
  5. 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 

string address /// Can't accept PO boxes8 

string city, string state,8 

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 {…} }

/// <param name = “address”>Can't accept PO boxes</param>
/// <param name = “zip”>
/// The zip must include the 4 digit extension. I.e. 43249-1234
/// </param>
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!

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Kaboa

Unknown
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberhnilobit8-Jun-12 5:29 
GeneralFor a shorter sourcecode PinmemberADLER14-Jul-08 7:12 
AnswerRe: For a shorter sourcecode PinmemberTristen Fielding11-Jul-08 6:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 23 Oct 2010
Article Copyright 2008 by Kaboa
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid