Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
.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" />
</services>
</add>
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 notbe 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 serverName="server1">
<services>
<add serviceName="abc" started="true" />
<add serviceName="abc2" started="true" />
</services>
</add>
<add serverName="server3">
<services copyFrom="server1">
<override serviceName="abc" started="false" />
<remove serviceName="abc2" />
</services>
</add>
</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();
}
else
{
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
{
get
{
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]
{
get
{
// Force the get by key, not index
object key = serverName;
return (ServerSettings)base.BaseGet(key);
}
}
public ServerSettings this[int index]
{
get
{
return (ServerSettings)base.BaseGet(index);
}
}
protected override ConfigurationElement CreateNewElement()
{
return new ServerSettings();
}
protected override Object GetElementKey(ConfigurationElement element)
{
return ((ServerSettings)element).ServerName;
}
}
#endregion
#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; }
}
}
#endregion
#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()
: base(StringComparer.OrdinalIgnoreCase)
{
}
// 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;
}
else
return base.OnDeserializeUnrecognizedAttribute(name, value);
}
public string CopyFrom
{
get { return _copyFrom; }
set { _copyFrom = value; }
}
public new ServiceSettings this[string serviceName]
{
get
{
// Force the get by key, not index
object key = serviceName;
return (ServiceSettings)base.BaseGet(key);
}
}
public ServiceSettings this[int index]
{
get
{
return (ServiceSettings)base.BaseGet(index);
}
}
protected override ConfigurationElement CreateNewElement()
{
return new ServiceSettings();
}
protected override Object GetElementKey(ConfigurationElement element)
{
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")
{
ServiceSettings elem = new ServiceSettings(reader);
_removes.Add(elem.ServiceName, elem);
handled = true;
}
else
return base.OnDeserializeUnrecognizedElement(elementName, reader);
return handled;
}
}
#endregion
#region ServiceSettings
public sealed class ServiceSettings : ConfigurationElement
{
private readonly ConfigurationProperty _serviceName =
new ConfigurationProperty("serviceName", typeof(string), String.Empty, null, null,
ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsKey);
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;
}
}
#endregion
}