예제 #1
0
        /// <summary>   If the setting is found in the <paramref name="settingsStore"/>, retrieves the value of the setting,
        ///             converts or deserializes it to the type of the wrapped property, and calls the property set method on
        ///             the <paramref name="baseOptionModel"/>. No exceptions should be thrown from
        ///             this method. No changes to the property will be made if the setting does not exist. </summary>
        /// <typeparam name="TOptMdl">  Type of the base option model. </typeparam>
        /// <param name="baseOptionModel">  The base option model which is used as the target object on which the property
        ///                                 will be set. It also can be used for deserialization of stored data.  </param>
        /// <param name="settingsStore">    The settings store to retrieve the setting value from. </param>
        /// <returns>   True if the value exists in the <paramref name="settingsStore"/>, and the property was updated in
        ///             <paramref name="baseOptionModel"/>, false if setting does not exist or any step of the process
        ///             failed. </returns>
        public virtual bool Load <TOptMdl>(BaseOptionModel <TOptMdl> baseOptionModel, SettingsStore settingsStore) where TOptMdl : BaseOptionModel <TOptMdl>, new()
        {
            string collectionName = OverrideCollectionName ?? baseOptionModel.CollectionName;

            object?value = null;

            try
            {
                if (!settingsStore.PropertyExists(collectionName, PropertyName))
                {
                    return(false);
                }

                value = SettingStoreGetMethod(settingsStore, collectionName, PropertyName);
                value = ConvertStorageTypeToPropertyType(value, baseOptionModel);
                WrappedPropertySetMethod(baseOptionModel, value);
                return(true);
            }
            catch (Exception ex)
            {
                ex.Log("BaseOptionModel<{0}>.{1} CollectionName:{2} PropertyName:{3} dataType:{4} PropertyType:{5} Value:{6}",
                       baseOptionModel.GetType().FullName, nameof(Load), collectionName, PropertyName, DataType, PropertyInfo.PropertyType,
                       value ?? "[NULL]");
            }

            return(false);
        }
예제 #2
0
        /// <summary>   The value of the wrapped property is retrieved by calling the property get method on <paramref name="baseOptionModel"/>.
        ///             This value is converted or serialized to a native type supported by the <paramref name="settingsStore"/>,
        ///             then persisted to the store, assuring the collection exists first. No exceptions should be thrown from
        ///             this method. </summary>
        /// <typeparam name="TOptMdl">  Type of the base option model. </typeparam>
        /// <param name="baseOptionModel">  The base option model which is used as the target object from which the property
        ///                                 value will be retrieved. It also can be used for serialization of stored data.  </param>
        /// <param name="settingsStore">    The settings store to set the setting value in. </param>
        /// <returns>   True if we were able to persist the value in the store. However, if the serialization results in a null value,
        ///             it cannot be persisted in the settings store and false will be returned. False is also returned if any step
        ///             of the process failed, and these are logged. </returns>
        public virtual bool Save <TOptMdl>(BaseOptionModel <TOptMdl> baseOptionModel, WritableSettingsStore settingsStore) where TOptMdl : BaseOptionModel <TOptMdl>, new()
        {
            string collectionName = OverrideCollectionName ?? baseOptionModel.CollectionName;
            object?value          = null;

            try
            {
                value = WrappedPropertyGetMethod(baseOptionModel);

                value = ConvertPropertyTypeToStorageType(value, baseOptionModel);

                if (value == null)
                {
                    Exception ex = new("Cannot store null in settings store.");
                    ex.LogAsync("BaseOptionModel<{0}>.{1} CollectionName:{2} PropertyName:{3} dataType:{4} PropertyType:{5} Value:{6}",
                                baseOptionModel.GetType().FullName, nameof(Load), collectionName, PropertyName, DataType, PropertyInfo.PropertyType,
                                value ?? "[NULL]").Forget();
                    return(false);
                }

                // Rather than if ! CollectionExists then CreateCollection this is likely more efficient.
                settingsStore.CreateCollection(collectionName);
                SettingStoreSetMethod(settingsStore, collectionName, PropertyName, value);

                return(true);
            }
            catch (Exception ex)
            {
                ex.Log("BaseOptionModel<{0}>.{1} CollectionName:{2} PropertyName:{3} dataType:{4} PropertyType:{5} Value:{6}",
                       baseOptionModel.GetType().FullName, nameof(Load), collectionName, PropertyName, DataType, PropertyInfo.PropertyType,
                       value ?? "[NULL]");
            }

            return(false);
        }
예제 #3
0
 /// <summary>
 /// Creates a new instance of the options page.
 /// </summary>
 public BaseOptionPage()
 {
     _model = ThreadHelper.JoinableTaskFactory.Run(BaseOptionModel <T> .CreateAsync);
 }
예제 #4
0
        /// <summary>Creates a new instance of the options page.</summary>
        public BaseOptionPage()
        {
#pragma warning disable VSTHRD104 // Offer async methods
            _model = ThreadHelper.JoinableTaskFactory.Run(BaseOptionModel <T> .CreateAsync);
#pragma warning restore VSTHRD104 // Offer async methods
        }
예제 #5
0
        /// <summary>   Convert the <paramref name="settingsStoreValue"/> retrieved from the settings store to the type of the
        /// property we are wrapping. See remarks at <see cref="ConvertPropertyTypeToStorageType{T}"/></summary>
        /// <typeparam name="TOptMdl">  Type of <see cref="BaseOptionModel{TOptMdl}"/>. </typeparam>
        /// <param name="settingsStoreValue">                The value retrieved from the settings store, as an object. This will not be null. </param>
        /// <param name="baseOptionModel">      Instance of <see cref="BaseOptionModel{TOptMdl}"/>. For types requiring deserialization, methods in this object are used. </param>
        /// <returns>   <paramref name="settingsStoreValue"/>, converted to the property type. </returns>
        protected virtual object?ConvertStorageTypeToPropertyType <TOptMdl>(object settingsStoreValue, BaseOptionModel <TOptMdl> baseOptionModel) where TOptMdl : BaseOptionModel <TOptMdl>, new()
        {
            Type typeOfWrappedProperty = PropertyInfo.PropertyType;

            if (typeOfWrappedProperty.IsEnum)
            {
                typeOfWrappedProperty = typeOfWrappedProperty.GetEnumUnderlyingType();
            }

            switch (DataType)
            {
            case SettingDataType.Serialized:
                if (NativeStorageType != NativeSettingsType.String)
                {
                    throw new InvalidOperationException($"The SettingDataType of Serialized must be SettingsType.String. Was: {NativeStorageType}");
                }
                return(baseOptionModel.DeserializeValue((string)settingsStoreValue, typeOfWrappedProperty, PropertyName));

            case SettingDataType.Legacy:
                if (NativeStorageType != NativeSettingsType.String)
                {
                    throw new InvalidOperationException($"The SettingDataType of Legacy must be SettingsType.String. Was: {NativeStorageType}");
                }
                return(LegacyDeserializeValue((string)settingsStoreValue, typeOfWrappedProperty));
            }

            if (TypeConverter != null)
            {
                Type valueType = settingsStoreValue.GetType();
                if (NativeStorageType == NativeSettingsType.Binary)
                {
                    // Type converter uses byte[] so extract byte array and set the conversion type.
                    valueType          = typeof(byte[]);
                    settingsStoreValue = ((MemoryStream)settingsStoreValue).ToArray();
                }
                if (!TypeConverter.CanConvertFrom(valueType))
                {
                    throw new InvalidOperationException($"TypeConverter {TypeConverter.GetType().FullName} can not convert from {valueType.Name} to {typeOfWrappedProperty.FullName}.");
                }

                object?returnObject = TypeConverter.ConvertFrom(null !, CultureInfo.InvariantCulture, settingsStoreValue);
                if (returnObject == null)
                {
                    if (typeOfWrappedProperty.IsValueType)
                    {
                        throw new InvalidOperationException($"TypeConverter {TypeConverter.GetType().FullName} attempt to convert from {valueType.Name} to {typeOfWrappedProperty.FullName} returned null for a value type.");
                    }
                    return(returnObject);
                }
                if (!typeOfWrappedProperty.IsInstanceOfType(returnObject))
                {
                    throw new InvalidOperationException($"TypeConverter {TypeConverter.GetType().FullName} attempt to convert from {valueType.Name} to {typeOfWrappedProperty.FullName} returned incompatible type {returnObject.GetType().FullName}.");
                }
                return(returnObject);
            }

            switch (NativeStorageType)
            {
            case NativeSettingsType.Int32:
                if (typeOfWrappedProperty == typeof(Color))
                {
                    return(Color.FromArgb((int)settingsStoreValue));
                }
                break;

            case NativeSettingsType.Int64:
                if (typeOfWrappedProperty == typeof(DateTime))
                {
                    return(DateTime.FromBinary((long)settingsStoreValue));
                }
                break;

            case NativeSettingsType.String:
                if (typeOfWrappedProperty == typeof(Guid))
                {
                    return(Guid.Parse((string)settingsStoreValue));
                }
                if (typeOfWrappedProperty == typeof(DateTimeOffset))
                {
                    return(DateTimeOffset.Parse((string)settingsStoreValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind));
                }
                break;

            case NativeSettingsType.Binary:
                if (typeOfWrappedProperty == typeof(MemoryStream))
                {
                    return((MemoryStream)settingsStoreValue);
                }
                if (typeOfWrappedProperty == typeof(byte[]))
                {
                    return(((MemoryStream)settingsStoreValue).ToArray());
                }
                throw new InvalidCastException($"Can not convert SettingsType.Binary to {typeOfWrappedProperty.FullName} - property type must be byte[] or MemoryStream.");
            }

            if (typeOfWrappedProperty.IsInstanceOfType(settingsStoreValue))
            {
                return(settingsStoreValue);
            }

            return(Convert.ChangeType(settingsStoreValue, typeOfWrappedProperty, CultureInfo.InvariantCulture));
        }
예제 #6
0
        /// <summary>   Convert the <paramref name="propertyValue"/> retrieved from the property to the type it will be stored as in the
        ///             <see cref="SettingsStore"/>. </summary>
        /// <typeparam name="TOptMdl">  Type of <see cref="BaseOptionModel{TOptMdl}"/>. </typeparam>
        /// <param name="propertyValue">        The value retrieved from the wrapped property, as an object. </param>
        /// <param name="baseOptionModel">      Instance of <see cref="BaseOptionModel{TOptMdl}"/>. For types requiring serialization, methods in this object are used. </param>
        /// <returns>   <paramref name="propertyValue"/>, converted to one of the types supported by <see cref="SettingsStore"/>. </returns>
        /// <remarks>
        /// The methods <see cref="ConvertPropertyTypeToStorageType{T}" />, <see cref="ConvertStorageTypeToPropertyType{T}" />, and <see cref="InferDataType"/> are designed to
        /// work in tandem, and are therefore tightly coupled. The <see cref="SettingsStore"/> cannot store null values, therefore any property that is converted
        /// to a reference type cannot round-trip successfully if that conversion yields <see cref="string"/>, <see cref="MemoryStream"/>, and arrays of
        /// <see cref="byte"/> - in these cases the equivalent of <c>empty</c> is stored, therefore when loaded the result will not match.
        /// <para />
        /// The method <see cref="InferDataType"/> returns an enumeration that identifies both the native storage type, and method of conversion, that
        /// will be used when storing the property value. These defaults can be overridden via the <see cref="OverrideDataTypeAttribute"/>.
        /// <para />
        /// The method <see cref="ConvertPropertyTypeToStorageType{T}" /> is provided the current value of the property. It's job is to convert this value to
        /// the native storage type based on <see cref="DataType"/> which is set via <see cref="InferDataType"/>.
        /// <para />
        /// The method <see cref="ConvertStorageTypeToPropertyType{T}" /> is the reverse of the above. Given an instance of the native storage type,
        /// it's job is to convert it to an instance the property type.
        /// <para />
        /// The conversions between types in the default implementation follows this:
        /// <list type="bullet">
        ///  <item> <description>A property with a setting data type of <see cref="SettingDataType.Legacy"/> uses <see cref="BinaryFormatter"/> and stores it as a base64 encoded string. <see langword="null"/> values are stored as an empty string. </description></item>
        ///  <item> <description>Array of <see cref="byte"/> is wrapped in a <see cref="MemoryStream"/>. <see langword="null"/> values are converted to an empty <see cref="MemoryStream"/>.</description></item>
        ///  <item> <description><see cref="Color"/>, with setting data type <see cref="SettingDataType.Int32"/> uses To[From]Argb to store it as an Int32.</description></item>
        ///  <item> <description><see cref="Guid"/>, with setting data type <see cref="SettingDataType.String"/> uses <see cref="Guid.ToString()"/> and <see cref="Guid.Parse"/> to convert to and from a string.</description></item>
        ///  <item> <description><see cref="DateTime"/>, with setting data type <see cref="SettingDataType.Int64"/> uses To[From]Binary to store it as an Int64.</description></item>
        ///  <item> <description><see cref="DateTimeOffset"/>, with setting data type <see cref="SettingDataType.String"/> uses the round-trip 'o' specifier to store as a string.</description></item>
        ///  <item> <description><see cref="float"/> and <see cref="double"/>, with setting data type <see cref="SettingDataType.String"/> uses the round-trip 'G9' and 'G17' specifier to store as a string, and is parsed via the standard Convert method.</description></item>
        ///  <item> <description><see cref="string"/>, if null, is stored as an empty string.</description></item>
        ///  <item> <description>Enumerations are converted to/from their underlying type.</description></item>
        ///  <item> <description><a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/integral-numeric-types">Integral numeric types</a>,
        ///                      <see cref="float"/>, <see cref="double"/>, <see cref="decimal"/>, and <see cref="char"/>
        ///                      use <see cref="Convert.ChangeType(object, Type, IFormatProvider)" />, using <see cref="CultureInfo.InvariantCulture"/>. Enumerations are
        ///                      stored as their underlying integral numeric type.</description></item>
        ///  <item> <description>Any type not described above, or a property with a setting data type of <see cref="SettingDataType.Serialized"/>
        ///                      uses <see cref="BaseOptionModel{T}.SerializeValue"/> and <see cref="BaseOptionModel{T}.DeserializeValue"/> and stores it as binary,
        ///                      refer to those overridable methods for details.</description></item>
        /// </list>
        /// </remarks>
        protected virtual object ConvertPropertyTypeToStorageType <TOptMdl>(object?propertyValue, BaseOptionModel <TOptMdl> baseOptionModel) where TOptMdl : BaseOptionModel <TOptMdl>, new()
        {
            switch (DataType)
            {
            case SettingDataType.Serialized:
                if (NativeStorageType != NativeSettingsType.String)
                {
                    throw new InvalidOperationException($"The SettingDataType of Serialized is not capable of supporting native storage type {NativeStorageType}");
                }
                string serializedString = baseOptionModel.SerializeValue(propertyValue, PropertyInfo.PropertyType, PropertyName);
                if (serializedString == null)
                {
                    throw new InvalidOperationException($"The SerializeValue method of {baseOptionModel.GetType().FullName} returned " +
                                                        " a null value. This method cannot return null.");
                }
                return(serializedString);

            case SettingDataType.Legacy:
                if (NativeStorageType != NativeSettingsType.String)
                {
                    throw new InvalidOperationException($"The SettingDataType of Legacy is not capable of supporting native storage type {NativeStorageType}");
                }
                return(LegacySerializeValue(propertyValue));
            }

            Type conversionType = NativeStorageType.GetDotNetType();

            if (TypeConverter != null)
            {
                bool returnMemoryStream = false;
                if (NativeStorageType == NativeSettingsType.Binary)
                {
                    // For binary, the type conversion should be to byte[], then we return a memory stream.
                    returnMemoryStream = true;
                    conversionType     = typeof(byte[]);
                }

                if (!TypeConverter.CanConvertTo(conversionType))
                {
                    throw new InvalidOperationException($"TypeConverter {TypeConverter.GetType().FullName} can not convert {PropertyInfo.PropertyType.FullName} to {NativeStorageType} ({conversionType.Name})");
                }

                object?convertedObj = TypeConverter.ConvertTo(null, CultureInfo.InvariantCulture, propertyValue, conversionType);
                if (convertedObj == null)
                {
                    throw new InvalidOperationException($"TypeConverter {TypeConverter.GetType().FullName} returned null converting from {PropertyInfo.PropertyType.FullName} to {NativeStorageType} ({conversionType.Name}), which is not supported.");
                }
                if (!conversionType.IsInstanceOfType(convertedObj))
                {
                    throw new InvalidOperationException($"TypeConverter {TypeConverter.GetType().FullName} returned type {convertedObj.GetType().FullName} when converting from {PropertyInfo.PropertyType.FullName} to {NativeStorageType} ({conversionType.Name}).");
                }
                if (returnMemoryStream)
                {
                    return(new MemoryStream((byte[])convertedObj));
                }
                return(convertedObj);
            }

            switch (NativeStorageType)
            {
            case NativeSettingsType.Int32:
                if (propertyValue is Color color)
                {
                    return(color.ToArgb());
                }
                break;

            case NativeSettingsType.Int64:
                if (propertyValue is DateTime dt)
                {
                    return(dt.ToBinary());
                }
                break;

            case NativeSettingsType.String:
                if (propertyValue is Guid guid)
                {
                    return(guid.ToString());
                }
                if (propertyValue is DateTimeOffset dtOffset)
                {
                    return(dtOffset.ToString("o", CultureInfo.InvariantCulture));
                }
                if (propertyValue is float floatVal)
                {
                    return(floatVal.ToString("G9", CultureInfo.InvariantCulture));
                }
                if (propertyValue is double doubleVal)
                {
                    return(doubleVal.ToString("G17", CultureInfo.InvariantCulture));
                }
                if (propertyValue == null)
                {
                    return(string.Empty);
                }
                break;

            case NativeSettingsType.Binary:
                if (propertyValue is byte[] bytes)
                {
                    return(new MemoryStream(bytes));
                }
                if (propertyValue is MemoryStream memStream)
                {
                    return(memStream);
                }
                if (propertyValue == null)
                {
                    return(new MemoryStream());
                }
                throw new InvalidOperationException($"Can not convert NativeStorageType of Binary to {propertyValue.GetType().FullName} - property type must be byte[] or MemoryStream.");
            }

            if (propertyValue == null)
            {
                throw new InvalidOperationException($"A null property value with SettingDataType of {DataType} is not supported.");
            }

            if (conversionType.IsInstanceOfType(propertyValue))
            {
                return(propertyValue);
            }

            return(Convert.ChangeType(propertyValue, conversionType, CultureInfo.InvariantCulture));
        }