//****************************************************************************************************** // SettingsBase.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 01/30/2009 - J. Ritchie Carroll // Generated original version of source code. // 03/31/2009 - J. Ritchie Carroll // Added code to allow override of name used to serialize field or property to configuration file // by applying a SettingNameAttribute to the member. // 04/01/2009 - J. Ritchie Carroll // Added code to optionally encrypt settings based on EncryptSettingAttribute and to pickup // DefaultValueAttribute value if provided and current value was uninitialized. // 08/05/2009 - Josh L. Patterson // Edited Comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 12/05/2010 - Pinal C. Patel // Added Culture property that can be used for specifying a culture to use for value conversion // and updated all value conversions to use the specified culture. // 01/04/2011 - J. Ritchie Carroll // Modified culture to default to InvariantCulture for English style parsing defaults. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Reflection; using GSF.Reflection; using GSF.Security.Cryptography; namespace GSF.Configuration { /// /// Represents the base class for application settings that are synchronized with its configuration file. /// /// /// In order to make custom types serializable for the configuration file, implement a for the type.
/// See MSDN for details. ///
[EditorBrowsable(EditorBrowsableState.Never)] public abstract class SettingsBase : IDisposable, IEnumerable { #region [ Members ] // Fields private CultureInfo m_culture; private BindingFlags m_memberAccessBindingFlags; private bool m_requireSerializeSettingAttribute; private bool m_disposed; #endregion #region [ Constructors ] /// /// Creates a new instance of the class for the application's configuration file. /// /// /// Assigns flag that determines if is required /// to exist before a field or property is serialized to the configuration file. /// protected SettingsBase(bool requireSerializeSettingAttribute) { m_culture = CultureInfo.InvariantCulture; m_requireSerializeSettingAttribute = requireSerializeSettingAttribute; m_memberAccessBindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~SettingsBase() { // If user failed to dispose class, we make sure settings get saved... Dispose(false); } #endregion #region [ Properties ] /// /// Gets or sets the to use for the conversion of setting values to and from . /// [Browsable(false), SerializeSetting(false)] public CultureInfo Culture { get { return m_culture; } set { if ((object)value != null) m_culture = value; else m_culture = CultureInfo.InvariantCulture; } } /// /// Gets or sets used to access fields and properties of derived class. /// /// /// Value defaults to | | . /// [EditorBrowsable(EditorBrowsableState.Advanced)] protected virtual BindingFlags MemberAccessBindingFlags { get { return m_memberAccessBindingFlags; } set { m_memberAccessBindingFlags = value; } } /// /// Gets or sets flag that determines if is /// required to exist before a field or property is serialized to the configuration /// file; defaults to False. /// [Browsable(false), SerializeSetting(false)] public bool RequireSerializeSettingAttribute { get { return m_requireSerializeSettingAttribute; } set { m_requireSerializeSettingAttribute = value; } } /// /// Gets or sets the value of the specified field or property. /// /// Field or property name. /// Value of setting. /// This is the default member of this class. public string this[string name] { get { return GetValue(name); } set { SetValue(name, value); } } #endregion #region [ Methods ] /// /// Releases all the resources used by the object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (!m_disposed) { try { // We'll make sure settings are saved when class is properly disposed... if (disposing) Save(); } finally { m_disposed = true; // Prevent duplicate dispose. } } } /// /// Implementor should create setting in configuration file (or other location). /// /// Field or property name, if useful (can be different from setting name). /// Setting name. /// Setting value. [EditorBrowsable(EditorBrowsableState.Advanced)] protected abstract void CreateSetting(string name, string setting, string value); /// /// Implementor should retrieve setting from configuration file (or other location). /// /// Field or property name, if useful (can be different from setting name). /// Setting name. /// Setting value. [EditorBrowsable(EditorBrowsableState.Advanced)] protected abstract string RetrieveSetting(string name, string setting); /// /// Implementor should store setting to configuration file (or other location). /// /// Field or property name, if useful (can be different from setting name). /// Setting name. /// Setting value. [EditorBrowsable(EditorBrowsableState.Advanced)] protected abstract void StoreSetting(string name, string setting, string value); /// /// Implementor should persist any pending changes to configuration file (or other location). /// [EditorBrowsable(EditorBrowsableState.Advanced)] protected abstract void PersistSettings(); /// /// Gets setting name to use for specified field or property. /// /// Field or property name. /// applied to specified field or property; or if attribute does not exist. /// /// Field or property name will be used for setting name unless user applied a /// on the field or property to override name used to serialize value in configuration file. /// /// cannot be null or empty. public string GetSettingName(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("name cannot be null or empty"); return GetAttributeValue(name, name, attribute => attribute.Name).NotEmpty(name); } /// /// Gets the default value specified by , if any, applied to the specified field or property. /// /// Field or property name. /// Default value applied to specified field or property; or null if one does not exist. /// cannot be null or empty. public object GetDefaultValue(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("name cannot be null or empty"); return GetAttributeValue(name, null, attribute => attribute.Value); } /// /// Gets the encryption status specified by , if any, applied to the specified field or property. /// /// Field or property name. /// Encryption status applied to specified field or property; or false if one does not exist. /// cannot be null or empty. public bool GetEncryptStatus(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("name cannot be null or empty"); return GetAttributeValue(name, false, attribute => attribute.Encrypt); } /// /// Gets the optional private encryption key specified by , if any, applied to the specified field or property. /// /// Field or property name. /// Encryption private key applied to specified field or property; or null if one does not exist. /// cannot be null or empty. public string GetEncryptKey(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("name cannot be null or empty"); return GetAttributeValue(name, null, attribute => attribute.PrivateKey).NotEmpty(name); } /// /// Adds a setting to the application's configuration file, if it doesn't already exist. /// /// Field or property name. /// Setting value. /// /// Use this function to ensure a setting exists, it will not override an existing value. /// public void CreateValue(string name, object value) { string setting = GetSettingName(name); if ((object)value == null) CreateSetting(name, setting, EncryptValue(name, "")); else CreateSetting(name, setting, EncryptValue(name, Common.TypeConvertToString(value, m_culture))); } /// /// Copies the given value into the specified application setting. /// /// Field or property name. /// Setting value. public void SetValue(string name, object value) { string setting = GetSettingName(name); if ((object)value == null) StoreSetting(name, setting, EncryptValue(name, "")); else StoreSetting(name, setting, EncryptValue(name, Common.TypeConvertToString(value, m_culture))); } /// /// Gets the application's configuration file setting converted to the given type. /// /// Type to use for setting conversion. /// Field or property name. /// Value of specified configuration file setting converted to the given type. public T GetValue(string name) { string setting = GetSettingName(name); return DecryptValue(name, RetrieveSetting(name, setting)).ConvertToType(m_culture); } /// /// Gets the application's configuration file setting converted to the given type. /// /// Field or property name. /// Setting type. /// Value of specified configuration file setting converted to the given type. public object GetValue(string name, Type type) { string setting = GetSettingName(name); return DecryptValue(name, RetrieveSetting(name, setting)).ConvertToType(type, m_culture); } /// /// Copies the specified application setting into the given value. /// /// Type to use for setting conversion. /// Field or property name. /// Setting value. public void GetValue(string name, out T value) { string setting = GetSettingName(name); value = DecryptValue(name, RetrieveSetting(name, setting)).ConvertToType(m_culture); } // Encrypt setting value and return a base64 encoded value private string EncryptValue(string name, string value) { // If encrypt attribute has been applied, encrypt value if (GetEncryptStatus(name)) return value.Encrypt(GetEncryptKey(name), CipherStrength.Aes256); return value; } // Decrypt setting value private string DecryptValue(string name, string value) { // If encrypt attribute has been applied, decrypt value if (GetEncryptStatus(name)) return value.Decrypt(GetEncryptKey(name), CipherStrength.Aes256); return value; } /// /// Initializes configuration settings from derived class fields or properties. /// protected virtual void Initialize() { // Make sure all desired settings exist initialized with default values. Settings are // assumed to be public fields or public properties in derived class - so we enumerate // through of these making sure a setting exists for each field and property // Verify a configuration setting exists for each field ExecuteActionForFields(field => CreateValue(field.Name, DeriveDefaultValue(field.Name, field.GetValue(this)))); // Verify a configuration setting exists for each property ExecuteActionForProperties(property => CreateValue(property.Name, DeriveDefaultValue(property.Name, property.GetValue(this, null))), BindingFlags.GetProperty); // If any new values were encountered, make sure they are flushed to config file PersistSettings(); // Load current settings Load(); } /// /// Restores the default settings of the configuration file. /// public virtual void RestoreDefaultSettings() { // Restore each field to its default value ExecuteActionForFields(field => SetValue(field.Name, GetDefaultValue(field.Name))); // Restore each property to its default value ExecuteActionForProperties(property => SetValue(property.Name, GetDefaultValue(property.Name)), BindingFlags.GetProperty); // If any values were changed, make sure they are flushed to config file PersistSettings(); // Load current settings Load(); } /// /// Attempts to get best default value for given member. /// /// Field or property name. /// Current field or property value. /// /// If is equal to its default(type) value, then any value derived from will be used instead. /// /// The object that is the best default value. [EditorBrowsable(EditorBrowsableState.Advanced)] protected object DeriveDefaultValue(string name, object value) { // See if value is equal to its default value (i.e., uninitialized) if (Common.IsDefaultValue(value)) { // See if any value exists in a DefaultValueAttribute object defaultValue = GetDefaultValue(name); if (defaultValue != null) return defaultValue; } return value; } /// /// Returns an enumerator based on elements that iterates over the field and property names of this class /// that are targeted for serialization to the configuration file. /// /// An object that can be used to iterate through the collection. public IEnumerator GetEnumerator() { List members = new List(); // Get names of fields ExecuteActionForFields(field => members.Add(field.Name)); // Get names of properties ExecuteActionForProperties(property => members.Add(property.Name), BindingFlags.GetProperty); return members.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Loads configuration file into setting fields. /// public virtual void Load() { // Load configuration file settings into fields ExecuteActionForFields(field => field.SetValue(this, GetValue(field.Name, field.FieldType))); // Load configuration file settings into properties ExecuteActionForProperties(property => property.SetValue(this, GetValue(property.Name, property.PropertyType), null), BindingFlags.SetProperty); } /// /// Saves setting fields into configuration file. /// public virtual void Save() { // Saves setting fields into configuration file values ExecuteActionForFields(field => SetValue(field.Name, field.GetValue(this))); // Saves setting properties into configuration file values ExecuteActionForProperties(property => SetValue(property.Name, property.GetValue(this, null)), BindingFlags.GetProperty); // Make sure any changes are flushed to config file PersistSettings(); } /// /// Executes specified action over all public derived class member fields. /// /// Action to execute for all derived class member fields. [EditorBrowsable(EditorBrowsableState.Advanced)] protected void ExecuteActionForFields(Action fieldAction) { ExecuteActionForMembers(fieldAction, this.GetType().GetFields(m_memberAccessBindingFlags)); } /// /// Executes specified action over all public derived class properties. /// /// Action to execute for all properties. /// or . [EditorBrowsable(EditorBrowsableState.Advanced)] protected void ExecuteActionForProperties(Action propertyAction, BindingFlags isGetOrSet) { bool get = isGetOrSet.HasFlag(BindingFlags.GetProperty); bool set = isGetOrSet.HasFlag(BindingFlags.SetProperty); Action memberAction = property => { if (property.GetIndexParameters().Length == 0) propertyAction(property); }; PropertyInfo[] properties = this.GetType() .GetProperties(m_memberAccessBindingFlags) .Where(property => (!get || property.CanRead) && (!set || property.CanWrite)) .ToArray(); // Make sure only non-indexer properties are used for settings ExecuteActionForMembers(memberAction, properties); } // Execute specified action over specified members private void ExecuteActionForMembers(Action memberAction, T[] members) where T : MemberInfo { SerializeSettingAttribute attribute; // Execute action for each member foreach (T member in members) { // See if serialize setting attribute exists if (member.TryGetAttribute(out attribute)) { // Found serialize setting attribute, perform action if setting is true if (attribute.Serialize) memberAction(member); } else if (!m_requireSerializeSettingAttribute) { // Didn't find serialize setting attribute and it's not required, so we perform action memberAction(member); } } } /// /// Attempts to find specified attribute and return specified value. /// /// Type of to find. /// Type of value attribute delegate returns. /// Name of field or property to search for attribute. /// Default value to return if attribute doesn't exist. /// Function delegate used to return desired attribute property. /// Specified attribute value if it exists; otherwise default value. [EditorBrowsable(EditorBrowsableState.Advanced)] protected TValue GetAttributeValue(string name, TValue defaultValue, Func attributeValue) where TAttribute : Attribute { TAttribute attribute; // See if field exists with specified name FieldInfo field = this.GetType().GetField(name, m_memberAccessBindingFlags); if ((object)field != null) { // See if attribute exists on field if (field.TryGetAttribute(out attribute)) { // Return value as specified by delegate return attributeValue(attribute); } // Attribute wasn't found, return default value return defaultValue; } // See if property exists with specified name PropertyInfo property = this.GetType().GetProperty(name, m_memberAccessBindingFlags); if ((object)property != null) { // See if attribute exists on property if (property.TryGetAttribute(out attribute)) { // Return value as specified by delegate return attributeValue(attribute); } // Attribute wasn't found, return default value return defaultValue; } // Return default value return defaultValue; } #endregion } }