Search code examples
c#web-config.net-4.5stack-overflowapp-config

StackOverflowException for nestes configuration elements


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)

enter image description here

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.

enter image description here


Solution

  • 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 />