Search code examples
c#configapp-configconfigsection

How to create a nested configuration section in app.config file? Error: "Unrecognized attribute"


I have a requirement to create a nested configuration section. The issue is that I need to write an application that accesses any number of databases. These could be oracle, sql, or anything else... I want to make my config section look like this:

<connectionconfigurations>
    <databaseConnection dbname="connection1" dbsourceConnect="connectionstring1" provider="sql">
      <sqlQueries>
        <add name="querynumber1"
             sqlFilePath="C:\Users\me\Desktop\sql"/>
        <add name="querynumber2"
             sqlFilePath="C:\Users\me\Desktop\sql"/>
      </sqlQueries>
    </databaseConnection>
    <databaseConnection dbname="connection1" dbsourceConnect="connectionstring2" provider="oracle">
      <sqlQueries>
        <add name="querynumber3"
             sqlFilePath="C:\Users\me\Desktop\oracle"/>
        <add name="querynumber4"
             sqlFilePath="C:\Users\me\Desktop\oracle"/>
      </sqlQueries>
    </databaseConnection>
  </connectionconfigurations>

The issue is that I am having trouble accessing all my parameters. How do I create nested config sections like this and access them through code?

I've made a class like this to handle the connection section:

public class Connectionconfigurations : ConfigurationSection
    {
        private static ConfigurationPropertyCollection _connectionConfiguration;
        private static ConfigurationPropertyCollection _sqlQueries;
        private static ConfigurationPropertyCollection _properties;



        static Connectionconfigurations()
        {

            _connectionConfiguration = new ConfigurationProperty(
                                       ConfigConstants.CONFIG_DATABASECONNECTION,
                                       typeof(DatabaseConnectionElementCollection),
                                       null,
                                       ConfigurationPropertyOptions.IsRequired);

            _sqlQueries = new ConfigurationProperty(
                         ConfigConstants.CONFIG_SQLQUERIES,
                         typeof(DatabaseConnectionElementCollection),
                         null,
                         ConfigurationPropertyOptions.IsRequired);

            _properties = new ConfigurationPropertyCollection();

            // Add other properties
            _properties.Add(_databaseConnection);
        }

        [ConfigurationProperty("databaseConnection")]
        public DatabaseConnectionElementCollection DatabaseConnection
        {
            get { return (DatabaseConnectionElementCollection)base[_databaseConnection]; }
        }
    }

However, I am getting the error: "Unrecognized attribute 'dbname'. Note that attribute names are case-sensitive."

DatabaseConnectionElement class:

   public class DatabaseConnectionElement : ConfigurationElement
    {

        private static ConfigurationProperty _name;
        private static ConfigurationProperty _sourceConnect;
        private static ConfigurationProperty _provider;
        private static SqlQueryElementCollection _sqlCollection; // Collection of sql queries for this DB element
        private static ConfigurationPropertyCollection _properties;

        static DatabaseConnectionElement()
        {
            _name = new ConfigurationProperty(ConfigConstants.CONFIG_DB_NAME, typeof(string), string.Empty,
                ConfigurationPropertyOptions.IsKey);

            _sourceConnect = new ConfigurationProperty(ConfigConstants.CONFIG_DB_SOURCECONNECT, typeof(string), string.Empty,
                ConfigurationPropertyOptions.IsRequired);

            _provider = new ConfigurationProperty(ConfigConstants.CONFIG_DB_PROVIDER, typeof(string), string.Empty,
                ConfigurationPropertyOptions.IsRequired);

            _sqlCollection = new SqlQueryElementCollection();

            _properties = new ConfigurationPropertyCollection();
            _properties.Add(_name);
            _properties.Add(_sourceConnect);
            _properties.Add(_reconnectDelay);
        }


        [ConfigurationProperty("dbname", IsKey = true)]
        public string Name
        {
            get
            {
                return (string)base[_name];
            }
        }

        [ConfigurationProperty("dbsourceConnect", IsRequired = true)]
        public string SourceConnect
        {
            get
            {
                return (string)base[_sourceConnect];
            }
        }

        [ConfigurationProperty("provider", IsRequired = true)]
        public string Provider
        {
            get
            {
                return (string)base[_provider];
            }
        }

        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                return _properties;
            }
        }
    }

Solution

  • I think this might be helpful. I created a generic config section that has a list of subelements. I just called them elements. You could call them whatever you want.

    /// <summary>
    /// Defines a generic custom configuration section with a collection of elements of type T.
    /// Reference: http://www.abhisheksur.com/2011/09/writing-custom-configurationsection-to.html
    /// </summary>
    public class GenericSection<T> : ConfigurationSection
        where T : ConfigurationElement, IConfigurationElement, new()
    {
    
        // Attribute argument must be a constant expression.
        protected const string _elementsTag = "elements";
    
        public GenericSection()
        {
        }
    
    
        [ConfigurationProperty(_elementsTag, Options = ConfigurationPropertyOptions.IsDefaultCollection)]
        public GenericElementCollection<T> Elements
        {
            get { return ((GenericElementCollection<T>)(base[_elementsTag])); }
            set { base[_elementsTag] = value; }
        }
    
    }
    

    Here's the Generic Element collection.

    /// <summary>
    /// Defines a generic ConfigurationElementCollection
    /// </summary>
    [ConfigurationCollection(typeof(IConfigurationElement))]
    public class GenericElementCollection<T> : ConfigurationElementCollection
        where T : ConfigurationElement, IConfigurationElement, new()
    {
        internal const string _elementName = "elements";
    
        protected override string ElementName
        {
            get { return _elementName; }
        }
    
        public override ConfigurationElementCollectionType CollectionType
        {
            get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
        }
    
        protected override bool IsElementName(string elementName)
        {
            return elementName.Equals(_elementName, StringComparison.InvariantCultureIgnoreCase);
        }
    
        public override bool IsReadOnly()
        {
            return false;
        }
    
        protected override ConfigurationElement CreateNewElement()
        {
            return new T();
        }
    
        /// <summary>
        /// Return key value for element.
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        /// <remarks></remarks>
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((T)element).Name;
        }
    
        /// <summary>
        /// Default index property.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T this[int index]  // #c160704 was IConfigruationElement
        {
            get { return (T)BaseGet(index); }
        }
    
    
        /// <summary>
        /// Returns content element by name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public T GetElementByName(string name)
        {
            return (T)BaseGet(name);
        }
    
    
        public IEnumerable<T> Elements
        {
            get
            {
                for (int index = 0; index < this.Count; index++) yield return (T)BaseGet(index);
            }
        }
    
        /// <summary>
        /// Add an element to the collection
        /// </summary>
        /// <param name="element"></param>
        public void AddElement(T element)
        {
            BaseAdd(element);
        }
    
    
    }
    

    This is the interface I use for the Elements. It requires that the element have a Name property and an active flag.

    public interface IConfigurationElement
    {
        string Name { get; set; }
        bool Active { get; set; }
    }
    

    Here's a an example of instantiating and using the generic to create configuration section that has a list of folders.

    Instantiation the GenericSection with the Element Type.

    /// <summary>
    /// Defines a custom configuration section for folder elements.
    /// Reference: http://www.abhisheksur.com/2011/09/writing-custom-configurationsection-to.html
    /// </summary>
    public class FolderSection : GenericSection<FolderElement>
    {
        // This section doesn't require any addition properties.
    }
    

    We don't need to do anything with the GenericElementCollection.

    Here's the FolderElement:

    /// <summary>
    /// Defines a configuration folder
    /// </summary>
    public class FolderElement : ConfigurationElement, IConfigurationElement
    {
        protected const string NameKey = "name";
        protected const string VolumeKey = "volume";
        protected const string PathKey = "path";
    
        [ConfigurationProperty(NameKey, DefaultValue = "", IsKey = true, IsRequired = true)]
        public string Name
        {
            get { return (string)base[NameKey]; }
            set { base[NameKey] = value; }
        }
    
        [ConfigurationProperty(VolumeKey, DefaultValue = "", IsKey = false, IsRequired = false)]
        public string VolumeLabel
        {
            get { return (string)base[VolumeKey]; }
            set { base[VolumeKey] = value; }
        }
    
        [ConfigurationProperty(PathKey, DefaultValue = "", IsKey = false, IsRequired = true)]
        public string Path
        {
            get { return (string)base[PathKey]; }
            set { base[PathKey] = value; }
        }
    
        [ConfigurationProperty("active", DefaultValue = "true", IsKey = false, IsRequired = false)]
        public bool Active
        {
            get { return (bool)base["active"]; }
            set { base["active"] = value; }
        }
    }
    

    Here's what my App.config looks like. I have two folder sections.

    <configuration>
      <configSections>
        <section name="recent-folders" type="Common.Config.FolderSection, Common.Core"/>
        <section name="hidden-folders" type="Common.Config.FolderSection, Common.Core"/>
      </configSections>
    ...
      <hidden-folders>
        <elements>
            <add name="folder1" volume="OS" path="C:\some\hidden\path" />
            <add name="folder2" volume="OS" path="C:\some\other\hidden\path" />
        </elements>
      </hidden-folders>
      <recent-folders>
        <elements>
            <add name="folder1" volume="OS" path="C:\Some\path" />
            <add name="folder2" volume="OS" path="C:\Some\other\path" />
        </elements>
      </recent-folders>
    </configururation>