Introduction
This article presents some of the solutions I found in trying to use custom Configuration Sections, and explains how the process can be simplified for the novice who doesn’t have the time to learn them thoroughly. I am greatly indebted to the many authors whose articles about Configuration Sections enabled me to eventually understand what the MSDN documentation so eloquently obscured. Reading the introductory materials from one or two of their articles (Mysteries of Configuration by John Rista and Read/Write App.Config File with .NET 2.0 by Alois Kraus are great resources) can help in understanding the material below, although it isn’t essential.
The presentation of information here reflects the evolution of my code, although in a more organized sequence than I experienced. Each step of the way added more functionality and understanding. As you read through the progression, feel free to jump out at the level that addresses your needs. If you know or find a simpler way to accomplish things, please let me know so I can add it to my knowledge and include it in updates to this article.
Background
The configuration file is an XML document for storing data relevant to how your application behaves. The file name is your application’s EXE file name plus the extension “.config”, like MyProg.exe.config. When you add an “app.config” file to your project, its contents becomes this “exe.config” file’s content.
The simplest, and most useless, configuration file contains only the basic structure of the XML document.
="1.0"="utf-8"
<configuration>
</configuration>
The beginner’s first use of configuration files usually makes use of the appSettings
section. This is a pre-defined section with some accessor tools built in to the .NET Framework. In this section, you can store parameters that “usually” aren’t modified, but you want to be able to modify them under special circumstances without rebuilding the application. A simple use might be as follows:
="1.0"="utf-8"
<configuration>
<appSettings>
<add key="Port" value="COM2"/>
<add key="Level" value="3"/>
</appSettings>
</configuration>
This defines two parameters that the application might access using the following code:
string port = Configuration.AppSettings["Port"];
int mevLevel = int.Parse(Configuration.AppSettings["Level"]);
You can distribute your application with a default set of values in the configuration file, and modify those values when necessary for a given installation. Note that parameters in the appSettings
section are stored and returned as strings. When converting to other types, be sure to provide appropriate exception handling.
You can also update these values programmatically if you want to allow users to modify them, although it is not as straightforward as you might want. You have to instantiate a Configuration
object to be able to save the changes, and property values have to be accessed through a different route. This is jumping ahead of where we are in our explanation, but the code looks like this:
Configuration config =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings["Port"].Value = "COM4";
config.Save(ConfigurationSaveMode.Modified);
The application configuration file is combined with other configuration files that the Framework knows about to make the complete Configuration
object, but people interested in that information are already well beyond the complexity I want to address here.
The “Hello, World” ConfigurationSection
My foray into custom configuration sections started with the recognition that the appSettings
section of the configuration file for our application was getting cluttered with a lot of disorganized data. This is one of the reasons the custom section exists. The first iteration created a simple class derived from ConfigurationSection
, something like this:
public class SomeSettings : ConfigurationSection
{
private SomeSettings() { }
[ConfigurationProperty("FillColor", DefaultValue="Cyan")]
public System.Drawing.Color FillColor
{
get { return (System.Drawing.Color)this["FillColor"]; }
set { this["FillColor"] = value; }
}
[ConfigurationProperty("TextSize", DefaultValue="8.5")]
public float TextSize
{
get { return (float)this["TextSize"]; }
set { this["TextSize"] = value; }
}
[ConfigurationProperty("FillOpacity", DefaultValue="40")]
public byte FillOpacity
{
get { return (byte)this["FillOpacity"]; }
set { this["FillOpacity"] = value; }
}
}
This is pretty basic, allowing us to use a configuration file that looks like this:
="1.0"="utf-8"
<configuration>
<configSections>
<section name="SomeSettings" type="MyApp.SomeSettings, SomeSettings" />
</configSections>
<SomeSettings FillColor="LightBlue" TextSize="9.5" FillOpacity="50" />
</configuration>
The settings we defined appear as an XML node, with attributes corresponding to the properties we included. There is also a new section named configSections
that describes our new XML node to the Framework. (Understanding the section
node took some work that I don’t wish you to repeat. Ignore that section for now, and we’ll take care of it later.) You can now access these data in your code with the following:
Configuration config =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
SomeSettings gui = (SomeSettings)config.Sections["SomeSettings"];
float fSize = gui.TextSize;
Note that even though the values are strings in the configuration file, the properties of the SomeSettings
class give us type-safe access to our parameters. The parsing is done for you. There are validating attributes, both simple and complex, that can also be applied to the properties to reduce the validation work we have to do in our code. Look at the MSDN documentation on IntegerValidator
for a starting point on understanding those.
If a few items are all you need, you don’t need this, and you can stick with the built-in appSettings
section. I had a lot more things to organize.
Adding More Data
Adding more properties to the SomeSettings
class adds more attributes to the XML node, and eventually makes it rather messy to read. We are looking for easy-to-read organization. Adding more classes to hold the additional data adds another section
node under configSections
for each such class. If your data fits that organization, that is the approach you want, but I want to group a bunch of related subgroups in one section. This can be done by deriving from the ConfigurationElement
class.
public class SomeSettings : ConfigurationElement
{
private SomeSettings() { }
[ConfigurationProperty("FillColor", DefaultValue="Cyan")]
public System.Drawing.Color FillColor
{
get { return (System.Drawing.Color)this["FillColor"]; }
set { this["FillColor"] = value; }
}
[ConfigurationProperty("TextSize", DefaultValue="8.5")]
public float TextSize
{
get { return (float)this["TextSize"]; }
set { this["TextSize"] = value; }
}
[ConfigurationProperty("FillOpacity", DefaultValue="40")]
public byte FillOpacity
{
get { return (byte)this["FillOpacity"]; }
set { this["FillOpacity"] = value; }
}
}
Note that the ConfigurationElement
class looks just like the ConfigurationSection
class does. All we changed was the base class. Our gain comes from using the element as a ConfigurationProperty
in the ConfigurationSection
.
public class MySection : ConfigurationSection
{
private MySection() { }
[ConfigurationProperty("Sector")]
public SomeSettings SectorConfig
{
get { return (SomeSettings)this["Sector"]; }
}
}
The exe.config file can now contain something like this:
="1.0"="utf-8"
<configuration>
<configSections>
<section name="MySection" type="MyApp.MySettings, MySection" />
</configSections>
<MySection>
<Sector FillColor="Cyan" TextSize="8.5" FillOpacity="40" />
</MySection>
</configuration>
Note that I can now use the node name of “Sector” in the configuration file instead of “SomeSettings” as it previously was. I can also give the corresponding property a different name, like “SectorConfig”. This is enough for you to access a configuration field with the following code:
Configuration config =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
MySection gui = (MySection)config.Sections["MySection"];
float fSize = gui.SectorConfig.TextSize;
SomeSettings set1 = gui.SectorConfig;
At this level, it doesn’t look much better than the first version, but now, I can create other ConfigurationElement
classes and add them to MySection
without making the file harder to understand, as we’ll see below.
Exploiting Laziness
As I hinted above, I don’t want to have to figure out the <configSections>
part of the configuration file, especially when I can get the system to do it for me. We also want a means of more easily accessing the section, without having to invoke OpenExeConfiguration
every time I need one of these parameters in a new part of the code. The following additions to the MySection
class provide this, along with some important flexibility:
public static MySection Open()
{
System.Reflection.Assembly assy =
System.Reflection.Assembly.GetEntryAssembly();
return Open(assy.Location);
}
public static MySection Open(string path)
{
if ((object)instance == null)
{
if (path.EndsWith(".config",
StringComparison.InvariantCultureIgnoreCase))
path = path.Remove(path.Length - 7);
Configuration config =
ConfigurationManager.OpenExeConfiguration(path);
if (config.Sections["MySection"] == null)
{
instance = new MySection();
config.Sections.Add("MySection", instance);
config.Save(ConfigurationSaveMode.Modified);
}
else
instance = (MySection)config.Sections["MySection"];
}
return instance;
}
#region Fields
private static MySection instance;
#endregion Fields
Now, we have the ability to access the section with this code:
MySection gui = MySection.Open();
float fSize = gui.SectorConfig.TextSize;
SomeSettings set1 = gui.SectorConfig;
The following features are worthy of note here:
- The configuration file doesn’t have to contain any of the custom data for this to work. The first time we access this
MySection.Open()
method, the Framework will insert all the necessary overhead into the configuration file for us. Sweet. If you have defined default values for all your properties, this is all you need to use the custom section. (And, it gets even sweeter below.) - The configuration section is accessed and saved in a static variable. Other segments of our code can call the same
MySection.Open()
method and obtain the same set of data. Changes made in one part of your code can be read by other parts. - By including the overload
MySection.Open(string path)
, I could access this ConfigurationSection
in any configuration file I choose. You’ll see how I exploited this later. The test for path.EndsWith(...)
comes in handy in those situations.
User Customization and Saving
Now that we have simple access to some nicely organized data, it would be even nicer if we could allow the user to customize his/her settings without manually editing the configuration file. Doing this requires that we have a way to save the changed settings. I touched on this earlier when discussing the appSettings
section. After a few false starts associated with the access of the Section in multiple threads (where the application sometimes had to be restarted for the changes to take effect), I ended up with the following Save
method that crosses thread boundaries like I wanted:
public class MySection : ConfigurationSection
{
...
public void Save()
{
Configuration config =
ConfigurationManager.OpenExeConfiguration(spath);
MySection section = (MySection)config.Sections["MySection"];
section.SectorConfig = this.SectorConfig;
config.Save(ConfigurationSaveMode.Full);
}
[ConfigurationProperty("Sector")]
public SomeSettings SectorConfig
{
get { return (SomeSettings)this["Sector"]; }
set { this["Sector"] = value; }
}
#region Fields
private static string spath;
private static MySection instance;
#endregion Fields
}
Not shown above is that the static spath
field is populated from the edited string parameter in the Open(string path)
method. The Save
method opens the configuration file, copies the values of all properties from the current instance of the Section to the newly opened one, and then writes the changes back to the file. To be able to assign the properties, we added a set
accessor to the SectorConfig
property. (My early versions tried keeping the Configuration
object static, but that did not produce the desired results, and it had to be made a dynamic object.) Saving new values is now this easy:
MySection settings = MySection.Open();
settings.SectorConfig.FillOpacity = (byte)75;
settings.Save();
One beautiful benefit of this results from the ConfigurationSaveMode.Full
argument in the config.Save
invocation. Because of this, with our first Save
to an empty configuration file, the Framework will create the complete section for us with all the default values we specified, and nicely formatted too. How sweet it is! I use this feature to generate the lines that I then copy into my app.config file. Note that this trick requires that all properties have default values. You can leave the mode as Full
, or change it to Modified
if you want; I haven’t noticed any discernable performance hit for it.
As an alternative to the assignment for the SectorConfig
property (and each of the other properties you may add later) in the Save
method, I have been experimenting with the following code which requires less custom editing when I repeat this in another application:
public void AltSave()
{
Configuration config = ConfigurationManager.OpenExeConfiguration(spath);
MySection section = (MySection)config.Sections["MySection"];
section.LockItem = true;
foreach (ConfigurationProperty prop in section.Properties)
{
string name = prop.Name;
section.SetPropertyValue(section.Properties[name], this[name], false);
}
config.Save(ConfigurationSaveMode.Full);
}
I can’t say yet that this works all the time, but it has so far in the few tests I’ve run, even with nested elements (see Adding Data Complexity below). The call to section.SetPropertyValue(...)
is necessary because the section.Properties
collection is read-only, and its elements can’t be modified directly.
Goof Protection
If the user can now alter (i.e., mess up) the settings, you want to give them a way to restore the defaults. This is easily accomplished with the following additions:
public static MySection Default
{
get { return defaultInstance; }
}
private readonly static MySection defaultInstance = new MySection();
Now, the default values can be accessed through the fields of MySection.Default
.
settings.SectorConfig.FillColor = MySection.Default.SectorConfig.FillColor;
Beware, however, of using code like the following to reset all the values to defaults:
settings = MySection.Default;
Both sides of that assignment are class instances, and any future attempts to modify settings
will result in an exception being thrown because you will be attempting to change the read-only field defaultInstance
. To avoid that risk, we can either access the default values individually, or use the following Copy
method:
public MySection Copy()
{
MySection copy = new MySection();
string xml = SerializeSection(this, "MySection",
ConfigurationSaveMode.Full);
System.Xml.XmlReader rdr =
new System.Xml.XmlTextReader(new System.IO.StringReader(xml));
copy.DeserializeSection(rdr);
return copy;
}
And, resetting the default values all at one time can now be done through this code:
settings = MySection.Default.Copy();
This is not the easiest way to do this, but it has a rather obvious fringe benefit of being able to make an independent copy of the configuration section (no matter how complex), and it also shows a use of the ConfigurationSection.SerializeSection
method. You could have the Default
property simply return a new instance of the class, as shown below, but that would instantiate yet another object every time you wanted a default value.
public static MySection Default
{
get { return new MySection(); }
}
Adding Data Complexity
So now, we have a custom section, and can access it easily. Next, let’s add some more complex organization to the section elements. First are a couple new ConfigurationElement
definitions, starting with a simple ConfigurationElement
having two properties.
public class AShapeSetting : ConfigurationElement
{
public AShapeSetting() { }
[ConfigurationProperty("Shape", DefaultValue="Circle")]
public string Shape
{
get { return this["Shape"]; }
set { this["Shape"] = value; }
}
[ConfigurationProperty("Size", DefaultValue="12")]
public int Size
{
get { return (int)this["Size"]; }
set { this["Size"] = value; }
}
}
The second ConfigurationElement
also has two simple properties, but adds two instances of our new AShapeSetting
element type.
public class ShapeSettings : ConfigurationElement
{
public ShapeSettings() { }
[ConfigurationProperty("SizeMultiple", DefaultValue="2")]
public int SizeMultiple
{
get { return (int)this["SizeMultiple"]; }
set { this["SizeMultiple"] = value; }
}
[ConfigurationProperty("Enable", DefaultValue="Yes")]
private string pEnable
{
get { return this["Enable"].ToString(); }
set { this["Enable"] = value; }
}
public bool Enable
{
get { return pEnable == "Yes"; }
set { pEnable = value ? "Yes" : "No"; }
}
[ConfigurationProperty("DevA")]
public AShapeSetting DevA
{
get { return (AShapeSetting)this["DevA"]; }
}
[ConfigurationProperty("DevB")]
public AShapeSetting DevB
{
get { return (AShapeSetting)this["DevB"]; }
}
}
We add this second element to the MySection
class as follows:
public class MySection : ConfigurationSection
{
private MySection() { }
public static MySection Open() ...
public static MySection Open(string path) ...
public MySection Copy() ...
public void Save()
{
Configuration config =
ConfigurationManager.OpenExeConfiguration(spath);
MySection section = (MySection)config.Sections["MySection"];
section.SectorConfig = this.SectorConfig;
section.UnitShapeConfig = this.UnitShapeConfig;
config.Save(ConfigurationSaveMode.Full);
}
[ConfigurationProperty("Sector")]
public SomeSettings SectorConfig ...
[ConfigurationProperty("UnitShapes")]
public ShapeSettings UnitShapeConfig
{
get { return (ShapeSettings)this["UnitShapes"]; }
set { this["UnitShapes"] = value; }
}
public static MySection Default ...
#region Fields
private static MySection instance;
private readonly static MySection defaultInstance = new MySection();
#endregion Fields
}
And, our configuration file section can now look like this:
<MySection>
<Sector FillColor="Cyan" TextSize="8.5" FillOpacity="40" />
<UnitShapes SizeMultiple="2" Enable="Yes">
<DevA Shape="Circle" Size="12" />
<DevB Shape="Star" Size="14" />
</UnitShapes>
</MySection>
Some niceties to note here include:
- Embedded multiple levels of organization that are easy to follow. The
UnitShapes
element contains its own properties of SizeMultiple
and Enable
, plus two elements of AShapeSetting
with their own properties, Shape
and Size
. These two occurrences of AShapeSetting
could be put in the more flexible ConfigurationElementCollection
, but that is outside my current scope and, for now, the number of occurrences is fixed. - The use of a private configuration property to translate between the boolean values True/False and the more intuitive strings Yes/No.
- The independence of the property name (
UnitShapeConfig
), the ConfigurationProperty
identifier (UnitShapes
), and the property type (ShapeSettings
), although it is usually helpful to have at least two of them match.
Do I Have To Do It Again?!
As we began to utilize this structure in more and more places, I got tired of copying the common code and changing the names. From that came a code template to create a starting custom configuration section, with code like that shown below:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Text;
namespace yourApp
{
class CustomConfigSection : ConfigurationSection
{
private CustomConfigSection() { }
#region Public Methods
public static CustomConfigSection Open()
{
System.Reflection.Assembly assy =
System.Reflection.Assembly.GetEntryAssembly();
return Open(assy.Location);
}
public static CustomConfigSection Open(string path)
{
if ((object)instance == null)
{
if (path.EndsWith(".config",
StringComparison.InvariantCultureIgnoreCase))
spath = path.Remove(path.Length - 7);
else
spath = path;
Configuration config = ConfigurationManager.OpenExeConfiguration(spath);
if (config.Sections["CustomConfigSection"] == null)
{
instance = new CustomConfigSection();
config.Sections.Add("CustomConfigSection", instance);
config.Save(ConfigurationSaveMode.Modified);
}
else
instance =
(CustomConfigSection)config.Sections["CustomConfigSection"];
}
return instance;
}
public CustomConfigSection Copy()
{
CustomConfigSection copy = new CustomConfigSection();
string xml = SerializeSection(this,
"CustomConfigSection1", ConfigurationSaveMode.Full);
System.Xml.XmlReader rdr =
new System.Xml.XmlTextReader(new System.IO.StringReader(xml));
copy.DeserializeSection(rdr);
return copy;
}
public void Save()
{
Configuration config = ConfigurationManager.OpenExeConfiguration(spath);
CustomConfigSection section =
(CustomConfigSection)config.Sections["CustomConfigSection"];
section.Sample = this.Sample;
config.Save(ConfigurationSaveMode.Modified);
}
#endregion Public Methods
#region Properties
public static CustomConfigSection Default
{
get { return defaultInstance; }
}
[ConfigurationProperty("Sample", DefaultValue = "sample string property")]
public string Sample
{
get { return (string)this["Sample"]; }
set { this["Sample"] = value; }
}
#endregion Properties
#region Fields
private static string spath;
private static CustomConfigSection instance = null;
private static readonly CustomConfigSection defaultInstance =
new CustomConfigSection();
#endregion Fields
}
}
Download this template and put the zip file in your ItemTemplates directory. For VS 2005, the default location of this directory is My Documents\Visual Studio 2005\Templates\ItemTemplates. (There must be a cleaner way... if I could just find it.) When you next do an “Add Item…” to your project, the CustomConfigSection
will show up under the heading “My Templates”.
A Sweet Discovery
The overloads for Open
came in handy when setting the options for a Service. Although this isn’t really about configuration files, it is a usage example, and carries some fringe benefit training with it.
The target configuration file is MyService.exe.config. I want to edit the MySettings
section using another application, MyServiceController.exe. This is done using the following (slightly modified) excerpt from MyServiceController
:
cntrl = new System.ServiceProcess.ServiceController("MyService");
RegistryKey HKLM_System = Registry.LocalMachine.OpenSubKey("System");
RegistryKey HKLM_CCS = HKLM_System.OpenSubKey("CurrentControlSet");
RegistryKey HKLM_Services = HKLM_CCS.OpenSubKey("Services");
RegistryKey HKLM_Service = HKLM_Services.OpenSubKey(cntrl.ServiceName);
string path = (string)HKLM_Service.GetValue("ImagePath");
path = path.Replace("\"", "");
mySettings = MySettings.Open(path);
The use of cntrl.ServiceName
in this instance is not necessary, but shows how you can get there from a ServiceController
object. From there, I can modify mySettings
and save the modifications, and the service will read the new settings the next time it runs.
History
- 13 Jan. 2009 -- Original version.