diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs
index de292a8112..c34a4a6ba4 100644
--- a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs
@@ -1,9 +1,7 @@
-using System;
using System.Collections;
namespace Umbraco.Cms.Core.Models.PublishedContent
{
-
///
/// Provides the published model creation service.
///
@@ -13,23 +11,40 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
/// Creates a strongly-typed model representing a published element.
///
/// The original published element.
- /// The strongly-typed model representing the published element, or the published element
- /// itself it the factory has no model for the corresponding element type.
+ ///
+ /// The strongly-typed model representing the published element,
+ /// or the published element itself it the factory has no model for the corresponding element type.
+ ///
IPublishedElement CreateModel(IPublishedElement element);
///
/// Creates a List{T} of a strongly-typed model for a model type alias.
///
/// The model type alias.
- /// A List{T} of the strongly-typed model, exposed as an IList.
+ ///
+ /// A List{T} of the strongly-typed model, exposed as an IList.
+ ///
IList? CreateModelList(string? alias);
+ ///
+ /// Gets the Type of a strongly-typed model for a model type alias.
+ ///
+ /// The model type alias.
+ ///
+ /// The type of the strongly-typed model.
+ ///
+ Type GetModelType(string? alias);
+
///
/// Maps a CLR type that may contain model types, to an actual CLR type.
///
/// The CLR type.
- /// The actual CLR type.
- /// See for more details.
+ ///
+ /// The actual CLR type.
+ ///
+ ///
+ /// See for more details.
+ ///
Type MapModelType(Type type);
}
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs
index f53a5236a9..93b6948edc 100644
--- a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs
@@ -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
///
public IList CreateModelList(string? alias) => new List();
+ ///
+ public Type GetModelType(string? alias) => typeof(IPublishedElement);
+
///
public Type MapModelType(Type type) => typeof(IPublishedElement);
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs
index 2d40874b57..7053a238e6 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs
@@ -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(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>(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();
-
- 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();
+ }
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>(declaring: listType);
- if(ctor is not null) return ctor();
+ if (ctor is not null)
+ {
+ return ctor();
+ }
+
return null;
}
+ ///
+ 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;
+ }
+
///
public Type MapModelType(Type type)
=> ModelType.Map(type, _modelTypeMap);
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorConverter.cs
index 8f68baeb40..99dfd29c0b 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorConverter.cs
@@ -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);
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
index dc1663b14d..a35bb83519 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
@@ -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);
///
- public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(BlockListModel);
+ public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
+ => typeof(BlockListModel);
///
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
///
- 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();
///
- 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(
- $"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($"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>();
+ if (blockListLayout is null)
+ {
+ return BlockListModel.Empty;
+ }
// Get configuration
var configuration = propertyType.DataType.ConfigurationAs();
@@ -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();
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();
- 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();
- 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();
+ 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> _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 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>(constructor);
}
}
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs
index 545ea73bf9..1f482d19fd 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs
@@ -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
///
public NestedContentManyValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory, IProfilingLogger proflog)
: base(publishedSnapshotAccessor, publishedModelFactory)
- {
- _proflog = proflog;
- }
+ => _proflog = proflog;
///
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()?.ContentTypes;
+
return contentTypes?.Length == 1
? typeof(IEnumerable<>).MakeGenericType(ModelType.For(contentTypes[0].Alias))
: typeof(IEnumerable);
@@ -48,9 +45,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
///
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
- {
- return source?.ToString();
- }
+ => source?.ToString();
///
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();
var value = (string?)inter;
- if (string.IsNullOrWhiteSpace(value)) return elements;
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return elements;
+ }
var objects = JsonConvert.DeserializeObject>(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;
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs
index 19ced217f7..0ec450606f 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs
@@ -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
///
public NestedContentSingleValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory, IProfilingLogger proflog)
: base(publishedSnapshotAccessor, publishedModelFactory)
- {
- _proflog = proflog;
- }
+ => _proflog = proflog;
///
public override bool IsConverter(IPublishedPropertyType propertyType)
@@ -36,10 +32,11 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
///
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
{
- var contentTypes = propertyType.DataType.ConfigurationAs()!.ContentTypes;
- return contentTypes?.Length > 1
- ? typeof(IPublishedElement)
- : ModelType.For(contentTypes?[0].Alias);
+ var contentTypes = propertyType.DataType.ConfigurationAs()?.ContentTypes;
+
+ return contentTypes?.Length == 1
+ ? ModelType.For(contentTypes[0].Alias)
+ : typeof(IPublishedElement);
}
///
@@ -48,9 +45,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
///
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
- {
- return source?.ToString();
- }
+ => source?.ToString();
///
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($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})"))
{
var value = (string?)inter;
- if (string.IsNullOrWhiteSpace(value)) return null;
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return null;
+ }
var objects = JsonConvert.DeserializeObject>(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);
}
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs
index 580cd22930..7942ab3c68 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs
@@ -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();
+
+ 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();
- 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();
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>();
-
- 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;
}
}
diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs b/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs
index cdf3d774c2..cd5272b500 100644
--- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs
+++ b/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs
@@ -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 _pureLiveDirectory = null!;
private bool _disposedValue;
-
public InMemoryModelFactory(
Lazy 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);
}
+ ///
+ 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();
- }
-
- 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();
}