Strongly-Typed Resources in the .NET Framework 2.0


Recall the code that retrieves the resource string:

 MessageBox.Show(resourceManager.GetString("InsufficientFunds")); 


This line is fragile. It relies upon a string, "InsufficientFunds", to identify the resource string. If this string includes a typo, the code will still compile successfully, but it will throw an exception at runtime. The problem is that this string cannot be verified at compile time, so it is a fragile solution. A better solution is one that the compiler can verify. To solve this problem Visual Studio 2005 introduces Strongly-Typed Resources. A strongly-typed resource is to resources what a strongly-typed dataset is to DataSets; it is a generated class that includes the resource key names as properties. The line of code can be rewritten to use a strongly-typed resource:

 MessageBox.Show(Form1Resources.InsufficientFunds); 


Form1Resources is a strongly-typed resource class in which each resource entry is represented by a property (i.e., "InsufficientFunds", in this example). The Form1.resourceManager field is no longer needed and can be removed completely. When a resource file is created in Visual Studio 2005, a corresponding file with the extension ".Designer.cs" is also created, so Form1Resources.resx has a corresponding file called Form1Resources.Designer.cs. You can see this in Solution Explorer by expanding the resx node. As you add, edit, or delete entries in the resx file, the designer file is updated. If you double-click the file, you will see the generated class. Here is the Form1Resources class with the comments stripped out (and formatted to fit this page):

 [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices. CompilerGeneratedAttribute()] internal class Form1Resources {     private static global::System.Resources.ResourceManager         resourceMan;     private static global::System.Globalization.CultureInfo         resourceCulture;     [global::System.Diagnostics.CodeAnalysis.         SuppressMessageAttribute("Microsoft.Performance",         "CA1811:AvoidUncalledPrivateCode")]     internal Form1Resources() {     }     [global::System.ComponentModel.EditorBrowsableAttribute(         global::System.ComponentModel.EditorBrowsableState.Advanced)]     internal static global::System.Resources.ResourceManager         ResourceManager {         get {             if (object.ReferenceEquals(resourceMan, null)) {                 global::System.Resources.ResourceManager temp =                     new global::System.Resources.ResourceManager(                     "WindowsApplication1.Form1Resources",                     typeof(Form1Resources).Assembly);                 resourceMan = temp;             }             return resourceMan;         }     }     [global::System.ComponentModel.EditorBrowsableAttribute(         global::System.ComponentModel.EditorBrowsableState.Advanced)]     internal static global::System.Globalization.CultureInfo         Culture {         get {             return resourceCulture;         }         set {             resourceCulture = value;         }     }     internal static string InsufficientFunds {         get {             System.Resources.ResourceManager rm = ResourceManager;             return rm.GetString(                 "InsufficientFunds", resourceCulture);         }     }     internal static System.Drawing.Bitmap NationalFlag {         get {             System.Resources.ResourceManager rm = ResourceManager;             return ((System.Drawing.Bitmap)                 (rm.GetObject("NationalFlag", resourceCulture)));         }     } } 


The class encapsulates its own ResourceManager object in a private static field called resourceMan. resourceMan is wrapped in a static ResourceManager property, which initializes resourceMan to this:

 new System.Resources.ResourceManager(   "WindowsApplication1.Form1Resources",   typeof(Form1Resources).Assembly); 


Not surprisingly, this is very similar to the line that we wrote earlier to initialize our resourceManager private field. For each entry in the resx file, a static property is created to return the resource's value. You can see in the InsufficientFunds property that it calls ResourceManager.GetString and passes the "InsufficientFunds" key. The resourceCulture private static field is initially null (therefore, ResourceManager uses Thread.CurrentThread.CurrentUICulture). You can set its equivalent static property, Culture, to specify that it should retrieve resources for a different culture. Although this is rare, you might, for example, want to display more than one culture at the same time.

In the "Localizable Strings" section of this chapter, I mentioned that the .NET Framework 2.0 help includes a set of resource key naming guidelines. If you follow these guidelines, you will encounter an apparent mismatch between this advice and the designer's warnings. One of the guidelines recommends that resource keys with a recognizable hierarchy should use names that represent that hierarchy in which the different elements of the hierarchy are separated by periods. For example, menu item resource keys might be named Menu.File.New and Menu.File.Open. This is good advice, but Visual Studio 2005 reports the warning "The resource name 'Menu.File.New' is not a valid identifier". This is a consequence of the strongly-typed resource class that is generated from the resource. It is not possible to have a property called "Menu.File.New" because the period is an invalid character for an identifier. Instead, the periods are replaced with underscores, and the property is called "Menu_File_New". Despite this, I recommend that you continue to follow the resource key naming guidelines and ignore the warnings that result from the use of the period in resource key names.

If you prefer not to use strongly-typed resources but are concerned that the strings passed to ResourceManager.GetString might or might not be valid, look at the "Resource string missing from fallback assembly" rule in Chapter 13.

ResGen

Visual Studio 2005's solution of automatically maintaining strongly-typed resources is very convenient and will be sufficient for many developers. However, if it does-n't meet your requirements because, say, you generate or maintain your own resources using a utility outside Visual Studio 2005, you need the resgen.exe command-line utility. You've seen that resgen.exe can generate binary resource files from resx XML resource files. It can also generate strongly-typed resources using the /str switch. The following command line uses the /str:C# switch to indicate that the generated file should be written in C#:

 resgen Form1Resources.resx /str:C# 


The output is:

 Read in 2 resources from "Form1Resources.resx" Writing resource file...  Done. Creating strongly typed resource class "Form1Resources"... Done. 


This example creates Form1Resources.cs, which isn't the same as the Visual Studiogenerated file. The syntax of the str switch is:

 /str:<language>[,<namespace>[,<class name>[,<file name>]]]] 


To get the same output with the same filename as Visual Studio, use the following command line:

 resgen Form1Resources.resx /str:C#, WindowsApplication1,Form1Resources,Form1Resources.Designer.cs 


You can ignore the "RG0000" warning that resgen emits; the resgen-generated code is identical to the code generated by Visual Studio 2005. Another resgen command-line parameter of interest is publicClass. This parameter causes the generated class to be public instead of internal so that it can be accessed by a different assembly.

StronglyTypedResourceBuilder

Both Visual Studio 2005 and resgen.exe use the System.Resources.Tools. StronglyTypedResourceBuilder class to generate strongly-typed resources. This documented .NET Framework 2.0 class is at your disposal in case you need to generate strongly-typed resources when the two existing utilities don't meet your requirements. Two such possibilities are encountered in Chapter 12 and are solved using StronglyTypedResourceBuilder:

  • Visual Studio accepts only resx files as input, and resgen accepts only resx, resources, restext, and txt files as input. If you maintain resources in another format, such as a database, you cannot generate strongly-typed resources.

  • The generated code uses the System.Resources.ResourceManager class to get resources. If you maintain resources in another format, such as a database, the generated class will be using the wrong resource manager class to load the resources.

The StronglyTypedResourceBuilder.Create method has four overloads, two of which accept a resx filename and two of which accept an IDictionary of resources to generate code for. The strategy for using a StronglyTypedResourceBuilder directly is to load your resources into an object that supports the IDictionary interface and pass this to the StronglyTypedResourceBuilder.Create method. The Create method returns a CodeDomCompileUnit object, which is the complete Code-Dom graph for the generated code. You would pass this to the CodeDomProvider. GenerateCodeFromCompileUnit method to generate the equivalent code and write it to a StreamWriter. Chapter 12 has a complete example.

In the .NET Framework 1.1, the GenerateCodeFromCompileUnit method is not available directly from the CodeDomProvider. Instead, create an ICodeProvider using CodeDomProvider.CreateGenerator and call the same method with the same parameters from the resulting ICodeProvider.





.NET Internationalization(c) The Developer's Guide to Building Global Windows and Web Applications
.NET Internationalization: The Developers Guide to Building Global Windows and Web Applications
ISBN: 0321341384
EAN: 2147483647
Year: 2006
Pages: 213

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net