Fix excessive datatype load times in propertyValueEditors and backoffice mapping (#15731)

* Introduced IDataTypeConfigurationCache
* Applied IDataTypeConfigurationCache to Property Editors and display mapping
* Invalidate new cache trough DataTypeConfigurationCacheRefresher
* Improve IDatatype service to use cached int path when fetching by guid (using idkeymap)
---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
Co-authored-by: Ronald Barendse <ronald@barend.se>
This commit is contained in:
Sven Geusens
2024-02-21 09:05:44 +01:00
committed by GitHub
parent 12355219d4
commit 129adf2699
18 changed files with 261 additions and 122 deletions

View File

@@ -19,6 +19,7 @@
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />

View File

@@ -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;
/// <summary>
/// This cache is a temporary measure to reduce the amount of computational power required to deserialize and initialize <see cref="IDataType" /> 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.
/// </summary>
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<T>(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<T>();
// 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<Guid> keys)
{
foreach (Guid key in keys)
{
_memoryCache.Remove(GetCacheKey(key));
}
}
private static string GetCacheKey(Guid key) => $"DataTypeConfigurationCache_{key}";
}

View File

@@ -0,0 +1,15 @@
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
namespace Umbraco.Cms.Core.Cache;
internal sealed class DataTypeConfigurationCacheRefresher : INotificationHandler<DataTypeCacheRefresherNotification>
{
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));
}

View File

@@ -0,0 +1,33 @@
namespace Umbraco.Cms.Core.Cache;
/// <summary>
/// Represents a cache for <see cref="Umbraco.Cms.Core.Models.IDataType" /> configuration.
/// </summary>
public interface IDataTypeConfigurationCache
{
/// <summary>
/// Gets the data type configuration.
/// </summary>
/// <param name="key">The data type key.</param>
/// <returns>
/// The data type configuration.
/// </returns>
object? GetConfiguration(Guid key) => GetConfigurationAs<object>(key);
/// <summary>
/// Gets the data type configuration as <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The data type configuration type.</typeparam>
/// <param name="key">The data type key.</param>
/// <returns>
/// The data type configuration as <typeparamref name="T" />.
/// </returns>
T? GetConfigurationAs<T>(Guid key)
where T : class;
/// <summary>
/// Clears the cache for the specified keys.
/// </summary>
/// <param name="keys">The keys.</param>
void ClearCache(IEnumerable<Guid> keys);
}

View File

@@ -337,6 +337,10 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddUnique<IWebhookLogService, WebhookLogService>();
Services.AddUnique<IWebhookLogFactory, WebhookLogFactory>();
Services.AddUnique<IWebhookRequestService, WebhookRequestService>();
// Data type configuration cache
Services.AddUnique<IDataTypeConfigurationCache, DataTypeConfigurationCache>();
Services.AddNotificationHandler<DataTypeCacheRefresherNotification, DataTypeConfigurationCacheRefresher>();
}
}
}

View File

@@ -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<ContentPropertyDisplay>
{
private readonly ICultureDictionary _cultureDictionary;
private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache;
private readonly ILocalizedTextService _textService;
public ContentPropertyDisplayMapper(
@@ -25,10 +27,12 @@ internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper<Content
IEntityService entityService,
ILocalizedTextService textService,
ILogger<ContentPropertyDisplayMapper> 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<Content
var config = originalProp.PropertyType is null
? null
: DataTypeService.GetDataType(originalProp.PropertyType.DataTypeId)?.Configuration;
: _dataTypeConfigurationCache.GetConfiguration(originalProp.PropertyType.DataTypeKey);
// TODO: IDataValueEditor configuration - general issue
// GetValueEditor() returns a non-configured IDataValueEditor

View File

@@ -1,4 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Dictionary;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models.ContentEditing;
@@ -24,7 +27,8 @@ public class ContentPropertyMapDefinition : IMapDefinition
IEntityService entityService,
ILocalizedTextService textService,
ILoggerFactory loggerFactory,
PropertyEditorCollection propertyEditors)
PropertyEditorCollection propertyEditors,
IDataTypeConfigurationCache dataTypeConfigurationCache)
{
_contentPropertyBasicConverter = new ContentPropertyBasicMapper<ContentPropertyBasic>(
dataTypeService,
@@ -42,9 +46,28 @@ public class ContentPropertyMapDefinition : IMapDefinition
entityService,
textService,
loggerFactory.CreateLogger<ContentPropertyDisplayMapper>(),
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<IDataTypeConfigurationCache>())
{ }
public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define<PropertyGroup, Tab<ContentPropertyDisplay>>(

View File

@@ -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<IIdKeyMap> _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<IEditorConfigurationParser>())
{
_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<Lazy<IIdKeyMap>>())
{ }
// 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<IIdKeyMap> 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
/// <param name="id">Unique guid Id of the DataType</param>
/// <returns><see cref="IDataType"/></returns>
public IDataType? GetDataType(Guid id)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
IQuery<IDataType> query = Query<IDataType>().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),
};
/// <summary>
/// Gets a <see cref="IDataType"/> by its control Id

View File

@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" />

View File

@@ -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<BlockEditorPropertyValueEditor> _logger;
@@ -25,17 +26,17 @@ internal abstract class BlockEditorPropertyValueEditor : BlockValuePropertyValue
DataEditorAttribute attribute,
PropertyEditorCollection propertyEditors,
DataValueReferenceFactoryCollection dataValueReferenceFactories,
IDataTypeService dataTypeService,
IDataTypeConfigurationCache dataTypeConfigurationCache,
ILocalizedTextService textService,
ILogger<BlockEditorPropertyValueEditor> 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;

View File

@@ -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<BlockEditorPropertyValueEditor> 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));

View File

@@ -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<BlockEditorPropertyValueEditor> 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));

View File

@@ -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<ITag> GetBlockValueTags(BlockValue blockValue, int? languageId)
{
var result = new List<ITag>();
// 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<BlockItemData> items)
{
var valEditors = new Dictionary<int, IDataValueEditor>();
var valEditors = new Dictionary<Guid, IDataValueEditor>();
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<string, BlockItemData.BlockPropertyValue> 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);

View File

@@ -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;
/// </summary>
internal class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core vs web?
{
private readonly IDataTypeService _dataTypeService;
private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache;
private readonly IFileStreamSecurityValidator _fileStreamSecurityValidator;
private readonly ILogger<ImageCropperPropertyValueEditor> _logger;
private readonly MediaFileManager _mediaFileManager;
@@ -39,14 +40,14 @@ internal class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core v
IOptionsMonitor<ContentSettings> 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<ImageCropperConfiguration>(property.PropertyType.DataTypeKey);
if (configuration is not null)
{
value?.ApplyConfiguration(dataType.ConfigurationAs<ImageCropperConfiguration>());
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>();
ImageCropperConfiguration? configuration = _dataTypeConfigurationCache.GetConfigurationAs<ImageCropperConfiguration>(propertyType.DataTypeKey);
ImageCropperConfiguration.Crop[] crops = configuration?.Crops ?? Array.Empty<ImageCropperConfiguration.Crop>();
return JsonConvert.SerializeObject(

View File

@@ -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<MediaPicker3Configuration>(property.PropertyType.DataTypeKey);
if (configuration is not null)
{
MediaPicker3Configuration? configuration = dataType.ConfigurationAs<MediaPicker3Configuration>();
foreach (MediaWithCropsDto dto in dtos)
{
dto.ApplyConfiguration(configuration);

View File

@@ -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<NestedContentPropertyEditor> _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<NestedContentValues.NestedContentRowValue> rows =
_nestedContentValues.GetPropertyValues(propertyValue);
IReadOnlyList<NestedContentValues.NestedContentRowValue> 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<string, NestedContentValues.NestedContentPropertyValue> prop in row.PropertyValues
.ToList())
foreach (KeyValuePair<string, NestedContentValues.NestedContentPropertyValue> 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<int, IDataValueEditor>();
var valEditors = new Dictionary<Guid, IDataValueEditor>();
IReadOnlyList<NestedContentValues.NestedContentRowValue> 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<NestedContentValues.NestedContentRowValue> rows =
_nestedContentValues.GetPropertyValues(editorValue.Value);
IReadOnlyList<NestedContentValues.NestedContentRowValue> 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<string, NestedContentValues.NestedContentPropertyValue> prop in row.PropertyValues
.ToList())
foreach (KeyValuePair<string, NestedContentValues.NestedContentPropertyValue> 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];

View File

@@ -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<RichTextPropertyValueEditor> 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;

View File

@@ -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<IDataTypeService>(),
Mock.Of<IDataTypeConfigurationCache>(),
Mock.Of<IContentTypeService>(),
Mock.Of<ILocalizedTextService>(),
Mock.Of<ILogger<BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor>>(),