Tuesday, August 14, 2012

AppSettingsReader class: Get typed values from app.config

Hello World,

I came across this question in one of our coffee table discussions that it should be possible using .NET framework class library to return typed values from application configuration file (app.config) instead of getting all values as string and then typecasting. Turns out, the AppSettingsReader class is tailor made for this.

The following is a sample app.config:


   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:      <appSettings>
   4:      <add key="doWork" value="true"/>
   5:      <add key="name" value="DoGood"/>
   6:      <add key="sleepFor" value="2"/>
   7:      </appSettings>
   8:  </configuration>
 
 
The sample code below shows how the values for the keys can be read as typed:


   1:  using System;
   2:  using System.Configuration;
   3:   
   4:  namespace ReaderExample
   5:  {
   6:      class Program
   7:      {
   8:          static void Main(string[] args)
   9:          {
  10:              Console.WriteLine("----------Reading Values-----------");
  11:              WriteKeyValue("doWork", typeof(System.Boolean));
  12:              WriteKeyValue("name", typeof(System.String));
  13:              WriteKeyValue("sleepFor", typeof(System.Int32));
  14:   
  15:              Console.WriteLine("----------Reading Value's Types-----------");
  16:              DisplayTypeOfValueForKey("doWork", typeof(System.Boolean));
  17:              DisplayTypeOfValueForKey("name", typeof(System.String));
  18:              DisplayTypeOfValueForKey("sleepFor", typeof(System.Int32));
  19:              Console.ReadKey();
  20:          }
  21:   
  22:          static void WriteKeyValue(string keyName, Type typeOfValue)
  23:          {
  24:              AppSettingsReader appSettingsReader = new AppSettingsReader();
  25:              var theValue = appSettingsReader.GetValue(keyName, typeOfValue);
  26:              Console.WriteLine("The value of key {0} is {1}", keyName, theValue);
  27:          }
  28:   
  29:          static void DisplayTypeOfValueForKey(string keyName, Type typeOfValue)
  30:          {
  31:              AppSettingsReader appSettingsReader = new AppSettingsReader();
  32:              var theValue = appSettingsReader.GetValue(keyName, typeOfValue);
  33:              Console.WriteLine("The type of value of key {0} is {1}", keyName, theValue.GetType());
  34:          }
  35:      }
  36:  }
Where can this be used?
Well, for a start, we all have written code such as below:

   1:  bool doWork = Convert.ToBoolean(ConfigurationManager.AppSettings["doWork"]);
We can do this in a clearer way as below:
   1:  try
   2:  {
   3:    bool? isValidBoolean = appSettingsReader.GetValue("doWork", typeof(System.Boolean)) as bool?;
   4:  }
   5:  catch (InvalidOperationException ex)
   6:  {
   7:    Console.WriteLine("Could not convert!!");
   8:  }
Now, if you check isValidBoolean.HasValue and it returns true, you know you have a valid bool value. isValidBoolean.Value would give you the actual value after you've confirmed that you have a value. One thing though: GetValue method throws an exception if it finds a value that cannot be converted to the specified type.
Does not look too promising? Wait, there's more: Let me show you an implementation using Generics where we can get typed values from app.config, raise exceptions/log if key is not found and even return a default value if the value found is not suitable:
   1:  class TypedAccessor<T>
   2:      {
   3:          // typesafe AppSettingsReader supporting default values
   4:          T _defaultValue;
   5:          bool _required;
   6:          bool _warn;
   7:          string _keyName;
   8:          readonly AppSettingsReader _reader;
   9:   
  10:          /// <summary>
  11:          /// Initializes a new instance of the <see cref="TypedAccessor&lt;T&gt;"/> class.
  12:          /// </summary>
  13:          /// <param name="reader">The reader.</param>
  14:          /// <param name="keyName">Name of the key.</param>
  15:          /// <param name="warnIfKeyNotFound">if set to <c>true</c> [warn if key not found].</param>
  16:          /// <param name="isRequired">if set to <c>true</c> [is required].</param>
  17:          /// <param name="defaultValue">The default value.</param>
  18:          public TypedAccessor(AppSettingsReader reader, string keyName, bool warnIfKeyNotFound, bool isRequired, T defaultValue)
  19:          {
  20:              // An additional parameter Logger can be added to trace what is happening here
  21:              _defaultValue = defaultValue;
  22:              _required = isRequired;
  23:              _warn = warnIfKeyNotFound;
  24:              _keyName = keyName;
  25:              _reader = reader;
  26:          }
  27:   
  28:          /// <summary>
  29:          /// Fetches this instance.
  30:          /// </summary>
  31:          /// <returns>A value of Type T</returns>
  32:          public T Fetch()
  33:          {
  34:              T value;
  35:   
  36:              if (_warn && !ConfigurationManager.AppSettings.AllKeys.Contains(_keyName))
  37:              {
  38:                  Console.WriteLine("The Key {0} is not present in the app.config file.", _keyName);
  39:                  throw new ApplicationException(string.Format("The Key {0} is not present in the app.config file.", _keyName));
  40:              }
  41:              
  42:              try
  43:              {
  44:                  value = (T)this._reader.GetValue(this._keyName, typeof(T));
  45:              }
  46:              catch (System.Exception ex)
  47:              {
  48:                  if (_warn)
  49:                  {
  50:                      // Log in a real application
  51:                      Console.WriteLine("Error encountered while fetching value of the Key {0}. Default value will be returned.", _keyName);
  52:                  }
  53:   
  54:                  if (_required)
  55:                  {
  56:                      // Log in a real application
  57:                      Console.WriteLine("The Key {0} is required, but is not present in the app.config file", _keyName);
  58:                  }
  59:   
  60:                  value = _defaultValue;
  61:              }
  62:   
  63:              return value;
  64:          }
  65:      }
How does that look? Looks very promising to me!!
More ideas on making this even more useful:
1) Most (if not all) of the config values would be of a built-in datatype, so an additional check can be added to check if a value is of a built-in type E.g. this stackoverflow discussion
2) You may want to encapsulate the logic of returning a default value to another type something like the below:
a) An enum of key names
b) A Dictionary containing the keyname, default value
c) A method on this type to return the default value for a specified key

Happy Coding

No comments:

Post a Comment