The Problem

We have a lot of C# code that wants to access some kind of configuration, and this is all done through:

ConfigurationManager.AppSettings["SomeSettingOrOther"]

For example, we had a CurrencyConversion class, that had the following code.

public class CurrencyConversion{    Currency GetDefaultCurrency() {        // get the config setting        string configCurrency = ConfigurationManager.AppSettings["MarketCurrency"];

        // return the equivalent Enum        return CurrencyFromString(configCurrency);    }}

This code has several issues.

There were higher level problems too. These calls were spread throughout the code, sometimes deep in shared libraries, and it was impossible to know which bits of code needed which settings (without reading all of the code).

This meant it was difficult to validate that a config file contained all the information it needed, and that nobody dared to remove config settings. This in turn led to our config files to become increadingly bloated and confusing.

The Solution

To solve these problems we moved to using interfaces to define our configuration.

If we refactor the example code above to take its configuration via an interface we get the following (in real life, being as there is only one setting, you might decide just to pass it in directly, but bear with me).

public interface ICurrencyConversionConfiguration{    Currency DefaultCurrency;}

public class CurrencyConversion{    readonly ICurrencyConversionConfiguration configuration;

    public CurrencyConversion(ICurrencyConversionConfiguration configuration) {        Contract.Requires(configuration != null);

        this.configuration = configuration;    }

    Currency GetDefaultCurrency() {        return configuration.DefaultCurrency;    }}

This code has the following improvements

To handle the responsibility for reading and parsing the configuration we add the code below. This relies on a simple Configuration class, which you can see on GitHub at https://github.com/resgroup/configuration.

public class EconomicModelConfiguration : ICurrencyConversionConfiguration {    readonly Configuration configuration;        public EconomicModelConfiguration(Configuration configuration) {        Contract.Requires(configuration != null);

        this.configuration = configuration;            Validate();    }        void Validate() =>        using (var validator = configuration.CreateValidator)            validator.Check(() => DefaultCurrency);

    public string DefaultCurrency =>         configuration.GetEnum<Currency>(MethodBase.GetCurrentMethod());}

The configuration class itself is instantiated with a Configuration Source, which reads from environment variables in the example below. This makes it easy to adhere to 12 Factor App recommendations (https://12factor.net/config).

new Configuration(new GetFromEnvironment());

This has the following improvements

If we move another class to use the new configuration system, we get something like this.

public class EconomicModelConfiguration : ICurrencyConversionConfiguration, IConcreteCostConfiguration {    readonly Configuration configuration;        public EconomicModelConfiguration(Configuration configuration) {        Contract.Requires(configuration != null);

        this.configuration = configuration;            Validate();    }        void Validate() {        using (var validator = configuration.CreateValidator) {            validator.Check(() => DefaultCurrency);            validator.Check(() => DefaultConcreteCost);        }    }

    public string DefaultCurrency =>         configuration.GetEnum<Currency>(MethodBase.GetCurrentMethod());

    public double DefaultConcreteCost =>         configuration.GetDouble(MethodBase.GetCurrentMethod());}

This process continues as we move more classes over to the new system, and has the benefit that setting up Inversion of Control is easy, as we can just register EconomicModelConfiguration with all the interfaces that it implements.

Legacy Code

Like any mature software team, we have some legacy code, some of which is not ready to be created via Inversion of Control.

For these classes we create a static Configuation class

public static class EconomicModelConfigurationStatic{    readonly static EconomicModelConfiguration base = new EconomicModelConfiguration();

    public static IEconomicModelConfiguration Settings =>        base;}

In the Legacy code, we then replace

ConfigurationManager.AppSettings["SomeSettingOrOther"]

with

EconomicModelConfigurationStatic.Settings.SomeSettingOrOther

This gets us a lot of the benefits of the new system, with only very minor and easy changes to the existing code.

Conclusions

Encapsulating configuration logic in this way, and providing configuration through an interface, has the following benefits.

If you would like to use it, there is a nuget package available, and the source is on GitHub.