This solution uses a Proxy class to wrap the subtype implementations. There is a comment at the bottom that even refers to naming the collection elements differently for the different subtypes, so along the lines of:
<yak>
<whaa hello="world" />
</yak>
<yak>
<whaaChild hello="world" blot="1" />
</yak>
Posting the excerpts below just in case the link dies someday, but please support their blog by trying the link first!
NestedConfiguration.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Text;
using System.Xml;
namespace NestedConfiguration
{
public class CollectionSection : ConfigurationSection
{
[ConfigurationProperty("collection", IsDefaultCollection = false)]
[ConfigurationCollection(typeof(CollectionConfig), AddItemName = "add")]
public CollectionConfig Collection
{
get
{
return (CollectionConfig) this["collection"];
}
set
{
this["collection"] = value;
}
}
}
public class Parent : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
public string Name
{
get
{
return (string) this["name"];
}
set
{
this["name"] = value;
}
}
[ConfigurationProperty("type", IsRequired = true)]
public string Type
{
get
{
return (string) this["type"];
}
set
{
this["type"] = value;
}
}
public void ProxyDeserializeElement(XmlReader reader, bool serializeCollectionKey)
{
DeserializeElement(reader, serializeCollectionKey);
}
}
public class One : Parent
{
[ConfigurationProperty("p1")]
public string P1
{
get
{
return (string)this["p1"];
}
set
{
this["p1"] = value;
}
}
}
public class Two : Parent
{
[ConfigurationProperty("p2")]
public string P2
{
get
{
return (string)this["p2"];
}
set
{
this["p2"] = value;
}
}
}
public class Proxy : ConfigurationElement
{
private Parent _Parent = null;
public Parent Parent
{
get
{
return _Parent;
}
}
public Proxy()
{
}
public Parent Instance
{
get
{
return _Parent;
}
}
protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
{
string type = reader.GetAttribute("type");
switch (type)
{
case "one":
_Parent = new One();
break;
case "two":
_Parent = new Two();
break;
default:
throw new ArgumentException(string.Format("Invalid type: {0}", type));
}
_Parent.ProxyDeserializeElement(reader, serializeCollectionKey);
}
}
public class CollectionConfig : ConfigurationElementCollection
{
public CollectionConfig()
{
}
protected override ConfigurationElement CreateNewElement()
{
return new Proxy();
}
protected override Object GetElementKey(ConfigurationElement element)
{
return ((Proxy)element).Parent.Name;
}
public Parent this[int index]
{
get
{
return ((Proxy)BaseGet(index)).Parent;
}
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
}
}
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="CollectionSection" type="NestedConfiguration.CollectionSection, NestedConfiguration" />
</configSections>
<CollectionSection>
<collection>
<add type="one" name="one-1" p1="one-1 p1" />
<add type="one" name="one-2" p1="one-2 p1" />
<add type="two" name="two-1" p2="two-1 p2" />
</collection>
</CollectionSection>
</configuration>
Program.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Text;
namespace NestedConfiguration
{
class Program
{
static void Main(string[] args)
{
try
{
ExeConfigurationFileMap map = new ExeConfigurationFileMap();
map.ExeConfigFilename = "NestedConfiguration.exe.config";
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
CollectionSection config = (CollectionSection)configuration.Sections[typeof(CollectionSection).Name];
Console.WriteLine("Nested configurations: {0}", config.Collection.Count);
foreach (Proxy proxy in config.Collection)
{
Console.WriteLine("Type: {0}", proxy.Parent.GetType());
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}