Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

ConfigManager

4.79/5 (7 votes)
14 Jun 2018CPOL2 min read 12K   167  
Simple config manager for your applications

Introduction

Saving and using property files is something that every application needs. In this tip, I will show you what I use, and think is the best option given the following requirements:

  • The property file should be easy for operations people to edit and use (no delimitation, extra formatting, etc.).
  • The property file should have the ability for comments.
  • Accessing the property values should be as fast as possible.
  • The compiler should help with property names so mistakes are caught as soon as possible.

Using the Code

The following snippet shows how you can use ConfigManager, first you initialize the manger by giving it a file name from which to load config properties, then you pass your config objects to the manager to fill for you, lastly, you can tell the manager to save all the properties to the file specified.

C#
// load the property file with quoted strings true for saving
ConfigManager cm = new ConfigManager("properties.config", true); 

cm.LoadProperties(new ClientEngine.Config()); // fill the object
cm.LoadProperties(new StorageEngine.Config());

ClientEngine.Config.TCPPort = 120;

var s = ClientEngine.Config.TempFolder;

var ss = StorageEngine.Config.TempFolder;

// if you need to access a property which you don't have the config object for
var sss = cm.GetProperty("ClientEngine.TCPPort"); 

cm.Save(); // save the current values to file

There is a special case when you want to use a config property but don't have access to the config object within your current project (created in another DLL, etc.). For this, you can use GetProperty() and give a key value. Be aware that this will get the last saved value, not the current value in the config object.

The Config Objects

The best option for config objects I have come to is the following class with static fields:

C#
namespace ClientEngine
{
    public class Config
    {
        [Description("Folder for server temporary files")]
        public static string TempFolder = "c:\\temp\\";

        [Description("TCP port which server listens to")]
        public static int TCPPort = 99;

        [Description("License recycle timeout for inactive users")]
        public static int LockTimeoutMins = 30;

        [Description("Compress Network traffic (0=off, 1=on)")]
        public static bool NetworkCompress = false;

        [Description("Use JSON for Network traffic (0=off, 1=on)")]
        public static bool NetworkJSON = false;

        public static bool MyBool = true;

        [NonSerialized]
        public static string NoSave = "hello";
    }
}

As you can see, you can use the Description and NonSerialized attributes to control the comments and the save of the values.

The above object has the fastest access time since it is direct access without look ups (with other config loaders which use dictionaries, etc.) and is compile time checked, so you can't make mistakes when using them.

The Saved Values

The above object gets saved to the following format:

// Folder for server temporary files
ClientEngine.TempFolder = "c:\temp\"

// TCP port which server listens to
ClientEngine.TCPPort = 120

// License recycle timeout for inactive users
ClientEngine.LockTimeoutMins = 30

// Compress Network traffic (0=off, 1=on)
ClientEngine.NetworkCompress = False

// Use JSON for Network traffic (0=off, 1=on)
ClientEngine.NetworkJSON = False

ClientEngine.MyBool = True

As you can see, the property values correspond to the property types, i.e., strings, numbers and booleans. Strings can be quoted or unquoted in the property file and the config manager will load in any case.

The property names are derived from the namespace and property/field name of the config object.

ConfigManager

The entire ConfigManager is below:

C#
class ConfigManager
{
    private List<object> _configs = new List<object>();
    private Dictionary<string, string> _props = new Dictionary<string, string>();
    private string[] _lines = new string[0];
    private string _filename = "";
    private bool _quoteStrings = true;

    public ConfigManager(string filename, bool quoteStrings)
    {
        _quoteStrings = quoteStrings;
        _filename = filename;
        if (File.Exists(_filename))
            _lines = File.ReadAllLines(_filename);

        if (_lines != null)
        {
            foreach (var l in _lines)
            {
                if (!l.Trim().StartsWith("//"))
                {
                    var str = l.Trim();
                    int i = str.IndexOf('=');
                    if (i > 0)
                    {
                        var k = str.Substring(0, i).Trim();
                        var v = str.Substring(i + 1).Trim();
                        int s = 0;
                        if (v[0] == '\"')
                            s = 1;
                        int e = v.Length;
                        if (v[v.Length - 1] == '\"')
                            e = v.Length - 2;

                        v = v.Substring(s, e);
                        if (!_props.ContainsKey(k))
                            _props.Add(k, v.Trim());
                    }
                }
            }
        }
    }

    /// <summary>
    /// Will get the saved value, not the current value in the config object
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public string GetProperty(string key)
    {
        string v = null;
        _props.TryGetValue(key, out v);
        return v;
    }

    public bool LoadProperties(object config)
    {
        if (_configs.Contains(config) == false)
            _configs.Add(config);

        foreach (var f in config.GetType().GetFields())
        {
            if (f.IsDefined(typeof(NonSerializedAttribute), false))
                continue;

            var s = f.DeclaringType.FullName;
            s = s.Substring(0, s.LastIndexOf('.') + 1) + f.Name;

            string v = null;
            if (_props.TryGetValue(s, out v))
            {
                if (f.FieldType == typeof(string))
                    f.SetValue(config, v);

                else if (f.FieldType == typeof(int))
                    f.SetValue(config, int.Parse(v));

                else if (f.FieldType == typeof(bool))
                    f.SetValue(config, bool.Parse(v));
            }
            else
                _props.Add(s, "" + f.GetValue(config));
        }

        foreach (var p in config.GetType().GetProperties())
        {
            if (p.IsDefined(typeof(NonSerializedAttribute), false))
                continue;

            var s = p.DeclaringType.FullName;
            s = s.Substring(0, s.LastIndexOf('.') + 1) + p.Name;

            string v = null;
            if (_props.TryGetValue(s, out v))
            {
                if (p.PropertyType == typeof(string))
                    p.SetValue(config, v, null);

                else if (p.PropertyType == typeof(int))
                    p.SetValue(config, int.Parse(v), null);

                else if (p.PropertyType == typeof(bool))
                    p.SetValue(config, bool.Parse(v), null);
            }
            else
                _props.Add(s, "" + p.GetValue(config, null));
        }
        return false;
    }

    public void Save()
    {
        List<string> lines = new List<string>(_lines);

        foreach (var config in _configs)
        {
            foreach (var f in config.GetType().GetFields())
            {
                if (f.IsDefined(typeof(NonSerializedAttribute), false))
                    continue;
                var d = "";
                if (f.IsDefined(typeof(DescriptionAttribute), false))
                    d = GetDescription(f, config);

                var s = f.DeclaringType.FullName;
                s = s.Substring(0, s.LastIndexOf('.') + 1) + f.Name;
                var v = f.GetValue(config);
                if (_quoteStrings && f.FieldType == typeof(string))
                    v = "\"" + v + "\"";
                AddtoLines(lines, d, s, v);
                _props[s] = "" + v;
            }

            foreach (var p in config.GetType().GetProperties())
            {
                if (p.IsDefined(typeof(NonSerializedAttribute), false))
                    continue;
                var d = "";
                if (p.IsDefined(typeof(DescriptionAttribute), false))
                    d = GetDescription(p, config);

                var s = p.DeclaringType.FullName;
                s = s.Substring(0, s.LastIndexOf('.') + 1) + p.Name;
                var v = p.GetValue(config, null);
                if (_quoteStrings && p.PropertyType == typeof(string))
                    v = "\"" + v + "\"";
                AddtoLines(lines, d, s, v);
                _props[s] = "" + v;
            }
        }

        StringBuilder sb = new StringBuilder();
        foreach (var l in lines)
            sb.AppendLine(l.Trim());

        File.WriteAllText(_filename, sb.ToString());
    }

    private static void AddtoLines(List<string> lines, string d, string s, object v)
    {
        bool found = false;
        for (int i = 0; i < lines.Count; i++)
        {
            var l = lines[i];
            if (l.Trim().StartsWith(s))
            {
                found = true;
                lines[i] = s + " = " + v;
                break;
            }
        }
        if (!found)
        {
            lines.Add("");
            if (d != "")
                lines.Add("// " + d);
            lines.Add(s + " = " + v);
        }
    }

    private string GetDescription(PropertyInfo p, object config)
    {
        foreach (var at in p.GetCustomAttributes(false))
        {
            var attrib = at as DescriptionAttribute;

            if (attrib != null)
                return attrib.Description;
        }
        return "";
    }

    private string GetDescription(FieldInfo p, object config)
    {
        foreach (var at in p.GetCustomAttributes(false))
        {
            var attrib = at as DescriptionAttribute;

            if (attrib != null)
                return attrib.Description;
        }
        return "";
    }
}

History

  • 14th June, 2018: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)