Convention Based Application Settings via Autofac

Cross posted from Fresh Brewed Code. A little more on that in a soon to be blog post.

A while back I stumbled on a blog post from Chad Myers about a cool feature of FubuCore around handling application configuration. It was one of those "duh, why didn't I think of this years ago" moments. You see, I've inherited an application which has a bunch of configurable options in the app.config. The code had already isolated those settings to classes with virtual members and a lot of calls to ConfigurationManager.AppSettings. It was testable by inheriting the settings class and overriding all of the properties. After seeing Chad's post though, I had to scrap it. POCO Settings -- yes please.

I've started a project to provide this functionality with Autofac. The project is still very new, currently I've only implemented sourcing settings via (app|web).config. I'm hoping to push in support for environment variables and the registry as sources for settings. Additionally I'm going to make the conventions easier to override since you might not like your classes ending in "Settings".

Let's go through some of the code and see how it all works together.

The coordinator for the whole shebang is the Autofac registration source:

public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) {
    var s = service as IServiceWithType;
    if (s != null && s.ServiceType.IsClass && s.ServiceType.Name.EndsWith("Settings")) {
        yield return RegistrationBuilder.ForDelegate((c, p) => c.Resolve<ISettingsFactory>().Create(s.ServiceType))
            .As(s.ServiceType)
            .SingleInstance()
            .CreateRegistration();
    }
}

This looks for types to resolve whose name ends with "Settings". When it's time to actually create the type, we'll ask the container for a Settings Factory and tell it to create the type being asked for. Then we'll register that in the container as a singleton. Not too much going on there.

The Settings Factory is responsible for creating the settings object, polling each settings provider for values, setting the values and verifying that it's all good before shipping it off:

public class SettingsFactory : ISettingsFactory {
    readonly IEnumerable _providers;

    public SettingsFactory(IEnumerable<ISettingsProvider> providers) {
        _providers = providers;
    }

    public object Create(Type type) {
        var obj = Activator.CreateInstance(type);

        var propertiesMissed = PopulateProperties(type, obj);

        if (propertiesMissed.Any()) {
            var message = String.Format("{0} is missing the following properties in configuration: {1}", type.Name, String.Join(",", propertiesMissed));
            throw new ConfigurationErrorsException(message);
        }

        return obj;
    }

    List<string> PopulateProperties(Type type, object obj) {
        var propertiesMissed = new List<string>();

        var props = type
            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.CanWrite);

        foreach (var prop in props) {
            var value = _providers
                .Select(p => p.ProvideValueFor(type, prop.Name))
                .FirstOrDefault(p => p != null);
            if (value != null)
                prop.Set(obj, value);
            else
                propertiesMissed.Add(prop.Name);
        }
        return propertiesMissed;
    }
}

This class is where most of the work is happening. I'm not totally sold on the implementation, but it will do for now. Right now I'm just taking the first value I get which will prioritize the first registrations.

The last bit is an implementation of a Settings Provider. ConfiguraionManager.AppSettings is just a NameValueCollection, so that makes things stupid easy. The Name/Value Provider looks like this:

public class NameValueSettingsProvider : ISettingsProvider {
    private readonly NameValueCollection _source;

    public NameValueSettingsProvider(NameValueCollection source) {
        _source = source;
    }

    public object ProvideValueFor(Type type, string propertyName) {
        var key = type.Name + "." + propertyName;
        return _source[key];
    }
}

If we have a FooSettings class with a property Bar then we'd expect to see "FooSettings.Bar" as a key. Really easy, nothing to see here.

The Payoff
Now, let's see it all together. Here's the spec that shows everything in play:

class FooSettings {
    public string Bar { get; set; }
}

class NeedsSomeSettings {
    public NeedsSomeSettings(FooSettings settings){
        Settings = settings;
    }

    public FooSettings Settings { get; private set; }
}

public class when_resolving_a_class_that_depends_on_settings {    
    static IContainer _container;
    static NeedsSomeSettings _needsomeSettings;

    Establish context = ()=> {
        var builder = new ContainerBuilder();
        builder.RegisterSource(new SettingsSource());
        builder.RegisterType<SettingsFactory>().AsImplementedInterfaces();
        builder.RegisterType<NameValueSettingsProvider>()
            .WithParameter("source", new NameValueCollection() {{"FooSettings.Bar", "w00t!"}})
            .AsImplementedInterfaces();
        builder.RegisterType<NeedsSomeSettings>().AsSelf();
        _container = builder.Build();
    };

    Because of = () => _needsomeSettings = _container.Resolve<NeedsSomeSettings>();

    It should_resolve_type = () => _needsomeSettings.ShouldNotBeNull();
    It should_have_settings = () => _needsomeSettings.Settings.ShouldNotBeNull();
    It should_have_settings_populated = () => _needsomeSettings.Settings.Bar.ShouldEqual("w00t!");
}

No more mucking around with problematic configuration. Just take a dependency on your POCO settings and off you go. If you forget to configure something you get a friendly error message. Like I said, there are bits of the API I'm not toally sold on and I have few more sources to implement. I also need to add a proper Autofac module to hide a lot of the registration noise.

So far I'm liking this a lot better than having random references to ConfigurationManager.AppSettings littered throughout the code. What do you think? Follow along on github and let me know what you like or hate.

  • http://www.bing.com/ Lawanda

    Intelligence and simlipicty – easy to understand how you think.

  • http://ynzfslbuokkc.com/ jqtwppijl

    YkTgck qrgfazevhsqb