Convention-based Configuration for Autofac

This is an old post and doesn't necessarily reflect my current thinking on a topic, and some links or images may not work. The text is preserved here for posterity.

Here's the configuration file of an application I'm working on:

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="Repositories.ConnectionString" value="Server=(local)\SQLEXPRESS;Database=XYZ;Trusted_connection=true;"/>

    <add key="Email.Pop3Host" value="pop.gmail.com"/>
    <add key="Email.Pop3Port" value="995"/>
    <add key="Email.Pop3Ssl" value="True"/>
    <add key="Email.Pop3User" value="someone@gmail.com"/>
    <add key="Email.Pop3Password" value="password"/>
  </appSettings>
</configuration>

As an Autofac user, I use modules to structure my application. That means I have modules like:

public class EmailModule : Module
{
    public string Pop3Host { get; set; }
    public int Pop3Port { get; set; }
    public bool Pop3Ssl { get; set; }
    public string Pop3User { get; set; }
    public string Pop3Password { get; set; }

    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<EmailReceiver>().As<IReceiver>().PropertiesAutowired();
        builder.RegisterType<EmailNotifier>().As<INotifier>().PropertiesAutowired();
        builder.RegisterType<EmailParser>().As<IEmailParser>().PropertiesAutowired();
        builder.RegisterType<EmailReplyInterpreter>().As<IEmailReplyInterpreter>().PropertiesAutowired();
        builder.RegisterType<Pop3MailClient>().As<IEmailClient>()
            .WithParameter("PopServer", Pop3Host)
            .WithParameter("Port", Pop3Port)
            .WithParameter("useSSL", Pop3Ssl)
            .WithParameter("Username", Pop3User)
            .WithParameter("Password", Pop3Password);
    }
}

The convention is that any property in a module can be set by specifying an AppSetting with a key of "Module.PropertyName". That means modules aren't aware of configuration files, and not every component has to be configured in XML (the way Autofac's OOTB XML configuration works).

The ContainerBuilder is built like so:

var modules = new List<Module>();
modules.Add(new RepositoriesModule());
modules.Add(new EmailModule());
modules.Add(...);

var builder = new ContainerBuilder();
builder.RegisterModule(new ConfiguredModules(modules));

The ConfiguredModules class is a module that configures and then registers the other modules. The implementation is pretty simple:

public class ConfiguredModules : Module
{
    private readonly IList<Module> modules;

    public ConfiguredModules(IList<Module> modules)
    {
        this.modules = modules;
    }

    protected override void Load(ContainerBuilder builder)
    {
        var settings = ConfigurationManager.AppSettings;
        var keys = settings.AllKeys;

        foreach (var setting in keys)
        {
            var parts = setting.Split('.');
            var moduleName = parts[0];
            var propertyName = parts[1];
            var value = settings[setting];

            var module = modules.First(x => x.GetType().Name == moduleName + "Module");
            var property = module.GetType().GetProperty(propertyName);
            property.SetValue(module, Convert.ChangeType(value, property.PropertyType), null);
        }

        foreach (var module in modules)
        {
            builder.RegisterModule(module);
        }
    }
}