Posted by: jaimalchohan on: April 29, 2008
Custom configuration sections were revamped in .net 2.0 and are a great way to validate and manage your applications configuration with a minimal amount of work.
Instead of storing multiple keys in the you can consolidate your keys into their own cleaner and clearer section. Consider the following section. This is a really simple example, the below appSettings would likely contain a mix of other keys.
<appSettings> <add key="euTax" value="on" /> <add key="euRate" value="15" /> <add key="euName" value="Tax" /> <add key="euTaxRegion" value="Europe" /> <add key="ukTax" value="off" /> <add key="ukRate" value="17.5" /> <add key="ukName" value="VAT" /> <add key="ukTaxRegion" value="UK" /> <add key="usTax" value="on" /> <add key="usRate" value="10" /> <add key="usName" value="Tax" /> <add key="usTaxRegion" value="WorldWide" /> </appSettings>
This would be better if it was condensed into
<taxSettings> <euTax mode="on" rate="15" name="Tax" taxRegion ="Europe"/> <ukTax mode="off" rate="17.5" name="VAT" taxRegion ="UK" /> <usTax mode="on" rate="10" name="Tax" taxRegion ="WorldWide" /> </taxSettings>
It’s clear that the TaxSettings configuration is a lot easier to read than the appSettings. Its also easier to manage and maintain.
With appSettings, if a key was missing how would you handle that? You’d have to create a class to access the keys, throw exceptions if required key is missing, write code to use default values if a non-required key was missing; essentially create your own Configuration Management Class.
.Net alrady hs this built it! By inheriting the ConfigurationSection and ConfigurationElement types, you can let .Net do a lot of the work for you.
Step 1 – Create classes to represent your config data
its pretty much your standard class format. I’ve defined a TaxSettings class almost as a base for each countries tax settings, and then in the TaxConfiguration class i’ve defined TaxSettings for EuTax, UkTax and UsTax.
public class TaxConfiguration
{
public TaxSetting EuTax
{
get { return ; }
}
public TaxSetting UkTax
{
get { return ; }
}
public TaxSetting UsTax
{
get { return ; }
}
}
public class TaxSetting
{
public Mode Mode
{
get { return ; }
}
public string Name
{
get { return ; }
}
public decimal Rate
{
get { return ; }
}
public TaxRegionType TaxRegion
{
get { return ; }
}
}
public enum TaxRegionType
{
UK,
Europe,
WorldWide
}
public enum Mode
{
On,
Off
}
Step 2 – Inherit & Decorate
Your the class which will represent your root node should inherit from ConfigurationSection
, with other classes which represent child nodes inheriting from ConfigurationElement.
Decorate each property you want to retrive from the config with ConfigurationProperty, defining the name of the node or attribute that you will use in the .config file. .Net will automagically map your .config file to your custom classes, to return a particular item just return this["nameOfyourNode"] and cast it to the appropriate type. (nameOfYourNode = the same name you used in the attribute)
The ConfigurationProperty has a number of parameters including DefaultValue and IsRequired which you can set too. I’ve also included a StringValidator on one of the attributes, theres a Regex Validator and IntValidator avaliable to use too you can even create your own!
public class TaxConfiguration : ConfigurationSection
{
[ConfigurationProperty("euTax")]
public TaxSetting EuTax
{
get { return (TaxSetting)this["euTax"]; }
}
[ConfigurationProperty("ukTax")]
public TaxSetting UkTax
{
get { return (TaxSetting)this["ukTax"]; }
}
[ConfigurationProperty("usTax")]
public TaxSetting UsTax
{
get { return (TaxSetting)this["usTax"]; }
}
}
public class TaxSetting : ConfigurationElement
{
[ConfigurationProperty("mode", DefaultValue = Mode.On, IsRequired = false)]
public Mode Mode
{
get { return (Mode)this["mode"]; }
}
[ConfigurationProperty("name", IsRequired = true)]
[StringValidator(MaxLength=10, InvalidCharacters="?@~#%$£&*()!_-'£")]
public string Name
{
get { return (string)this["name"]; }
}
[ConfigurationProperty("rate", IsRequired = true)]
public decimal Rate
{
get { return (decimal)this["rate"]; }
}
[ConfigurationProperty("taxRegion", IsRequired = true)]
public TaxRegionType TaxRegion
{
get { return (TaxRegionType)this["taxRegion"]; }
}
}
public enum TaxRegionType
{
UK,
Europe,
WorldWide
}
public enum Mode
{
On,
Off
}
Step 3 – Configure Your .Config
Add a configSection to your .config, the name can be anything you want and will be use to map to the class derived from ConfigurationSection (TaxConfiguration in our case), the type consists of 2 parts, the first is the full name of the class deriving from ConfigurationSection, the second is the name of the Assembly (you can get this from the project properties dialog)
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="TaxSettings" type="ConfigurationTest.TaxConfiguration, ConfigurationTest" />
</configSections>
<taxSettings>
<euTax mode="On" rate="15" name="Tax" taxRegion ="Europe"/>
<ukTax mode="Off" rate="17.5" name="VAT" taxRegion ="UK" />
<usTax mode="On" rate="10" name="Tax" taxRegion ="WorldWide" />
</taxSettings>
</configuration>
Step 4: Access your Custom Configuration
What I usually do is wrap this up in a static class (as static properties) so that I don’t have to cast the ConfigSection to my type every time I want to access the data.
class Program
{
static void Main(string[] args)
{
//You could do this everytime you needed to access the config data
decimal UkTaxRate = ((TaxConfiguration)ConfigurationManager.GetSection(“TaxSettings”)).UkTax.Rate;
//Or you could do this, by creating a static class as below
string taxName = MyConfiguration.EuTaxSettings.Name;
}
}
public static class MyConfiguration
{
public static TaxSetting EuTaxSettings
{
get { return ((TaxConfiguration)ConfigurationManager.GetSection(“TaxSettings”)).EuTax; }
}
public static TaxSetting UkTaxSettings
{
get { return ((TaxConfiguration)ConfigurationManager.GetSection(“TaxSettings”)).UkTax; }
}
public static TaxSetting UsTaxSettings
{
get { return ((TaxConfiguration)ConfigurationManager.GetSection(“TaxSettings”)).UsTax; }
}
}
Collections
What I’ve done above is okay, but it’s not easy to extend. Every time I wasnt to add a new country Ill have to create new properties in the TaxConfiguration and MyConiguration classes. What I’d rather do is have the following fomat so I don’t have to edit my Configuration handlers every time I need to add a new country:
<taxSettings>
<settings>
<add key="EU" mode="On" rate="15" name="Tax" taxRegion ="Europe"/>
<add key="UK" mode="Off" rate="17.5" name="VAT" taxRegion ="UK" />
<add key="US" mode="On" rate="10" name="Tax" taxRegion ="WorldWide" />
</settings>
</taxSettings>
To do this were going to have to inheirt from ConfigurationElementCollection. The complete modified class is below.
class Program
{
static void Main(string[] args)
{
string taxName = MyConfiguration.TaxSettings["EU"].Name;
}
}
public static class MyConfiguration
{
public static TaxSettingsCollection TaxSettings
{
get { return ((TaxConfiguration)ConfigurationManager.GetSection(“TaxSettings”)).TaxSettingsCollection; }
}
}
public class TaxConfiguration : ConfigurationSection
{
[ConfigurationProperty("Settings", IsDefaultCollection=false)]
[ConfigurationCollection(typeof(TaxSetting))]
public TaxSettingsCollection TaxSettingsCollection
{
get { return (TaxSettingsCollection)this["Settings"]; }
}
}
public class TaxSettingsCollection : ConfigurationElementCollection
{
[ConfigurationProperty("euTax")]
public TaxSetting EuTax
{
get { return (TaxSetting)this["euTax"]; }
}
public TaxSetting this[int index]
{
get { return (TaxSetting)BaseGet(index); }
}
public TaxSetting this[string key]
{
get {
for(int i = 0; i < Count; i++)
if (this[i].Key == key)
return this[i];
throw new KeyNotFoundException();
}
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((TaxSetting)element).Key;
}
protected override ConfigurationElement CreateNewElement()
{
return new TaxSetting();
}
}
public class TaxSetting : ConfigurationElement
{
[ConfigurationProperty("key", IsRequired=true, IsKey=true)] //note the use of IsKey
public string Key
{
get { return (string)this["key"]; }
}
[ConfigurationProperty("mode", DefaultValue = Mode.On, IsRequired = false)]
public Mode Mode
{
get { return (Mode)this["mode"]; }
}
[ConfigurationProperty("name", IsRequired = true)]
[StringValidator(MaxLength=10, InvalidCharacters="?@~#%$£&*()!_-'£")]
public string Name
{
get { return (string)this["name"]; }
}
[ConfigurationProperty("rate", IsRequired = true)]
public decimal Rate
{
get { return (decimal)this["rate"]; }
}
[ConfigurationProperty("taxRegion", IsRequired = true)]
public TaxRegionType TaxRegion
{
get { return (TaxRegionType)this["taxRegion"]; }
}
}
public enum TaxRegionType
{
UK,
Europe,
WorldWide
}
public enum Mode
{
On,
Off
}