Reading and Writing Resources


With the Resource Administrator and Add Resource String utilities described, we can look at the foundation on which they are built. The starting point is the reading and writing of resources. Figure 10.12 shows the .NET Framework classes for reading and writing resx and resources files.

Figure 10.12. .NET Framework Classes for Reading and Writing Resources


From this diagram, you can see that there are no classes for reading and writing .txt or .restext files. The .NET Framework utilities (e.g., ResGen) that need to read and write .txt or .restext files do so by using StreamReader's ReadLine and WriteLine methods.

Although these classes are all part of the System.Resources namespace, they are not all part of the same assembly. The ResourceReader and ResourceWriter classes are part of mscorlib.dll and are, therefore, available to most applications by default. The ResXResourceReader and ResXResourceWriter classes, however, are part of System.Windows.Forms.dll, and you must add a reference to this assembly if you want to use these classes and this assembly is not referenced (in a console application, for example).


Reading Resources

The ResXResourceReader and ResourceReader classes work almost the same way (with a single difference, which I cover in the "ResX File References" section), so I use ResXResourceReader as an example. To read resources from a resx file, you construct a new ResXResourceReader, get an enumerator, and enumerate through all the DictionaryEntry items. The following code adds all the items to a ListBox called listBox1:

 using (ResXResourceReader reader =     new ResXResourceReader("Form1Resources.resx")) {     IEnumerator enumerator = reader.GetEnumerator();     while (enumerator.MoveNext()) {         DictionaryEntry entry = (DictionaryEntry) enumerator.Current;         listBox1.Items.Add(entry.Key.ToString() + ": " +             entry.Value.ToString());     } } 


The DictionaryEntry items have a key and value that correspond to the "Name" and "Value" columns you see in the Visual Studio Resource Editor.

An alternative to reading items from the source directly is to keep them in a cache in the form of a ResourceSet. To do this, you can supply the resource reader to the ResourceSet constructor (which accepts an IResourceReader parameter):

 using (ResourceSet resourceSet = new ResourceSet(     new ResXResourceReader("Form1Resources.resx"))) {     IEnumerator enumerator = resourceSet.GetEnumerator();     while (enumerator.MoveNext())     {         DictionaryEntry entry = (DictionaryEntry) enumerator.Current;         listBox1.Items.Add(entry.Key.ToString() + ": " +             entry.Value.ToString());     } } 


Note that the IResourceReader (i.e., ResXResourceReader, in this example) is not closed until the ResourceSet that uses it to read from is closed. This is why the ResourceManager.CreateFileBasedResourceManager method mentioned in Chapter 12 keeps .resource files locked while the application is running.

You might have noticed from the ResourceSet constructors that no public constructor enables you to create an empty ResourceSet:

 public ResourceSet(IResourceReader reader); public ResourceSet(Stream stream); public ResourceSet(string fileName); 


You can work around this inconvenience by implementing an IResourceReader that enumerates through an empty Hashtable:

 public class EmptyResourceReader: IResourceReader {     public IDictionaryEnumerator GetEnumerator()     {         return new Hashtable().GetEnumerator();     }     public void Close()     {     }     IEnumerator System.Collections.IEnumerable.GetEnumerator()     {         return this.GetEnumerator();     }     public void Dispose()     {     } } 


The following code creates an empty ResourceSet:

 ResourceSet resourceSet =     new ResourceSet(new EmptyResourceReader()); 


You might argue, however, that because ResourceSets do not provide any means of adding new entries, there is little benefit in creating an empty Resource-Set. This is true for the publicly documented interface, but the protected field Table does allow entries to be added and deleted.

Writing Resources

To write to a resource, you use the ResXResourceWriter or ResourceWriter classes. The following WriteResourceSet method accepts a filename and a ResourceSet, and writes the ResourceSet to the filename:

 private void WriteResourceSet(     string fileName, ResourceSet resourceSet) {     using (ResXResourceWriter writer =         new ResXResourceWriter(fileName))     {         foreach(DictionaryEntry dictionaryEntry in resourceSet)         {             writer.AddResource(                 dictionaryEntry.Key.ToString(),                 dictionaryEntry.Value);         }         writer.Generate();         writer.Close();     } } 


The IResourceWriter interface does not have an "append" mode, so even if you want to add a single entry to the resource, you must first add all the existing resources using AddResource and then add the new entry.

ResXDataNodes and Comments

If you have used the Visual Studio Resource Editor, you might be wondering how you can read and write comments. Recall that the Resource Editor enables you to store a comment against each entry in a resx file. Only resx files support comments (resources, .txt, and .restext files do not support comments), so this section is specific to resx files and the ResXResourceReader and ResXResourceWriter classes. The short answer to this apparent mismatch is that in the .NET Framework 1.1, ResX ResourceReader does not read the comments. The Visual Studio 2003 Resource Editor gets around this limitation by reading the resx file directly using XmlTextReader and writing it back using XmlTextWriter. If you are using the .NET Framework 1.1 and want to read and write comments, you have to take the same approach: Abandon the ResXResourceReader and ResXResourceWriter classes, and use the Xml TextReader and XmlTextWriter classes instead.

The long answer is that the .NET Framework 2.0 has a more sophisticated solution. The .NET Framework 2.0 ResXResourceReader class has a Boolean property called UseResXDataNodes. By default, UseResXDataNodes is false, so ResXResourceReader behaves just as it did in the .NET Framework 1.1. If you set ResXDataNodes to true, the value of each DictionaryEntry.Value read by the ResXResourceReader is a ResXDataNode object. Here is the ResXDataNode class:

 public sealed class ResXDataNode : ISerializable {       public ResXDataNode(string name, object value);       public ResXDataNode(string name, ResXfileref fileRef);       public Point GetNodePosition();       public object GetValue(AssemblyName[] names);       public object GetValue(ITypeResolutionService typeResolver);       public string GetValueTypeName(AssemblyName[] names);       public string GetValueTypeName(           ITypeResolutionService typeResolver);       public string Comment { get; set; }       public ResXfileref FileRef { get; }       public string Name { get; set; } } 


You can see that it has a Name property (which is the same as the Dictionary Entry.Key) and a Comment property, but no Value property. Instead, the value is retrieved using one of the GetValue methods. The following code is a modified version of our first attempt to read resources, with the difference being that this code reads ResXDataNodes and shows the comment in the ListBox:

 using (ResXResourceReader reader =     new ResXResourceReader("Form1Resources.resx")) {     reader.UseResXDataNodes = true;     IEnumerator enumerator = reader.GetEnumerator();     while (enumerator.MoveNext())     {         DictionaryEntry entry = (DictionaryEntry) enumerator.Current;         ResXDataNode dataNode = (ResXDataNode)entry.Value;         listBox1.Items.Add(entry.Key.ToString() + ": " +             dataNode.GetValue(             (ITypeResolutionService) null).ToString() +             ", " + dataNode.Comment);     } } 


After the ResXResourceReader is constructed, UseResXDataNode is set to true. The resource entries are enumerated as before, but the DictionaryEntry's Value is now a ResXDataNode. We get the value from the ResXDataNode using the following expression:

 dataNode.GetValue((ITypeResolutionService) null).ToString() 


The GetValue method has two overloads that tell the ResXDataNode how to resolve its value. We pass null as this value to indicate that no special steps need to be taken to resolve the value. Note, however, that we cannot simply pass null alone because this would match both GetValue overloads, so we cast null to an ITypeResolutionService interface to indicate which overload to use. Finally, the comment is now available simply as dataNode.Comment.

ResX File References

Both versions of the .NET Framework allow files such as bitmaps to be included in resources. These files are included through two methods: file embedding and file referencing. Both versions of the framework support both methods. The difference between the two methods lies solely in the packaging of the original resource source. When a file is embedded in a resource, the file's complete data stream is copied from the original file into the resx file, and the original file is no longer needed. When a file is referenced in a resource, the resource simply contains a link to the original file and both files are needed. The Visual Studio 2005 Resource Editor uses file references to add files to a resource. The Visual Studio 2003 Resource Editor has no direct support for adding files to a resource, so the method chosen depends on the approach that you take (see the "Adding Images and Files in Visual Studio 2003" section of Chapter 3, "An Introduction to Internationalization"). The following resource entry represents a bitmap that has been embedded in a .NET Framework 1.1 resx file:

 <data name="NationalFlag" type="System.Drawing.Bitmap, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" mimetype="application/x- microsoft.net.object.bytearray.base64">   <value>     iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4     c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAH     UwAADqYAAAOpgAABdwnLpRPAAAAMtJREFUOE+lks0NwjAMhR8zeSdPwaFrd     AFG4YDcK10CifbMwTwnVVvET1Jq6ZOjKHmxn3MQbR1dB0BIxLJu0zr4ESIU     gDqErDPXBimCENDWXDWzFvKKoIB6EJcjs5W5EhNWUCAJAE2+FHlqJfb9MRR     JLZwug9vNU9YwdRI1pQ8FcgVrUiVsi0J+bIosFZi7kVmQIjUxm7iMMPshFS     OMMaPv+9eHwrgNgTSmytc+nYO1NIsf6V9wv56Ls/72H8Zx9P0tvJm4wcA4u     ruCJ1wJmJQKMPjaAAAAAElFTkSuQmCC   </value> </data> 


The following resource entry represents a bitmap that has been referenced in a .NET Framework 2.0 resx file:

 <data name="NationalFlag"     type="System.Resources.ResXfileref, System.Windows.Forms">   <value>Resources\NationalFlag.gif;System.Drawing.Bitmap,     System.Drawing, Version=2.0.0.0, Culture=neutral,     PublicKeyToken=b03f5f7f11d50a3a   </value> </data> 


Notice that the type is no longer a System.Drawing.Bitmap, but a System.Resources.ResXfileref. In the value element, you can see the file reference (Resources\NationalFlag.gif) and also the type of the contents of the file (System.Drawing.Bitmap). From the point of view of the resource assembly that gets compiled from these resources, there is no difference between resources that are referenced and those that are embedded because the file references are resolved before the assembly is generated, and the resulting assembly contains the complete "embedded" definition of the resource (so the referenced files do not need to be deployed and are not required at runtime). From the point of view of version control, the benefit of using file references is that you get better granularity of resource versioning. From the point of view of code that processes resx files, the distinction between referenced and embedded is important. Recall our first code snippet, which read a resx file and added the contents of each DictionaryEntry into a ListBox. This code will most likely fail if it processes a resx file with file references. The ArgumentException that occurs reports the problem:

 ResX file Could not find a part of the path 'C:\Books\I18N\Tests\VS2005\WindowsApplication1\bin\Debug\ Resources\NationalFlag.gif'. Line 329, position 5. cannot be parsed. 


Recall that the file reference is to "Resources\NationalFlag.gif"i.e., a relative reference, not an absolute reference. The path is relative to the executing assembly, so it looks in the WindowsApplication1\bin\Debug\Resources folder and not the WindowsApplication1\Resources folder, where the files are. If you are using the .NET Framework 1.1, the simplest solution is to use absolute file references. If you are using the .NET Framework 2.0, you can tell the ResXResourceReader where to find the resource files using the ResXResourceReader.BasePath property:

 ResXResourceReader reader =     new ResXResourceReader("Form1Resources.resx"); reader.BasePath = @"..\.."; 


Alternatively, a more generic solution to the same problem is to simply set the BasePath to the same folder as the resx file that the ResXResourceReader is processing:

 string fileName = @"C:\Apps\WindowsApplication1\Form1Resources.resx"; ResXResourceReader reader = new ResXResourceReader(fileName); reader.BasePath = Path.GetDirectoryName(fileName); 


The new support for file references that was added in the Visual Studio 2005 Resource Editor has an implication for code that processes resx files. As we have already seen, the code that reads entries using a ResXResourceReader works well in both the .NET Framework 1.1 and 2.0, as long as resources are embedded or absolute file paths are used. The Visual Studio 2005 Resource Editor, however, defaults to file references (using relative paths), so code that processes resx files that worked under the .NET Framework 1.1 will mostly likely fail when processing the .NET Framework 2.0 resx files (because the file references are relative) and must be updated to set the ResXResourceReader's BasePath property.




.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