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.

IntelliSense for custom ConfigurationSections

To be frank: I don’t like XML for configuration purposes. I think it’s a plague and nothing less. It is verbose and prone to typos. It does not give you any type safety. Thus I wage a constant war against XML config files in my projects.
But sometimes you can’t avoid XML for one reason or another. And if you can’t avoid it you have to cope with its shortcomings. Configuration in code gives you (among many other things I value) IntelliSense support. You get help with possible values for variables, properties or parameters, method names and much more. To get (some of) the same convenience for config files you have to provide xsd schema files. Established frameworks sometimes ship with those schemas. But if you write custom config sections you are completely on your own.

Rob Seder has a great post on writing custom configuration sections. The last paragraph explains how you can create xsd’s for your components.

While playing with Enumeration Classes I also wanted to see how I can use them with config files. So I wrote a custom config section for evaluation purposes.

public class MyConfigSection : ConfigurationSection
{
  private readonly ConfigurationProperty myEnum;
  public MyConfigSection()
  {
    this.myEnum = new ConfigurationProperty(
      "myEnum", 
      typeof(Numbers),
      Numbers.None,
      new EnumClassConverter<Numbers>(),
      null,
      ConfigurationPropertyOptions.IsRequired);
    this.Properties.Add(this.myEnum);
  }
  public Numbers MyEnum
  {
    get { return (Numbers)base[this.myEnum]; }
    set { base[this.myEnum] = value; }
  }
}

Numbers is an enumeration class from a prior article.

[side note] I love the way Microsoft’s engineers allow you to control how the configuration system handles your custom classes by letting you specify a TypeConverter [/side note]

I followed the steps outlined in Rob’s post and created a schema using the XML –> Create Schema option from Visual Studio. I stripped away all the fluff that belonged to the config file schema until all that was left was the part for my custom section. I then added comments and allowed values for Numbers along with some more comments. I ended up with the following xsd.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
           targetNamespace="http://tecx.codeplex.com/tecx/2012/enum"
           attributeFormDefault="unqualified"
           elementFormDefault="qualified">
  <xs:element name="mySection">
		<xs:annotation>
			<xs:documentation>This demonstrates IntelliSense for config files</xs:documentation>
		</xs:annotation>
    <xs:complexType>
      <xs:attribute name="myEnum" use="required" >
        <xs:annotation>
          <xs:documentation>Predefined set of numbers</xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="None">
              <xs:annotation>
                <xs:documentation>NaN</xs:documentation>
              </xs:annotation>
            </xs:enumeration>
            <xs:enumeration value="One">
              <xs:annotation>
                <xs:documentation>Number 1.</xs:documentation>
              </xs:annotation>
            </xs:enumeration>
            <xs:enumeration value="Two">
              <xs:annotation>
                <xs:documentation>Number 2.</xs:documentation>
              </xs:annotation>
            </xs:enumeration>
            <xs:enumeration value="Four">
              <xs:annotation>
                <xs:documentation>Number 4.</xs:documentation>
              </xs:annotation>
            </xs:enumeration>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
</xs:schema>

That’s a lot of xml for one single property but there you are… One important thing to notice is the targetNamespace attribute in the schema element. This will be used to reference the schema in your config file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="mySection" type="TecX.EnumClasses.Test.MyConfigSection, TecX.EnumClasses.Test"/>
  </configSections>
  <mySection myEnum="Four" xmlns="http://tecx.codeplex.com/tecx/2012/enum" />
</configuration>

The section contains a reference to the targetNamespace of your custom schema. Now you need to add the xsd to the list of schemas for your config file. VS did that automatically for me when I placed the schema file in the same folder as the config file. If your’s doesn’t you can add the schema manually. Right click in the editor window for your app.config, select Properties from the context menu and the properties view will show up. It lists a setting called Schemas where the path to the DotNetConfig.xsd will already be listed. Click the […] button and add your custom schema using the dialog that pops up.

IntelliSense for custom configuration sections

And finally Visual Studio will give you some help with the tedious task of handling configuration through XML.

On top of writing the code for the configuration section you have to put some effort into writing the schema. I would not make that investment until the API is mostly stable for the current release. But then it definitely makes sense and developers that use your components will love you for that. Its the kind of documentation that does not force them to leave their working environment, lookup something in a .chm help file or a wiki page but instead they can stay focused on their current task.