diff --git a/Directory.Packages.props b/Directory.Packages.props index 59b0f032a1..c80183b9c1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,6 +19,7 @@ + diff --git a/src/Umbraco.Core/Cache/DataTypeConfigurationCache.cs b/src/Umbraco.Core/Cache/DataTypeConfigurationCache.cs new file mode 100644 index 0000000000..78ee17d2f8 --- /dev/null +++ b/src/Umbraco.Core/Cache/DataTypeConfigurationCache.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Caching.Memory; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Cache; + +/// +/// This cache is a temporary measure to reduce the amount of computational power required to deserialize and initialize when fetched from the main cache/database, +/// because datatypes are fetched multiple times troughout a (backoffice content) request with a lot of content (or nested content) and each of these fetches initializes certain fields on the datatypes. +/// +internal sealed class DataTypeConfigurationCache : IDataTypeConfigurationCache +{ + private readonly IDataTypeService _dataTypeService; + private readonly IMemoryCache _memoryCache; + + public DataTypeConfigurationCache(IDataTypeService dataTypeService, IMemoryCache memoryCache, IIdKeyMap idKeyMap) + { + _dataTypeService = dataTypeService; + _memoryCache = memoryCache; + } + + public T? GetConfigurationAs(Guid key) + where T : class + { + var cacheKey = GetCacheKey(key); + if (_memoryCache.TryGetValue(cacheKey, out T? configuration) is false) + { + IDataType? dataType = _dataTypeService.GetDataType(key); + configuration = dataType?.ConfigurationAs(); + + // Only cache if data type was found (but still cache null configurations) + if (dataType is not null) + { + _memoryCache.Set(cacheKey, configuration); + } + } + + return configuration; + } + + public void ClearCache(IEnumerable keys) + { + foreach (Guid key in keys) + { + _memoryCache.Remove(GetCacheKey(key)); + } + } + + private static string GetCacheKey(Guid key) => $"DataTypeConfigurationCache_{key}"; +} diff --git a/src/Umbraco.Core/Cache/DataTypeConfigurationCacheRefresher.cs b/src/Umbraco.Core/Cache/DataTypeConfigurationCacheRefresher.cs new file mode 100644 index 0000000000..071acc795f --- /dev/null +++ b/src/Umbraco.Core/Cache/DataTypeConfigurationCacheRefresher.cs @@ -0,0 +1,15 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco.Cms.Core.Cache; + +internal sealed class DataTypeConfigurationCacheRefresher : INotificationHandler +{ + private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache; + + public DataTypeConfigurationCacheRefresher(IDataTypeConfigurationCache dataTypeConfigurationCache) + => _dataTypeConfigurationCache = dataTypeConfigurationCache; + + public void Handle(DataTypeCacheRefresherNotification notification) + => _dataTypeConfigurationCache.ClearCache(((DataTypeCacheRefresher.JsonPayload[])notification.MessageObject).Select(x => x.Key)); +} diff --git a/src/Umbraco.Core/Cache/IDataTypeConfigurationCache.cs b/src/Umbraco.Core/Cache/IDataTypeConfigurationCache.cs new file mode 100644 index 0000000000..7bb7821fc4 --- /dev/null +++ b/src/Umbraco.Core/Cache/IDataTypeConfigurationCache.cs @@ -0,0 +1,33 @@ +namespace Umbraco.Cms.Core.Cache; + +/// +/// Represents a cache for configuration. +/// +public interface IDataTypeConfigurationCache +{ + /// + /// Gets the data type configuration. + /// + /// The data type key. + /// + /// The data type configuration. + /// + object? GetConfiguration(Guid key) => GetConfigurationAs(key); + + /// + /// Gets the data type configuration as . + /// + /// The data type configuration type. + /// The data type key. + /// + /// The data type configuration as . + /// + T? GetConfigurationAs(Guid key) + where T : class; + + /// + /// Clears the cache for the specified keys. + /// + /// The keys. + void ClearCache(IEnumerable keys); +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 885bc81a4b..96d200bf7b 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -337,6 +337,10 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); + + // Data type configuration cache + Services.AddUnique(); + Services.AddNotificationHandler(); } } } diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs index 22407219eb..237f1f2f40 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Dictionary; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; @@ -17,6 +18,7 @@ namespace Umbraco.Cms.Core.Models.Mapping; internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper { private readonly ICultureDictionary _cultureDictionary; + private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache; private readonly ILocalizedTextService _textService; public ContentPropertyDisplayMapper( @@ -25,10 +27,12 @@ internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper logger, - PropertyEditorCollection propertyEditors) + PropertyEditorCollection propertyEditors, + IDataTypeConfigurationCache dataTypeConfigurationCache) : base(dataTypeService, entityService, logger, propertyEditors) { _cultureDictionary = cultureDictionary; + _dataTypeConfigurationCache = dataTypeConfigurationCache; _textService = textService; } @@ -38,7 +42,7 @@ internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper( dataTypeService, @@ -42,9 +46,28 @@ public class ContentPropertyMapDefinition : IMapDefinition entityService, textService, loggerFactory.CreateLogger(), - propertyEditors); + propertyEditors, + dataTypeConfigurationCache); } + [Obsolete("Please use constructor that takes an IDataTypeConfigurationCache. Will be removed in V14.")] + public ContentPropertyMapDefinition( + ICultureDictionary cultureDictionary, + IDataTypeService dataTypeService, + IEntityService entityService, + ILocalizedTextService textService, + ILoggerFactory loggerFactory, + PropertyEditorCollection propertyEditors) + : this( + cultureDictionary, + dataTypeService, + entityService, + textService, + loggerFactory, + propertyEditors, + StaticServiceProvider.Instance.GetRequiredService()) + { } + public void DefineMaps(IUmbracoMapper mapper) { mapper.Define>( diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 32ba152e28..1be05dad7c 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -14,7 +14,6 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -using static System.Formats.Asn1.AsnWriter; namespace Umbraco.Cms.Core.Services.Implement { @@ -30,28 +29,25 @@ namespace Umbraco.Cms.Core.Services.Implement private readonly IAuditRepository _auditRepository; private readonly IEntityRepository _entityRepository; private readonly IIOHelper _ioHelper; - private readonly ILocalizedTextService _localizedTextService; - private readonly ILocalizationService _localizationService; - private readonly IShortStringHelper _shortStringHelper; - private readonly IJsonSerializer _jsonSerializer; private readonly IEditorConfigurationParser _editorConfigurationParser; + private readonly Lazy _idKeyMap; - [Obsolete("Please use constructor that takes an IEditorConfigurationParser. Will be removed in V13.")] + [Obsolete("Please use constructor that takes an IEditorConfigurationParser. Will be removed in V14.")] public DataTypeService( - IDataValueEditorFactory dataValueEditorFactory, - ICoreScopeProvider provider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IDataTypeRepository dataTypeRepository, - IDataTypeContainerRepository dataTypeContainerRepository, - IAuditRepository auditRepository, - IEntityRepository entityRepository, - IContentTypeRepository contentTypeRepository, - IIOHelper ioHelper, - ILocalizedTextService localizedTextService, - ILocalizationService localizationService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer) + IDataValueEditorFactory dataValueEditorFactory, + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDataTypeRepository dataTypeRepository, + IDataTypeContainerRepository dataTypeContainerRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, + IContentTypeRepository contentTypeRepository, + IIOHelper ioHelper, + ILocalizedTextService localizedTextService, + ILocalizationService localizationService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer) : this( dataValueEditorFactory, provider, @@ -68,20 +64,9 @@ namespace Umbraco.Cms.Core.Services.Implement shortStringHelper, jsonSerializer, StaticServiceProvider.Instance.GetRequiredService()) - { - _dataValueEditorFactory = dataValueEditorFactory; - _dataTypeRepository = dataTypeRepository; - _dataTypeContainerRepository = dataTypeContainerRepository; - _auditRepository = auditRepository; - _entityRepository = entityRepository; - _contentTypeRepository = contentTypeRepository; - _ioHelper = ioHelper; - _localizedTextService = localizedTextService; - _localizationService = localizationService; - _shortStringHelper = shortStringHelper; - _jsonSerializer = jsonSerializer; - } + { } + [Obsolete("Please use constructor that takes an IIdKeyMap. Will be removed in V14.")] public DataTypeService( IDataValueEditorFactory dataValueEditorFactory, ICoreScopeProvider provider, @@ -98,6 +83,43 @@ namespace Umbraco.Cms.Core.Services.Implement IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IEditorConfigurationParser editorConfigurationParser) + : this( + dataValueEditorFactory, + provider, + loggerFactory, + eventMessagesFactory, + dataTypeRepository, + dataTypeContainerRepository, + auditRepository, + entityRepository, + contentTypeRepository, + ioHelper, + localizedTextService, + localizationService, + shortStringHelper, + jsonSerializer, + editorConfigurationParser, + StaticServiceProvider.Instance.GetRequiredService>()) + { } + + // TODO: Unused parameters can be removed once the obsolete constructors are removed (kept to avoid ambiguous constructors) + public DataTypeService( + IDataValueEditorFactory dataValueEditorFactory, + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDataTypeRepository dataTypeRepository, + IDataTypeContainerRepository dataTypeContainerRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, + IContentTypeRepository contentTypeRepository, + IIOHelper ioHelper, + ILocalizedTextService localizedTextService, + ILocalizationService localizationService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IEditorConfigurationParser editorConfigurationParser, + Lazy idKeyMap) : base(provider, loggerFactory, eventMessagesFactory) { _dataValueEditorFactory = dataValueEditorFactory; @@ -107,11 +129,8 @@ namespace Umbraco.Cms.Core.Services.Implement _entityRepository = entityRepository; _contentTypeRepository = contentTypeRepository; _ioHelper = ioHelper; - _localizedTextService = localizedTextService; - _localizationService = localizationService; - _shortStringHelper = shortStringHelper; - _jsonSerializer = jsonSerializer; _editorConfigurationParser = editorConfigurationParser; + _idKeyMap = idKeyMap; } #region Containers @@ -336,13 +355,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// Unique guid Id of the DataType /// public IDataType? GetDataType(Guid id) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); - IQuery query = Query().Where(x => x.Key == id); - IDataType? dataType = _dataTypeRepository.Get(query).FirstOrDefault(); - ConvertMissingEditorOfDataTypeToLabel(dataType); - return dataType; - } + // Lookup the integer ID, so the data type can be retrieved from the repository cache + =>_idKeyMap.Value.GetIdForKey(id, UmbracoObjectTypes.DataType) switch + { + { Success: false } => null, + { Result: var intId } => GetDataType(intId), + }; /// /// Gets a by its control Id diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 24a0663d83..0203059b5e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs index b4bd57ce33..e986905aa7 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; @@ -16,7 +17,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; internal abstract class BlockEditorPropertyValueEditor : BlockValuePropertyValueEditorBase { private BlockEditorValues? _blockEditorValues; - private readonly IDataTypeService _dataTypeService; + private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache; private readonly PropertyEditorCollection _propertyEditors; private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories; private readonly ILogger _logger; @@ -25,17 +26,17 @@ internal abstract class BlockEditorPropertyValueEditor : BlockValuePropertyValue DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories, - IDataTypeService dataTypeService, + IDataTypeConfigurationCache dataTypeConfigurationCache, ILocalizedTextService textService, ILogger logger, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper) - : base(attribute, propertyEditors, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper, dataValueReferenceFactories) + : base(attribute, propertyEditors, dataTypeConfigurationCache, textService, logger, shortStringHelper, jsonSerializer, ioHelper, dataValueReferenceFactories) { _propertyEditors = propertyEditors; _dataValueReferenceFactories = dataValueReferenceFactories; - _dataTypeService = dataTypeService; + _dataTypeConfigurationCache = dataTypeConfigurationCache; _logger = logger; } @@ -75,7 +76,7 @@ internal abstract class BlockEditorPropertyValueEditor : BlockValuePropertyValue continue; } - object? configuration = _dataTypeService.GetDataType(propertyValue.PropertyType.DataTypeKey)?.Configuration; + object? configuration = _dataTypeConfigurationCache.GetConfiguration(propertyValue.PropertyType.DataTypeKey); foreach (ITag tag in dataValueTags.GetTags(propertyValue.Value, configuration, languageId)) { yield return tag; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs index 7e67c2d8a7..fe72d83927 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; @@ -51,7 +52,7 @@ public abstract class BlockGridPropertyEditorBase : DataEditor DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories, - IDataTypeService dataTypeService, + IDataTypeConfigurationCache dataTypeConfigurationCache, ILocalizedTextService textService, ILogger logger, IShortStringHelper shortStringHelper, @@ -59,7 +60,7 @@ public abstract class BlockGridPropertyEditorBase : DataEditor IIOHelper ioHelper, IContentTypeService contentTypeService, IPropertyValidationService propertyValidationService) - : base(attribute, propertyEditors, dataValueReferenceFactories, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper) + : base(attribute, propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, textService, logger, shortStringHelper, jsonSerializer, ioHelper) { BlockEditorValues = new BlockEditorValues(new BlockGridEditorDataConverter(jsonSerializer), contentTypeService, logger); Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService)); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs index 3d39ba6eb5..5d2c968c72 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; @@ -53,7 +54,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor BlockEditorDataConverter blockEditorDataConverter, PropertyEditorCollection propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories, - IDataTypeService dataTypeService, + IDataTypeConfigurationCache dataTypeConfigurationCache, IContentTypeService contentTypeService, ILocalizedTextService textService, ILogger logger, @@ -61,7 +62,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor IJsonSerializer jsonSerializer, IIOHelper ioHelper, IPropertyValidationService propertyValidationService) : - base(attribute, propertyEditors, dataValueReferenceFactories,dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper) + base(attribute, propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, textService, logger, shortStringHelper, jsonSerializer, ioHelper) { BlockEditorValues = new BlockEditorValues(blockEditorDataConverter, contentTypeService, logger); Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService)); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs index 775a3cd532..0684f563fc 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; @@ -11,7 +12,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; internal abstract class BlockValuePropertyValueEditorBase : DataValueEditor, IDataValueReference, IDataValueTags { - private readonly IDataTypeService _dataTypeService; + private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache; private readonly PropertyEditorCollection _propertyEditors; private readonly ILogger _logger; private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactoryCollection; @@ -19,7 +20,7 @@ internal abstract class BlockValuePropertyValueEditorBase : DataValueEditor, IDa protected BlockValuePropertyValueEditorBase( DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, + IDataTypeConfigurationCache dataTypeConfigurationCache, ILocalizedTextService textService, ILogger logger, IShortStringHelper shortStringHelper, @@ -29,7 +30,7 @@ internal abstract class BlockValuePropertyValueEditorBase : DataValueEditor, IDa : base(textService, shortStringHelper, jsonSerializer, ioHelper, attribute) { _propertyEditors = propertyEditors; - _dataTypeService = dataTypeService; + _dataTypeConfigurationCache = dataTypeConfigurationCache; _logger = logger; _dataValueReferenceFactoryCollection = dataValueReferenceFactoryCollection; } @@ -76,6 +77,7 @@ internal abstract class BlockValuePropertyValueEditorBase : DataValueEditor, IDa protected IEnumerable GetBlockValueTags(BlockValue blockValue, int? languageId) { var result = new List(); + // loop through all content and settings data foreach (BlockItemData row in blockValue.ContentData.Concat(blockValue.SettingsData)) { @@ -89,7 +91,7 @@ internal abstract class BlockValuePropertyValueEditorBase : DataValueEditor, IDa continue; } - object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration; + object? configuration = _dataTypeConfigurationCache.GetConfiguration(prop.Value.PropertyType.DataTypeKey); result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId)); } @@ -112,7 +114,7 @@ internal abstract class BlockValuePropertyValueEditorBase : DataValueEditor, IDa private void MapBlockItemDataToEditor(IProperty property, List items) { - var valEditors = new Dictionary(); + var valEditors = new Dictionary(); foreach (BlockItemData row in items) { @@ -134,25 +136,13 @@ internal abstract class BlockValuePropertyValueEditorBase : DataValueEditor, IDa continue; } - IDataType? dataType = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId); - if (dataType == null) + Guid dataTypeKey = prop.Value.PropertyType.DataTypeKey; + if (!valEditors.TryGetValue(dataTypeKey, out IDataValueEditor? valEditor)) { - // deal with weird situations by ignoring them (no comment) - row.PropertyValues.Remove(prop.Key); - _logger.LogWarning( - "ToEditor removed property value {PropertyKey} in row {RowId} for property type {PropertyTypeAlias}", - prop.Key, - row.Key, - property.PropertyType.Alias); - continue; - } + var configuration = _dataTypeConfigurationCache.GetConfiguration(dataTypeKey); + valEditor = propEditor.GetValueEditor(configuration); - if (!valEditors.TryGetValue(dataType.Id, out IDataValueEditor? valEditor)) - { - var tempConfig = dataType.Configuration; - valEditor = propEditor.GetValueEditor(tempConfig); - - valEditors.Add(dataType.Id, valEditor); + valEditors.Add(dataTypeKey, valEditor); } var convValue = valEditor.ToEditor(tempProp); @@ -170,7 +160,7 @@ internal abstract class BlockValuePropertyValueEditorBase : DataValueEditor, IDa foreach (KeyValuePair prop in row.PropertyValues) { // Fetch the property types prevalue - var propConfiguration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId)?.Configuration; + var configuration = _dataTypeConfigurationCache.GetConfiguration(prop.Value.PropertyType.DataTypeKey); // Lookup the property editor IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; @@ -180,7 +170,7 @@ internal abstract class BlockValuePropertyValueEditorBase : DataValueEditor, IDa } // Create a fake content property data object - var contentPropData = new ContentPropertyData(prop.Value.Value, propConfiguration); + var contentPropData = new ContentPropertyData(prop.Value.Value, configuration); // Get the property editor to do it's conversion var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, prop.Value.Value); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs index 2c44ef57e3..1c0567bce2 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -24,7 +25,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// internal class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core vs web? { - private readonly IDataTypeService _dataTypeService; + private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache; private readonly IFileStreamSecurityValidator _fileStreamSecurityValidator; private readonly ILogger _logger; private readonly MediaFileManager _mediaFileManager; @@ -39,14 +40,14 @@ internal class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core v IOptionsMonitor contentSettings, IJsonSerializer jsonSerializer, IIOHelper ioHelper, - IDataTypeService dataTypeService, + IDataTypeConfigurationCache dataTypeConfigurationCache, IFileStreamSecurityValidator fileStreamSecurityValidator) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _mediaFileManager = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); _contentSettings = contentSettings.CurrentValue; - _dataTypeService = dataTypeService; + _dataTypeConfigurationCache = dataTypeConfigurationCache; _fileStreamSecurityValidator = fileStreamSecurityValidator; contentSettings.OnChange(x => _contentSettings = x); } @@ -73,10 +74,10 @@ internal class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core v value = new ImageCropperValue { Src = val.ToString() }; } - IDataType? dataType = _dataTypeService.GetDataType(property.PropertyType.DataTypeId); - if (dataType?.Configuration != null) + var configuration = _dataTypeConfigurationCache.GetConfigurationAs(property.PropertyType.DataTypeKey); + if (configuration is not null) { - value?.ApplyConfiguration(dataType.ConfigurationAs()); + value?.ApplyConfiguration(configuration); } return value; @@ -216,8 +217,7 @@ internal class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core v } // more magic here ;-( - ImageCropperConfiguration? configuration = _dataTypeService.GetDataType(propertyType.DataTypeId) - ?.ConfigurationAs(); + ImageCropperConfiguration? configuration = _dataTypeConfigurationCache.GetConfigurationAs(propertyType.DataTypeKey); ImageCropperConfiguration.Crop[] crops = configuration?.Crops ?? Array.Empty(); return JsonConvert.SerializeObject( diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs index c2dedef852..5d3d270385 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -72,7 +73,7 @@ public class MediaPicker3PropertyEditor : DataEditor internal class MediaPicker3PropertyValueEditor : DataValueEditor, IDataValueReference { - private readonly IDataTypeService _dataTypeService; + private readonly IDataTypeConfigurationCache _dataTypeReadCache; private readonly IJsonSerializer _jsonSerializer; private readonly ITemporaryMediaService _temporaryMediaService; @@ -83,12 +84,12 @@ public class MediaPicker3PropertyEditor : DataEditor IJsonSerializer jsonSerializer, IIOHelper ioHelper, DataEditorAttribute attribute, - IDataTypeService dataTypeService, + IDataTypeConfigurationCache dataTypeReadCache, ITemporaryMediaService temporaryMediaService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { _jsonSerializer = jsonSerializer; - _dataTypeService = dataTypeService; + _dataTypeReadCache = dataTypeReadCache; _temporaryMediaService = temporaryMediaService; } @@ -110,11 +111,9 @@ public class MediaPicker3PropertyEditor : DataEditor var dtos = Deserialize(_jsonSerializer, value).ToList(); - IDataType? dataType = _dataTypeService.GetDataType(property.PropertyType.DataTypeId); - if (dataType?.Configuration != null) + var configuration = _dataTypeReadCache.GetConfigurationAs(property.PropertyType.DataTypeKey); + if (configuration is not null) { - MediaPicker3Configuration? configuration = dataType.ConfigurationAs(); - foreach (MediaWithCropsDto dto in dtos) { dto.ApplyConfiguration(configuration); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs index 4cdbfeb419..ba8f94ea82 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -90,14 +91,14 @@ public class NestedContentPropertyEditor : DataEditor internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference, IDataValueTags { - private readonly IDataTypeService _dataTypeService; + private readonly IDataTypeConfigurationCache _dataTypeReadCache; private readonly PropertyEditorCollection _propertyEditors; private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories; private readonly ILogger _logger; private readonly NestedContentValues _nestedContentValues; public NestedContentPropertyValueEditor( - IDataTypeService dataTypeService, + IDataTypeConfigurationCache dataTypeReadCache, ILocalizedTextService localizedTextService, IContentTypeService contentTypeService, IShortStringHelper shortStringHelper, @@ -110,7 +111,7 @@ public class NestedContentPropertyEditor : DataEditor IPropertyValidationService propertyValidationService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { - _dataTypeService = dataTypeService; + _dataTypeReadCache = dataTypeReadCache; _propertyEditors = propertyEditors; _dataValueReferenceFactories = dataValueReferenceFactories; _logger = logger; @@ -173,7 +174,7 @@ public class NestedContentPropertyEditor : DataEditor continue; } - object? configuration = _dataTypeService.GetDataType(propertyValue.PropertyType.DataTypeKey)?.Configuration; + object? configuration = _dataTypeReadCache.GetConfiguration(propertyValue.PropertyType.DataTypeKey); foreach (ITag tag in dataValueTags.GetTags(propertyValue.Value, configuration, languageId)) { yield return tag; @@ -188,8 +189,7 @@ public class NestedContentPropertyEditor : DataEditor public override string ConvertDbToString(IPropertyType propertyType, object? propertyValue) { - IReadOnlyList rows = - _nestedContentValues.GetPropertyValues(propertyValue); + IReadOnlyList rows = _nestedContentValues.GetPropertyValues(propertyValue); if (rows.Count == 0) { @@ -198,8 +198,7 @@ public class NestedContentPropertyEditor : DataEditor foreach (NestedContentValues.NestedContentRowValue row in rows.ToList()) { - foreach (KeyValuePair prop in row.PropertyValues - .ToList()) + foreach (KeyValuePair prop in row.PropertyValues.ToList()) { try { @@ -210,9 +209,8 @@ public class NestedContentPropertyEditor : DataEditor continue; } - var tempConfig = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId) - ?.Configuration; - IDataValueEditor valEditor = propEditor.GetValueEditor(tempConfig); + var configuration = _dataTypeReadCache.GetConfiguration(prop.Value.PropertyType.DataTypeKey); + IDataValueEditor valEditor = propEditor.GetValueEditor(configuration); var convValue = valEditor.ConvertDbToString(prop.Value.PropertyType, prop.Value.Value); // update the raw value since this is what will get serialized out @@ -246,7 +244,7 @@ public class NestedContentPropertyEditor : DataEditor public override object ToEditor(IProperty property, string? culture = null, string? segment = null) { var val = property.GetValue(culture, segment); - var valEditors = new Dictionary(); + var valEditors = new Dictionary(); IReadOnlyList rows = _nestedContentValues.GetPropertyValues(val); @@ -278,13 +276,13 @@ public class NestedContentPropertyEditor : DataEditor continue; } - var dataTypeId = prop.Value.PropertyType.DataTypeId; - if (!valEditors.TryGetValue(dataTypeId, out IDataValueEditor? valEditor)) + Guid dataTypeKey = prop.Value.PropertyType.DataTypeKey; + if (!valEditors.TryGetValue(dataTypeKey, out IDataValueEditor? valEditor)) { - var tempConfig = _dataTypeService.GetDataType(dataTypeId)?.Configuration; + var tempConfig = _dataTypeReadCache.GetConfiguration(dataTypeKey); valEditor = propEditor.GetValueEditor(tempConfig); - valEditors.Add(dataTypeId, valEditor); + valEditors.Add(dataTypeKey, valEditor); } var convValue = valEditor.ToEditor(tempProp); @@ -318,8 +316,7 @@ public class NestedContentPropertyEditor : DataEditor return null; } - IReadOnlyList rows = - _nestedContentValues.GetPropertyValues(editorValue.Value); + IReadOnlyList rows = _nestedContentValues.GetPropertyValues(editorValue.Value); if (rows.Count == 0) { @@ -328,12 +325,10 @@ public class NestedContentPropertyEditor : DataEditor foreach (NestedContentValues.NestedContentRowValue row in rows.ToList()) { - foreach (KeyValuePair prop in row.PropertyValues - .ToList()) + foreach (KeyValuePair prop in row.PropertyValues.ToList()) { // Fetch the property types prevalue - var propConfiguration = - _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId)?.Configuration; + var propConfiguration = _dataTypeReadCache.GetConfiguration(prop.Value.PropertyType.DataTypeKey); // Lookup the property editor IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index d06fa9ffe1..2447803277 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; @@ -176,7 +177,7 @@ public class RichTextPropertyEditor : DataEditor public RichTextPropertyValueEditor( DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, + IDataTypeConfigurationCache dataTypeReadCache, ILogger logger, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, ILocalizedTextService localizedTextService, @@ -191,7 +192,7 @@ public class RichTextPropertyEditor : DataEditor IContentTypeService contentTypeService, IPropertyValidationService propertyValidationService, DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection) - : base(attribute, propertyEditors, dataTypeService, localizedTextService, logger, shortStringHelper, jsonSerializer, ioHelper, dataValueReferenceFactoryCollection) + : base(attribute, propertyEditors, dataTypeReadCache, localizedTextService, logger, shortStringHelper, jsonSerializer, ioHelper, dataValueReferenceFactoryCollection) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _imageSourceParser = imageSourceParser; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs index f4949c2fb6..0874b85987 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.PropertyEditors; @@ -42,7 +43,7 @@ public class DataValueEditorReuseTests new BlockListEditorDataConverter(), _propertyEditorCollection, _dataValueReferenceFactories, - Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of>(),