0

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;
            }
        }
    }
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Hooplator15
  • 1,540
  • 7
  • 31
  • 58
  • What have you tried? What problems did you encounter when trying these things? "I am having trouble accessing all my parameters" is unclear. – Jakotheshadows Jul 21 '16 at 20:27
  • Start by subclassing [`ConfigurationSection`](https://msdn.microsoft.com/en-us/library/system.configuration.configurationsection(v=vs.110).aspx) and populating with subclasses of `ConfigurationElement`. Collections are a little more complex but doable. [Also some tutorials can be found](https://msdn.microsoft.com/en-us/library/2tw134k3(v=vs.100).aspx). – Richard Jul 21 '16 at 20:27
  • I updated the question with the class I had to hadel the app.config along with the error message I am getting. – Hooplator15 Jul 21 '16 at 20:39
  • In order to resolve, we would need to see the entity that DatabaseConnectionElementCollection groups together, assuming it's DatabaseConnectionElement class. Could you post that? – Brian Mains Jul 21 '16 at 20:46
  • Ok, I've posted that class. I feel like the issue is that I am not properly loading the multiple nested collections. As in, there are multiple "sqlQueries" inside each dbconnection collection. – Hooplator15 Jul 21 '16 at 21:00

1 Answers1

1

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>
Darrel Lee
  • 2,372
  • 22
  • 22
  • 1
    This was very helpful. This along with this guide: http://blogs.quovantis.com/net-creating-a-custom-configuration-section-that-contains-a-collection-of-collections/ brought me to the solution. I'm not going to post it here since it ended up being a mess of 5 diffrent classes, but if you follow the guide in the link to the letter it shuold work perfectly. Thank you for the help! – Hooplator15 Jul 22 '16 at 15:14