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:
Ronald Barendse
2022-05-03 19:23:15 +02:00
committed by GitHub
parent bc49fa5b3f
commit 23072a500c
9 changed files with 254 additions and 155 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>();
}