Feb 11 2010

Using StructureMap to provide strongly-typed access to AppSettings

Category: IoCMatt @ 10:19

I’ve been using StructureMap for the last couple of months.  I love it, and my only regret is that I didn’t start using it sooner.  I always felt like I wrote clean, testable code before, but I can see a noticeable improvement since jumping on the IoC container bandwagon. If you aren’t using an IoC container and are working on anything above a trivially simple app, you should look in to it. 

Anyway, I’m knee deep in rewriting our crawler infrastructure, and I found myself working with some classes that need access to external configuration data.  The config data is all fairly simple stuff, for example: “the path to plug-ins”, “the number of threads to use here”, “the amount of time to wait before timing out”.  We can argue about whether these things should actually be configurable outside of the code later, let’s just assume that they have to be for now.  Well, in certain configurations, our crawler pulls these settings from the app.config file.   In others, the settings are pulled from a configuration database.  That immediately means that the code can’t just reference ConfigurationManager.AppSettings, it needs to access an abstraction, we’ll call it a configuration provider.  Through the magic of IoC, all these configurable classes need to do is declare a dependency on this configuration provider, and the container will make sure they’re given whatever concrete config provider we have configured.  This is an improvement over our previous approach, where we were declaring our config provider at the top-level of the application, then passing it down, down, down the call stack to the implementations that actually needed it.  StructureMap has made things simpler, but it’s actually possible to improve things even further.

The very first version of our configuration provider abstraction looked something like this:

public interface IConfigurationProvider
{
    string this[string settingName]
    {
        get;
    }
}

Users of the provider (and by users here, I mean calling classes, not people) ended up sharing a lot of duplicate logic: extract a setting, make sure it’s there, try to convert it to the correct type, and substitute a default if all else fails.  We improved things a bit with the second version of the abstraction:

public interface IConfigurationProvider
{
    bool GetIntSetting(string name, out int holder, int defaultSetting);

    bool GetStringSetting(string name, out string holder, string defaultSetting);

    bool GetBooleanSetting(string name, out bool holder, bool defaultSetting);

    bool GetDoubleSetting(string name, out double holder, double defaultSetting);
    
    bool GetFloatSetting(string name, out float holder, float defaultSetting);
}

This is better, now callers can get their settings in a single line of code, but there was still a lot of repetitive logic across the various users.  There’s also a lot of magic strings in there.  These can be encapsulated in consts, but they’re still there.  Testing things that depend on these providers is also a little more difficult than it should be, though we do have a few classes to help. 

A better solution would not depend on strings, would remove the burden of extracting settings from the caller, and be easy to mock or replace for testing.  Perhaps something like this:

public class WidgetFactory
{
    ...
    public WidgetFactory(WidgetFactorySettings settings) 
    {
        this.Settings = settings;
    }
    ...
}

Here, my class is saying “I depend on this configuration data”, and it would be swell if the IoC container could populate it magically with the correct settings.  With just a small change, this actually becomes very easy to do:

public class WidgetFactory
{
    ...
    public WidgetFactory(IConfiguration<WidgetFactorySettings> config) 
    {
        this.Settings = config.Settings;
    }
    ...
}

The interface for the provider is quite simple:

public interface IConfiguration<TSettings> where TSettings : new()
{
    TSettings Settings { get; }
}

At run-time, the IoC container will try to find a type to satisfy this dependency.  No, you don’t have to create concrete types for everything that will ever need configuration data.  All you need is one generic type per configuration source.  Here’s a generic provider for extracting data from an app.config file:

public class AppConfigConfiguration<TSettings> : IConfiguration<TSettings> where TSettings : new()
{
    public TSettings Settings { get; private set; }

    public AppConfigConfiguration() : this(ConfigurationManager.AppSettings)
    {
    }

    //You can easily unit test this class by giving it a NameValueCollection.
    internal AppConfigConfiguration(NameValueCollection appSettings)
    {
        Settings = new TSettings();

        //Get properties we can potentially write from
        var properties = from prop in typeof (TSettings).GetProperties()
                         where prop.CanWrite
                         where prop.CanRead
                         let setting = appSettings[typeof (TSettings).Name + "." + prop.Name]
                         where setting != null
                         where TypeDescriptor.GetConverter(prop.PropertyType).CanConvertFrom(typeof (string))
                         let value = TypeDescriptor.GetConverter(prop.PropertyType).ConvertFromString(setting)
                         select new {prop, value};

        //Assign the properties.
        properties.ForEach(p => p.prop.SetValue(Settings, p.value, null));
    }
}

This provider uses a little bit of reflection and a little bit of LINQ to do it’s heavy lifting.  It grabs all the public properties off of the generic TSettings type, then scans for values matching the format “{TypeName}.{PropertyName}”.  If a match is found, and the value can be converted to the property’s concrete type, it is assigned. 

We still need to give StructureMap a little bit of direction about how to resolve the IConfiguration<TSettings> type:

ObjectFactory.Container.Configure(
    config =>
    {
        //We want to pull our configuration data from the app.config file.
        config.For(typeof (IConfiguration<>)).Use(typeof (AppConfigConfiguration<>));
    }
);

Now, anything that depends on an IConfiguration<whatever> will receive an AppConfigConfiguration<whatever>.   If we want classes to pull their configuration data from a database instead, we simply adjust our StructureMap bootstrapping.  Nothing else in the entire system has to change.

So, there you have it: a strongly-typed, generic, IoC-friendly way to handle your configuration data.  Thoughts?

Tags:

blog comments powered by Disqus