//****************************************************************************************************** // CategorizedSettingsElement.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: // ---------------------------------------------------------------------------------------------------- // 04/11/2006 - Pinal C. Patel // Generated original version of source code. // 05/25/2006 - J. Ritchie Carroll // Added Try/Catch safety wrapper around GetTypedValue implementation. // 06/01/2006 - J. Ritchie Carroll // Added GetTypedValue overload to handle boolean types as a special case. // 08/17/2007 - Darrell Zuercher // Edited code comments. // 09/17/2008 - Pinal C. Patel // Converted code to C#. // 09/22/2008 - J. Ritchie Carroll // Made boolean types a special case (i.e., using ParseBoolean extension). // 09/29/2008 - Pinal C. Patel // Reviewed code comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 04/15/2010 - Pinal C. Patel // Modified property setters to update the internal property bag only if values have changed. // 04/20/2010 - Pinal C. Patel // Added new Category property for the purpose of managing user scope setting. // Removed publicly accessible constructors for manageability. // Added Scope property as a way of identifying user scope settings. // 04/21/2010 - Pinal C. Patel // Removed unnecessary overloads of Update() for manageability. // 04/22/2010 - Pinal C. Patel // Fixed encryption related bug introduced when adding support for user scope settings. // 12/05/2010 - Pinal C. Patel // Modified Update() and ValueAs() to specify CultureInfo for the conversion. // 01/11/2011 - Pinal C. Patel // Added support for use of Eval() in Value to reference other setting values and static members // of .NET types. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header, // //****************************************************************************************************** using System; using System.Configuration; using System.Reflection; using System.Text.RegularExpressions; using GSF.Security.Cryptography; namespace GSF.Configuration { #region [ Enumerations ] /// /// Specifies the scope of a setting represented by . /// public enum SettingScope { /// /// Settings is intended for user specific use. /// User, /// /// Settings is intended for application wide use. /// Application } #endregion /// /// Represents a settings entry in the config file. /// public class CategorizedSettingsElement : ConfigurationElement { #region [ Members ] // Constants /// /// Specifies the default value for the property. /// public const string DefaultValue = ""; /// /// Specifies the default value for the property. /// public const string DefaultDescription = ""; /// /// Specifies the default value for the property. /// public const bool DefaultEncrypted = false; /// /// Specifies the default value for the property. /// public const SettingScope DefaultScope = SettingScope.Application; private const string DefaultCryptoKey = "0679d9ae-aca5-4702-a3f5-604415096987"; private const string EvalRegex = @"Eval\((?(?[\w\d]+\.?)+)\.(?[\w\d]+)\)"; // Fields private string m_cryptoKey; private CategorizedSettingsElementCollection m_category; #endregion #region [ Constructors ] /// /// Required by the configuration API and is for internal use only. /// internal CategorizedSettingsElement(CategorizedSettingsElementCollection category) : this(category, "") { } /// /// Required by the configuration API and is for internal use only. /// internal CategorizedSettingsElement(CategorizedSettingsElementCollection category, string name) { m_category = category; this.Name = name; m_cryptoKey = DefaultCryptoKey; } #endregion #region [ Properties ] /// /// Gets or sets the to which this belongs. /// public CategorizedSettingsElementCollection Category { get { return m_category; } internal set { m_category = value; } } /// /// Gets or sets the identifier of the setting. /// /// The identifier of the setting. [ConfigurationProperty("name", IsKey = true, IsRequired = true)] public string Name { get { return (string)base["name"]; } set { base["name"] = value; Modified = true; } } /// /// Gets or sets the value of the setting. /// /// The value of the setting. /// /// can reference the value of another setting using Eval([Section].[Setting]) syntax or the value of a known .NET /// type's static member (field, non-indexed property or parameterless method) using Eval([Type].[Member]) syntax as shown in the example below: /// /// /// /// ///
/// /// /// /// /// /// /// /// /// /// ]]> /// /// [ConfigurationProperty("value", IsRequired = true, DefaultValue = DefaultValue)] public string Value { get { return EvaluateValue(DecryptValue(GetRawValue())); } set { // Continue only if values are different. string currentValue; try { currentValue = DecryptValue(GetRawValue()); } catch (ConfigurationErrorsException) { // Clear value if it fails to decrypt, updating anyway currentValue = string.Empty; } if (value.ToNonNullString().Equals(currentValue)) return; // Ensure only Eval() can replace Eval(). if (Regex.IsMatch(currentValue, EvalRegex, RegexOptions.IgnoreCase) && !Regex.IsMatch(value, EvalRegex, RegexOptions.IgnoreCase)) return; value = EncryptValue(value); if (Scope == SettingScope.Application || (object)Category[Name] == null) { // Setting is application wide or is being added for the first time. base["value"] = value; Modified = true; } else { // Setting is user specific so update setting in user settings store. Category.Section.File.UserSettings.Write(Category.Name, Name, value); } } } /// /// Gets or sets the description of the setting. /// /// The description of the setting. [ConfigurationProperty("description", IsRequired = true, DefaultValue = DefaultDescription)] public string Description { get { return (string)base["description"]; } set { // Continue only if values are different. if (value.ToNonNullString().Equals(Description)) return; base["description"] = value; Modified = true; } } /// /// Gets or sets a boolean value that indicates whether the setting value is to be encrypted. /// /// true, if the setting value is to be encrypted; otherwise false. [ConfigurationProperty("encrypted", IsRequired = true, DefaultValue = DefaultEncrypted)] public bool Encrypted { get { return (bool)base["encrypted"]; } set { // Continue only if values are different. if (value.Equals(Encrypted)) return; base["encrypted"] = value; Modified = true; } } /// /// Gets or sets the . /// [ConfigurationProperty("scope", IsRequired = false, DefaultValue = DefaultScope)] public SettingScope Scope { get { return (SettingScope)base["scope"]; } set { // Continue only if values are different. if (value.Equals(Scope)) return; base["scope"] = value; Modified = true; } } /// /// Gets value that will actually be serialized to the configuration file. /// public string SerializedValue { get { return EncryptValue((string)base["value"]); } } internal bool Modified { set { if ((object)m_category != null) m_category.Modified = value; } } #endregion #region [ Methods ] /// /// Sets the key to be used for encrypting and decrypting the . /// /// New crypto key. public void SetCryptoKey(string cryptoKey) { if (!string.IsNullOrEmpty(cryptoKey)) { // Re-encrypt the existing value with the new key. This is done because the value gets encrypted, // if specified, with the default crypto key when the value is set during instantiation. string decryptedValue; try { decryptedValue = Value; } catch (ConfigurationErrorsException) { // Clear value if it fails to decrypt decryptedValue = string.Empty; } m_cryptoKey = cryptoKey; Value = decryptedValue; Modified = true; } } /// /// Updates setting information. /// /// New setting value. public void Update(object value) { Update(value, Description, Encrypted, Scope); } /// /// Updates setting information. /// /// New setting value. /// New setting description. public void Update(object value, string description) { Update(value, description, Encrypted, Scope); } /// /// Updates setting information. /// /// New setting value. /// New setting description. /// A boolean value that indicated whether the new setting value is to be encrypted. public void Update(object value, string description, bool encrypted) { Update(value, description, encrypted, Scope); } /// /// Updates setting information. /// /// New setting value. /// New setting description. /// A boolean value that indicated whether the new setting value is to be encrypted. /// One of the values. public void Update(object value, string description, bool encrypted, SettingScope scope) { this.Scope = scope; this.Encrypted = encrypted; this.Value = Common.TypeConvertToString(value, m_category.Section.File.Culture); this.Description = description; } /// /// Gets the setting value as the specified type. /// /// Type to which the setting value is to be converted. /// The type-coerced value of the setting. /// /// If this function fails to properly coerce value to specified type, the default value is returned. /// public T ValueAs() { return this.ValueAs(default(T)); } /// /// Gets the setting value as the specified type. /// /// Type to which the setting value is to be converted. /// The default value to return if the setting value is empty. /// The type-coerced value of the setting. /// /// If this function fails to properly coerce value to specified type, the default value is returned. /// public T ValueAs(T defaultValue) { try { string value = Value; // Value is an empty string - use default value. if (string.IsNullOrEmpty(value)) return defaultValue; return value.ConvertToType(null, m_category.Section.File.Culture); } catch { // Conversion to target type failed so use the default value. return defaultValue; } } /// /// Gets the setting value as a string. /// /// Value as string. public string ValueAsString() { return ValueAsString(string.Empty); } /// /// Gets the setting value as a string. /// /// The default value to return if the setting value is empty. /// Value as string. public string ValueAsString(string defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as a boolean. /// /// Value as boolean. public bool ValueAsBoolean() { return ValueAsBoolean(default(bool)); } /// /// Gets the setting value as a boolean. /// /// The default value to return if the setting value is empty. /// Value as boolean. public bool ValueAsBoolean(bool defaultValue) { try { string value = Value; // Value is an empty string - use default value. if (string.IsNullOrEmpty(value)) return defaultValue; // Value is not empty string - convert to boolean. return value.ParseBoolean(); } catch { // Conversion to target type failed so use the default value. return defaultValue; } } /// /// Gets the setting value as a byte. /// /// Value as byte. public byte ValueAsByte() { return ValueAsByte(default(byte)); } /// /// Gets the setting value as a byte. /// /// The default value to return if the setting value is empty. /// Value as byte. public byte ValueAsByte(byte defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as a signed byte. /// /// Value as signed byte. public sbyte ValueAsSByte() { return ValueAsSByte(default(sbyte)); } /// /// Gets the setting value as a signed byte. /// /// The default value to return if the setting value is empty. /// Value as signed byte. public sbyte ValueAsSByte(sbyte defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as a char. /// /// Value as char. public char ValueAsChar() { return ValueAsChar(default(char)); } /// /// Gets the setting value as a char. /// /// The default value to return if the setting value is empty. /// Value as char. public char ValueAsChar(char defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as a short. /// /// Value as short. public short ValueAsInt16() { return ValueAsInt16(default(short)); } /// /// Gets the setting value as a short. /// /// The default value to return if the setting value is empty. /// Value as short. public short ValueAsInt16(short defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as an int. /// /// Value as int. public int ValueAsInt32() { return ValueAsInt32(default(int)); } /// /// Gets the setting value as an int. /// /// The default value to return if the setting value is empty. /// Value as int. public int ValueAsInt32(int defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as a long. /// /// Value as long. public long ValueAsInt64() { return ValueAsInt64(default(long)); } /// /// Gets the setting value as a long. /// /// The default value to return if the setting value is empty. /// Value as long. public long ValueAsInt64(long defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as an unsigned short. /// /// Value as unsigned short. public ushort ValueAsUInt16() { return ValueAsUInt16(default(ushort)); } /// /// Gets the setting value as an unsigned short. /// /// The default value to return if the setting value is empty. /// Value as unsigned short. public ushort ValueAsUInt16(ushort defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as an unsigned int. /// /// Value as unsigned int. public uint ValueAsUInt32() { return ValueAsUInt32(default(uint)); } /// /// Gets the setting value as an unsigned int. /// /// The default value to return if the setting value is empty. /// Value as unsigned int. public uint ValueAsUInt32(uint defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as an unsigned long. /// /// Value as unsigned long. public ulong ValueAsUInt64() { return ValueAsUInt64(default(ulong)); } /// /// Gets the setting value as an unsigned long. /// /// The default value to return if the setting value is empty. /// Value as unsigned long. public ulong ValueAsUInt64(ulong defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as a float. /// /// Value as float. public float ValueAsSingle() { return ValueAsSingle(default(float)); } /// /// Gets the setting value as a float. /// /// The default value to return if the setting value is empty. /// Value as float. public float ValueAsSingle(float defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as a double. /// /// Value as double. public double ValueAsDouble() { return ValueAsDouble(default(double)); } /// /// Gets the setting value as a double. /// /// The default value to return if the setting value is empty. /// Value as double. public double ValueAsDouble(double defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as a decimal. /// /// Value as decimal. public decimal ValueAsDecimal() { return ValueAsDecimal(default(decimal)); } /// /// Gets the setting value as a decimal. /// /// The default value to return if the setting value is empty. /// Value as decimal. public decimal ValueAsDecimal(decimal defaultValue) { return ValueAs(defaultValue); } /// /// Gets the setting value as DateTime. /// /// Value as DateTime. public DateTime ValueAsDateTime() { return ValueAsDateTime(default(DateTime)); } /// /// Gets the setting value as DateTime. /// /// The default value to return if the setting value is empty. /// Value as DateTime. public DateTime ValueAsDateTime(DateTime defaultValue) { return ValueAs(defaultValue); } private string GetRawValue() { string value = (string)base["value"]; // If setting is user specific, retrieve value from user settings store. if (Scope == SettingScope.User) value = Category.Section.File.UserSettings.Read(Category.Name, Name, value); return value; } private string EncryptValue(string value) { if (Encrypted && !string.IsNullOrEmpty(value)) { try { // Encrypts the element's value. value = value.Encrypt(m_cryptoKey, CipherStrength.Aes256); } catch (Exception ex) { throw new ConfigurationErrorsException(string.Format("Failed to encrypt '{0}'", value), ex); } } return value; } private string DecryptValue(string value) { if (Encrypted && !string.IsNullOrEmpty(value)) { try { // Decrypts the element's value. value = value.Decrypt(m_cryptoKey, CipherStrength.Aes256); } catch (Exception ex) { throw new ConfigurationErrorsException(string.Format("Failed to decrypt '{0}'", value), ex); } } return value; } private string EvaluateValue(string value) { ConfigurationFile config = m_category.Section.File; CategorizedSettingsElement setting; foreach (Match match in Regex.Matches(value, EvalRegex, RegexOptions.IgnoreCase)) { // Try replacing Eval() with the actual value. setting = config.Settings[match.Groups["Container"].Value][match.Groups["Target"].Value]; if ((object)setting != null) { // Replacement is the value of another setting. value = value.Replace(match.Value, setting.Value); } else { // Replacement could be the value of .NET type's static member. Type target = Type.GetType(match.Groups["Container"].Value); if ((object)target != null) { // Specified .NET type is found. MemberInfo[] members = target.GetMember(match.Groups["Target"].Value, BindingFlags.Public | BindingFlags.Static); if (members.Length > 0) { // Specified target member is found in the .NET type. switch (members[0].MemberType) { case MemberTypes.Field: // Member is a static field. value = value.Replace(match.Value, ((FieldInfo)members[0]).GetValue(null).ToNonNullString()); break; case MemberTypes.Method: // Member is a static method. value = value.Replace(match.Value, ((MethodInfo)members[0]).Invoke(null, new object[0]).ToNonNullString()); break; case MemberTypes.Property: // Member is a static property. value = value.Replace(match.Value, ((PropertyInfo)members[0]).GetValue(null, new object[0]).ToNonNullString()); break; } } } } } return value; } #endregion } }