Im trying to make configuration with nested elemets. but parent-child relation is one-to-one, not one-to-many. Meaning storageProvider could have only one nestedProvider.
Nesting level is not limited.
...
<store storeName="123">
<storageProvider type="disk">
<nestedProvider type="share">
<nestedProvider type="amazon-s3">
</nestedProvider>
</nestedProvider>
</storageProvider>
</store>
...
Question When I create StorageProviderElement with NestedProvider property and try to read configuration I catch StackOverflowException in mscorlib. Like there is a bug in .NET (im using .NET 4.5)
Am I doing something wrong or it is expected behavior?
At this point I had to change this property to a collection (like you do in any other way) but still I wonder why I can't make nested one-to-one elements.
Code: StoreElement
public class StoreElement : ConfigurationElement
{
private const string storeName = "storeName";
private const string storageProvider = "storageProvider";
[ConfigurationProperty(storeName, IsKey = true, IsRequired = true)]
public string StoreName
{
get
{
return (string)base[storeName];
}
}
[ConfigurationProperty(storageProvider, IsRequired = true)]
public StorageProviderElement StorageProvider
{
get
{
return (StorageProviderElement)this[storageProvider];
}
}
}
StorageProviderElement (this is the one with recursion)
public class StorageProviderElement : ConfigurationElement
{
private const string type = "type";
private const string options = "options";
private const string nestedProvider = "nestedProvider";
[ConfigurationProperty(type, IsRequired = true)]
public string Type
{
get
{
return (string)base[type];
}
}
[ConfigurationProperty(options, IsDefaultCollection = false, IsRequired = false)]
public GenericConfigurationElementCollection<StorageProviderOptionElement> Options
{
get
{
return (GenericConfigurationElementCollection<StorageProviderOptionElement>) this[options];
}
}
// this is what trigger stack overflow exception
[ConfigurationProperty(nestedProvider, IsDefaultCollection = false, IsRequired = false)]
public StorageProviderElement NestedProvider
{
get
{
return (StorageProviderElement)this[nestedProvider];
}
}
}
UPDATE: Screenshot showing why the StackOverflowException is difficult to debug.
The source of this exception is this method of ConfigurationElement
:
private static ConfigurationProperty CreateConfigurationPropertyFromAttributes(PropertyInfo propertyInformation)
{
ConfigurationProperty configurationProperty = (ConfigurationProperty) null;
if (Attribute.GetCustomAttribute((MemberInfo) propertyInformation, typeof (ConfigurationPropertyAttribute)) is ConfigurationPropertyAttribute)
configurationProperty = new ConfigurationProperty(propertyInformation);
if (configurationProperty != null && typeof (ConfigurationElement).IsAssignableFrom(configurationProperty.Type))
{
ConfigurationPropertyCollection result = (ConfigurationPropertyCollection) null;
ConfigurationElement.PropertiesFromType(configurationProperty.Type, out result);
}
return configurationProperty;
}
It checks if there is ConfigurationProperty
attribute on given property and if yes and property type inherits from ConfigurationElement
(your case) - it recursively inspects that property type again. If property type is the same as outer class type - recursion never ends and causes stackoverflow exception.
So in short - you cannot do this (will throw stackoverflow immediately when you try to get corresponding section, without actually calling any of your methods):
public class StorageProviderElement : ConfigurationElement
{
[ConfigurationProperty("whatever")]
public StorageProviderElement Whatever
{
get;
}
}
Looks like a bug to me indeed, not sure, maybe there is some valid reasoning behind that, but I cannot find any.
Short example to reproduce:
class Program {
static void Main(string[] args) {
// throws
ConfigurationManager.GetSection("store");
}
}
public class StoreElement : ConfigurationSection
{
[ConfigurationProperty("storageProvider")]
public StorageProviderElement StorageProvider { get; }
}
public class StorageProviderElement : ConfigurationElement {
[ConfigurationProperty("whatever")]
public StorageProviderElement Whatever { get; }
}
In app.config
<configSections>
<section name="store" type="ConsoleApp4.StoreElement, ConsoleApp4"/>
</configSections>
<store />