Code required to use foreach on my own custom appSettings

app-config, appsettings, config, configuration, configuration-files

Solution

I've written something similar once, as an example for a C# course. In my opinion it mainly demonstrates how awful the .NET configuration subsystem is, although the code does work. I've not adapted it to your settings, as it's fairly easy to introduce a mistake and so far the SO editor does not validate posted code samples ;)

First, the configuration section declaration:

    <configSections>
        <section name="passwordSafe"
                 type="Codeworks.PasswordSafe.Model.Configuration.PasswordSafeSection, Codeworks.PasswordSafe.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </configSections>

    <passwordSafe hashAlgorithm="SHA256">
        <users>
            <user name="mm" password="Jok2eyBcFs4y7UIAlCuLix4mLfxw2byfvHfElpmk8d8=" />
            <user name="joe" password="Jok2eyBcFs4y7UIAlCuLix4mLfxw2byfvHfElpmk8d8=" />
        </users>
    </passwordSafe>

To match the above snippet we first need the configuration section:

    public class PasswordSafeSection : ConfigurationSection
    {
        #region Static Accessors
        /// <summary>
        /// Gets the configuration section using the default element name.
        /// </summary>
        public static PasswordSafeSection GetSection()
        {
            return GetSection( "passwordSafe" );
        }
        
        /// <summary>
        /// Gets the configuration section using the specified element name.
        /// </summary>
        public static PasswordSafeSection GetSection( string sectionName )
        {
            PasswordSafeSection section = ConfigurationManager.GetSection( sectionName ) as PasswordSafeSection;
            if( section == null )
            {
                string message = string.Format( "The specified configuration section (<{0}>) was not found.", sectionName );
                throw new ConfigurationErrorsException( message );
            }    
            return section;
        }
        #endregion

        #region Configuration Properties
        [ConfigurationProperty( "hashAlgorithm" )]
        public string HashAlgorithm
        {
            get { return (string) this[ "hashAlgorithm" ]; }
            set { this[ "hashAlgorithm" ] = value; }
        }

        [ConfigurationProperty( "users", IsDefaultCollection=true )]
        public UserElementCollection Users
        {
            get { return (UserElementCollection) this[ "users" ]; }
            set { this[ "users" ] = value; }
        }

        public override bool IsReadOnly()
        {
            return false;
        }
        #endregion
    }

We are using a custom element collection, so let's declare that too:

    [ConfigurationCollection( typeof(UserElement), CollectionType = ConfigurationElementCollectionType.BasicMap )]
    public class UserElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new UserElement();
        }

        protected override string ElementName
        {
            get { return "user"; }
        }
        public override ConfigurationElementCollectionType CollectionType
        {
            get { return ConfigurationElementCollectionType.BasicMap; }
        }

        public override bool IsReadOnly()
        {
            return false;
        }

        #region Indexers
        public UserElement this[ int index ]
        {
            get { return BaseGet( index ) as UserElement; }
            set
            {
                if( BaseGet( index ) != null )
                {
                    BaseRemoveAt( index );
                }
                BaseAdd( index, value );
            }
        }

        public new UserElement this[ string name ]
        {
            get { return BaseGet( name ) as UserElement; }
        }
        #endregion

        #region Lookup Methods
        protected override object GetElementKey( ConfigurationElement element )
        {
            UserElement user = element as UserElement;
            return user != null ? user.UserName : "error";
        }

        public string GetKey( int index )
        {
            return (string) BaseGetKey( index );
        }
        #endregion

        #region Add/Remove/Clear Methods
        public void Add( UserElement item )
        {
            BaseAdd( item );
        }

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

        public void Remove( UserElement item )
        {
            BaseRemove( GetElementKey( item ) );
        }

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

        public void Clear()
        {
            BaseClear();
        }
        #endregion
    }

And finally we need to declare the custom element used in the element collection:

    public class UserElement : ConfigurationElement
    {
        #region Constructors
        public UserElement()
        {
        }

        public UserElement( string userName, string passwordHash )
        {
            UserName = userName;
            PasswordHash = passwordHash;
        }
        #endregion

        #region Configuration Properties
        [ConfigurationProperty( "name", IsKey = true )]
        public string UserName
        {
            get { return (string) this[ "name" ]; }
            set { this[ "name" ] = value; }
        }

        [ConfigurationProperty( "password", IsRequired = true )]
        public string PasswordHash
        {
            get { return (string) this[ "password" ]; }
            set { this[ "password" ] = value; }
        }

        public override bool IsReadOnly()
        {
            return false;
        }
        #endregion
    }

Now, having all this in place we're ready to access the configuration file. I'm using a Configurator helper class to make this slightly less cumbersome:

    public static class Configurator
    {
        #region AppSettings Helpers
        public static int SplashScreenDisplayTime
        {
            get { return Convert.ToInt32( ConfigurationManager.AppSettings[ "splash.display.msecs" ] ); }
        }
        #endregion

        #region User Helpers
        public static bool TryGetUserPasswordHash( string userName, out string passwordHash )
        {
            UserElement user = GetUser( userName );
            passwordHash = user != null ? user.PasswordHash : null;
            return ! string.IsNullOrEmpty( passwordHash );
        }

        private static UserElement GetUser( string userName )
        {
            SystemConfiguration config = GetConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal );
            PasswordSafeSection section = config.Sections[ "passwordSafe" ] as PasswordSafeSection;
            return section.Users[ userName ];
        }
        public static void AddUser( string userName, string passwordHash, string encryptionKey )
        {
            SystemConfiguration config = GetConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal );
            PasswordSafeSection section = config.Sections[ "passwordSafe" ] as PasswordSafeSection;
            UserElement user = section.Users[ userName ];
            if( user == null )
            {
                user = new UserElement( userName, passwordHash, encryptionKey );
                section.Users.Add( user );
                config.Save( ConfigurationSaveMode.Modified );
            }
        }
        public static void RemoveUser( string userName )
        {
            SystemConfiguration config = GetConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal );
            PasswordSafeSection section = config.Sections[ "passwordSafe" ] as PasswordSafeSection;
            section.Users.Remove( userName );
            config.Save( ConfigurationSaveMode.Modified );
        }
        public static void UpdateUser( string userName, string passwordHash )
        {
            SystemConfiguration config = GetConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal );
            PasswordSafeSection section = config.Sections[ "passwordSafe" ] as PasswordSafeSection;
            UserElement user = section.Users[ userName ];
            if( user != null )
            {
                user.PasswordHash = passwordHash;
                config.Save( ConfigurationSaveMode.Modified );
            }
        }
        #endregion

        #region Configuration Helpers
        private static SystemConfiguration GetConfiguration( ConfigurationUserLevel userLevel )
        {
            SystemConfiguration config = InitializeConfiguration( userLevel );
            return config;
        }

        private static SystemConfiguration InitializeConfiguration( ConfigurationUserLevel userLevel )
        {    
            SystemConfiguration config = ConfigurationManager.OpenExeConfiguration( userLevel );
            PasswordSafeSection section = config.Sections[ "passwordSafe" ] as PasswordSafeSection;
            if( section == null )
            {
                section = new PasswordSafeSection();
                section.SectionInformation.AllowExeDefinition = ConfigurationAllowExeDefinition.MachineToLocalUser;
                section.SectionInformation.ForceSave = true;
                config.Sections.Add( "passwordSafe", section );
                config.Save( ConfigurationSaveMode.Full );
            }
            return config;
        }
        #endregion
    }

Hope this helps.

Problem

I've searched the site and haven't found exactly what I'm looking for. Close, but no cigar. Basically I want to have a config section like this: ``` <configSections> <section name="PhoneNotificationsSection" type="Alerts.PhoneAlertConfigSection,Alerts,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"/> </configSections> <PhoneNotificationsSection> <phones> <add phone="MyMobile" value="1234567890@vtext.com" /> <add phone="OtherMobile" value="1234567890@txt.att.com" /> </phones> </PhoneNotificationsSection> ``` Then I'd like to, in my appSettings consuming code, be able to write something like this (pseudo code): ``` foreach (phone p in phones) { //'phone' attribute is just helpful/descriptive DoSomething(p.value); } ``` I've done enough research to know I probably need a few of my own classes that implement and/or inherit from certain Configuration classes to make the above code possible. I just haven't found anything that clearly demonstrates this scenario and how to code for it - and when I try to learn the whole .NET configuration world my brain starts to hurt. Anyone have some code like what I'm looking for that they can share?

Original source