DMK Software


Extended Strongly Typed Resource Generator

- v2.7 of the Extended Strongly Typed Resource Generator has been released (April 17, 2009)

Introduction

One of the new great features of Visual Studio .NET 2005/2008 IDE is a custom tool called ResXFileCodeGenerator that is automatically associated with resources (.resx files) every time they are added into a project. Whenever your project is rebuilt, a resource file is saved or a custom tool is run manually, the tool in question generates a managed class that exposes every resource you have in the .resx file as a strongly typed static property. Now any type of a resource supported - including images, icons, string, etc. - is a piece of cake to retrieve.

Two screenshots below illustrate the default properties of a resource file added to Visual Studio .NET 2005 and the Resource.Designer.cs source file, which is dependent upon the Resource.resx and automatically generated by the ResXFileCodeGenerator custom tool.

All the properties exposed by the generated class are always static. The class exposes the following properties:

  • The ResourceManager property with the return type System.Resources.ResourceManager used to access culture-specific resources at runtime.
  • The Culture property with return type System.Globalization.CultureInfo and both get and set accessors. The set accessor of the Culture property could be used for specifying the requisite culture that the resource is localized for. By default, the Culture property returns the null meaning that the culture information is obtained using the culture's CurrentUICulture property.
  • Properties used for resource access named as corresponding resources (property names could be adjusted according to the used code generator requirements). Their types correspond to resource types in a .resx file.

If your resource file contains only one string resource (like in the picture above), then the generated class would be like this:

/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[GeneratedCodeAttribute("Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
[DebuggerNonUserCodeAttribute()]
[CompilerGeneratedAttribute()]
internal class Resource {

private static ResourceManager resourceMan;

private static CultureInfo resourceCulture;

[SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resource() {
}

/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
internal static ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
ResourceManager temp = new ResourceManager("MyApp.Resource",
typeof(Resource).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}

/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
internal static CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}

/// <summary>
/// Looks up a localized string similar to Message text.
/// </summary>
internal static string Message {
get {
return ResourceManager.GetString("Message", resourceCulture);
}
}
}

Background

Despite the fact that ResXFileCodeGenerator custom tool simplifies the process of resource access a lot, one can point out the following four major drawbacks:

  • The strongly types resource classes generated by the ResXFileCodeGenerator custom tool always have internal visibility. Since the generated class is marked as internal, it cannot be accessed from assemblies other than friend assemblies. However, the resgen.exe utility with the /publicClass option generates a strongly typed resource class as a public class, but then all advantages of custom tools are lost in this case.

    Note: Visual Studio .NET 2008 introduces the new custom tool called PublicResXFileCodeGenerator generating public resource class wrappers.

  • In most cases, .resx files contain only strings including format strings (a string containing zero or more format items) used by .NET Framework formatting mechanism. It has always been problematic to load a format string from a resource and pass the correct amount of parameters to it. Passing the incorrect amount of parameters did not lead to a compile error, but rather to the annoying runtime error.
  • Thread unsafe initialization of the ResourceManager class instance in the ResourceManager property.
  • Resource names are not accessible through resource class wrappers.
  • Generated resource class wrappers are not compatible with the .NET Compact Framework.

The Extended Strongly Typed Resource Generator

In regard to the above described ResXFileCodeGenerator disadvantages, it was decided to develop the extended version of a strongly typed resource generator that remedies deficiencies of the existing ResXFileCodeGenerator custom tool.

Using an extended version of the strongly typed resource generator is extremely straightforward and does not differ from using the resource code generator shipped with Visual Studio .NET 2005 and 2008. The extended strongly typed resource generator is represented by two new custom tools:
- ResXFileCodeGeneratorEx: custom tool generating public resource wrappers
- InternalResXFileCodeGeneratorEx: custom tool generating internal resource wrappers

First of all, you have to install and register the extended strongly types resource generator (e.g. ResXFileCodeGeneratorEx and InternalResXFileCodeGeneratorEx custom tools) on your box. Please remember that you must have the administrator privileges to install and register new VS.NET custom tools.

There are two ways to register the extended strongly typed resource generator on your computer:

  • The preferred way is to download the Windows Installer package and install it to the specific location on your computer. The installer will automatically register ResXFileCodeGeneratorEx and InternalResXFileCodeGeneratorEx Visual Studio .NET custom tools on your box. It's recommended to unzip the contents of the archive with the installer and run the Setup.exe to launch the installation. This way of installing is compatible with Vista.
  • Get the sources from the provided archive and rebuild them. In case of success, ResXFileCodeGeneratorEx and InternalResXFileCodeGeneratorEx custom tools will be registered on your PC. To make the tools function properly, you have to keep the build output in the output directory since VS.NET custom tools are the COM objects and should remain in the same directory where they were registered.

After the extended strongly typed resource generator is installed on you box, you have to restart all running instances of VS.NET 2005 and 2008.

From this point on, you can use all advantages of the extended strongly typed resource generator in your projects. You can manually specify the ResXFileCodeGeneratorEx or InternalResXFileCodeGeneratorEx as a custom tool for your resource files or you can as well adjust default VS.NET 2005/2008 item templates.

Let's take the MyApp project as a real-world example and add one more resource entry containing formatted string. The most important step is to change the custom tool name to the extended strongly typed resource generator (ResXFileCodeGeneratorEx or InternalResXFileCodeGeneratorEx) and run the custom tool by either saving the resource file or running the custom tool manually (you have to right-click on the resource file in VS.NET and choose 'Run Custom Tool' in the drop-down menu).

The ResXFileCodeGeneratorEx custom tool generates the resource wrapper class shown in the example below:

/// <summary>
/// A strongly-typed resource class, for looking up localized strings, formatting them, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilderEx class via the
// ResXFileCodeGeneratorEx custom tool. To add or remove a member, edit your .ResX file
// then rerun the ResXFileCodeGeneratorEx custom tool or rebuild your VS.NET project.
// Copyright (c) Dmytro Kryvko 2006-2008 (http://dmytro.kryvko.googlepages.com/)
[GeneratedCodeAttribute("DMKSoftware.CodeGenerators.Tools.StronglyTypedResourceBuilderEx", "2.7.0.0")]
[DebuggerNonUserCodeAttribute()]
#if !SILVERLIGHT && !PocketPC && !Smartphone && !WindowsCE
  [ObfuscationAttribute(Exclude=true, ApplyToMembers=true)]
#endif
[SuppressMessageAttribute("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")]
public partial class Resource {

private static ResourceManager _resourceManager;

private static object _internalSyncObject;

private static CultureInfo _resourceCulture;

[SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public Resource() {
}

/// <summary>
/// Thread safe lock object used by this class.
/// </summary>
public static object InternalSyncObject {
get {
if (object.ReferenceEquals(_internalSyncObject, null)) {
Interlocked.CompareExchange(ref _internalSyncObject, new object(), null);
}
return _internalSyncObject;
}
}

/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
public static ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(_resourceManager, null)) {
Monitor.Enter(InternalSyncObject);
try {
if (object.ReferenceEquals(_resourceManager, null)) {
Interlocked.Exchange(ref _resourceManager,
new ResourceManager("MyApp.Resource", typeof(Resource).Assembly));
}
}
finally {
Monitor.Exit(InternalSyncObject);
}
}
return _resourceManager;
}
}

/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
public static CultureInfo Culture {
get {
return _resourceCulture;
}
set {
_resourceCulture = value;
}
}

/// <summary>
/// Looks up a localized string similar to 'Hello, {0}!'.
/// </summary>
public static string Hello {
get {
return ResourceManager.GetString(ResourceNames.Hello, _resourceCulture);
}
}

/// <summary>
/// Looks up a localized string similar to 'Message text'.
/// </summary>
public static string Message {
get {
return ResourceManager.GetString(ResourceNames.Message, _resourceCulture);
}
}

/// <summary>
/// Formats a localized string similar to 'Hello, {0}!'.
/// </summary>
/// <param name="arg0">An object (0) to format.</param>
/// <returns>A copy of format string in which the format
/// items have been replaced by the String equivalent of
/// the corresponding instances of Object in arguments.</returns>
public static string HelloFormat(object arg0) {
return string.Format(_resourceCulture, Hello, arg0);
}

/// <summary>
/// Lists all the resource names as constant string fields.
/// </summary>
public class ResourceNames {

/// <summary>
/// Stores the resource name 'Hello'.
/// </summary>
public const string Hello = "Hello";

/// <summary>
/// Stores the resource name 'Message'.
/// </summary>
public const string Message = "Message";
}
}

As you can see, the generated class is public, which allows you to make shared resources between assemblies.

However, the major difference is that one additional method called HelloFormat has been added. This method is a result of analysis and validation of the Hello resource entry string value. The extended strongly typed resource generator automatically determines whether a resource string value is a valid .NET Framework format string and generates code correspondingly.

The name of a format method is always generated in the following way: resource property plus the "Format" suffix. The amount of arguments is calculated automatically and equals to the amount of parameters that String.Format() method expects. On the other hand, there is still a possibility to get the format string using the exposed Hello property.

As it was mentioned above, the extended strongly typed resource generator performs format string validation. For example, you could by mistake write invalid format string like that: Hello, {{0}. The (Internal)ResXFileCodeGeneratorEx custom tool will resolve the invalid format and will show you the warning about that.

Another set of small improvements over the standard visual studio resource wrapper generator:

  • Absence of [CompilerGeneratedAttribute()] in the resource wrapper class which makes it compatible with the .NET Compact Framework.
  • Generation of the nested class ResourceNames defining all resource names as string constants. The nested class visibility is the same as visibility of its parent.

Generation of public resource class wrappers suits almost everybody, however, some folks still want have an ability to generate internal resource wrappers. Therefore the version 2.1 brings in the InternalResXFileCodeGeneratorEx VS.NET custom tool, generating strongly typed internal resource wrappers. The output of the InternalResXFileCodeGeneratorEx is shown in the example below:

/// <summary>
/// A strongly-typed resource class, for looking up localized strings, formatting them, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilderEx class via the
// InternalResXFileCodeGeneratorEx custom tool. To add or remove a member, edit your .ResX file
// then rerun the InternalResXFileCodeGeneratorEx custom tool or rebuild your VS.NET project.
// Copyright (c) Dmytro Kryvko 2006-2008 (http://dmytro.kryvko.googlepages.com/)
[GeneratedCodeAttribute("DMKSoftware.CodeGenerators.Tools.StronglyTypedResourceBuilderEx", "2.7.0.0")]
[DebuggerNonUserCodeAttribute()]
#if !SILVERLIGHT && !PocketPC && !Smartphone && !WindowsCE
  [ObfuscationAttribute(Exclude=true, ApplyToMembers=true)]
#endif
[SuppressMessageAttribute("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")]
internal partial class Resource {

private static ResourceManager _resourceManager;

private static object _internalSyncObject;

private static CultureInfo _resourceCulture;

[SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resource() {
}

/// <summary>
/// Thread safe lock object used by this class.
/// </summary>
internal static object InternalSyncObject {
get {
if (object.ReferenceEquals(_internalSyncObject, null)) {
Interlocked.CompareExchange(ref _internalSyncObject, new object(), null);
}
return _internalSyncObject;
}
}

/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
internal static ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(_resourceManager, null)) {
Monitor.Enter(InternalSyncObject);
try {
if (object.ReferenceEquals(_resourceManager, null)) {
Interlocked.Exchange(ref _resourceManager,
new ResourceManager("MyApp.Resource", typeof(Resource).Assembly));
}
}
finally {
Monitor.Exit(InternalSyncObject);
}
}
return _resourceManager;
}
}

/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
internal static CultureInfo Culture {
get {
return _resourceCulture;
}
set {
_resourceCulture = value;
}
}

/// <summary>
/// Looks up a localized string similar to 'Hello, {0}!'.
/// </summary>
internal static string Hello {
get {
return ResourceManager.GetString(ResourceNames.Hello, _resourceCulture);
}
}

/// <summary>
/// Looks up a localized string similar to 'Message text'.
/// </summary>
internal static string Message {
get {
return ResourceManager.GetString(ResourceNames.Message, _resourceCulture);
}
}

/// <summary>
/// Formats a localized string similar to 'Hello, {0}!'.
/// </summary>
/// <param name="arg0">An object (0) to format.</param>
/// <returns>A copy of format string in which the format
/// items have been replaced by the String equivalent of
/// the corresponding instances of Object in arguments.</returns>
internal static string HelloFormat(object arg0) {
return string.Format(_resourceCulture, Hello, arg0);
}

/// <summary>
/// Lists all the resource names as constant string fields.
/// </summary>
internal class ResourceNames {

/// <summary>
/// Stores the resource name 'Hello'.
/// </summary>
internal const string Hello = "Hello";

/// <summary>
/// Stores the resource name 'Message'.
/// </summary>
internal const string Message = "Message";
}
}

License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, distribute, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:


The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

THE AUTHORS ARE PRETTY CONFIDENT IN ITS SOFTWARE AND SERVICES, BUT IF IT HAPPENS TO ENTIRELY ACCIDENTALLY TURN YOUR MACHINE INTO A COLD FUSION REACTOR AND OPEN A GIANT HOLE IN SPACE THAT ENDANGERS THE WORLD AS WE KNOW IT, THEN THE AUTHORS CANNOT IN ANY WAY BE MADE RESPONSIBLE.

Downloads

v2.7 - April 17th, 2009

Changes:
The .NET Compact Framework compatibility has been restored. It's been previously broken by addition of the ObfuscationAttribute() attribute to the class level. Thanks to pfennigfuchser for reporting this one.

v2.6 - March 25th, 2009

Changes:
1. The Silverlight compatibility has been restored. It's been previously broken by addition of the ObfuscationAttribute() attribute to the class level. Thanks to Eric Smith and r2musings for reporting this issue.
2. J# support has been removed.

v2.5 - February 20th, 2009

Changes:
1. The ObfuscationAttribute() has been added to the generated resource wrapper class (thanks to Friedhelm).
2. The issue with missing XML documentation on the resource wrapper class constructor has been addressed (thanks to Casey Barton).

v2.4 - October 17th, 2008

Changes:

Visual Studio Express 2005 and 2008 editions support has been added (thanks to Fabien Letort and Ondrej Bohaciak).

v2.3 - October 7th, 2008

Changes:
1. The nested class ResourceNames listing all resource names as constants has been added.
2. Parameterless {PROPERTY_NAME}Format() method generation has been removed. There were some confusion about their presence, so apparently it wasn't a good idea, sorry.
3. Generated resource wrapper classes made partial (thanks to DameonBlack).
4. Silverlight compatibility has been added by making resource wrapper classes constructors public (thanks to Slyi).
5. The language issue in setup has been fixed (thanks to Jasoncd).
6. Resource wrapper generation performance has been improved.

v2.2 - May 24th, 2008

Changes:
1. The issue with the duplicate format methods generated under some circumstances has been fixed (thanks to Doug Richardson).
2. All types are referenced as members of the global space (thanks to Doug Richardson).
3. The missing comment on the InternalSyncObject property has been added (thanks to Jesse Napier).
4. The attributes suppressing code analysis warnings have been added (thanks to Jesse Napier).
5. The code refactoring has been performed.

v2.1 - February 18th, 2008

Changes:
1. The InternalResXFileCodeGeneratorEx custom tool has been added (thanks to Bernd Hoffmann).
2. Generated resource wrapper classes are not sealed anymore (thanks to Andrea from Italy and Miki from Germany :)).
3. The issue with the SuppressMessage attribute on the resource wrapper class constructor has been fixed.
4. The CompilerGenerated attribute is not used in resource wrapper classes for compatibility with the .NET Compact Framework (thanks to reklats).
5. Parameterless formatting methods have been added (thanks to Dave Apelt).
6. Improved generation of non-string resource fetching properties.

v2.0 - February 5th, 2008 (Second major release)

Changes:
1. The issue with non-string property comments fixed (thanks to Anthony Meehan and Matt Rice).
2. Resource manager initialization became thread safe (thanks to Borys Byk).
3. Added VS.NET 2008 compatibility.

v1.1 - April 22nd, 2006 (Improved format method generation according to Steve Hansen's suggestion)

v1.0 - April 17th, 2006 (Initial release)

Support

You can support development of ResXFileCodeGeneratorEx by reporting bugs, sending suggestions or by donating money to this project. It will help me to compensate the cost of Visual Studio .NET editions used for the ResXFileCodeGeneratorEx implementation :)