Configuration and Dependency Injection

In this post, I wrote about how to mock out the configuration for an application. What I did not cover in that post was whether it was a good idea to pass around IConfiguration. The answer is that, at least in my opinion, it’s a very bad idea.

In this post, I’d like to discuss why I think it’s a bad idea, and my take on a better idea.

Single Responsibility

The first reason is that you are, essentially, breaking the principle of single responsibility. Let’s imagine that we have a class that accepts an IConfiguration as a parameter. When you need an element from the configuration, you have to issue a command like this:

var myValue = new _configuration.GetValue<string>("MyValue");

So, what’s wrong with this? It’s not exactly complex code, and it’s intent is clear. However, your method is now doing two things: it’s doing what it was intended to do, and it’s reading from the configuration. If MyValue changes (say you want to call it “MyNewValue”) then you have to change your method – but your method should not care where, or how this value is retrieved! Let’s say my full class is this:

public class CalculateTemperature 
{
    private IConfiguration _configuration;
    public CalculateTemperature(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public bool IsFreezing(double temperature)
    {
        string scale = _configuration.GetValue<string>("TempScale");
        switch (scale)
        {
            case "Fahrenheit":
                return temperature == 32;

            case "Centigrade":
                return temperature == 0;

            case "Kelvin":
                return temperature == 273;

        }
    }
}

Okay, so I have a class, and a method – the class is responsible for temperature calculations, and the method for telling me if a given temperature is the freezing point of water. However, the IsFreezing method also gets a setting from the configuration file. What this means is that the method above already has a potential bug: what if the setting is not present, or it’s set to some other temperature scale, “Celcius” for example; so you now end up with a method that looks like this:

    public bool IsFreezing(double temperature)
    {
        string scale = _configuration.GetValue<string>("TempScale");
        switch (scale)
        {
            case "Fahrenheit":
                return temperature == 32;

            case "Centigrade":
            case "Celcius":
                return temperature == 0;

            case "Kelvin":
                return temperature == 273;

            case default:
                throw new Exception($"Temperature scale is unsupported");
        }
    }

Buy why should this method have to deal with what’s in the configuration file? A better implementation of this method would be something like this:

    public bool IsFreezing(double temperature, Scale scale)
    {
        switch (scale)
        {
            case Scale.Fahrenheit:
                return temperature == 32;

            case Scale.Centigrade:
                return temperature == 0;

            case Scale.Kelvin:
                return temperature == 273;
        }
    }

Now I can create a config full of ANSI art for all this method cares; and as a result, I can get rid of IConfiguration out of the class.

Testing

The second reason, as I think I implied, is that it is not straight-forward to mock out the IConfiguration class. That fact that I created a blog post on the subject means that it needs one (at least for me), which means it’s far too much hassle; which means that classes and methods that contain this are less likely to be tested.

The Real World

The example I’ve given above isn’t a very realistic example, and I’m sure that no-one reading this would create a method that read from the config for a setting that so clearly relates to the method; however, the following is a very common thing, and I’m sure that you have written something akin to this, somewhere (I know I have):

public class TemperatureRepository
{
    private IConfiguration _configuration;
    public CalculateTemperature(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public bool WasFreezingLastWeek(DateTime dateTime)
    {
        string connectionString = _configuration.GetValue<string>("ConnectionString");

        . . . 
    }
}

The exact same argument applies to this, as did for the method above, but this doesn’t feel so wrong. The reason is that you know what the Startup.cs call looks like when your class looks like this:

public class TemperatureRepository
{
    private string _connectionString;
    public CalculateTemperature(string connectionString)
    {
        _connectionString = connectionString;
    }

    public bool WasFreezingLastWeek(DateTime dateTime)
    {
        . . . 
    }
}

You either need to create the class before you inject it, or do something like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<ITemperatureRepository>(srv => 
    {        
          new  TemperatureRepository(Configuration.GetValue<string>("ConnectionString"));
    });
}

In fact, I don’t think this looks too bad but, recently, I’ve been using a slightly different approach, which allows you to avoid this: that is to register a class that contains your config. If you have three or four values, then maybe have one config class for your entire application; if there’s more then have as many as make sense:

public void ConfigureServices(IServiceCollection services)
{
    var configClass = new ConfigClass()
    {
        ConnectionString = Configuration.GetValue<string>("ConnectionString");
    };

    services.AddScoped<ConfigClass>(configClass);
    services.AddScoped<ITemperatureRepository>();
}

Summary

I’m not sure this warranted such a long blog post, but it’s a Saturday afternoon and the pubs are closed!

Caveat

None of the code in this post has been tested – it’s all been written in OneNote – I didn’t think the specific code syntax was particularly relevant, given it’s more of an opinion post.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.