I wrote something similar once, as an example for a C # course. In my opinion, this basically demonstrates how terrible the .NET configuration subsystem is, although the code does work. I did not adapt it to your settings, since it is quite easy to introduce an error, and so far the SO editor does not check the posted code samples;)
First, the declaration of the configuration section:
<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 a 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 use a collection of custom elements, so we’ll also announce this:
[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 a custom item used in the item 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 that we have all this in place, we are ready to access the configuration file. I use the helper class Configurator to make this a little 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.