My software development journey

Configuration in .NET Core part 3/3

September 26, 2021

So one last thing I would like to discuss regarding configurations is how to implement a custom configuration source. In my sample, I will implement a configuration source that parses command-line arguments using a parsing library I prefer. Note that there already exists a command-line arguments extension from Microsoft, but I find it rather limited in terms of features. Whether you agree with me or not, it will still make for a good example of how to implement a custom configuration source. So let’s add the CommandLineParser Nuget package to our project file. For reference, I will be using version 2.8 of the library. Next, we add a class that will contain command line settings and will be used to override configuration settings:

public class CommandlineOption
{
    [Option('s', "setting")]
    public string MySetting { get; set; }

    [Option('i', "intValue")]
    public string MyIntValue { get; set; }

    [Option("intArray")]
    public IEnumerable<int> MyIntArray
      { get; set; } = Array.Empty<int>();
}

Let’s implement configuration provider:

public class CommandLineProvider : ConfigurationProvider
{
    private readonly CommandlineOption _option;

    public CommandLineProvider(CommandlineOption option)
    {
        _option = option;
    }

    public override void Load()
    {
        var data = new Dictionary<string, string>
        {
            [nameof(MySettings.MySetting)] = _option.MySetting,
            [$"{nameof(MySettings.Subsection)}" +
              $":{nameof(MySubsection.MyIntValue)}"]
              = _option.MyIntValue
        };


        var subsectionArray = _option.MyIntArray.ToArray();
        for (var i = 0; i < subsectionArray.Length; i++)
        {
            data[$"{nameof(MySettings.Subsection)}" +
              $":{nameof(MySubsection.IntArray)}:{i}"]
              = subsectionArray[i].ToString();
        }

        Data = data.Where(kv => !string.IsNullOrEmpty(kv.Value))
            .ToDictionary(kv => kv.Key, kv => kv.Value);
    }
}

In this case, I opted to inherit from ConfigurationProvider, which in turn allows me to only implement the Load method. In a nutshell, you need something that implements the IConfigurationProvider interface. Other points to mention are:

  • CommandlineOption class is serialized into dictionary
  • Note the usage of : to separate sections. Also, array entries are separated with a : followed by the index.

To configure our provider, we need to implement IConfigurationSource:

public class CommandLineSource : IConfigurationSource
{
    private readonly CommandlineOption _option;

    public CommandLineSource(CommandlineOption option)
    {
        _option = option;
    }

    public IConfigurationProvider
        Build(IConfigurationBuilder builder)
    {
        return new CommandLineProvider(_option);
    }
}

In our case configuration source just passes the command-line options to the provider. Now, all we need to do is to add the configuration source to our configuration builder.

var configBuilder = new ConfigurationBuilder()
    .Add(new CommandLineSource(option));

Here the option is our parsed command-line parameter. In terms of implementation, serializing the already parsed command line settings is a drawback. The benefit on the other hand is, that those end up merged with other settings and then also follow the same precedence of a settings source based on the order of registration. Another point to mention is, that since the command line arguments don’t change after the start, we could have created the same dictionary and use it as input to the in-memory configuration source. But then we would have to skip the fun part of implementing a custom provider! 😉 Another good example of how to implement a custom configuration provider can be found in Microsoft docs.

Sources: