v10: Fix Block List settings exception and optimize PVCs (#12342)
* Don't use MapModelType to get model type * Optimize block list item activation (cache constructors) * Fix exceptions in NestedContentSingleValueConverter (zero content types or multiple stored items) * Add IPublishedModelFactory.GetModelType method to remove work-around
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides the published model creation service.
|
||||
/// </summary>
|
||||
@@ -13,23 +11,40 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
/// Creates a strongly-typed model representing a published element.
|
||||
/// </summary>
|
||||
/// <param name="element">The original published element.</param>
|
||||
/// <returns>The strongly-typed model representing the published element, or the published element
|
||||
/// itself it the factory has no model for the corresponding element type.</returns>
|
||||
/// <returns>
|
||||
/// The strongly-typed model representing the published element,
|
||||
/// or the published element itself it the factory has no model for the corresponding element type.
|
||||
/// </returns>
|
||||
IPublishedElement CreateModel(IPublishedElement element);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a List{T} of a strongly-typed model for a model type alias.
|
||||
/// </summary>
|
||||
/// <param name="alias">The model type alias.</param>
|
||||
/// <returns>A List{T} of the strongly-typed model, exposed as an IList.</returns>
|
||||
/// <returns>
|
||||
/// A List{T} of the strongly-typed model, exposed as an IList.
|
||||
/// </returns>
|
||||
IList? CreateModelList(string? alias);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Type of a strongly-typed model for a model type alias.
|
||||
/// </summary>
|
||||
/// <param name="alias">The model type alias.</param>
|
||||
/// <returns>
|
||||
/// The type of the strongly-typed model.
|
||||
/// </returns>
|
||||
Type GetModelType(string? alias);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a CLR type that may contain model types, to an actual CLR type.
|
||||
/// </summary>
|
||||
/// <param name="type">The CLR type.</param>
|
||||
/// <returns>The actual CLR type.</returns>
|
||||
/// <remarks>See <seealso cref="ModelType"/> for more details.</remarks>
|
||||
/// <returns>
|
||||
/// The actual CLR type.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// See <seealso cref="ModelType" /> for more details.
|
||||
/// </remarks>
|
||||
Type MapModelType(Type type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
{
|
||||
@@ -14,6 +12,9 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
/// <inheritdoc />
|
||||
public IList CreateModelList(string? alias) => new List<IPublishedElement>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type GetModelType(string? alias) => typeof(IPublishedElement);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type MapModelType(Type type) => typeof(IPublishedElement);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
@@ -59,20 +57,27 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
if (parms.Length == 2 && typeof(IPublishedElement).IsAssignableFrom(parms[0].ParameterType) && typeof(IPublishedValueFallback).IsAssignableFrom(parms[1].ParameterType))
|
||||
{
|
||||
if (constructor != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPublishedElement.");
|
||||
}
|
||||
|
||||
constructor = ctor;
|
||||
parameterType = parms[0].ParameterType;
|
||||
}
|
||||
}
|
||||
|
||||
if (constructor == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPublishedElement.");
|
||||
}
|
||||
|
||||
var attribute = type.GetCustomAttribute<PublishedModelAttribute>(false);
|
||||
var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias;
|
||||
|
||||
if (modelInfos.TryGetValue(typeName, out var modelInfo))
|
||||
{
|
||||
throw new InvalidOperationException($"Both types '{type.AssemblyQualifiedName}' and '{modelInfo.ModelType?.AssemblyQualifiedName}' want to be a model type for content type with alias \"{typeName}\".");
|
||||
}
|
||||
|
||||
// have to use an unsafe ctor because we don't know the types, really
|
||||
var modelCtor = ReflectionUtilities.EmitConstructorUnsafe<Func<object, IPublishedValueFallback, object>>(constructor);
|
||||
@@ -89,15 +94,16 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
public IPublishedElement CreateModel(IPublishedElement element)
|
||||
{
|
||||
// fail fast
|
||||
if (_modelInfos == null)
|
||||
return element;
|
||||
|
||||
if (element.ContentType.Alias is null || !_modelInfos.TryGetValue(element.ContentType.Alias, out var modelInfo))
|
||||
if (_modelInfos is null || element.ContentType.Alias is null || !_modelInfos.TryGetValue(element.ContentType.Alias, out var modelInfo))
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
// ReSharper disable once UseMethodIsInstanceOfType
|
||||
if (modelInfo.ParameterType?.IsAssignableFrom(element.GetType()) == false)
|
||||
{
|
||||
throw new InvalidOperationException($"Model {modelInfo.ModelType} expects argument of type {modelInfo.ParameterType.FullName}, but got {element.GetType().FullName}.");
|
||||
}
|
||||
|
||||
// can cast, because we checked when creating the ctor
|
||||
return (IPublishedElement)modelInfo.Ctor!(element, _publishedValueFallback);
|
||||
@@ -107,21 +113,42 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
|
||||
public IList? CreateModelList(string? alias)
|
||||
{
|
||||
// fail fast
|
||||
if (_modelInfos == null)
|
||||
return new List<IPublishedElement>();
|
||||
|
||||
if (alias is null || !_modelInfos.TryGetValue(alias, out var modelInfo) || modelInfo.ModelType is null)
|
||||
if (_modelInfos is null || alias is null || !_modelInfos.TryGetValue(alias, out var modelInfo) || modelInfo.ModelType is null)
|
||||
{
|
||||
return new List<IPublishedElement>();
|
||||
}
|
||||
|
||||
var ctor = modelInfo.ListCtor;
|
||||
if (ctor != null) return ctor();
|
||||
if (ctor != null)
|
||||
{
|
||||
return ctor();
|
||||
}
|
||||
|
||||
var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType);
|
||||
ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstructor<Func<IList>>(declaring: listType);
|
||||
if(ctor is not null) return ctor();
|
||||
if (ctor is not null)
|
||||
{
|
||||
return ctor();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type GetModelType(string? alias)
|
||||
{
|
||||
// fail fast
|
||||
if (_modelInfos is null ||
|
||||
alias is null ||
|
||||
!_modelInfos.TryGetValue(alias, out var modelInfo) ||
|
||||
modelInfo.ModelType is null)
|
||||
{
|
||||
return typeof(IPublishedElement);
|
||||
}
|
||||
|
||||
return modelInfo.ModelType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type MapModelType(Type type)
|
||||
=> ModelType.Map(type, _modelTypeMap);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using Umbraco.Cms.Core.Models.Blocks;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
@@ -53,11 +52,9 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
{
|
||||
var publishedContentCache = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Content;
|
||||
var publishedContentType = publishedContentCache?.GetContentType(contentTypeKey);
|
||||
if (publishedContentType != null)
|
||||
if (publishedContentType is not null && publishedContentType.IsElement)
|
||||
{
|
||||
var modelType = ModelType.For(publishedContentType.Alias);
|
||||
|
||||
return _publishedModelFactory.MapModelType(modelType);
|
||||
return _publishedModelFactory.GetModelType(publishedContentType.Alias);
|
||||
}
|
||||
|
||||
return typeof(IPublishedElement);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Models.Blocks;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
@@ -30,37 +27,42 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.BlockList);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(BlockListModel);
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
=> typeof(BlockListModel);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
|
||||
=> PropertyCacheLevel.Element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ConvertSourceToIntermediate(IPublishedElement owner,
|
||||
IPublishedPropertyType propertyType, object? source, bool preview)
|
||||
{
|
||||
return source?.ToString();
|
||||
}
|
||||
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
|
||||
=> source?.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType,
|
||||
PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)
|
||||
public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)
|
||||
{
|
||||
// NOTE: The intermediate object is just a json string, we don't actually convert from source -> intermediate since source is always just a json string
|
||||
|
||||
using (_proflog.DebugDuration<BlockListPropertyValueConverter>(
|
||||
$"ConvertPropertyToBlockList ({propertyType.DataType.Id})"))
|
||||
// NOTE: The intermediate object is just a JSON string, we don't actually convert from source -> intermediate since source is always just a JSON string
|
||||
using (_proflog.DebugDuration<BlockListPropertyValueConverter>($"ConvertPropertyToBlockList ({propertyType.DataType.Id})"))
|
||||
{
|
||||
var value = (string?)inter;
|
||||
|
||||
// Short-circuit on empty values
|
||||
if (string.IsNullOrWhiteSpace(value)) return BlockListModel.Empty;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return BlockListModel.Empty;
|
||||
}
|
||||
|
||||
var converted = _blockListEditorDataConverter.Deserialize(value);
|
||||
if (converted.BlockValue.ContentData.Count == 0) return BlockListModel.Empty;
|
||||
if (converted.BlockValue.ContentData.Count == 0)
|
||||
{
|
||||
return BlockListModel.Empty;
|
||||
}
|
||||
|
||||
var blockListLayout = converted.Layout?.ToObject<IEnumerable<BlockListLayoutItem>>();
|
||||
if (blockListLayout is null)
|
||||
{
|
||||
return BlockListModel.Empty;
|
||||
}
|
||||
|
||||
// Get configuration
|
||||
var configuration = propertyType.DataType.ConfigurationAs<BlockListConfiguration>();
|
||||
@@ -68,84 +70,130 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var blockConfigMap = configuration.Blocks.ToDictionary(x => x.ContentElementTypeKey);
|
||||
var validSettingsElementTypes = blockConfigMap.Values.Select(x => x.SettingsElementTypeKey)
|
||||
.Where(x => x.HasValue).Distinct().ToList();
|
||||
|
||||
// Convert the content data
|
||||
var contentPublishedElements = new Dictionary<Guid, IPublishedElement>();
|
||||
foreach (var data in converted.BlockValue.ContentData)
|
||||
{
|
||||
if (!blockConfigMap.ContainsKey(data.ContentTypeKey)) continue;
|
||||
if (!blockConfigMap.ContainsKey(data.ContentTypeKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
|
||||
if (element == null) continue;
|
||||
if (element == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
contentPublishedElements[element.Key] = element;
|
||||
}
|
||||
|
||||
// If there are no content elements, it doesn't matter what is stored in layout
|
||||
if (contentPublishedElements.Count == 0) return BlockListModel.Empty;
|
||||
if (contentPublishedElements.Count == 0)
|
||||
{
|
||||
return BlockListModel.Empty;
|
||||
}
|
||||
|
||||
// Convert the settings data
|
||||
var settingsPublishedElements = new Dictionary<Guid, IPublishedElement>();
|
||||
foreach (var data in converted.BlockValue.SettingsData)
|
||||
var validSettingsElementTypes = blockConfigMap.Values.Select(x => x.SettingsElementTypeKey).Where(x => x.HasValue).Distinct().ToList();
|
||||
if (validSettingsElementTypes is not null)
|
||||
{
|
||||
if (!validSettingsElementTypes?.Contains(data.ContentTypeKey) ?? false) continue;
|
||||
|
||||
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
|
||||
if (element == null) continue;
|
||||
|
||||
settingsPublishedElements[element.Key] = element;
|
||||
}
|
||||
|
||||
var layout = new List<BlockListItem>();
|
||||
if (blockListLayout is not null)
|
||||
{
|
||||
foreach (var layoutItem in blockListLayout)
|
||||
foreach (var data in converted.BlockValue.SettingsData)
|
||||
{
|
||||
// Get the content reference
|
||||
var contentGuidUdi = (GuidUdi?)layoutItem.ContentUdi;
|
||||
if (contentGuidUdi is null || !contentPublishedElements.TryGetValue(contentGuidUdi.Guid, out var contentData))
|
||||
continue;
|
||||
|
||||
if (contentData is null || (!blockConfigMap.TryGetValue(contentData.ContentType.Key, out var blockConfig)))
|
||||
continue;
|
||||
|
||||
// Get the setting reference
|
||||
IPublishedElement? settingsData = null;
|
||||
var settingGuidUdi = layoutItem.SettingsUdi is not null ? (GuidUdi)layoutItem.SettingsUdi : null;
|
||||
if (settingGuidUdi is not null)
|
||||
settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData);
|
||||
|
||||
// This can happen if they have a settings type, save content, remove the settings type, and display the front-end page before saving the content again
|
||||
// We also ensure that the content types match, since maybe the settings type has been changed after this has been persisted
|
||||
if (settingsData != null && (!blockConfig.SettingsElementTypeKey.HasValue ||
|
||||
settingsData.ContentType.Key !=
|
||||
blockConfig.SettingsElementTypeKey))
|
||||
if (!validSettingsElementTypes.Contains(data.ContentTypeKey))
|
||||
{
|
||||
settingsData = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get settings type from configuration
|
||||
var settingsType = blockConfig.SettingsElementTypeKey.HasValue
|
||||
? _blockConverter.GetModelType(blockConfig.SettingsElementTypeKey.Value)
|
||||
: typeof(IPublishedElement);
|
||||
|
||||
// TODO: This should be optimized/cached, as calling Activator.CreateInstance is slow
|
||||
var layoutType = typeof(BlockListItem<,>).MakeGenericType(contentData.GetType(), settingsType);
|
||||
var layoutRef = (BlockListItem?)Activator.CreateInstance(layoutType, contentGuidUdi, contentData,
|
||||
settingGuidUdi, settingsData);
|
||||
|
||||
if (layoutRef is not null)
|
||||
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
|
||||
if (element is null)
|
||||
{
|
||||
layout.Add(layoutRef);
|
||||
continue;
|
||||
}
|
||||
|
||||
settingsPublishedElements[element.Key] = element;
|
||||
}
|
||||
}
|
||||
|
||||
var model = new BlockListModel(layout);
|
||||
return model;
|
||||
// Cache constructors locally (it's tied to the current IPublishedSnapshot and IPublishedModelFactory)
|
||||
var blockListItemActivator = new BlockListItemActivator(_blockConverter);
|
||||
|
||||
var list = new List<BlockListItem>();
|
||||
foreach (var layoutItem in blockListLayout)
|
||||
{
|
||||
// Get the content reference
|
||||
var contentGuidUdi = (GuidUdi?)layoutItem.ContentUdi;
|
||||
if (contentGuidUdi is null || !contentPublishedElements.TryGetValue(contentGuidUdi.Guid, out var contentData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!blockConfigMap.TryGetValue(contentData.ContentType.Key, out var blockConfig))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the setting reference
|
||||
IPublishedElement? settingsData = null;
|
||||
var settingGuidUdi = (GuidUdi?)layoutItem.SettingsUdi;
|
||||
if (settingGuidUdi is not null)
|
||||
{
|
||||
settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData);
|
||||
}
|
||||
|
||||
// This can happen if they have a settings type, save content, remove the settings type, and display the front-end page before saving the content again
|
||||
// We also ensure that the content types match, since maybe the settings type has been changed after this has been persisted
|
||||
if (settingsData is not null && (!blockConfig.SettingsElementTypeKey.HasValue || settingsData.ContentType.Key != blockConfig.SettingsElementTypeKey))
|
||||
{
|
||||
settingsData = null;
|
||||
}
|
||||
|
||||
// Create instance (use content/settings type from configuration)
|
||||
var layoutRef = blockListItemActivator.CreateInstance(blockConfig.ContentElementTypeKey, blockConfig.SettingsElementTypeKey, contentGuidUdi, contentData, settingGuidUdi, settingsData);
|
||||
|
||||
list.Add(layoutRef);
|
||||
}
|
||||
|
||||
return new BlockListModel(list);
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockListItemActivator
|
||||
{
|
||||
private readonly BlockEditorConverter _blockConverter;
|
||||
private readonly Dictionary<(Guid, Guid?), Func<Udi, IPublishedElement, Udi?, IPublishedElement?, BlockListItem>> _contructorCache = new();
|
||||
|
||||
public BlockListItemActivator(BlockEditorConverter blockConverter)
|
||||
=> _blockConverter = blockConverter;
|
||||
|
||||
public BlockListItem CreateInstance(Guid contentTypeKey, Guid? settingsTypeKey, Udi contentUdi, IPublishedElement contentData, Udi? settingsUdi, IPublishedElement? settingsData)
|
||||
{
|
||||
if (!_contructorCache.TryGetValue((contentTypeKey, settingsTypeKey), out var constructor))
|
||||
{
|
||||
constructor = _contructorCache[(contentTypeKey, settingsTypeKey)] = EmitConstructor(contentTypeKey, settingsTypeKey);
|
||||
}
|
||||
|
||||
return constructor(contentUdi, contentData, settingsUdi, settingsData);
|
||||
}
|
||||
|
||||
private Func<Udi, IPublishedElement, Udi?, IPublishedElement?, BlockListItem> EmitConstructor(Guid contentTypeKey, Guid? settingsTypeKey)
|
||||
{
|
||||
var contentType = _blockConverter.GetModelType(contentTypeKey);
|
||||
var settingsType = settingsTypeKey.HasValue ? _blockConverter.GetModelType(settingsTypeKey.Value) : typeof(IPublishedElement);
|
||||
var type = typeof(BlockListItem<,>).MakeGenericType(contentType, settingsType);
|
||||
|
||||
var constructor = type.GetConstructor(new[] { typeof(Udi), contentType, typeof(Udi), settingsType });
|
||||
if (constructor == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not find the required public constructor on {type}.");
|
||||
}
|
||||
|
||||
// We use unsafe here, because we know the contructor parameter count and types match
|
||||
return ReflectionUtilities.EmitConstructorUnsafe<Func<Udi, IPublishedElement, Udi?, IPublishedElement?, BlockListItem>>(constructor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
@@ -25,9 +23,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
/// </summary>
|
||||
public NestedContentManyValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory, IProfilingLogger proflog)
|
||||
: base(publishedSnapshotAccessor, publishedModelFactory)
|
||||
{
|
||||
_proflog = proflog;
|
||||
}
|
||||
=> _proflog = proflog;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
@@ -37,6 +33,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
{
|
||||
var contentTypes = propertyType.DataType.ConfigurationAs<NestedContentConfiguration>()?.ContentTypes;
|
||||
|
||||
return contentTypes?.Length == 1
|
||||
? typeof(IEnumerable<>).MakeGenericType(ModelType.For(contentTypes[0].Alias))
|
||||
: typeof(IEnumerable<IPublishedElement>);
|
||||
@@ -48,9 +45,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
|
||||
{
|
||||
return source?.ToString();
|
||||
}
|
||||
=> source?.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)
|
||||
@@ -64,16 +59,24 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
: new List<IPublishedElement>();
|
||||
|
||||
var value = (string?)inter;
|
||||
if (string.IsNullOrWhiteSpace(value)) return elements;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return elements;
|
||||
}
|
||||
|
||||
var objects = JsonConvert.DeserializeObject<List<JObject>>(value);
|
||||
if (objects is null || objects.Count == 0) return elements;
|
||||
if (objects is null || objects.Count == 0)
|
||||
{
|
||||
return elements;
|
||||
}
|
||||
|
||||
foreach (var sourceObject in objects)
|
||||
{
|
||||
var element = ConvertToElement(sourceObject, referenceCacheLevel, preview);
|
||||
if (element != null)
|
||||
{
|
||||
elements.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
@@ -25,9 +23,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
/// </summary>
|
||||
public NestedContentSingleValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory, IProfilingLogger proflog)
|
||||
: base(publishedSnapshotAccessor, publishedModelFactory)
|
||||
{
|
||||
_proflog = proflog;
|
||||
}
|
||||
=> _proflog = proflog;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
@@ -36,10 +32,11 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
/// <inheritdoc />
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
{
|
||||
var contentTypes = propertyType.DataType.ConfigurationAs<NestedContentConfiguration>()!.ContentTypes;
|
||||
return contentTypes?.Length > 1
|
||||
? typeof(IPublishedElement)
|
||||
: ModelType.For(contentTypes?[0].Alias);
|
||||
var contentTypes = propertyType.DataType.ConfigurationAs<NestedContentConfiguration>()?.ContentTypes;
|
||||
|
||||
return contentTypes?.Length == 1
|
||||
? ModelType.For(contentTypes[0].Alias)
|
||||
: typeof(IPublishedElement);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -48,9 +45,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
|
||||
{
|
||||
return source?.ToString();
|
||||
}
|
||||
=> source?.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)
|
||||
@@ -58,14 +53,18 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
using (_proflog.DebugDuration<NestedContentSingleValueConverter>($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})"))
|
||||
{
|
||||
var value = (string?)inter;
|
||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var objects = JsonConvert.DeserializeObject<List<JObject>>(value)!;
|
||||
if (objects.Count == 0)
|
||||
{
|
||||
return null;
|
||||
if (objects.Count > 1)
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
// Only return the first (existing data might contain more than is currently configured)
|
||||
return ConvertToElement(objects[0], referenceCacheLevel, preview);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
@@ -14,52 +12,56 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
{
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
|
||||
protected IPublishedModelFactory PublishedModelFactory { get; }
|
||||
|
||||
protected NestedContentValueConverterBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory)
|
||||
{
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
PublishedModelFactory = publishedModelFactory;
|
||||
}
|
||||
|
||||
protected IPublishedModelFactory PublishedModelFactory { get; }
|
||||
|
||||
public static bool IsNested(IPublishedPropertyType publishedProperty)
|
||||
=> publishedProperty.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.NestedContent);
|
||||
|
||||
private static bool IsSingle(IPublishedPropertyType publishedProperty)
|
||||
{
|
||||
return publishedProperty.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.NestedContent);
|
||||
var config = publishedProperty.DataType.ConfigurationAs<NestedContentConfiguration>();
|
||||
|
||||
return config is not null && config.MinItems == 1 && config.MaxItems == 1;
|
||||
}
|
||||
|
||||
public static bool IsNestedSingle(IPublishedPropertyType publishedProperty)
|
||||
{
|
||||
if (!IsNested(publishedProperty))
|
||||
return false;
|
||||
|
||||
var config = publishedProperty.DataType.ConfigurationAs<NestedContentConfiguration>();
|
||||
return config?.MinItems == 1 && config.MaxItems == 1;
|
||||
}
|
||||
=> IsNested(publishedProperty) && IsSingle(publishedProperty);
|
||||
|
||||
public static bool IsNestedMany(IPublishedPropertyType publishedProperty)
|
||||
{
|
||||
return IsNested(publishedProperty) && !IsNestedSingle(publishedProperty);
|
||||
}
|
||||
=> IsNested(publishedProperty) && !IsSingle(publishedProperty);
|
||||
|
||||
protected IPublishedElement? ConvertToElement(JObject sourceObject, PropertyCacheLevel referenceCacheLevel, bool preview)
|
||||
{
|
||||
var elementTypeAlias = sourceObject[NestedContentPropertyEditor.ContentTypeAliasPropertyKey]?.ToObject<string>();
|
||||
if (string.IsNullOrEmpty(elementTypeAlias))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
|
||||
// only convert element types - content types will cause an exception when PublishedModelFactory creates the model
|
||||
|
||||
// Only convert element types - content types will cause an exception when PublishedModelFactory creates the model
|
||||
var publishedContentType = publishedSnapshot.Content?.GetContentType(elementTypeAlias);
|
||||
if (publishedContentType == null || publishedContentType.IsElement == false)
|
||||
if (publishedContentType is null || publishedContentType.IsElement == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var propertyValues = sourceObject.ToObject<Dictionary<string, object?>>();
|
||||
|
||||
if (propertyValues is null || !propertyValues.TryGetValue("key", out var keyo)
|
||||
|| !Guid.TryParse(keyo!.ToString(), out var key))
|
||||
if (propertyValues is null || !propertyValues.TryGetValue("key", out var keyo) || !Guid.TryParse(keyo?.ToString(), out var key))
|
||||
{
|
||||
key = Guid.Empty;
|
||||
}
|
||||
|
||||
IPublishedElement element = new PublishedElement(publishedContentType, key, propertyValues, preview, referenceCacheLevel, _publishedSnapshotAccessor);
|
||||
element = PublishedModelFactory.CreateModel(element);
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -54,7 +48,6 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
|
||||
private readonly Lazy<string> _pureLiveDirectory = null!;
|
||||
private bool _disposedValue;
|
||||
|
||||
|
||||
public InMemoryModelFactory(
|
||||
Lazy<UmbracoServices> umbracoServices,
|
||||
IProfilingLogger profilingLogger,
|
||||
@@ -76,6 +69,7 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
|
||||
_errors = new ModelsGenerationError(config, _hostingEnvironment);
|
||||
_ver = 1; // zero is for when we had no version
|
||||
_skipver = -1; // nothing to skip
|
||||
|
||||
if (!hostingEnvironment.IsHosted)
|
||||
{
|
||||
return;
|
||||
@@ -169,6 +163,24 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
|
||||
return info is null || info.Ctor is null ? element : info.Ctor(element, _publishedValueFallback);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type GetModelType(string? alias)
|
||||
{
|
||||
Infos infos = EnsureModels();
|
||||
|
||||
// fail fast
|
||||
if (infos is null ||
|
||||
alias is null ||
|
||||
infos.ModelInfos is null ||
|
||||
!infos.ModelInfos.TryGetValue(alias, out ModelInfo? modelInfo) ||
|
||||
modelInfo.ModelType is null)
|
||||
{
|
||||
return typeof(IPublishedElement);
|
||||
}
|
||||
|
||||
return modelInfo.ModelType;
|
||||
}
|
||||
|
||||
// this runs only once the factory is ready
|
||||
// NOT when building models
|
||||
public Type MapModelType(Type type)
|
||||
@@ -184,12 +196,7 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
|
||||
Infos infos = EnsureModels();
|
||||
|
||||
// fail fast
|
||||
if (infos is null || alias is null)
|
||||
{
|
||||
return new List<IPublishedElement>();
|
||||
}
|
||||
|
||||
if (infos.ModelInfos is null || !infos.ModelInfos.TryGetValue(alias, out ModelInfo? modelInfo))
|
||||
if (infos is null || alias is null || infos.ModelInfos is null || !infos.ModelInfos.TryGetValue(alias, out ModelInfo? modelInfo))
|
||||
{
|
||||
return new List<IPublishedElement>();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user