diff --git a/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs index ea661c5498..394630fa64 100644 --- a/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs @@ -88,10 +88,6 @@ public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase _publishedSnapshotService.Notify(payloads)); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs index 76f5b62265..14e80952f4 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Globalization; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; @@ -6,74 +6,123 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; +/// +/// The slider property value converter. +/// +/// [DefaultPropertyValueConverter] public class SliderValueConverter : PropertyValueConverterBase { - private static readonly ConcurrentDictionary Storages = new(); - private readonly IDataTypeService _dataTypeService; + /// + /// Initializes a new instance of the class. + /// + public SliderValueConverter() + { } - public SliderValueConverter(IDataTypeService dataTypeService) => _dataTypeService = - dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); + /// + /// Initializes a new instance of the class. + /// + /// The data type service. + [Obsolete("The IDataTypeService is not used anymore. This constructor will be removed in a future version.")] + public SliderValueConverter(IDataTypeService dataTypeService) + { } - public static void ClearCaches() => Storages.Clear(); + /// + /// Clears the data type configuration caches. + /// + [Obsolete("Caching of data type configuration is not done anymore. This method will be removed in a future version.")] + public static void ClearCaches() + { } + /// public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Slider); + /// public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => IsRangeDataType(propertyType.DataType.Id) ? typeof(Range) : typeof(decimal); + => IsRange(propertyType) ? typeof(Range) : typeof(decimal); + /// public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; + /// public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) { - if (source == null) + bool isRange = IsRange(propertyType); + + var sourceString = source?.ToString(); + + return isRange + ? HandleRange(sourceString) + : HandleDecimal(sourceString); + } + + private static Range HandleRange(string? sourceString) + { + if (sourceString is null) { - return null; + return new Range(); } - if (IsRangeDataType(propertyType.DataType.Id)) - { - var rangeRawValues = source.ToString()!.Split(Constants.CharArrays.Comma); - Attempt minimumAttempt = rangeRawValues[0].TryConvertTo(); - Attempt maximumAttempt = rangeRawValues[1].TryConvertTo(); + string[] rangeRawValues = sourceString.Split(Constants.CharArrays.Comma); - if (minimumAttempt.Success && maximumAttempt.Success) + if (TryParseDecimal(rangeRawValues[0], out var minimum)) + { + if (rangeRawValues.Length == 1) { - return new Range { Maximum = maximumAttempt.Result, Minimum = minimumAttempt.Result }; + // Configuration is probably changed from single to range, return range with same min/max + return new Range + { + Minimum = minimum, + Maximum = minimum + }; + } + + if (rangeRawValues.Length == 2 && TryParseDecimal(rangeRawValues[1], out var maximum)) + { + return new Range + { + Minimum = minimum, + Maximum = maximum + }; } } - Attempt valueAttempt = source.ToString().TryConvertTo(); - if (valueAttempt.Success) + return new Range(); + } + + private static decimal HandleDecimal(string? sourceString) + { + if (string.IsNullOrEmpty(sourceString)) { - return valueAttempt.Result; + return default; } - // Something failed in the conversion of the strings to decimals - return null; + // This used to be a range slider, so we'll assign the minimum value as the new value + if (sourceString.Contains(',')) + { + var minimumValueRepresentation = sourceString.Split(Constants.CharArrays.Comma)[0]; + + if (TryParseDecimal(minimumValueRepresentation, out var minimum)) + { + return minimum; + } + } + else if (TryParseDecimal(sourceString, out var value)) + { + return value; + } + + return default; } /// - /// Discovers if the slider is set to range mode. + /// Helper method for parsing a double consistently /// - /// - /// The data type id. - /// - /// - /// The . - /// - private bool IsRangeDataType(int dataTypeId) => + private static bool TryParseDecimal(string? representation, out decimal value) + => decimal.TryParse(representation, NumberStyles.Number, CultureInfo.InvariantCulture, out value); - // GetPreValuesCollectionByDataTypeId is cached at repository level; - // still, the collection is deep-cloned so this is kinda expensive, - // better to cache here + trigger refresh in DataTypeCacheRefresher - // TODO: this is cheap now, remove the caching - Storages.GetOrAdd(dataTypeId, id => - { - IDataType? dataType = _dataTypeService.GetDataType(id); - SliderConfiguration? configuration = dataType?.ConfigurationAs(); - return configuration?.EnableRange ?? false; - }); + private static bool IsRange(IPublishedPropertyType propertyType) + => propertyType.DataType.ConfigurationAs()?.EnableRange == true; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs index 3afc5a6596..2dd1c1d56e 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Serialization; @@ -7,69 +6,66 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; +/// +/// The tags property value converter. +/// +/// [DefaultPropertyValueConverter] public class TagsValueConverter : PropertyValueConverterBase { - private static readonly ConcurrentDictionary Storages = new(); - private readonly IDataTypeService _dataTypeService; private readonly IJsonSerializer _jsonSerializer; + /// + /// Initializes a new instance of the class. + /// + /// The JSON serializer. + /// jsonSerializer + public TagsValueConverter(IJsonSerializer jsonSerializer) + => _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); + + /// + /// Initializes a new instance of the class. + /// + /// The data type service. + /// The JSON serializer. + [Obsolete("The IDataTypeService is not used anymore. This constructor will be removed in a future version.")] public TagsValueConverter(IDataTypeService dataTypeService, IJsonSerializer jsonSerializer) - { - _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); - _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); - } + : this(jsonSerializer) + { } - public static void ClearCaches() => Storages.Clear(); + /// + /// Clears the data type configuration caches. + /// + [Obsolete("Caching of data type configuration is not done anymore. This method will be removed in a future version.")] + public static void ClearCaches() + { } + /// public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Tags); + /// public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(IEnumerable); + /// public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; + /// public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { - if (source == null) + string? sourceString = source?.ToString(); + if (string.IsNullOrEmpty(sourceString)) { return Array.Empty(); } - // if Json storage type deserialize and return as string array - if (JsonStorageType(propertyType.DataType.Id)) - { - var array = source.ToString() is not null - ? _jsonSerializer.Deserialize(source.ToString()!) - : null; - return array ?? Array.Empty(); - } - - // Otherwise assume CSV storage type and return as string array - return source.ToString()?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); + return IsJson(propertyType) + ? _jsonSerializer.Deserialize(sourceString) ?? Array.Empty() + : sourceString.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) => (string[]?)source; - - /// - /// Discovers if the tags data type is storing its data in a Json format - /// - /// - /// The data type id. - /// - /// - /// The . - /// - private bool JsonStorageType(int dataTypeId) => - - // GetDataType(id) is cached at repository level; still, there is some - // deep-cloning involved (expensive) - better cache here + trigger - // refresh in DataTypeCacheRefresher - Storages.GetOrAdd(dataTypeId, id => - { - TagConfiguration? configuration = _dataTypeService.GetDataType(id)?.ConfigurationAs(); - return configuration?.StorageType == TagsStorageType.Json; - }); + private static bool IsJson(IPublishedPropertyType propertyType) + => propertyType.DataType.ConfigurationAs()?.StorageType == TagsStorageType.Json; }