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.
ConfigManager cm = new ConfigManager("properties.config", true);
cm.LoadProperties(new ClientEngine.Config());
cm.LoadProperties(new StorageEngine.Config());
ClientEngine.Config.TCPPort = 120;
var s = ClientEngine.Config.TempFolder;
var ss = StorageEngine.Config.TempFolder;
var sss = cm.GetProperty("ClientEngine.TCPPort");
cm.Save();
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:
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:
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());
}
}
}
}
}
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