Constructing settings classes that get read out of AppSettings on demand

My take on convention-based configuration for Autofac

I have an application that has external configuration. You probably do too. Here is my configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="Email:Port" value="2345"/>
    <add key="Email:Ssl" value="true"/>
    <add key="Email:Username" value="MyUserName" />
    <add key="Akismet:ApiKey" value="SomeKey" />
    <add key="Blog:EnableComments" value="true" />
    <add key="Blog:EnableHistory" value="true" />
  </appSettings>
</configuration>

As an Autofac user, I was already structuring my application with modules so I was really happy to see this post by Paul Stovell on Convention-based Configuration for Autofac. There were a few things I didn't like about it though.

Firstly, You are required to register modules in a weird way. Secondly, you need to specify the properties twice, once on the module and once as parameters in the module load method (and they need to be added to any constructors that need them too). I was sure Autofac could do better.

What I really wanted was to be able to create a setting class for each group of settings to be passed around and to be able to import that dependency through a constructor parameter into any class that needed it. Here is what it would look like:

class EmailSettings
{
    public string Port { get; set; }
    public bool UseSsl { get; set; }
    public string Username { get; set; }
}

class EmailServer
{
    private readonly EmailSettings _settings;
    public EmailServer(EmailSettings settings)
    {
        _settings = settings;
    }
}

The first task is to find a way to fill in EmailSettings from the AppSettings configuration node. Here is the class that does the job:

interface ISettingsReader
{
    object Load(Type type);
}

class SimpleSettingsReader : ISettingsReader
{
    private readonly NameValueCollection _settings;

    public SimpleSettingsReader(NameValueCollection settings)
    {
        _settings = settings;
    }

    public object Load(Type type)
    {
        if(type == null) throw new ArgumentNullException("type");
        var settingsObj = Activator.CreateInstance(type);
        var settingsPrefix = type.Name.Replace("Settings", "") + ":";
        foreach(var key in _settings.AllKeys.Where(x => x.StartsWith(settingsPrefix)))
        {
            var propertyName = key.Replace(settingsPrefix, "");
            var property = type.GetProperty(propertyName);
            if(property == null) 
                throw new Exception(String.Format("Settings class {0} has no property called {1}", type.Name, propertyName));
            var propertyValue = Convert.ChangeType(_settings[key], property.PropertyType);
            property.SetValue(settingsObj, propertyValue, null);
        }
        return settingsObj;
    }
}

It's not doing anything too complex. It creates an object of the settings type, checks the config file for matching settings and for each one it tries to find a corresponding property. This means that "Foo.Bar" will get mapped to the Bar property on the FooSettings class. Note that rather than rely on the actual configuration file, this class relies on NameValueCollection which makes it much easier to test. To use it with the configuration file I can register an instance wrapped around the config file app settings in my container builder like this:

var settingsReader = new SimpleSettingsReader(ConfigurationManager.AppSettings);
builder.RegisterInstance(settingsReader).As<ISettingsReader>();

And to use it in a class looks like this:

class EmailEngine
{
    private readonly EmailSettings _settings;

    public EmailEngine(ISettingsReader settingsReader)
    {
        _settings = (EmailSettings)settingsReader.Load(typeof(EmailSettings));
    }
}

Ugly. What I really want to be able to do is take a direct dependency on EmailSettings and have Autofac create the reader and read the settings for me at resolve time. As it turns out that is a great use for a custom registration source.

When Autofac is trying to resolve a Service it asks its internal collection of IRegistrationSource objects for advice on what to do. What we need to do is to create a registration source for settings classes and then tell our Autofac container about it. Here is the code for the registration source:

class SettingsSource : IRegistrationSource
{
    public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
    {
        var typedService = service as IServiceWithType;
        if (typedService != null && typedService.ServiceType.IsClass && typedService.ServiceType.Name.EndsWith("Settings"))
        {
            yield return RegistrationBuilder.ForDelegate(
                (c, p) => c.Resolve<ISettingsReader>().Load(typedService.ServiceType)
            ).As(typedService.ServiceType)
            .CreateRegistration();
        }
    }

    public bool IsAdapterForIndividualComponents
    {
        get { return false; }
    }
}

This class only has one method in it that can be roughly translated as "If you're looking for a type whose name ends with Settings, resolve an ISettingsReader and ask it to do the work". Finally we need to register the source so that our container will have access to it. I'm doing that in a module (along with the registration for the reader):

class SettingsModule : Module
{
    private readonly NameValueCollection _settings;

    public SettingsModule(NameValueCollection settings)
    {
        _settings = settings;
    }

    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<SimpleSettingsReader>()
            .As<ISettingsReader>()
            .WithParameter(TypedParameter.From(_settings));

        builder.RegisterSource(new SettingsSource());
    }
}

Now we can register the module:

builder.RegisterModule(new SettingsModule(ConfigurationManager.AppSettings));

And now the EmailEngine can take a direct dependency on the EmailSettings class:

class EmailEngine
{
    private readonly EmailSettings _settings;

    public EmailEngine(EmailSettings settings)
    {
        _settings = settings;
    }
}

Awesome! To add new settings to our application we just need a class whose name ends in Settings and we can start adding it to constructors and autowired properties right away.

P.S. As the properties on the settings classes are being set by reflection they MUST have a setter but that setter does not need to be public so I'd make them private.

autofac
Posted by: Mike Minutillo
Last revised: 22 Jun, 2011 06:09 AM History

Comments

Wallace Turner
Wallace Turner
23 Jun, 2011 12:28 AM

The first task is to find a way to fill in EmailSettings from the AppSettings configuration node. Here is the class that does the job:

Did you consider using the existing ConfigurationManager.GetSection method ?

that is, modify your EmailSettings class

public class EmailSettingsSection : ConfigurationSection
{
    [ConfigurationProperty("Port", IsRequired = true)]
    public string Port
    {
        get { return (string)this["Port"]; }
        set { this["Port"] = value; }
    }
    [ConfigurationProperty("Port", IsRequired = true)]
    public string Username
    {
        get { return (string)this["Username"]; }
        set { this["Username"] = value; }
    }
}

and then read the settings in by using

var settings = (EmailSettings)ConfigurationManager.GetSection("EmailSettings");

Your config file would then be structured like this:

  <EmailSettings Port="443" Username="bob"></EmailSettings>

This becomes a bit cleaner (in my opinion) and leverages the existing configuration framework. Naturally you could 'autofac' the loading (GetSection) to make it more convention-based.

23 Jun, 2011 04:27 AM

Hi Wallace,

While I agree that using the standard configuration sections mechanism provides a more readable config file and would prevent me from having to "re-invent the wheel" as it were, that solution is bound to the config files and to System.configuration. In addition to that, any components that take a dependency on that settings class also take a dependency on System.configuration.

The solution presented by the post could easily be changed to read configuration data out of a database, an arbitrary XML file, a web service, some embedded content that is deployed as a part of the assembly, or even some combination of all of them simply by changing the implementation of ISettingsReader that is registered in the container (using Autofac you could even change the settings reader within a particular application scope if required). You could also scan assemblies for types that meet the convention to see what settings are available to be set.

Finally, you can add new settings to your app just by creating a class what matches a naming convention. There is no special base-class to inherit from, no special attributes to remember, you don't need to register a new configuration section anywhere. All of that reduces friction and lets you get the job done faster in a way that I don't think sacrifices that much in terms of the finished product.

No new comments are allowed on this post.