Resources


A resource is any content that is subject to change based on culture. Resources are bundled into individual per-culture files and then retrieved by the application at runtime using the resource APIs. The resource subsystem takes care of locating, loading, and retrieving this information as requested, using culture information to ensure the correct version is used. This requires that you as the application developer separate out any localizable information into resource bundles and create specific bundles for the various cultures you wish to support.

Most resource content takes the form of key-value pairs, where the key is some unique identifier and the value is some text or binary content associated with that key. An application then looks up this information using the identifier, which is redirected to the appropriate localized version of the resource bundle based on the user's culture. This process is managed by the resources subsystem, using a fallback mechanism to ensure that — where locale-specific content isn't available (either completely or partially) — some reasonable default is used instead. This data might be destined for a UI label, a message box, exception text, and so on.

Creating Resources

Depending on the format of the data to be embedded, there are slightly different processes you'll go through to generate resource files. There are two primary types of localizable data you will be dealing with: text and opaque files (e.g., bitmaps). Each uses the convention of using a unique identifier for each localizable piece of content. The Framework SDK contains a tool called ResGen.exe, which recognizes these two formats and compiles them into the appropriate .resources file format for deployment. You can, of course, venture outside of the bounds of these tools (e.g., with arbitrary culture-specific assemblies, containing various bits and bytes of data, including executable code), but we will limit discussion here to the vanilla resource subsystem in this section.

Text Resources

Localized strings are stored under and referenced by the program using a unique string-based key. You can easily create a resource file containing purely textual key-value pairs in your favorite text editor. ResGen.exe can parse simple <key> = <value> pairs, and recognizes \n and \t characters for new-line and tabs, respectively. Furthermore, you may add comments using a semicolon (;) or hash (#):

 # Sample resources file # This entire region is a comment SomeRandomKey = Hello, there ErrorMessage = Oops! An error occurred.\n\nPlease send email reporting it. 

Compiling this into a binary .resources file for distribution can be accomplished via the ResGen.exe tool; for example, pretend that the above was in a file called MyResources.txt:

 ResGen.exe MyResources.txt MyResources.resources 

The result is a MyResources.resources file that can then be embedded into an assembly. We discuss precisely how to embed the file into an assembly later.

Binary Resources

Obviously, creating resources by hand (as specified above) won't work for binary resources. The ResGen.exe utility recognizes a separate format, .resx, which stores key-value pairs in Extensible Markup Language (XML). This file format supports embedded encoded binary content. This is easy to do using the Visual Studio Resource Editor. Alternatively, you might consider using the System.Resources.ResXResourceWriter class located (of all places) in the System.Windows.Forms.dll assembly. This permits you to programmatically add a binary file to your resource.

For instance, I wrote this small command-line utility (AddResX.exe) to make adding binary resources to existing .resx files easier:

 using System; using System.Collections; using System.IO; using System.Resources; class Program {     static void Main(string[] args)     {         string resXFile = args[0];         string resKey = args[1];         string resValueFile = args[2];         using (ResXResourceWriter writer = new ResXResourceWriter(resXFile))         {             Console.WriteLine("Associating {0} with {1}'s contents",                 resKey, resValueFile);             Console.Write("To {0}...", resXFile);             // Clone the existing content:             using (ResXResourceReader reader =                 new ResXResourceReader(resXFile))             {                 foreach (DictionaryEntry node in reader)                     writer.AddResource((string)node.Key, node.Value);             }             // And now just add the new key:             writer.AddResource(resKey, File.ReadAllBytes(resValueFile));         }         Console.WriteLine("done.");     } } 

Now let's see how to add a bitmap to the existing resources defined above. We can easily convert the above MyResources.txt into a .resx file:

 ResGen.exe MyResources.txt MyResources.resx 

This creates an XML file containing our strings, that is:

 <?xml version="1.0" encoding=" utf-8"?> <root> <!-- Whole lot of XML Scheme stuff omitted for brevity -->   <data name="SomeRandomKey">     <value xml:space="preserve">Hello, there</value>   </data>   <data name="ErrorMessage">     <value xml:space="preserve">Oops! An error occurred. Please send email reporting it.</value>   </data> </root> 

And then we can add a new key with attached binary data, using the tool shown above:

 AddResX.exe MyResources.resx LogoBitmap myLogo.bmp 

The resulting .resx file now contains myLogo.bmp's contents base-64 encoded under the key "LogoBitmap."

Packaging and Deployment

Once you've generated resource files, you must package them in such a way that the .NET Framework can find them at runtime. The most common model is called satellite assemblies, which are by definition assemblies that contain only resource information for a single culture. Your application must store these files in a well-known directory structure, based on culture. We discussed in Chapter 4 the process of locating assemblies with culture information. Namely that the runtime will first search the GAC for a matching assembly (assuming that you are using strong names), and then look in a directory under the application's base matching the culture.

For example, if your application had English, German, and Japanese resources, you would have a directory structure as depicted in Figure 8-3.

image from book
Figure 8-3: Example satellite assembly structure.

You will notice three .NET assemblies in this diagram. YourApp.exe is the primary application, into which App.resources have been linked (we'll see how shortly). App.resources is the default (in this case, English) resources file that cultures w/out specific support will use. In the de and ja directories are the satellite assemblies for the German and Japanese languages. App.de.resources is the translated resource content in German, and App.ja.resources is the same for Japanese. These are used to generate the YourApp.xx.dll assemblies, which contain nothing but correct culture marking in the assembly manifest and the resources information. The .resources files are not required for deployment purposes (assuming that you've embedded rather than linked them into the satellite assemblies).

Linking your primary App.resources file with YourApp.exe can be accomplished using your language compiler. For example, with csc.exe you simply pass the /resource switch, specifying the file (App.resources) to embed into your primary assembly. To generate a satellite assembly, on the other hand, you can either use a language compiler or the al.exe utility. For example, to generate the YourApp.de.dll file, you would simply run the following in your de directory:

 al.exe /t:lib /culture:de /embed:App.de.resources /out:YourApp.de.dll 

Of course, you can go strongly name your satellite assemblies too using the various command-line switches that al.exe offers.

Accessing Resources

Once you've packaged your resources as specified above, you will need to access resource information from within your program. There are two general models supported by the Framework: strongly and weakly typed. The former is preferable and has been added in 2.0. You might have to use the latter when maintaining legacy code or if you're doing development on 1.x.

Strongly Typed Resources

Strongly typed resources are actually implemented as a code generation portion of the ResGen.exe SDK utility. When compiling your text or .resx resources into a .resources file, you can supply the /str:<language> switch — short for strongly typed — and the utility responds by generating a source file in the language you specified. Supported languages include C#, VB, and C++, among others. This file can then be compiled and used in your program. By default, it generates internal classes; passing the /publicClass switch causes it to generate public classes.

The resulting class has a set of static properties — one for each resource key — which can then be accessed in your program. It encapsulates all of the logic to locate satellite assemblies where appropriate (or fallback to your default resources), so that you don't have to worry about it. For example, we might run the .resx file shown at the beginning of this section as follows:

 ResGen.exe /str:C# MyResources.resx 

The resulting MyResources.cs contains a class roughly equivalent to:

 using System.Globalization; using System.Resources; internal class MyResources {     internal static ResourceManager ResourceManager { get; }     internal static CultureInfo Culture { get; set; }     internal static string ErrorMessage { get; }     internal static string SomeRandomKey { get; }     internal static byte[] LogoBitmap { get; } } 

Of course, you can then just call the properties without worry for where specifically the information is coming from. Under the covers, this is using the same resource infrastructure I previously labeled as "weakly typed."

Weakly Typed Resources

Because weakly typed resources are no longer as widely used, we won't dwell on them in great detail. Most of the information you need is located in the auto-generated resources class file.

Everything starts with a System.Resources.ResourceManager. You must supply the base name of the resources file when you instantiate one, along with the primary assembly that will serve as the basis for searching for satellites (and also for fallback in the case that an appropriate satellite was not found). For example, in the application we depicted in Figure 8-3, we might construct one as follows:

 ResourceManager rm = new ResourceManager("App",     Assembly.GetExecutingAssembly()); 

We then look up particular resources using one of the GetXxx methods. GetString accepts a resource key, and returns that resource as a string. GetObject similarly accepts a key and can be used to retrieve serialized byte[] data. The obvious drawback when compared to strongly typed resources is that passing text-based keys to weakly typed resources is prone to typos, leading to spurious NullReferenceExceptions at runtime.




Professional. NET Framework 2.0
Professional .NET Framework 2.0 (Programmer to Programmer)
ISBN: 0764571354
EAN: 2147483647
Year: N/A
Pages: 116
Authors: Joe Duffy

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