0

I want to store program configuration and I don't want to tie to any implementation (be it XML or ADO.NET). The configuration itself is hierarchical in its nature. For example, it contains:

  • database host name
  • database port
  • database user
  • turn X support on/off
  • turn Y support on/off
  • parameters for Z

Until now I've created string-to-object dictionaries, like:

var config = new Dictionary<string, string>();
config["database/host"] = "localhost";
config["database/port"] = "port";

It is, however, a bad thing, since it is difficult to keep track of which modules use which keys and it's difficult to refactor. Also obviously it's not hierarchical.

So... I want to replace my magic strings map with something else. I'd like the solution to:

  • support hierarchy so it's easy to see how the data is structured,
  • use no magic strings at all,
  • use as little boilerplate as possible.
rr-
  • 14,303
  • 6
  • 45
  • 67
  • possible duplicate of [Nested Configuration Section app.config](http://stackoverflow.com/questions/5027284/nested-configuration-section-app-config) – Victor Zakharov Aug 15 '14 at 15:30
  • Nope, I said I don't want to tie it to any existing technology. This includes `app.config`. – rr- Aug 15 '14 at 15:49
  • 1
    Too bad then, yet another bicycle to give headaches to future developers. – Victor Zakharov Aug 15 '14 at 15:53
  • I want the technologies in my project to be easily interchangeable. In order to do that, I need proxies, DTO-s and other things like that. What's so wrong about it? – rr- Aug 15 '14 at 16:15
  • You never said why app.config does not work for you. If you explained this part a bit, people could justify the need for what you are suggesting. Maybe compare your solution to the one that uses app.config. Pinpoint where code savings occur or maintainability is increased. – Victor Zakharov Aug 15 '14 at 17:41

1 Answers1

0

Assuming configuration manager is flat, i.e. it looks like this:

interface IConfigurationManager
{
    void Get(ConfigurationKey key);
    void Set(ConfigurationKey key, string value);
}

public class ConfigurationKey
{
    public ConfigurationKey(string name)
    {
        Name = name;
    }

    public string Name { get; private set; }
}

I came up with this approach:

public static class ConfigurationKeys
{
    public static class Root
    {
        public static class Database
        {
            public static ConfigurationKey Host;
            public static ConfigurationKey Port;
            public static ConfigurationKey User;
        }

        public static class X
        {
            public static ConfigurationKey Enabled;
        }

        public static class Y
        {
            public static ConfigurationKey Enabled;
        }
    }

    public static void Build()
    {
        Build(typeof (ConfigurationKeys));
    }

    private static void Build(Type type)
    {
        var configurationKeyFields = type
            .GetFields()
            .Where(field => field.FieldType == typeof (ConfigurationKey));

        foreach (FieldInfo field in configurationKeyFields)
            field.SetValue(null, CreateConfigurationKey(type, field));

        var nestedClasses = type.GetNestedTypes().Where(nestedType => nestedType.IsClass);
        foreach (Type nestedClass in nestedClasses)
            Build(nestedClass.UnderlyingSystemType);
    }

    private static ConfigurationKey CreateConfigurationKey(Type type, FieldInfo field)
    {
        var parentPath = type.FullName.Substring(typeof (Root).FullName.Length + 1).Replace("+", "/");
        return new ConfigurationKey(parentPath + "/" + field.Name);
    }
}

Now it's easy to access it by something like:

ConfigurationKeys.Build();
var cm = new ConfigurationManager();
cm.Set(ConfigurationKeys.Root.Database.Host, "localhost");

ConfigurationKeys.Root.Database.Host // == "Database/Host"

Pros:

  • readable
  • easy to refactor
  • almost no boilerplate
  • nested, thus successfully depicting reality and Intellisense-friendly

Cons:

  • almost impossible to mock it in tests due to static classes and nested types
  • difficult to port to other languages due to its reliance on reflection usage
  • bogus call to a method like Build(); unfortunately, it cannot be superseded with static constructor because accessing nested types wouldn't fire parent static constructors, and supplying each nested type with static constructor adds boilerplate
rr-
  • 14,303
  • 6
  • 45
  • 67
  • What happens if in `ConfigurationKeys.Root.Database.Host` **Database** is nothing? – Victor Zakharov Aug 15 '14 at 15:28
  • What do you mean? If you'll try to use `cm.Set(ConfigurationKeys.Root.Database, "whatever")` it won't let you, because `ConfigurationKeys.Root.Database` is not of type `ConfigurationKey`. If, on the other hand, you want to have both `Database` section *and* `Database` *key*, it won't be possible, unfortunately due to name clash and you'll have to stick with something like `ConfigurationKeys.Root.Database.Value`. – rr- Aug 15 '14 at 15:50
  • I am saying that your classes hierarchy does not support nested keys in a real life setting. By logic, `ConfigurationKeys.Root.Database.Host` should return Nothing, but I can't see how it will do that with your current code structure. The fact that it works for you now does not mean it will continue working 6 months ahead. Scale it up to 10000 parameters, all nested in each other. Make some unconditionally required. Others - based on values of other parameters. Your code would need to validate if the configuration is valid. Otherwise don't even start working.etc. – Victor Zakharov Aug 15 '14 at 15:58
  • Would be interesting to see how you are planning to organize even simple validation logic, such as IP or host validation. Or you are going to assume the configuration is always correct? – Victor Zakharov Aug 15 '14 at 15:59
  • `ConfigurationKeys` are just that. Possible keys. They contain no logic whatsoever. They don't even hold values. Things like validation, storage etc. belong to `ConfigManager`. As for 10000 parameters, you could always define them in partial classes to keep things clean. Then again, if you need 10000 parameters, something is wrong. – rr- Aug 15 '14 at 16:13
  • Sorry I misread your code previously, did not notice classes being static. It's good to have lunch sometimes. What you wrote is essentially syntactic sugar, i.e. intellisense for magic strings syntax. Which does not make them any less magic. :) – Victor Zakharov Aug 15 '14 at 17:45