Search code examples
configurationapp-configconfiguration-filesconfigappsettings

Code required to use foreach on my own custom appSettings


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="[email protected]" />
    <add phone="OtherMobile" value="[email protected]" />
  </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?


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.