Introduction
I seem to be putting an ever increasing amount of appSettings keys into my web and app.config files these days, and often these keys take the form of a list of settings that can be grouped together and apply to a single object/class. For example, we had an application that generates a report containing Excel chart objects and the colour palettes for these charts need to be user, or at least client, configurable. To achieve this with appSettings keys only we'd end up with a list resembling the following: –
<add key="chart9index1" value="Blue1"/>
<add key="chart9index2" value="Orange1"/>
<add key="chart9index3" value="Green1"/>
<add key="chart9index4" value="Pink1"/>
<add key="chart9index5" value="Blue3"/>
<add key="chart9index6" value="Orange3"/>
These settings define the colours used for a single chart in the report, so we'd need a list like this for each chart in the application. The first issue with this approach is that the list gets unwieldy pretty quickly; imagine if the application had twenty or so charts (which it does). Secondly, we're tied to the one key/one value structure, suppose we also wanted to define what the red, green and blue components were of the colour Blue1. We would need something along the lines of: –
<add key="Blue1R" value="63"/>
<add key="Blue1G" value="166"/>
<add key="Blue1B" value="204"/>
Subsequently, we'd then need to write the code to read these values and associate them with the colour Blue1. Wouldn't it be nicer if we could write something like: –
<add key="Blue1" R="63" G="166" B="204"/>
<add key="Blue1" R="63" G="166" B="204"/>
Well, you can't, at least, not in the appSettings section of your config file. You can however create your own configuration section and do exactly this.
The configSections element
<configuration>
<configSections>
<sectionGroup name="colors">
<section name="custom" type="StuartWhiteford.Reporting.Custom, StuartWhiteford.Reporting"/>
<section name="palettes" type="StuartWhiteford.Reporting.ReportConsole.Palettes, StuartWhiteford.Reporting.ReportConsole"/>
</sectionGroup>
</configSections>
...
</configuration>
Within our main configuration element we've added a configSections element. This will contain the definitions for any custom configuration sections that we want to include. Within this element we have a sectionGroup element and named it colors. The sectionGroup isn't a requirement, but it does give us a way of organising our sections logically.
The ConfigurationSection class
Delving deeper, we get to the section elements, and you'll notice that these elements have a type attribute. This identifies the classes that we will de-serialise our subsequent configuration entries into. With hindsight giving a class the name Custom is a bit daft, it tells you precisely nothing about it. However, the code for this class is as follows: –
public class Custom : ConfigurationSection
{
public static Custom GetConfig()
{
return ConfigurationManager.GetSection("colors/custom") as Custom;
}
[ConfigurationProperty("customColors")]
public CustomColorCollection CustomColors
{
get
{
return this["customColors"] as CustomColorCollection;
}
}
}
Not much to it. Firstly, we've inherited from the ConfigurationSection class, which provides the mechanisms for serialising and de-serialising to and from our config file. Secondly we require a way to access this data in code, so we have the GetConfig method that returns an object of type Custom, de-serialised from the information contained in the section with the name custom that resides in the sectionGroup colors. Generically we'd call ConfigurationManager.GetSection(<sectionGroup name>/<section name>
) as <Class>
omitting the sectionGroup if necessary.
Lastly, we define what we can put into our custom section. In this case we have a customColors element which de-serialises as a CustomColorCollection object, shown below: –
public class CustomColorCollection : ConfigurationElementCollection
{
public CustomColor this[int index]
{
get
{
return base.BaseGet(index) as CustomColor;
}
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}
public CustomColor this[object name]
{
get
{
return base.BaseGet(name) as CustomColor;
}
}
protected override ConfigurationElement CreateNewElement()
{
return new CustomColor();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((CustomColor)element).Name;
}
}
Since this is a collection object, the two public properties allow us to retrieve a CustomColor object either by its index or name. The CreateNewElement method returns a new instance of the CustomColor class and finally the GetElementKey method identifies which property of our CustomColor class we will use as the key in the collection, in this case we'll use the Name. The CustomColor class is as follows: –
public class CustomColor : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
public string Name
{
get
{
return this["name"] as string;
}
}
[ConfigurationProperty("R", IsRequired = true)]
public int R
{
get
{
return (int)this["R"];
}
}
[ConfigurationProperty("G", IsRequired = true)]
public int G
{
get
{
return (int)this["G"];
}
}
[ConfigurationProperty("B", IsRequired = true)]
public int B
{
get
{
return (int)this["B"];
}
}
public Color ToColor()
{
return Color.FromArgb(this.R, this.G, this.B);
}
public int ToColorRef()
{
return (this.R + (this.G << 8) + (this.B << 16));
}
}
We have four properties: Name, R, G and B, all hopefully self-explanatory. We also have two methods that aren't exposed in the XML.ToColor(), which returns a System.Drawing.Color object consisting of the defined red, green and blue values and ToColorRef(), which returns an integer that Excel understands as a colour.
The final piece of the puzzle to make this all work is the section in the config file that will describe our custom colours: –
<colors>
<custom>
<customColors>
<add name="Blue1" R="63" G="166" B="204"/>
<add name="Blue2" R="108" G="188" B="216"/>
<add name="Blue3" R="155" G="210" B="229"/>
<add name="Blue4" R="210" G="234" B="242"/>
<add name="Orange1" R="240" G="106" B="0"/>
<add name="Orange2" R="243" G="152" B="68"/>
...
</customColors>
</custom>
</colors>
Now, in our application code, we can call CustomColorCollection _colors = Custom.GetConfig().CustomColors;
and we've loaded all our stunning custom colours into a single object. No more messing around with ConfigurationManager.AppSettings[] calls and string manipulation. If we need the Blue2 Color object all we need to do is call _colors["Blue2"].ToColor();
.
Conclusion
Granted, this is a fair amount of work for something so simple, but hopefully it has shown how you can store application configuration data, that you might have previously used a database for, in its proper place.