vbbox.plan

It is by will alone I write this stuff.

 

.NET Resource Management: StringTable

The CLR's resource embedding support is very cool. You can embed just about anything in an assembly — the most common use of this feature (in my code at least) is to load XML files with some sort of information in them to be used by the application at runtime. HTML files, configuration parameters, name tables to be used with a class factory or just about anything you need can be embedded in your assemblies and easily loaded. Not to say you couldn't do this before with standard .res files and a few APIs (or in the case of VB6, a LoadResData or LoadResString call), but .NET makes it a lot easier. In the case of arbitrary data contained in files, VS makes it easy to do the embedding part — just add a file to your solution and set its Build Action property to "Embedded Resource". And as far as resource files (.resx) go, they are in fact XML files that can be easily manipulated with either the grid editor built into Visual Studio or an external tool.

Note that there's an important difference between an embedded resource that is an arbitrary file compiled into the assembly, and a .NET Resource file (.resx). In the first case, the content is essentially opaque to the CLR and you need to treat it as a stream. Resource files on the other hand can contain a range of different objects, such as strings and bitmaps, and are managed through the classes found under the System.Resources namespace.

The trick of course is to figure out how to load and work with these things. For starters, here's how you load an XML file embedded (i.e., something that is not a resx file) in your assembly called "XMLFile1.xml", in C#:

using System;
using System.IO;
using System.Reflection;
using System.Xml;

class ConsoleApplication
{
  static void Main(string[] args) {
    Assembly assembly = Assembly.GetExecutingAssembly();
    Stream s = assembly.GetManifestResourceStream("MyApplication.XMLFile1.xml");
    XmlDocument xdoc = new XmlDocument();
    StreamReader reader = new StreamReader(s);
    xdoc.LoadXml(reader.ReadToEnd());
    reader.Close();
  }
}

And VB.NET:

Imports System.Xml
Imports System.Reflection
Imports System.IO

Module Main

  Sub Main()
    Dim asm As Assembly = Assembly.GetExecutingAssembly()
    Dim s As Stream = asm.GetManifestResourceStream("XMLFile1.xml")
    Dim xdoc As New XmlDocument
    Dim reader As New StreamReader(s)
    xdoc.LoadXml(reader.ReadToEnd())
    reader.Close()
  End Sub

End Module

Notice anything different? In the C# version we qualify the resource identifier with the assembly name, while in the VB.NET sample we don't need to do that. Why? Who knows. Probably VB.NET just trying to be a bit more friendly.

And what about other types of embedded data, such as images? Well, you can either embed them directly in the assembly, or have them be compiled into a resx file, which is then also embedded into the assembly. In the first case you still have to treat them as streams; if the image is serialized into a .resx file then you can use the ResourceManager to retrieve them as objects. For example, let's assume that you added a GIF file to the solution directly. This is how you load and display it in a picture box control on a form:

Stream stm = Assembly.GetExecutingAssembly().GetManifestResourceStream("MyApplication.myimage.gif");
Image img = Image.FromStream(stm);
stm.Close();
this.pictureBox1.Image = img;

Again, note that in order for this to work you must set the file's Build Action property to "Embedded Resource". VS.NET's default is to mark it as "Content", which is used with installers (the file is marked as being part of the "Output Group") but does not compile the file's content into the assembly.

That's as far as embedded arbitrary data. What about formal resources, especially string tables? String tables are important because they allow the developer to separate content from code to a certain extent. For example, a common use of string tables is (and was) to create lists of keyed error messages that can be loaded as needed. The text of the messages can change over time, but if they're in a string table you don't have to change your code. Below is a ready-made class that encapsulates the CLR's ResourceManager class, which is what one normally uses to interact with resources.

using System;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;

namespace System.Resources 
{
  [ReflectionPermission(SecurityAction.Demand, Unrestricted = true)]
  public class StringTable 
  {
    private string _stringTable = "";
    private ResourceManager _rm = null;

    public StringTable(string table) : 
      this(table, true, Assembly.GetCallingAssembly()) {/* */}

    public StringTable(string table, bool ignoreCase) : 
      this(table, ignoreCase, Assembly.GetCallingAssembly()) {/* */}

    public StringTable(string table, Assembly assembly) : 
      this(table, true, assembly) {/* */}

    public StringTable(string table, bool ignoreCase, Assembly assembly) {
      _stringTable = table;

      Assembly sourceAssembly = assembly;
      if (sourceAssembly == null)
        sourceAssembly = Assembly.GetExecutingAssembly();
        
      _rm = new ResourceManager(_stringTable, sourceAssembly);
      _rm.IgnoreCase = ignoreCase;
    }

    public virtual string SourceTable {
      get { return _stringTable; }
    }

    public virtual string this[string s] {
      get { return this.Load(s); }
    }

    // Returns null if the string is not found. 
    protected virtual string Load(string identifier) {
      return _rm.GetString(identifier);
    }

    protected System.Resources.ResourceManager ResourceManager {
      get { return _rm; }
    }
  }
}

If you use this, make sure you maintain state by declaring an instance of the class for every string table you want to attach to, because you'll want to avoid re-creating and re-binding the ResourceManager every single time you read a string. One way is to simply expose them as static members of an internal struct:

...
namespace Application.Resources
{
  internal struct StringTables
  {
    public static StringTable Errors = new StringTable("MyApplication.Errors");
    public static StringTable Messages = new StringTable("MyApplication.Messages");
    ...
  }
}

Note that, as in the first two examples that load embedded resources as streams, you'll need to adjust the name you pass to the StringTable constructor. The general rule is to use the resource file name without the extension (if the file is called "Errors.resx" you use "Errors"), and prepend the assembly name if you're using C# or leave it as is if you're using VB.NET.

Another note about the StringTable class — as written, it's designed to be compiled into its own assembly. If you're going to use it by simply adding it to your project, you can get rid of the declarative security attribute applied at the class scope as well as the constructors that accept an Assembly reference, or modify them so that they simply use Assembly.GetExecutingAssembly().

Other than that you also have the option of making the key lookup case-sensitive; the default is to just ignore case. You could also create your own custom implementation:

class MyStringTable : StringTable 
{
  public MyStringTable() : 
    base("ConsoleTesting.Application", Assembly.GetExecutingAssembly()){
  }
 
  public override string this[string s] {
    get {
      return base[s].ToUpper();
    }
  }
}

Or something like that.

 

 

 

 

 

 

 

 

© 2003-2004 Klaus H. Probst BLOGGER