.NET 2.0 settings designer that generates a strongly typed class representing the applicationSetting and userSettings sections is a great improvement over the old appSettings or custom xml serialization code… But, sometimes you just need a bit more…
If you see the configuration below, what would you expect the output to be?
<add serverName="server1">
<services>
<add serviceName="Dhcp" started="true" />
<add serviceName="MSSqlServer" started="true" />
<add serviceName="vds" started="true" />
</services>
</add>
<add serverName="server2">
<services copyFrom="server1">
<override serviceName="MSSqlServer" started="false" />
<remove serviceName="vds" />
Of course, you’d expect something like this:
· On server1, services Dhcp, MSSqlServer and vds should be started
· On server2, Dhcp should be started, MSSqlServer should not be started, and we don’t care about vds.
Now, the question is, how do you create a strongly typed configuration classes that would figure out all the add, remove and override keywords, and allow the client to use it in a very intuitive way… something like this:
ServiceMonitorSettingsSection settings = ServiceMonitorSettingsSection.Settings;
foreach (ServerSettings server in settings.Servers)
{
foreach (ServiceSettings service in server.Services)
System.Diagnostics.Debug.WriteLine(string.Format("Service {0} is expected to be {1}started on server {2}",
service.ServiceName, service.Started == true ? "" : "not ", server.ServerName));
}
Below is the code that does just that!
Here is the full configuration file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="ServiceMonitorSettings" type="WindowsApplication1.ServiceMonitorSettingsSection, WindowsApplication1" />
</configSections>
<ServiceMonitorSettings>
<servers>
<add serviceName="abc" started="true" />
<add serviceName="abc2" started="true" />
<add serverName="server3">
<override serviceName="abc" started="false" />
<remove serviceName="abc2" />
</servers>
</ServiceMonitorSettings>
</configuration>
And here is the actual code:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Xml;
namespace WindowsApplication1
#region ServiceMonitorSettingsSection
public sealed class ServiceMonitorSettingsSection : ConfigurationSection
private readonly ConfigurationProperty _servers =
new ConfigurationProperty("servers", typeof(ServerCollection), null,
ConfigurationPropertyOptions.IsRequired);
public static ServiceMonitorSettingsSection Settings
get
ServiceMonitorSettingsSection result = null;
try
result = (ServiceMonitorSettingsSection)System.Configuration.ConfigurationManager.GetSection("ServiceMonitorSettings");
if (result == null)
throw new ApplicationException("Configuration file is missing 'ServiceMonitorSettings' section", null);
else
// Implement copyFrom
foreach (ServerSettings serverSettings in result.Servers)
string copyFrom = serverSettings.Services.CopyFrom;
if (copyFrom != null && copyFrom.Trim().Length > 0)
ServerSettings from = result.Servers[copyFrom];
if (from != null)
foreach (ServiceSettings fromService in from.Services)
serverSettings.Services.Add(fromService.Clone());
// All done -- set to read only
serverSettings.Services.SetInitialized();
throw new ApplicationException(string.Format("Invalid copyFrom setting. Server {0} doesn't exist in configuration file", copyFrom), null);
catch (Exception ex)
System.Diagnostics.Debugger.Break();
// TODO: log and rethrow
return result;
public ServiceMonitorSettingsSection()
[ConfigurationProperty("servers", Options = ConfigurationPropertyOptions.IsRequired)]
public ServerCollection Servers
return (ServerCollection)base[_servers];
#endregion
#region ServerCollection
[ConfigurationCollection(typeof(ServerSettings))]
public sealed class ServerCollection : ConfigurationElementCollection
public ServerCollection()
: base(StringComparer.OrdinalIgnoreCase)
public new ServerSettings this[string serverName]
// Force the get by key, not index
object key = serverName;
return (ServerSettings)base.BaseGet(key);
public ServerSettings this[int index]
return (ServerSettings)base.BaseGet(index);
protected override ConfigurationElement CreateNewElement()
return new ServerSettings();
protected override Object GetElementKey(ConfigurationElement element)
return ((ServerSettings)element).ServerName;
#region ServerSettings
public sealed class ServerSettings : ConfigurationElement
internal static readonly ConfigurationValidatorBase NonEmptyStringValidator = new StringValidator(1);
private readonly ConfigurationProperty _serverName =
new ConfigurationProperty("serverName", typeof(string), String.Empty, null, null,
ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsKey);
private readonly ConfigurationProperty _services =
new ConfigurationProperty("services", typeof(ServiceCollection), new ServiceCollection(), null, null,
ConfigurationPropertyOptions.None);
public ServerSettings()
[ConfigurationProperty("serverName", Options = ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsKey, DefaultValue = "")]
public string ServerName
get { return (string)base[_serverName]; }
set { base[_serverName] = value; }
[ConfigurationProperty("services", Options = ConfigurationPropertyOptions.None)]
public ServiceCollection Services
get { return (ServiceCollection)base[_services]; }
set { base[_services] = value; }
#region ServiceCollection
[ConfigurationCollection(typeof(ServiceSettings))]
public sealed class ServiceCollection : ConfigurationElementCollection
private string _copyFrom = null;
private Dictionary<string, ServiceSettings> _overrides = new Dictionary<string, ServiceSettings>();
private Dictionary<string, ServiceSettings> _removes = new Dictionary<string, ServiceSettings>();
public ServiceCollection()
// Called by Copy
internal void Add(ServiceSettings element)
this.BaseAdd(element);
protected override void SetReadOnly()
// Ignore, so we can set it after overrides are done...
internal void SetInitialized()
// Implement override
foreach (string serviceName in _overrides.Keys)
if (this[serviceName] != null)
this[serviceName].CopyFrom(_overrides[serviceName]);
// Removes marked items
foreach (string serviceName in _removes.Keys)
base.BaseRemove(serviceName);
base.SetReadOnly();
_overrides = null;
_removes = null;
protected override bool OnDeserializeUnrecognizedAttribute(string name, string value)
if (string.Compare(name, "copyFrom", true) == 0)
_copyFrom = value;
// Handled, for now... The actual copying will be done in ServiceMonitorSettingsSection.Settings
return true;
return base.OnDeserializeUnrecognizedAttribute(name, value);
public string CopyFrom
get { return _copyFrom; }
set { _copyFrom = value; }
public new ServiceSettings this[string serviceName]
object key = serviceName;
return (ServiceSettings)base.BaseGet(key);
public ServiceSettings this[int index]
return (ServiceSettings)base.BaseGet(index);
return new ServiceSettings();
return ((ServiceSettings)element).ServiceName;
protected override bool OnDeserializeUnrecognizedElement(String elementName, XmlReader reader)
bool handled = false;
if (elementName == "override")
ServiceSettings elem = new ServiceSettings(reader);
_overrides.Add(elem.ServiceName, elem);
handled = true;
else if (elementName == "remove")
_removes.Add(elem.ServiceName, elem);
return base.OnDeserializeUnrecognizedElement(elementName, reader);
return handled;
#region ServiceSettings
public sealed class ServiceSettings : ConfigurationElement
private readonly ConfigurationProperty _serviceName =
new ConfigurationProperty("serviceName", typeof(string), String.Empty, null, null,
private readonly ConfigurationProperty _started =
new ConfigurationProperty("started", typeof(bool), true, ConfigurationPropertyOptions.None);
public ServiceSettings()
public ServiceSettings(XmlReader reader)
base.DeserializeElement(reader, false);
[ConfigurationProperty("serviceName", Options = ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsKey, DefaultValue = "")]
public string ServiceName
get { return (string)base[_serviceName]; }
set { base[_serviceName] = value; }
[ConfigurationProperty("started", Options = ConfigurationPropertyOptions.None, DefaultValue = true)]
public bool Started
get { return (bool)base[_started]; }
set { base[_started] = value; }
public void CopyFrom(ServiceSettings item)
foreach (ConfigurationProperty prop in item.Properties)
this[prop.Name] = item[prop];
public ServiceSettings Clone()
return this.MemberwiseClone() as ServiceSettings;