Correct implementation of a custom config section with nested collections?
Asked Answered
O

2

48

In a web application, I want to be able to define some mapping using a config section like this:

<configuration>
    <configSections>
        <sectionGroup name="MyCustomer">
            <section name="CatalogMappings" type="MyCustom.MyConfigSection" />
        </sectionGroup>
    </configSections>
    <MyCustomer>
        <catalogMappings>
            <catalog name="toto">
                <mapping value="1" displayText="titi" />
                <mapping value="2" displayText="tata" />
            </catalog>
            <catalog name="toto2">
                <mapping value="1" displayText="titi2" />
                <mapping value="2" displayText="tata2" />
            </catalog>
        </catalogMappings>
    </MyCustomer>
</configuration>

I'm struggling to achieve this goal, especially when defining my collection of collections. What are the classes that I need to implement to achieve this?

Currently, I have:

public class CatalogMappingSection : System.Configuration.ConfigurationSection
{
    public class Mapping : ConfigurationElement
    {
        [ConfigurationProperty("externalKey")]
        public string ExternalKey { get; set; }
        [ConfigurationProperty("displayText", IsRequired=true)]
        public string DisplayText { get; set; }
        [ConfigurationProperty("value", IsRequired=true, IsKey=true)]
        public int Value { get; set; }
    }

    public class Catalog : ConfigurationElementCollection
    {
        [ConfigurationProperty("name", IsRequired=true, IsKey=true)]
        public string Name { get; set; }

        protected override ConfigurationElement CreateNewElement()
        {
            return new Mapping();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((Mapping)element).Value;
        }
    }

    public class CatalogCollection : ConfigurationElementCollection
    {
        [ConfigurationProperty("catalog")]
        [ConfigurationCollection(typeof(Catalog))]
        public Catalog CatalogMappingCollection
        {
            get
            {
                return (Catalog)base["catalog"];
            }
        }

        protected override ConfigurationElement CreateNewElement()
        {
            return new Catalog();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((Catalog)element).Name;
        }
    }

    [ConfigurationProperty("catalogMappings")]
    [ConfigurationCollection(typeof(CatalogCollection))]
    public CatalogCollection CatalogMappings
    {
        get
        {
            return (CatalogCollection)base["catalogMappings"];
        }
    }
}

But, this is not working as expected.

Oberammergau answered 14/4, 2011 at 10:1 Comment(1)
Did you get it working? Can you post the completed code if possible?Pastose
P
76

I finally found this guy's example. It was coded and worked right out of the box.

http://manyrootsofallevilrants.blogspot.com/2011/07/nested-custom-configuration-collections.html

I am going to paste the code here......only because I cannot stand it when someone says "Your answer is here", and the link is dead.

Please try his website first, and leave a "thank you" if it works.

Note, I also have a "Usa-States and Usa-Counties" example (an answer to THIS SOF question) if you want a "more well known" (object model) example.

using System;
using System.Configuration;
 
namespace SSHTunnelWF
{
    public class TunnelSection : ConfigurationSection
    {
        [ConfigurationProperty("", IsDefaultCollection = true)]  
        public HostCollection Tunnels
        {
            get
            {
                HostCollection hostCollection = (HostCollection)base[""];
                return hostCollection;                
            }
        }
    }
 
    public class HostCollection : ConfigurationElementCollection
    {
        public HostCollection()
        {
            HostConfigElement details = (HostConfigElement)CreateNewElement();
            if (details.SSHServerHostname != "")
            {
                Add(details);
            }
        }
 
        public override ConfigurationElementCollectionType CollectionType
        {
            get
            {
                return ConfigurationElementCollectionType.BasicMap;
            }
        }
 
        protected override ConfigurationElement CreateNewElement()
        {
            return new HostConfigElement();
        }
 
        protected override Object GetElementKey(ConfigurationElement element)
        {
            return ((HostConfigElement)element).SSHServerHostname;
        }
 
        public HostConfigElement this[int index]
        {
            get
            {
                return (HostConfigElement)BaseGet(index);
            }
            set
            {
                if (BaseGet(index) != null)
                {
                    BaseRemoveAt(index);
                }
                BaseAdd(index, value);
            }
        }
 
        new public HostConfigElement this[string name]
        {
            get
            {
                return (HostConfigElement)BaseGet(name);
            }
        }
 
        public int IndexOf(HostConfigElement details)
        {
            return BaseIndexOf(details);
        }
 
        public void Add(HostConfigElement details)
        {
            BaseAdd(details);
        }

        protected override void BaseAdd(ConfigurationElement element)
        {
            BaseAdd(element, false);
        }
 
        public void Remove(HostConfigElement details)
        {
            if (BaseIndexOf(details) >= 0)
                BaseRemove(details.SSHServerHostname);
        }
 
        public void RemoveAt(int index)
        {
            BaseRemoveAt(index);
        }
 
        public void Remove(string name)
        {
            BaseRemove(name);
        }
 
        public void Clear()
        {
            BaseClear();
        }
 
        protected override string ElementName
        {
            get { return "host"; }
        }
    }
 
    public class HostConfigElement:ConfigurationElement
    {
        [ConfigurationProperty("SSHServerHostname", IsRequired = true, IsKey = true)]
        [StringValidator(InvalidCharacters = "  ~!@#$%^&*()[]{}/;’\"|\\")]
        public string SSHServerHostname
        {
            get { return (string)this["SSHServerHostname"]; }
            set { this["SSHServerHostname"] = value; }
        }
 
        [ConfigurationProperty("username", IsRequired = true)]
        [StringValidator(InvalidCharacters = "  ~!@#$%^&*()[]{}/;’\"|\\")]
        public string Username
        {
            get { return (string)this["username"]; }
            set { this["username"] = value; }
        }

        [ConfigurationProperty("SSHport", IsRequired = true, DefaultValue = 22)]
        [IntegerValidator(MinValue = 1, MaxValue = 65536)]
        public int SSHPort
        {
            get { return (int)this["SSHport"]; }
            set { this["SSHport"] = value; }
        }
 
        [ConfigurationProperty("password", IsRequired = false)]
        public string Password
        {
            get { return (string)this["password"]; }
            set { this["password"] = value; }
        }
 
        [ConfigurationProperty("privatekey", IsRequired = false)]
        public string Privatekey
        {
            get { return (string)this["privatekey"]; }
            set { this["privatekey"] = value; }
        }
 
        [ConfigurationProperty("privatekeypassphrase", IsRequired = false)]
        public string Privatekeypassphrase
        {
            get { return (string)this["privatekeypassphrase"]; }
            set { this["privatekeypassphrase"] = value; }
        }
 
        [ConfigurationProperty("tunnels", IsDefaultCollection = false)]
        public TunnelCollection Tunnels
        {
            get { return (TunnelCollection)base["tunnels"]; }
        }
    }
 
    public class TunnelCollection : ConfigurationElementCollection
    {
        public new TunnelConfigElement this[string name]
        {
            get
            {
                if (IndexOf(name) < 0) return null;
                return (TunnelConfigElement)BaseGet(name);
            }
        }
 
        public TunnelConfigElement this[int index]
        {
            get { return (TunnelConfigElement)BaseGet(index); }
        }
 
        public int IndexOf(string name)
        {
            name = name.ToLower();
 
            for (int idx = 0; idx < base.Count; idx++)
            {
                if (this[idx].Name.ToLower() == name)
                    return idx;
            }
            return -1;
        }
 
        public override ConfigurationElementCollectionType CollectionType
        {
            get { return ConfigurationElementCollectionType.BasicMap; }
        }
 
        protected override ConfigurationElement CreateNewElement()
        {
            return new TunnelConfigElement();
        }
 
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((TunnelConfigElement)element).Name;
        }
 
        protected override string ElementName
        {
            get { return "tunnel"; }
        }
    }
 
    public class TunnelConfigElement : ConfigurationElement
    {        
        public TunnelConfigElement()
        {
        }
 
        public TunnelConfigElement(string name, int localport, int remoteport, string destinationserver)
        {
            this.DestinationServer = destinationserver;
            this.RemotePort = remoteport;
            this.LocalPort = localport;            
            this.Name = name;
        }
 
        [ConfigurationProperty("name", IsRequired = true, IsKey = true, DefaultValue = "")]       
        public string Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }        
 
        [ConfigurationProperty("localport", IsRequired = true, DefaultValue =1)]
        [IntegerValidator(MinValue = 1, MaxValue = 65536)]
        public int LocalPort
        {
            get { return (int)this["localport"]; }
            set { this["localport"] = value; }
        }
 
        [ConfigurationProperty("remoteport", IsRequired = true, DefaultValue =1)]
        [IntegerValidator(MinValue = 1, MaxValue = 65536)]
        public int RemotePort
        {
            get { return (int)this["remoteport"]; }
            set { this["remoteport"] = value; }
        }
 
        [ConfigurationProperty("destinationserver", IsRequired = true)]
        [StringValidator(InvalidCharacters = "  ~!@#$%^&*()[]{}/;’\"|\\")]
        public string DestinationServer
        {
            get { return (string)this["destinationserver"]; }
            set { this["destinationserver"] = value; }
        }
    }
}

And the configuration code

 <?xml version="1.0"?>
 <configuration>
   <configSections>
     <section name="TunnelSection" type="SSHTunnelWF.TunnelSection,SSHTunnelWF" />
   </configSections>
   <TunnelSection>
     <host SSHServerHostname="tsg.edssdn.net" username="user" SSHport="22" password="pass" privatekey="" privatekeypassphrase="">
       <tunnels>
         <tunnel name="tfs" localport="8081"  remoteport="8080" destinationserver="tfs2010.dev.com"  />
         <tunnel name="sql" localport="14331"  remoteport="1433" destinationserver="sql2008.dev.com"  />
         <tunnel name="crm2011app" localport="81"  remoteport="80" destinationserver="crm2011betaapp.dev.com"  />
       </tunnels>
     </host>
     <host SSHServerHostname="blade16" username="root" SSHport="22"  password="pass" privatekey="" privatekeypassphrase="">
      <tunnels>
        <tunnel name="vnc" localport="5902"  remoteport="5902" destinationserver="blade1.dev.com" />
      </tunnels>
     </host>
   </TunnelSection>
 </configuration>

And then the "call"

TunnelSection tunnels = ConfigurationManager.GetSection("TunnelSection") as TunnelSection
Pastose answered 8/2, 2013 at 22:0 Comment(7)
For clarity, the class <element> nesting order is: TunnelSection <TunnelSection> => HostCollection => HostConfigElement <host> => TunnelCollection <tunnels> => TunnelConfigElement <tunnel>Monocyclic
The key for me here was setting CollectionType on the nested collection to ConfigurationElementCollectionType.BasicMap. Without it I kept getting Unrecognized element 'tunnel'Amine
@Amine Yep. Same thing in my other answer too. " public class UsaCountyCollection : ConfigurationElementCollection, IEnumerable<UsaCountyConfigurationElement> { public override ConfigurationElementCollectionType CollectionType { get { return ConfigurationElementCollectionType.BasicMap; } }"Pastose
Link to the ConfigurationElementCollectionType for future readers learn.microsoft.com/en-us/dotnet/api/…Pastose
I'm looking for a solution of the original question as I have similar issue. The answer doesn't work for this. In the question "catalog" element have both children (it would indicate ConfigurationElementCollection) and its own attribute "name" (it would indicate ConfigurateElement). The answer is correct but not for this case as "host" have only one children (not collection) "tunnels" and the "tunnels" element contain collection of childrens but it doesnt have any attributes. Still looking for some solution.Sty
@rob I would advise to post a NEW (and thoughtful) question on SOF.Pastose
This was super useful and fixed the issue I was having with #72520909. Thanks!Knavery
P
9

Due to the popularity of my other answer, I've written a custom example using Usa-States and Usa-Counties. An example that's a tad easier to follow.

I've also added some interfaces, which allows some mocking for unit-tests, something I wasn't fully aware of when I wrote this original answer.

I also added putting the settings in its own file, so your app.config file doesn't have a billion things in it.

I've also added a "Finder" to encapsulate searching for a specific item in the collection.

I've also added a custom Enum.

"UsaStatesSettings.config" (make sure you mark this as "copy-always")

<?xml version="1.0" encoding="utf-8" ?>
<UsaStateDefinitionConfigurationSectionName>
  <usaStateDefinition usaStateFullName="Virginia" usaStateAbbreviation="VA" countyLabelName="County" usaStateDefinitionUniqueIdentifier="111" isContenential="true" >
    <usaCounties>
      <usaCounty usaCountyValue="Bath" />
      <usaCounty usaCountyValue="Bedford" />
      <usaCounty usaCountyValue="Bland" />
    </usaCounties>
  </usaStateDefinition>
  <!-- -->
  <usaStateDefinition usaStateFullName="North Carolina" usaStateAbbreviation="NC" countyLabelName="County" usaStateDefinitionUniqueIdentifier="222" isContenential="true" >
    <usaCounties>
      <usaCounty usaCountyValue="Catawba" />
      <usaCounty usaCountyValue="Chatham" />
      <usaCounty usaCountyValue="Chowan" />
    </usaCounties>
  </usaStateDefinition>
  <!-- -->
  <usaStateDefinition usaStateFullName="Alaska" usaStateAbbreviation="AK" countyLabelName="Borough" usaStateDefinitionUniqueIdentifier="333" isContenential="false" >
    <usaCounties>
      <usaCounty usaCountyValue="Aleutians East" />
    </usaCounties>
  </usaStateDefinition>
  <!-- -->
  <usaStateDefinition usaStateFullName="Lousiania" usaStateAbbreviation="LA" countyLabelName="Parish" usaStateDefinitionUniqueIdentifier="444" isContenential="true" >
    <usaCounties>
      <usaCounty usaCountyValue="Acadia" />
    </usaCounties>
  </usaStateDefinition>
</UsaStateDefinitionConfigurationSectionName>

The "app.config"

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>

    <section name="UsaStateDefinitionConfigurationSectionName" type="GranadaCoder.CustomConfigurationExample.Configuration.UsaStateDefinitionConfigurationSection,GranadaCoder.CustomConfigurationExample.Configuration" />
  </configSections>


  <UsaStateDefinitionConfigurationSectionName configSource="UsaStatesSettings.config" />

  <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

and now the "code"

/**/

// file="CaseInsensitiveEnumConfigurationValueConverter.cs"

using System;
using System.ComponentModel;
using System.Configuration;
using System.Globalization;

namespace GranadaCoder.CustomConfigurationExample.Configuration
{
    public class CaseInsensitiveEnumConfigurationValueConverter<T> : ConfigurationConverterBase
    {
        public override object ConvertFrom(ITypeDescriptorContext ctx, CultureInfo ci, object data)
        {
            return Enum.Parse(typeof(T), (string)data, true);
        }
    }
}


/**/


// file="UsaCountyCollection.cs"


using System.Collections.Generic;
using System.Configuration;

namespace GranadaCoder.CustomConfigurationExample.Configuration
{
    [ConfigurationCollection(typeof(UsaCountyConfigurationElement))]
    public class UsaCountyCollection : ConfigurationElementCollection, IEnumerable<UsaCountyConfigurationElement>
    {
        public override ConfigurationElementCollectionType CollectionType
        {
            get { return ConfigurationElementCollectionType.BasicMap; }
        }

        protected override string ElementName
        {
            get { return "usaCounty"; }
        }

        public UsaCountyConfigurationElement this[int index]
        {
            get { return (UsaCountyConfigurationElement)BaseGet(index); }
        }

        public new UsaCountyConfigurationElement this[string name]
        {
            get
            {
                if (this.IndexOf(name) < 0)
                {
                    return null;
                }

                return (UsaCountyConfigurationElement)BaseGet(name);
            }
        }

        public void Add(UsaCountyConfigurationElement newItem)
        {
            this.BaseAdd(newItem);
        }

        public int IndexOf(string usaCountyValue)
        {
            for (int idx = 0; idx < this.Count; idx++)
            {
                if (this[idx].UsaCountyValue.ToLower(System.Globalization.CultureInfo.CurrentCulture) == usaCountyValue.ToLower(System.Globalization.CultureInfo.CurrentCulture))
                {
                    return idx;
                }
            }

            return -1;
        }

        public new IEnumerator<UsaCountyConfigurationElement> GetEnumerator()
        {
            int count = this.Count;

            for (int i = 0; i < count; i++)
            {
                yield return this.BaseGet(i) as UsaCountyConfigurationElement;
            }
        }

        protected override ConfigurationElement CreateNewElement()
        {
            return new UsaCountyConfigurationElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((UsaCountyConfigurationElement)element).UsaCountyValue;
        }
    }
}



/**/




// file="UsaCountyConfigurationElement.cs"




using System.ComponentModel;
using System.Configuration;

namespace GranadaCoder.CustomConfigurationExample.Configuration
{
    [System.Diagnostics.DebuggerDisplay("UsaCountyValue='{UsaCountyValue}'")]
    public class UsaCountyConfigurationElement : ConfigurationElement
    {
        private const string UsaCountyValuePropertyName = "usaCountyValue";

        public UsaCountyConfigurationElement()
        {
        }

        public UsaCountyConfigurationElement(string usaCountyValue)
        {
            this.UsaCountyValue = usaCountyValue;
        }

        [ConfigurationProperty(UsaCountyValuePropertyName, IsRequired = true, IsKey = true, DefaultValue = "")]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;’\"|\\")]
        public string UsaCountyValue
        {
            get { return (string)this[UsaCountyValuePropertyName]; }
            set { this[UsaCountyValuePropertyName] = value; }
        }
    }
}


/**/




// file="UsaCountyLabelEnum.cs"




namespace GranadaCoder.CustomConfigurationExample.Configuration
{
    public enum UsaCountyLabelEnum
    {
        Unknown,
        County,
        Parish,
        Borough
    }
}


/**/




// file="UsaStateDefinitionCollection.cs"




using System;
using System.Collections.Generic;
using System.Configuration;

using GranadaCoder.CustomConfigurationExample.Configuration.Interfaces;

namespace GranadaCoder.CustomConfigurationExample.Configuration
{
    [ConfigurationCollection(typeof(UsaStateDefinitionConfigurationElement))]
    public class UsaStateDefinitionCollection : ConfigurationElementCollection, IUsaStateDefinitionCollection
    {
        public UsaStateDefinitionCollection()
        {
            UsaStateDefinitionConfigurationElement details = (UsaStateDefinitionConfigurationElement)this.CreateNewElement();
            if (!string.IsNullOrEmpty(details.UsaStateFullName))
            {
                this.Add(details);
            }
        }

        public override ConfigurationElementCollectionType CollectionType
        {
            get
            {
                return ConfigurationElementCollectionType.BasicMap;
            }
        }

        bool ICollection<UsaStateDefinitionConfigurationElement>.IsReadOnly
        {
            get
            {
                return false;
            }
        }

        protected override string ElementName
        {
            get { return "usaStateDefinition"; }
        }

        public UsaStateDefinitionConfigurationElement this[int index]
        {
            get
            {
                return (UsaStateDefinitionConfigurationElement)BaseGet(index);
            }

            set
            {
                if (this.BaseGet(index) != null)
                {
                    this.BaseRemoveAt(index);
                }

                this.BaseAdd(index, value);
            }
        }

        public new UsaStateDefinitionConfigurationElement this[string name]
        {
            get
            {
                return (UsaStateDefinitionConfigurationElement)this.BaseGet(name);
            }
        }

        public int IndexOf(UsaStateDefinitionConfigurationElement details)
        {
            return this.BaseIndexOf(details);
        }

        public void Add(UsaStateDefinitionConfigurationElement newItem)
        {
            this.BaseAdd(newItem);
        }

        public bool Remove(UsaStateDefinitionConfigurationElement details)
        {
            if (this.BaseIndexOf(details) >= 0)
            {
                this.BaseRemove(details.UsaStateFullName);
                return true;
            }

            return false;
        }

        public void RemoveAt(int index)
        {
            this.BaseRemoveAt(index);
        }

        public void Remove(string name)
        {
            this.BaseRemove(name);
        }

        public void Clear()
        {
            this.BaseClear();
        }

        public bool Contains(UsaStateDefinitionConfigurationElement item)
        {
            if (this.IndexOf(item) >= 0)
            {
                return true;
            }

            return false;
        }

        public void CopyTo(UsaStateDefinitionConfigurationElement[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }

        public void Insert(int index, UsaStateDefinitionConfigurationElement item)
        {
            this.BaseAdd(index, item);
        }

        public new IEnumerator<UsaStateDefinitionConfigurationElement> GetEnumerator()
        {
            int count = this.Count;

            for (int i = 0; i < count; i++)
            {
                yield return this.BaseGet(i) as UsaStateDefinitionConfigurationElement;
            }
        }

        protected override ConfigurationElement CreateNewElement()
        {
            return new UsaStateDefinitionConfigurationElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((UsaStateDefinitionConfigurationElement)element).UsaStateFullName;
        }

        protected override void BaseAdd(ConfigurationElement element)
        {
            this.BaseAdd(element, false);
        }
    }
}


/**/




// file="UsaStateDefinitionConfigurationElement.cs"




using System;
using System.ComponentModel;
using System.Configuration;

namespace GranadaCoder.CustomConfigurationExample.Configuration
{
    [System.Diagnostics.DebuggerDisplay("UsaStateFullName = '{UsaStateFullName}'")]
    public class UsaStateDefinitionConfigurationElement : ConfigurationElement
    {
        private const string UsaStateFullNamePropertyName = "usaStateFullName";
        private const string UsaStateAbbreviationPropertyName = "usaStateAbbreviation";
        private const string UsaStateDefinitionUniqueIdentifierPropertyName = "usaStateDefinitionUniqueIdentifier";
        private const string IsContenentialPropertyName = "isContenential";
        private const string CountyLabelNamePropertyName = "countyLabelName";

        private const string UsaCountiesPropertyName = "usaCounties";
        private const string ServiceBusQueuesPropertyName = "serviceBusQueues";

        [ConfigurationProperty(UsaStateFullNamePropertyName, IsRequired = true, IsKey = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;’\"|\\")]
        public string UsaStateFullName
        {
            get { return (string)this[UsaStateFullNamePropertyName]; }
            set { this[UsaStateFullNamePropertyName] = value; }
        }

        [ConfigurationProperty(UsaStateAbbreviationPropertyName, IsRequired = true, IsKey = true)]
        [StringValidator(InvalidCharacters = "  ~!@#$%^&*()[]{}/;’\"|\\")]
        public string UsaStateAbbreviation
        {
            get { return (string)this[UsaStateAbbreviationPropertyName]; }
            set { this[UsaStateAbbreviationPropertyName] = value; }
        }

        [ConfigurationProperty(UsaStateDefinitionUniqueIdentifierPropertyName, IsRequired = true)]
        public int UsaStateDefinitionUniqueIdentifier
        {
            get { return (int)this[UsaStateDefinitionUniqueIdentifierPropertyName]; }
            set { this[UsaStateDefinitionUniqueIdentifierPropertyName] = value; }
        }

        [ConfigurationProperty(IsContenentialPropertyName, IsRequired = true)]
        public bool IsContenential
        {
            get { return (bool)this[IsContenentialPropertyName]; }
            set { this[IsContenentialPropertyName] = value; }
        }

        [ConfigurationProperty(UsaCountiesPropertyName, IsDefaultCollection = false)]
        public UsaCountyCollection UsaCounties
        {
            get { return (UsaCountyCollection)base[UsaCountiesPropertyName]; }
            set { this[UsaCountiesPropertyName] = value; } /* set is here for UnitTests */
        }

        [ConfigurationProperty(CountyLabelNamePropertyName, DefaultValue = UsaCountyLabelEnum.Unknown)]
        [TypeConverter(typeof(CaseInsensitiveEnumConfigurationValueConverter<UsaCountyLabelEnum>))]
        public UsaCountyLabelEnum CountyLabelType
        {
            get
            {
                return (UsaCountyLabelEnum)this[CountyLabelNamePropertyName];
            }

            set
            {
                base[CountyLabelNamePropertyName] = value;
            }
        }
    }
}



/**/




// file="UsaStateDefinitionConfigurationRetriever.cs"




using System;
using System.Configuration;
using System.Linq;

using GranadaCoder.CustomConfigurationExample.Configuration.Interfaces;

namespace GranadaCoder.CustomConfigurationExample.Configuration
{
    public class UsaStateDefinitionConfigurationRetriever : IUsaStateDefinitionConfigurationSectionRetriever
    {
        public static readonly string ConfigurationSectionName = "UsaStateDefinitionConfigurationSectionName";

        public IUsaStateDefinitionConfigurationSection GetIUsaStateDefinitionConfigurationSection()
        {
            UsaStateDefinitionConfigurationSection returnSection = (UsaStateDefinitionConfigurationSection)ConfigurationManager.GetSection(ConfigurationSectionName);
            if (returnSection != null)
            {
                var duplicates = returnSection.IUsaStateDefinitions.GroupBy(i => new { i.UsaStateDefinitionUniqueIdentifier })
                  .Where(g => g.Count() > 1)
                  .Select(g => g.Key);

                if (duplicates.Count() > 1)
                {
                    throw new ArgumentOutOfRangeException("Duplicate UsaStateDefinitionUniqueIdentifier values.", Convert.ToString(duplicates.First().UsaStateDefinitionUniqueIdentifier));
                }

                return returnSection;
            }

            return null;
        }
    }
}


/**/




// file="UsaStateDefinitionConfigurationSection.cs"




using System.Configuration;

using GranadaCoder.CustomConfigurationExample.Configuration.Interfaces;

namespace GranadaCoder.CustomConfigurationExample.Configuration
{
    public class UsaStateDefinitionConfigurationSection : ConfigurationSection, IUsaStateDefinitionConfigurationSection
    {
        [ConfigurationProperty("", IsDefaultCollection = true)]
        public UsaStateDefinitionCollection UsaStateDefinitions
        {
            get
            {
                UsaStateDefinitionCollection coll = (UsaStateDefinitionCollection)base[string.Empty];
                return coll;
            }
        }

        /* Here is the interface property that simply returns the non-interface property from above.  This allows System.Configuration to hydrate the non-interface property, but allows the code to be written (and unit test mocks to be written) against the interface property. */
        public IUsaStateDefinitionCollection IUsaStateDefinitions
        {
            get { return this.UsaStateDefinitions; }
        }
    }
}



/**/




// file="UsaStateDefinitionFinder.cs"




using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;

using GranadaCoder.CustomConfigurationExample.Configuration.Interfaces;

namespace GranadaCoder.CustomConfigurationExample.Configuration
{
    public class UsaStateDefinitionFinder : IUsaStateDefinitionFinder
    {
        private const string ErrorMessageMoreThanOneMatch = "More than item was found with the selection criteria. ({0})";

        private const string ErrorMessageNoMatch = "No item was found with the selection criteria. ({0})";

        public UsaStateDefinitionConfigurationElement FindUsaStateDefinitionConfigurationElement(IUsaStateDefinitionConfigurationSection settings, string usaStateFullName)
        {
            UsaStateDefinitionConfigurationElement returnItem = null;

            if (null != settings && null != settings.IUsaStateDefinitions)
            {
                ICollection<UsaStateDefinitionConfigurationElement> matchingFarmItems;
                matchingFarmItems = settings.IUsaStateDefinitions.Where(ele => usaStateFullName.Equals(ele.UsaStateFullName, StringComparison.OrdinalIgnoreCase)).ToList();

                if (matchingFarmItems.Count > 1)
                {
                    string errorDetails = this.BuildErrorDetails(matchingFarmItems);
                    throw new IndexOutOfRangeException(string.Format(ErrorMessageMoreThanOneMatch, errorDetails));
                }

                returnItem = matchingFarmItems.FirstOrDefault();
            }

            return returnItem;
        }

        public UsaStateDefinitionConfigurationElement FindUsaStateDefinitionConfigurationElement(string usaStateFullName)
        {
            IUsaStateDefinitionConfigurationSection settings = new UsaStateDefinitionConfigurationRetriever().GetIUsaStateDefinitionConfigurationSection();
            return this.FindUsaStateDefinitionConfigurationElement(settings, usaStateFullName);
        }

        public UsaStateDefinitionConfigurationElement FindUsaStateDefinitionConfigurationElementByUniqueId(int id)
        {
            IUsaStateDefinitionConfigurationSection settings = new UsaStateDefinitionConfigurationRetriever().GetIUsaStateDefinitionConfigurationSection();
            return this.FindUsaStateDefinitionConfigurationElementByUniqueId(settings, id);
        }

        public UsaStateDefinitionConfigurationElement FindUsaStateDefinitionConfigurationElementByUniqueId(IUsaStateDefinitionConfigurationSection settings, int id)
        {
            UsaStateDefinitionConfigurationElement returnItem = null;

            if (null != settings && null != settings.IUsaStateDefinitions)
            {
                ICollection<UsaStateDefinitionConfigurationElement> matchingFarmItems;
                matchingFarmItems = settings.IUsaStateDefinitions.Where(ele => id == ele.UsaStateDefinitionUniqueIdentifier).ToList();

                if (matchingFarmItems.Count > 1)
                {
                    string errorDetails = this.BuildErrorDetails(matchingFarmItems);
                    throw new IndexOutOfRangeException(string.Format(ErrorMessageMoreThanOneMatch, errorDetails));
                }

                returnItem = matchingFarmItems.FirstOrDefault();
            }

            return returnItem;
        }

        private string BuildErrorDetails(ICollection<UsaStateDefinitionConfigurationElement> items)
        {
            string returnValue;

            StringBuilder sb = new StringBuilder();

            if (null != items)
            {
                foreach (UsaStateDefinitionConfigurationElement item in items)
                {
                    sb.Append(string.Format("UsaStateFullName='{0}'.", item.UsaStateFullName));
                }
            }

            returnValue = sb.ToString();

            return returnValue;
        }
    }
}



/**/




// file="IUsaStateDefinitionCollection.cs"




using System;
using System.Collections.Generic;

namespace GranadaCoder.CustomConfigurationExample.Configuration.Interfaces
{
    public interface IUsaStateDefinitionCollection : IList<UsaStateDefinitionConfigurationElement>, IEnumerable<UsaStateDefinitionConfigurationElement> /* Implement IEnumerable to allow Linq queries */
    {
        UsaStateDefinitionConfigurationElement this[string name] { get; }

        void Remove(string name);
    }
}



/**/




// file="IUsaStateDefinitionConfigurationSection.cs"




using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace GranadaCoder.CustomConfigurationExample.Configuration.Interfaces
{
    public interface IUsaStateDefinitionConfigurationSection
    {
        IUsaStateDefinitionCollection IUsaStateDefinitions { get; }
    }
}


/**/




// file="IUsaStateDefinitionConfigurationSectionRetriever.cs"




namespace GranadaCoder.CustomConfigurationExample.Configuration.Interfaces
{
    public interface IUsaStateDefinitionConfigurationSectionRetriever
    {
        IUsaStateDefinitionConfigurationSection GetIUsaStateDefinitionConfigurationSection();
    }
}



/**/




// file="IUsaStateDefinitionFinder.cs"




using System;

namespace GranadaCoder.CustomConfigurationExample.Configuration.Interfaces
{
    public interface IUsaStateDefinitionFinder
    {
        UsaStateDefinitionConfigurationElement FindUsaStateDefinitionConfigurationElement(IUsaStateDefinitionConfigurationSection settings, string usaStateFullName);

        UsaStateDefinitionConfigurationElement FindUsaStateDefinitionConfigurationElement(string usaStateFullName);

        UsaStateDefinitionConfigurationElement FindUsaStateDefinitionConfigurationElementByUniqueId(int id);

        UsaStateDefinitionConfigurationElement FindUsaStateDefinitionConfigurationElementByUniqueId(IUsaStateDefinitionConfigurationSection settings, int id);
  }
}



/**/




// file="Program.cs"

using System;
using System.Text;

using GranadaCoder.CustomConfigurationExample.Configuration;
using GranadaCoder.CustomConfigurationExample.Configuration.Interfaces;

using log4net;
using Microsoft.Practices.Unity;

namespace GranadaCoder.CustomConfigurationExample.ConsoleAppOne
{
    public static class Program
    {
        public const int UnhandledExceptionErrorCode = 1;

        private static readonly ILog TheLogger = LogManager.GetLogger(typeof(Program));

        public static int Main(string[] args)
        {
            int returnValue = 0;
            string logMsg = string.Empty;

            try
            {
                //// For future //log4net.Config.XmlConfigurator.Configure();
                //// For future //IUnityContainer container = new UnityContainer();
                //// For future //container.RegisterType<ILog>(new InjectionFactory(x => LogManager.GetLogger(typeof(GranadaCoder.CustomConfigurationExample.ConsoleAppOne.Program))));

                IUsaStateDefinitionConfigurationSectionRetriever retr = new UsaStateDefinitionConfigurationRetriever();
                IUsaStateDefinitionConfigurationSection section = retr.GetIUsaStateDefinitionConfigurationSection();

                IUsaStateDefinitionFinder finder = new UsaStateDefinitionFinder();
                UsaStateDefinitionConfigurationElement foundElement = finder.FindUsaStateDefinitionConfigurationElement("Virginia");

                if (null != section)
                {
                    foreach (UsaStateDefinitionConfigurationElement state in section.IUsaStateDefinitions)
                    {
                        Console.WriteLine("state.UsaStateFullName='{0}', state.UsaStateAbbreviation='{1}', state.IsContenential='{2}', state.CountyLabelType='{3}'", state.UsaStateFullName, state.UsaStateAbbreviation, state.IsContenential, state.CountyLabelType);
                        foreach (UsaCountyConfigurationElement county in state.UsaCounties)
                        {
                            Console.WriteLine("county.UsaCountyValue='{0}'", county.UsaCountyValue);
                        }

                        Console.WriteLine("--------------------");
                    }
                }
            }
            catch (Exception e)
            {
                TheLogger.Error(e.Message, e);
                Console.WriteLine("Unexpected Exception : {0}", e.Message);
                returnValue = UnhandledExceptionErrorCode;
            }

            Console.WriteLine("END : {0}", System.Diagnostics.Process.GetCurrentProcess().ProcessName);
            Console.WriteLine(string.Empty);

            Console.WriteLine("Press ENTER to exit");
            Console.ReadLine();

            return returnValue;
        }
    }
}
Pastose answered 27/9, 2017 at 16:49 Comment(1)
For future readers, I have taken the above UsaState and UsaCounty example and created a DotNet CORE (currently 3.1) example. See : github.com/granadacoder/DotNetCore3PlusCustomConfiguraitonOnePastose

© 2022 - 2024 — McMap. All rights reserved.