Strongly typed app settings with Castle DictionaryAdapter

A while ago (almost 4 years ago, to be precise) Ben Hall wrote a blogpost about using Castle DictionaryAdapter to build a simple, strongly typed wrapper aroud application settings.

While extremely simple, and gets the job done, there are a few ways it can be improved. With that, this blogpost can be treated as an introduction to Castle DictionaryAdapter (which sadly has precisely zero documentation).

While the apprpach is really simple, we’re going to take a detailed look and take it slowly, which is why this post is fairly long. Buckle up.

What is DictionaryAdapter

In a nutshell DictionaryAdapter is a simple tool to provide strongly typed wrappers around IDictionary<string , object>

Here’s a simple example:

// we have a dictionary...
var dictionary = new Dictionary<string, object>
{
	{ "Name", "Stefan" },
	{ "Age", 30 }
};

// ...and an adapter factory
factory = new DictionaryAdapterFactory();

// we build the adapter for the dictionary
var adapter = factory.GetAdapter<IPerson>(dictionary);

Debug.Assert(adapter.Name == "Stefan");

Wrapping app settings

While the project is called DictionaryAdapter in fact it can do a bit more than just wrap dictionaries (as in IDictionary). It can be used to wrap XmlNodes or NameValueCollections like Configuration.AppSettings and this last scenario, is what we’re going to concentrate on.

The goals

We’ve got a few goals in mind for that project:

  • strongly typed – we don’t want our settings to be all just strings
  • grouped – settings that go together, should come together (think username and password should be part of a single object)
  • partitioned – settings that are separate should be separate (think SQL database connection string and Azure service bus url)
  • fail fast – when a required setting is missing we want to know ASAP, not much later, deep in the codebase when we first try to use it
  • simple to use – we want the junior developer who joins the team next week to be able to use it properly.

With that in mind, let’s get to it.

Getting started

DictionaryAdapter lives in Castle.Core (just like DynamicProxy), so to get started we need to get the package from Nuget:

Install-Package Castle.Core

Once this is done and you’re not using Resharper make sure to add using Castle.Components.DictionaryAdapter; to make the right types available.

The config file

Here’s our config file, we’ll be dealing with. Notice it follows a simple naming convention with prefixes to make it easy to group common settings together. This is based on a real project.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="environment-type" value="Local" />
    <add key="environment-log-minimum-level" value="Debug" />
    <add key="auth0-client-id" value="abc123abc" />
    <add key="auth0-client-secret" value="123abc123abc" />
    <add key="auth0-domain" value="abc123.auth0.com" />
    <add key="auth0-database-connection-name" value="ABC123" />
    <add key="auth0-token-expiration-seconds" value="3600" />
  </appSettings>
</configuration>

As you can see we have two sets of settings here. One for general environment configuration, and another one for integration with Auth0.

Config interfaces

Now let’s proceed to creating our interfaces, exposing the configuration values to our application. We follow a naming convention where the name of config value corresponds to type/property on the interface. This makes it trivial to see which config value maps to which property on which interface.

For clarity I’d also recommend putting the interfaces in a designated namespace, like MyApp.Configuration.

public interface IEnvironment
{
    EnvironmentType Type { get; }
    LogEventLevel LogMinimumLevel { get; }
}

public interface IAuth0
{
    string ClientId { get; }
    string ClientSecret { get; }
    string Domain { get; }
    string DatabaseConnectionName { get; }
    int TokenExpirationSeconds { get; }
}

Having the interfaces and the config we can start writing the code to put the two together

Bare minimum

Let’s rewrite the code from the beginning of the post to use our config file and interfaces. If you’re not using Resharper you’ll have to manually add a reference to System.Configuration.dll for the following to work.

var factory = new DictionaryAdapterFactory();

var environment = factory.GetAdapter<IEnvironment>(ConfigurationManager.AppSettings);

Debug.Assert(environment.Type == EnvironmentType.Local);

If we run it now, we’ll get a failure. DictionaryAdapter doesn’t know about our naming convention, so we have to teach it how to map the type/property to a config value.

Implementing DictionaryBehaviorAttribute

There are two ways to customise how DictionaryAdapter operates.

  • Transparent, using an overload to GetAdapter and passing a PropertyDescriptor with customisations there.
  • Declarative, using attributes on the adapter interfaces to specify the desired behaviour. If you’ve used Action Filters in ASP.NET MVC, it will feel familiar.

In this example we’re going to use the latter. To begin, we need to create a new attribute, inheriting from DictionaryBehaviorAttribute, and apply it to our two interfaces

public class AppSettingWrapperAttribute : DictionaryBehaviorAttribute, IDictionaryKeyBuilder
{
    private readonly Regex converter = new Regex("([A-Z])", RegexOptions.Compiled);

    public string GetKey(IDictionaryAdapter dictionaryAdapter, string key, PropertyDescriptor property)
    {
        var name = dictionaryAdapter.Meta.Type.Name.Remove(0, 1) + key;
        var adjustedKey = converter.Replace(name, "-$1").Trim('-');
        return adjustedKey;
    }
}

We create the attribute and implement IDictionaryKeyBuilder interface, which tells DictionaryAdapter we want to have a say at mapping the property to a proper key in the dictionary. Using a trivial regular expression we map NameLikeThis to Name-Like-This.

Having done that, if we run the application again, the assertion will pass.

Fail Fast

If we remove the environment-log-minimum-level setting from the config file, and run the app again, it will still pass just fine. In fact, since LogEventLevel (which comes from Serilog logging framework) in an enum, therefore a value type, if we read the property everything will seem to have worked just fine. A default value for the enum will be returned.

This is not the behaviour that we want. We want to fail fast, that is if the value is not present or not valid, we want to know. To do it, we need two modifications to our AppSettingWrapperAttribute

Eager fetching

First we want the adapter to eagerly fetch values for every property on the interface. That way if something is wrong, we’ll know, even before we try to access the property in our code.

To do that, we implement one more interface – IPropertyDescriptorInitializer. It comes with a single method, which we implement as follows:

public void Initialize(PropertyDescriptor propertyDescriptor, object[] behaviors)
{
    propertyDescriptor.Fetch = true;
}

Validating missing properties

To ensure that each setting has a value, we need to insert some code into the process of reading the values from the dictionary. To do that, there is another interface we need to implement: IDictionaryPropertyGetter. This will allow us to inspect the value that has been read, and throw a helpful exception if there is no value.

public object GetPropertyValue(IDictionaryAdapter dictionaryAdapter, string key, object storedValue, PropertyDescriptor property, bool ifExists)
{
    if (storedValue != null) return storedValue;
    throw new InvalidOperationException(string.Format("App setting \"{0}\" not found!", key.ToLowerInvariant()));
}

If we run the app now, we’ll see that it fails, as we expect, telling us that we forgot to set environment-log-minimum-level in our config file.

Wrapping up

That’s it. Single attribute and a minimal amount of bootstrap code is all that’s needed to get nice, simple to use wrappers around application settings.

Additional bonus is, that this is trivial to integrate with your favourite IoC container.

All code is available in this gist.

  • Artur

    Good article.

    I’ve used DictionaryAdapter a number of times, but I always missed information on the following:
    1. I think I read somewhere that there should be just one DictionaryAdapterFactory per application. Can you comment on that? Do you know if there are any problems caused by having more than one, e.g. instantiating a new one whenever new DictionaryAdapter is needed?
    2. What’s the culture used for parsing/storing the values? I suppose it’s CurrentCulture, which seems to be a big no-no when working with something like AppSettings where InvariantCulture is much more appropriate. Do you know if there is a way to specify the culture to be used by DictionaryAdapter work?

    And one more note, but I’ve learnt an answer to that the hard way already – even when you work with an interface with just getters and the wrapped dictionary is readonly (like AppSettings), the dictionary adapter is not thread safe – in particular it should not be registered with singleton lifestyle in an IoC container.

    • Krzysztof

      Artur,

      1. By having multiple factories you give up factory type caching. So you may end up with a memory leak, creating new in-memory assemblies for he types
      2. Conversion is handled through standard `TypeConverter`, which by default is obtained by `TypeDescriptor.GetConverter()` method, so it will have whatever behaviour is exhibited by default. I agree that it might be an issue in some cases, but then it can be easily changed.

      Can you elaborate on the non-thread-safe part?