//******************************************************************************************************
// CategorizedSettingsBase.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
// Made initialize during constructor optional for languages that do not initialize
// member variables before call to constructor (e.g., Visual Basic.NET).
// Updated class to pick up DesctiptionAttribute and apply value to settings.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 04/21/2010 - J. Ritchie Carroll
// Added attribute check for UserScopedSetting to apply to settings.
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.ComponentModel;
using System.Configuration;
using ExpressionEvaluator;
using GSF.ComponentModel;
// ReSharper disable VirtualMemberCallInConstructor
namespace GSF.Configuration
{
///
/// Represents the base class for application settings that are synchronized with a categorized section in a configuration file.
///
///
///
/// In order to make custom types serializable for the configuration file, implement a for the type.
/// See MSDN for details.
///
///
/// Here is an example class derived from that automatically
/// serializes its fields and properties to the configuration file.
///
/// public enum MyEnum
/// {
/// One,
/// Two,
/// Three
/// }
///
/// public class MySettings : CategorizedSettingsBase
/// {
/// // Private property fields (private fields will not be serialized)
/// private double m_doubleVal;
///
/// // Public settings fields
/// public bool BoolVal = true;
/// public int IntVal = 1;
/// public float FloatVal = 3.14F;
/// public string StrVal = "This is a test...";
/// public MyEnum EnumVal = MyEnum.Three;
///
/// [SettingName("UserOptions"), EncryptSetting()]
/// public string Password = "default";
///
/// // Mark this field to not be serialized to configuration file...
/// [SerializeSetting(false)]
/// public decimal DecimalVal;
///
/// public MySettings()
/// : base("GeneralSettings") {}
///
/// [Category("OtherSettings"), Description("My double value setting description."), DefaultValue(1.159D)]
/// public double DoubleVal
/// {
/// get
/// {
/// return m_doubleVal;
/// }
/// set
/// {
/// m_doubleVal = value;
/// }
/// }
///
/// [SerializeSetting(false)]
/// public bool DontSerializeMe { get; set; }
/// }
///
///
///
public abstract class CategorizedSettingsBase : SettingsBase
{
#region [ Members ]
// Fields
private ConfigurationFile m_configFile;
private string m_categoryName;
private bool m_useCategoryAttributes;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class for the application's configuration file.
///
/// Name of default category to use to get and set settings from configuration file.
protected CategorizedSettingsBase(string categoryName)
: this(ConfigurationFile.Current, categoryName, true, false, true)
{
}
///
/// Creates a new instance of the class for the application's configuration file.
///
/// Name of default category to use to get and set settings from configuration file.
/// Determines if category attributes will be used for category names.
///
/// Assigns flag that determines if is required
/// to exist before a field or property is serialized to the configuration file.
///
///
/// If is false, all settings will be placed in section labeled by the
/// value; otherwise, if a exists on a field or
/// property then the member value will serialized into the configuration file in a section labeled the same
/// as the value and if the attribute doesn't exist the member value
/// will serialized into the section labeled by the value.
///
protected CategorizedSettingsBase(string categoryName, bool useCategoryAttributes, bool requireSerializeSettingAttribute)
: this(ConfigurationFile.Current, categoryName, useCategoryAttributes, requireSerializeSettingAttribute, true)
{
}
///
/// Creates a new instance of the class for the application's configuration file.
///
/// Configuration file used for accessing settings.
/// Name of default category to use to get and set settings from configuration file.
/// Determines if category attributes will be used for category names.
///
/// Assigns flag that determines if is required
/// to exist before a field or property is serialized to the configuration file.
///
/// Determines if method should be called from constructor.
///
///
/// If is false, all settings will be placed in section labeled by the
/// value; otherwise, if a exists on a field or
/// property then the member value will serialized into the configuration file in a section labeled the same
/// as the value and if the attribute doesn't exist the member value
/// will serialized into the section labeled by the value.
///
///
/// Note that some .NET languages (e.g., Visual Basic) will not initialize member elements before call to constructor,
/// in this case should be set to false, then the
/// method should be called manually after all properties have been initialized. Alternately, consider using the
/// on the fields or properties and this will be used to initialize the values.
///
///
protected CategorizedSettingsBase(ConfigurationFile configFile, string categoryName, bool useCategoryAttributes, bool requireSerializeSettingAttribute, bool initialize)
: base(requireSerializeSettingAttribute)
{
m_configFile = configFile;
m_categoryName = categoryName;
m_useCategoryAttributes = useCategoryAttributes;
// Make sure settings exist and load current values
if (initialize)
Initialize();
}
#endregion
#region [ Properties ]
///
/// Gets or sets reference to working configuration file.
///
/// value cannot be null.
protected ConfigurationFile ConfigFile
{
get
{
return m_configFile;
}
set
{
if ((object)value == null)
throw new NullReferenceException("value cannot be null");
m_configFile = value;
}
}
///
/// Gets or sets default category name of section used to access settings in configuration file.
///
[Browsable(false), SerializeSetting(false)]
public string CategoryName
{
get
{
return m_categoryName;
}
set
{
m_categoryName = value;
}
}
///
/// Gets or sets value that determines whether a applied to a field or property
/// will be used for the category name.
///
///
/// If is false, all settings will be placed in section labeled by the
/// value; otherwise, if a exists on a field or
/// property then the member value will serialized into the configuration file in a section labeled the same
/// as the value and if the attribute doesn't exist the member value
/// will serialized into the section labeled by the value.
///
[Browsable(false), SerializeSetting(false)]
public bool UseCategoryAttributes
{
get
{
return m_useCategoryAttributes;
}
set
{
m_useCategoryAttributes = value;
}
}
#endregion
#region [ Methods ]
///
/// Create setting in configuration file if it doesn't already exist.
/// This method is for internal use.
///
/// Field or property name, if useful (can be different from setting name).
/// Setting name.
/// Setting value.
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void CreateSetting(string name, string setting, string value)
{
m_configFile.Settings[GetFieldCategoryName(name)].Add(setting, value, GetFieldDescription(name), GetEncryptStatus(name), GetFieldSettingScope(name));
}
///
/// Retrieves setting from configuration file.
/// This method is for internal use.
///
/// Field or property name, if useful (can be different from setting name).
/// Setting name.
/// Setting value.
[EditorBrowsable(EditorBrowsableState.Never)]
protected override string RetrieveSetting(string name, string setting)
{
return m_configFile.Settings[GetFieldCategoryName(name)][setting].Value;
}
///
/// Stores setting to configuration file.
/// This method is for internal use.
///
/// Field or property name, if useful (can be different from setting name).
/// Setting name.
/// Setting value.
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void StoreSetting(string name, string setting, string value)
{
m_configFile.Settings[GetFieldCategoryName(name)][setting].Value = value;
}
///
/// Persist any pending changes to configuration file.
/// This method is for internal use.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void PersistSettings()
{
m_configFile.Save();
}
///
/// Gets the category name to use for the specified field or property.
///
/// Field or property name.
/// applied to specified field or property; or if attribute does not exist.
/// cannot be null or empty.
///
/// will only be returned if is true; otherwise
/// value will be returned.
///
public string GetFieldCategoryName(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("name cannot be null or empty");
// If user wants to respect category attributes, we attempt to use those as configuration section names
if (m_useCategoryAttributes)
return GetAttributeValue(name, m_categoryName, attribute => attribute.Category);
// Otherwise return default category name
return m_categoryName;
}
///
/// Gets the description specified by , if any, applied to the specified field or property.
///
/// Field or property name.
/// Description applied to specified field or property; or null if one does not exist.
/// cannot be null or empty.
public string GetFieldDescription(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("name cannot be null or empty");
return GetAttributeValue(name, "", attribute => attribute.Description);
}
///
/// Gets the specified by , if any, applied to the specified field or property.
///
/// Field or property name.
/// Description applied to specified field or property; or null if one does not exist.
/// cannot be null or empty.
public SettingScope GetFieldSettingScope(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("name cannot be null or empty");
return GetAttributeValue(name, SettingScope.Application, attribute => SettingScope.User);
}
#endregion
}
///
/// Represents the base class for application settings that are synchronized with a categorized section in a configuration file
/// and will perform bi-directional synchronization of elements marked with
/// to an external source, e.g., user interface elements.
///
///
/// Consumer will need to call method when modeled
/// external sources are updated to ensure properties stay in-sync with external source.
///
/// Type of derived .
public abstract class CategorizedSettingsBase : CategorizedSettingsBase where TCategorizedSettings : CategorizedSettingsBase
{
#region [ Constructors ]
///
/// Creates a new instance of the class for the application's configuration file.
///
/// Name of default category to use to get and set settings from configuration file.
///
/// Type registry to use when parsing instances, or null
/// to use .
///
protected CategorizedSettingsBase(string categoryName, TypeRegistry typeRegistry = null)
: this(ConfigurationFile.Current, categoryName, true, false, true, typeRegistry)
{
}
///
/// Creates a new instance of the class for the application's configuration file.
///
/// Name of default category to use to get and set settings from configuration file.
/// Determines if category attributes will be used for category names.
///
/// Assigns flag that determines if is required
/// to exist before a field or property is serialized to the configuration file.
///
///
/// Type registry to use when parsing instances, or null
/// to use .
///
///
/// If is false, all settings will be placed in section labeled by the
/// value; otherwise, if a exists on a field or
/// property then the member value will serialized into the configuration file in a section labeled the same
/// as the value and if the attribute doesn't exist the member value
/// will serialized into the section labeled by the value.
///
protected CategorizedSettingsBase(string categoryName, bool useCategoryAttributes, bool requireSerializeSettingAttribute, TypeRegistry typeRegistry = null)
: this(ConfigurationFile.Current, categoryName, useCategoryAttributes, requireSerializeSettingAttribute, true, typeRegistry)
{
}
///
/// Creates a new instance of the class for the application's configuration file.
///
/// Configuration file used for accessing settings.
/// Name of default category to use to get and set settings from configuration file.
/// Determines if category attributes will be used for category names.
///
/// Assigns flag that determines if is required
/// to exist before a field or property is serialized to the configuration file.
///
/// Determines if method should be called from constructor.
///
/// Type registry to use when parsing instances, or null
/// to use .
///
///
///
/// If is false, all settings will be placed in section labeled by the
/// value; otherwise, if a exists on a field or
/// property then the member value will serialized into the configuration file in a section labeled the same
/// as the value and if the attribute doesn't exist the member value
/// will serialized into the section labeled by the value.
///
///
/// Note that some .NET languages (e.g., Visual Basic) will not initialize member elements before call to constructor,
/// in this case should be set to false, then the
/// method should be called manually after all properties have been initialized. Alternately, consider using the
/// on the fields or properties and this will be used to initialize the values.
///
///
protected CategorizedSettingsBase(ConfigurationFile configFile, string categoryName, bool useCategoryAttributes, bool requireSerializeSettingAttribute, bool initialize, TypeRegistry typeRegistry = null)
: base(configFile, categoryName, useCategoryAttributes, requireSerializeSettingAttribute, initialize, typeRegistry)
{
}
#endregion
}
///
/// Represents the base class for application settings that are synchronized with a categorized section in a configuration file
/// and will perform bi-directional synchronization of elements marked with to
/// an external source, e.g., user interface elements.
///
///
/// Consumer will need to call method when modeled external sources are updated to ensure properties
/// stay in-sync with external source.
///
/// Type of used for run-time value synchronization.
/// Type of derived .
public abstract class CategorizedSettingsBase : CategorizedSettingsBase where TValueExpressionAttribute : Attribute, IValueExpressionAttribute where TCategorizedSettings : CategorizedSettingsBase
{
#region [ Members ]
// Fields
private readonly Action m_updateExpressions;
private readonly Action m_updateProperties;
private readonly TCategorizedSettings m_instance;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class for the application's configuration file.
///
/// Name of default category to use to get and set settings from configuration file.
///
/// Type registry to use when parsing instances, or null
/// to use .
///
protected CategorizedSettingsBase(string categoryName, TypeRegistry typeRegistry = null)
: this(ConfigurationFile.Current, categoryName, true, false, true, typeRegistry)
{
}
///
/// Creates a new instance of the class for the application's configuration file.
///
/// Name of default category to use to get and set settings from configuration file.
/// Determines if category attributes will be used for category names.
///
/// Assigns flag that determines if is required
/// to exist before a field or property is serialized to the configuration file.
///
///
/// Type registry to use when parsing instances, or null
/// to use .
///
///
/// If is false, all settings will be placed in section labeled by the
/// value; otherwise, if a exists on a field or
/// property then the member value will serialized into the configuration file in a section labeled the same
/// as the value and if the attribute doesn't exist the member value
/// will serialized into the section labeled by the value.
///
protected CategorizedSettingsBase(string categoryName, bool useCategoryAttributes, bool requireSerializeSettingAttribute, TypeRegistry typeRegistry = null)
: this(ConfigurationFile.Current, categoryName, useCategoryAttributes, requireSerializeSettingAttribute, true, typeRegistry)
{
}
///
/// Creates a new instance of the class for the application's configuration file.
///
/// Configuration file used for accessing settings.
/// Name of default category to use to get and set settings from configuration file.
/// Determines if category attributes will be used for category names.
///
/// Assigns flag that determines if is required
/// to exist before a field or property is serialized to the configuration file.
///
/// Determines if method should be called from constructor.
///
/// Type registry to use when parsing instances, or null
/// to use .
///
///
///
/// If is false, all settings will be placed in section labeled by the
/// value; otherwise, if a exists on a field or
/// property then the member value will serialized into the configuration file in a section labeled the same
/// as the value and if the attribute doesn't exist the member value
/// will serialized into the section labeled by the value.
///
///
/// Note that some .NET languages (e.g., Visual Basic) will not initialize member elements before call to constructor,
/// in this case should be set to false, then the
/// method should be called manually after all properties have been initialized. Alternately, consider using the
/// on the fields or properties and this will be used to initialize the values.
///
///
protected CategorizedSettingsBase(ConfigurationFile configFile, string categoryName, bool useCategoryAttributes, bool requireSerializeSettingAttribute, bool initialize, TypeRegistry typeRegistry = null)
: base(configFile, categoryName, useCategoryAttributes, requireSerializeSettingAttribute, false)
{
// Compile delegates to handle updating expressions and properties
m_updateExpressions = ValueExpressionParser.UpdateExpressionsForType(null, typeRegistry);
m_updateProperties = ValueExpressionParser.UpdateInstanceForType(null, typeRegistry);
m_instance = (TCategorizedSettings)(object)this;
if (!initialize)
return;
// Update instance properties with modeled value expressions evaluated and applied, in this case,
// any initial modeled value expressions values get applied as the default setting values
m_updateProperties(m_instance);
// Load current config file settings
Initialize();
// Apply loaded settings to modeled value expressions
m_updateExpressions(m_instance);
}
#endregion
#region [ Methods ]
///
/// Refresh property values from modeled value expressions.
///
public void UpdateProperties() => m_updateProperties(m_instance);
///
/// Refresh modeled value expressions from property values.
///
public void UpdateExpressions() => m_updateExpressions(m_instance);
#endregion
}
}