Customer-extensible configuration

While playing around with custom resources I wondered what ways there are to configure which IResourceManager is used for those generated classes.

As I mentioned before I’m not particularly fond of XML for configuration purposes. But the *.config files are still the most commonly used means to configure a .NET application.

My goal was to allow a developer to configure which of a set of predefined resource managers to use (an obvious choice would be the file based approach with the .NET ResourceManager and maybe a database based implementation) while allowing him to add his own implementations later on. That kind of extensibility calls for an abstract factory approach.

The config file should look something like this

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="i18n" type="Playground.I18nSettingsSection, Playground" />
  </configSections>
  <connectionStrings>
    <clear />
    <add name="DEFAULT" connectionString="localhost"/>
  </connectionStrings>
  <i18n>
    <resxManager>
      <db connectionStringName="DEFAULT" />
    </resxManager>
  </i18n>
</configuration>

File 1: What I wanted my App.config to look like

The code behind that solution should be quite simple. Along the lines of:

public class I18nSection : ConfigurationSection
{
  [ConfigurationProperty("resxManager")]
  public ResourceManagerSettings ResourceManager
  {
    get { return (ResourceManagerSettings) base["resxManager"]; }

    set { base["resxManager"] = value; }
  }
}

public abstract class ResourceManagerSettings : ConfigurationElement
{
  public abstract IResourceManager GetResourceManager(Type resourceFileType);
}

File 2: What I thought might be a good idea for the code behind

When was anything ever that easy? The whole thing blew up in my face. The underlying problem being that the .NET configuration system cannot create an instance of an abstract class (the ResourceManagerSettings) and you can neither get access to the code where the instantiation happens (it’s a private method somewhere inside ConfigurationElement) nor can you handle it via overriding OnDeserializeUnrecognizedElement in your section class. The element is not truly unrecognized if you decorate the ResourceManager property with the ConfigurationPropertyAttribute so the method would never be triggered. You can remove the attribute but then where do you store the created object? You can’t call base["resxManager"] anymore because there is no longer a ConfigurationProperty with that name. And what if you had more than one “unrecognized element” in that section? You couldn’t assume that you where to create a concrete implementation of your abstract ResourceManagerSettings which would make figuring out which class to instantiate quite a bit harder (remember that you want the developer to be able to extend that solution later).

So I had to figure out another approach. What eventually worked was moving the whole concept up one level in the hierarchy of the config system. Where I formerly used a ConfigurationSection I had to use a ConfigurationSectionGroup as base class. And the ConfigurationElement became a ConfigurationSection.

What I ended up with was an App.config that looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="i18n" type="Playground.I18nSettingsSectionGroup, Playground">
      <!--<section name="file" type="Playground.FileResourceManagerSettings, Playground"/>-->
      <!--<section name="null" type="Playground.NullResourceManagerSettings, Playground"/>-->
      <section name="db" type="Playground.DbResourceManagerSettings, Playground"/>
    </sectionGroup>
  </configSections>
  <connectionStrings>
    <clear />
    <add name="DEFAULT" connectionString="localhost"/>
  </connectionStrings>
  <i18n>
    <db connectionStringName="DEFAULT" />
  </i18n>
</configuration>

File 3: Actual App.config

And the code behind to match the .config file:

public class I18nSettingsSectionGroup : ConfigurationSectionGroup
{
  public const string NAME = "i18n";

  private ResourceManagerSettings _ResourceManagerSettings;

  public ResourceManagerSettings ResourceManager
  {
    get
    {
      if (this._ResourceManagerSettings != null)
      {
        return this._ResourceManagerSettings;
      }

      if ((this._ResourceManagerSettings = this.Sections.OfType<ResourceManagerSettings>().SingleOrDefault()) == null)
      {
        this._ResourceManagerSettings = new FileResourceManagerSettings();
        this.Sections.Add(FileResourceManagerSettings.NAME, this._ResourceManagerSettings);
      }

      return this._ResourceManagerSettings;
    }

    set
    {
      ResourceManagerSettings resourceManagerSettings = this.Sections.OfType<ResourceManagerSettings>().SingleOrDefault();

      if (resourceManagerSettings != null && !Equals(resourceManagerSettings, value))
      {
        this.Sections.Remove(resourceManagerSettings.SectionInformation.SectionName);
      }

      this.Sections.Add(value.SectionInformation.Name, value);
    }
  }
}

public abstract class ResourceManagerSettings : ConfigurationSection
{
  public abstract IResourceManager GetResourceManager(Type resourceFileType);
}

public class FileResourceManagerSettings : ResourceManagerSettings
{
  public const string NAME = "file";

  public override IResourceManager GetResourceManager(Type resourceFileType)
  {
    return new ResourceManagerWrapper(new ResourceManager(resourceFileType.FullName, resourceFileType.Assembly));
  }
}

File 4: Actual implementation

In the <configSections> part of the .config file I configure the I18nSectionGroup with at most one (!) implementation of the ResourceManagerSettings. If you add multiple implementations the .NET configuration system will instantiate all of them when you read from the file. And now you would have to figure out which one you actually wanted to use. So (by design!) the code above will fail in case you configured more than one ResourceManagerSetting.

If you don’t configure anything the I18nSectionGroup will fall back to the FileResourceManagerSettings. I believe that’s an acceptable default.

When you set the I18nSectionGroup.ResourceManager property at run-time and save the configuration back to disk the correct section type will be persisted in the <configSections> part of your .config file.

So what I got is not exactly what I wanted. If the .NET framework weren’t as uptight about the object creation (the configuration system is by no means the only part of the framework that behaves like that!) the whole exercise would have been a lot easier. To MS’ defense: From the comments in the code of the ConfigurationElement they seemed to have some security considerations going on. Still, they might (at least) have provided a way to influence what type of object should be created if they couldn’t/didn’t want to let you handle the instantiation itself.

Anyway. You now have a means to configure where your resources are loaded from and if you want to add another source (like RavenDB for example) you can always do so with little effort. If you either mark your resource classes with an interface or a custom Attribute or decide on a naming convention it is really easy to brew up a little reflection code that sets the static ResourceManager property at application start-up.

Advertisement

Item templates and custom resources

In a previous post I wrote about Custom resources with T4 templates. Back then I used the ReSharper multi-file templates to add the .resx and .tt files to my projects. The one downside of that approach: it requires R# 8.x which might not be readily available.

Thus I wanted to find out how to create a Visual Studio item template that achieves the same goal. Microsoft has a step-by-step guide that explains the process. That gives you the basics. But there is some fine-tuning that they don’t explain.

If you add a .resx file to your project the generated .Designer.cs file is hidden in the solution explorer. I wanted to do the same and hide the .tt file underneath the .resx file. Preferably I wanted both the .tt and the generated .Designer.cs file on the level directly below the .resx file (as shown in the first screenshot). But the TextTemplatingFileGenerator always puts the generated output file on the level below the .tt file (as in the second screenshot) on every run. I decided to stop battling VS. Its not worth the effort in this case.

Screenshot 1: Nice to have

Screenshot 1: Nice to have

Screenshot 2: What you actually get

Screenshot 2: What you actually get

To actually hide the .tt file via the item template is quite easy if not really prominently advertised. There’s a post on stackoverflow that explains how you need to modify your template.

The second <ProjectItem> is the interesting part. You need to prefix the .tt file with the path to the .resx file. When the item template is invoked that will translate to a <DependentUpon> clause in your project file.

<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
  <TemplateData>
    <DefaultName>Resource.resx</DefaultName>
    <Name>Customizable resources</Name>
    <Description>Creates a .resx file that uses a T4 template to generate strongly typed resources.</Description>
    <ProjectType>CSharp</ProjectType>
    <SortOrder>10</SortOrder>
    <Icon>__TemplateIcon.ico</Icon>
  </TemplateData>
  <TemplateContent>
    <References>
      <Reference>
        <Assembly>System</Assembly>
      </Reference>
      <Reference>
        <Assembly>mscorlib</Assembly>
      </Reference>
    </References>
    <ProjectItem SubType="" TargetFileName="$fileinputname$.resx" ReplaceParameters="true">Resource.resx</ProjectItem>
    <ProjectItem SubType="" TargetFileName="$fileinputname$.resx\$fileinputname$.tt" ReplaceParameters="true">Resource.tt</ProjectItem>
  </TemplateContent>
</VSTemplate>

File 1: MyTemplate.vstemplate

Also make sure that you set ReplaceParameters="true" for the .resx file. You will want to add template parameters for the namespace and the class name.

For that I used two of the predefined parameters: $rootnamespace$ and $safeitemname$. Note that the first one gives you the full path to your .resx file and not the root namespace of the current project! If you place the resources in project Foo in the folder Assets the value of $rootnamespace$ will thus be Foo.Assets. Maybe someone at MS thought that’s a funny way to lead developers on a wild goose chase …

And that’s what the .tt file looks like

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.34003
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".Designer.cs" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Xml.Linq" #>

namespace $rootnamespace$
{
    using System;
    using System.ComponentModel;
    using System.Globalization;
    using System.Resources;
    using System.Threading;
    using MyI18n;

    public class $safeitemname$
    {
        private static IResourceManager resourceManager;
        private static CultureInfo resourceCulture;

        [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
        public static IResourceManager ResourceManager
        {
            get
            {
                if(resourceManager == null)
                {
                    IResourceManager temp = new ResourceManagerWrapper(new ResourceManager("$rootnamespace$.$safeitemname$", typeof($safeitemname$).Assembly));
                    resourceManager = temp;
                }

                return resourceManager;
            }

            set
            {
                resourceManager = value;
            }
        }

        [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
        public static CultureInfo Culture
        {
            get
            {
                return resourceCulture;
            }

            set
            {
                resourceCulture = value;
            }
        }
<#
    string resxFileName = this.Host.TemplateFile.Replace(".tt", ".resx");
    XDocument doc = XDocument.Load(resxFileName);

    if(doc != null && doc.Root != null)
    {
        foreach(XElement x in doc.Root.Descendants("data"))
        {
            string name = x.Attribute("name").Value;
            WriteLine(string.Empty);
            WriteLine("        public static string " + name);
            WriteLine("        {");
            WriteLine("            get { return $safeitemname$.ResourceManager.GetString(\"" + name + "\", resourceCulture ?? CultureInfo.CurrentUICulture); }");
            WriteLine("        }");
        }
    }
#>
    }
}

File 2: Resource.tt

Don’t forget to adjust your using statements to the location of the IResourceManager interface and the ResourceManagerWrapper class.

Your .zip file should look something like this:

Screenshot 3: Contents of the .zip file

Screenshot 3: Contents of the .zip file

 

Now copy it to "C:\Users\[YOUR USERNAME]\Documents\Visual Studio [YOUR VISUAL STUDIO VERSION]\Templates\ItemTemplates\Visual C#" and fire up VS. Once you click “Add new item” your new template should appear right on top of the “Visual C# Items”.

Screenshot 4: Add new item

Screenshot 4: Add new item

 

You can download the template from my pet project’s site.