← Back to articles

Simple app.config appSettings deserializer (with tree structure)

Reading appSettings shouldn't require custom sections, XML readers or per-access string conversion. A small base class that maps keys like Config.SmtpClient.Host to a typed tree, converted once at startup.

Tedd

There are many ways to read configuraiton files in .Net. Using ConfigurationManager you can easily read for example appSettings section. But usually I want this to be in managed code. The solution often suggested is to create separate sections with a varying degree of manual work, field mapping and xml readers.

Many of the solutions also suggest that you read and convert the type from string on every access, which gives you an overhead if you access config variables a lot. So I figured I’d throw together something simple that works. A baseclass that supports a tree structure without all the hassle of creating sections, defining types and names, etc… It reads and converts the variables once at startup to avoid conversion overhead on every access of properties.

In short, Config.SmtpClient.Host  corresponds to appSettings value "SmtpClient.Hosts ". You can add as many levels as you like.

app.config with appSettings section

The config variable names corresponds to properties in the classes. When there is a hierarchy of classes the names in the path will be separated by a dot.

<configuration>
 <appSettings>
 <add key="SmtpClient.Host" value="localhost" />
 <add key="SpecialWebApi.Url" value="https://some.api.org/" />
 </appSettings>
</configuration> 

Creating the config object

Config = new ConfigModel();

The implementation

In the implementation of config model we can create a hierarchy of models. As long as they inherid DictionaryConfigReader  and override the mandetory base class constructor they will automatically read config variables corresponding to their name.

public class ConfigModel : DictionaryConfigReader
{
 public ConfigModel(Dictionary<string, string> config = null, string prefix = "") : base(config, prefix) { }

 public SmtpClientModel SmtpClient { get; private set; }
 public SpecialWebApiModel SpecialWebApi { get; private set; }
}

public class SmtpClientModel : DictionaryConfigReader
{
 public SmtpClientModel(Dictionary<string, string> config = null, string prefix = "") : base(config, prefix) { }

 public string Host { get; private set; }
}

public class SpecialWebApiModel : DictionaryConfigReader
{
 public SpecialWebApiModel(Dictionary<string, string> config = null, string prefix = "") : base(config, prefix) { }

 public string Url { get; private set; }
}

Baseclass

public abstract class DictionaryConfigReader
{
 public static Dictionary<string, string> GetConfig()
 {
 var ret = new Dictionary<string, string>();
 foreach (var key in ConfigurationManager.AppSettings.AllKeys)
 ret.Add(key, ConfigurationManager.AppSettings[key]);
 return ret;
 }

 public DictionaryConfigReader(Dictionary<string, string> config = null, string prefix = "")
 {
 if (config == null)
 config = DictionaryConfigReader.GetConfig();

 if (prefix == null)
 prefix = "";
 if (!string.IsNullOrEmpty(prefix))
 prefix += ".";

 var type = this.GetType();
 string value;
 foreach (var property in type.GetProperties())
 {
 // Child objects are populated similarly
 if (property.PropertyType.BaseType == typeof(DictionaryConfigReader))
 {
 var prefix2 = prefix + property.Name;
 var obj = Activator.CreateInstance(property.PropertyType, config, prefix2);
 property.SetValue(this, obj, null);
 continue;
 }

 if (config.TryGetValue(prefix + property.Name, out value))
 {
 // Here we are converting a string into a Type. If we have any special types then Convert.ChangeType or TypeConverter probably will fail.
 // In these cases, use something like this:
 //if (property.PropertyType == typeof(AwesomeClass))
 //{
 // property.SetValue(this, new AwesomeClass(value), null);
 // continue;
 //}


 // Some takes on dynamic type conversion

 // Version 1
 //var typeConverter = TypeDescriptor.GetConverter(property.PropertyType);
 //object propValue = typeConverter.ConvertFromString(value);
 //property.SetValue(this, propValue);

 // Version 2
 //property.SetValue(this, Convert.ChangeType(value, property.PropertyType), null);

 // Version 3: With support for nullable types
 var t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
 var safeValue = (value == null) ? null : Convert.ChangeType(value, t);
 property.SetValue(this, safeValue, null);
 }

 }
 }
}

Written by Tedd · Oslo, Norway

Updated 31 Oct 2017

Follow the journal via RSS