diff --git a/src/Umbraco.Compat7/Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs b/src/Umbraco.Compat7/Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs index a8774743c6..1f4ab8acf9 100644 --- a/src/Umbraco.Compat7/Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs +++ b/src/Umbraco.Compat7/Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs @@ -13,11 +13,11 @@ namespace Umbraco.Core.Models.PublishedContent public static bool HasCurrent => true; - public void SetFactory(IPublishedContentModelFactory factory) + public void SetFactory(IPublishedModelFactory factory) { CoreCurrent.Container.RegisterSingleton(_ => factory); } - public IPublishedContentModelFactory Factory => CoreCurrent.PublishedContentModelFactory; + public IPublishedModelFactory Factory => CoreCurrent.PublishedModelFactory; } } diff --git a/src/Umbraco.Core/Components/CompositionExtensions.cs b/src/Umbraco.Core/Components/CompositionExtensions.cs index 9500ccaa2d..f3764e13f9 100644 --- a/src/Umbraco.Core/Components/CompositionExtensions.cs +++ b/src/Umbraco.Core/Components/CompositionExtensions.cs @@ -125,9 +125,9 @@ namespace Umbraco.Core.Components /// The type of the factory. /// The composition. public static void SetPublishedContentModelFactory(this Composition composition) - where T : IPublishedContentModelFactory + where T : IPublishedModelFactory { - composition.Container.RegisterSingleton(); + composition.Container.RegisterSingleton(); } /// @@ -135,7 +135,7 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a published content model factory. - public static void SetPublishedContentModelFactory(this Composition composition, Func factory) + public static void SetPublishedContentModelFactory(this Composition composition, Func factory) { composition.Container.RegisterSingleton(factory); } @@ -145,7 +145,7 @@ namespace Umbraco.Core.Components /// /// The composition. /// A published content model factory. - public static void SetPublishedContentModelFactory(this Composition composition, IPublishedContentModelFactory factory) + public static void SetPublishedContentModelFactory(this Composition composition, IPublishedModelFactory factory) { composition.Container.RegisterSingleton(_ => factory); } diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index 31b3ed6831..8326be987f 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -123,8 +123,8 @@ namespace Umbraco.Core.Composing internal static PropertyValueConverterCollection PropertyValueConverters => Container.GetInstance(); - internal static IPublishedContentModelFactory PublishedContentModelFactory - => Container.GetInstance(); + internal static IPublishedModelFactory PublishedModelFactory + => Container.GetInstance(); public static IServerMessenger ServerMessenger => Container.GetInstance(); diff --git a/src/Umbraco.Core/CoreRuntimeComponent.cs b/src/Umbraco.Core/CoreRuntimeComponent.cs index ba87b49c50..06567d2691 100644 --- a/src/Umbraco.Core/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/CoreRuntimeComponent.cs @@ -120,7 +120,7 @@ namespace Umbraco.Core .Append(); // by default, register a noop factory - composition.Container.RegisterSingleton(); + composition.Container.RegisterSingleton(); } internal void Initialize(IEnumerable mapperProfiles) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index db6dc4d88d..6b65b7b421 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; namespace Umbraco.Core.Models.PublishedContent { + /// /// /// Represents a cached content. /// @@ -17,13 +18,16 @@ namespace Umbraco.Core.Models.PublishedContent { #region Content + // fixme - all these are colliding with models => ? + // or could we force them to be 'new' in models? + int Id { get; } int TemplateId { get; } int SortOrder { get; } string Name { get; } - string UrlName { get; } - string DocumentTypeAlias { get; } - int DocumentTypeId { get; } + string UrlName { get; } // fixme rename + string DocumentTypeAlias { get; } // fixme obsolete + int DocumentTypeId { get; } // fixme obsolete string WriterName { get; } string CreatorName { get; } int WriterId { get; } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentExtended.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentExtended.cs deleted file mode 100644 index 524ef969b3..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentExtended.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Provides methods to handle extended content. - /// - internal interface IPublishedContentExtended : IPublishedContent - { - /// - /// Adds a property to the extended content. - /// - /// The property to add. - void AddProperty(IPublishedProperty property); - - /// - /// Gets a value indicating whether properties were added to the extended content. - /// - bool HasAddedProperties { get; } - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentModelFactory.cs deleted file mode 100644 index 350e5970eb..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentModelFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Provides the model creation service. - /// - public interface IPublishedContentModelFactory // fixme rename IFacadeModelFactory - { - /// - /// Creates a strongly-typed model representing a property set. - /// - /// The original property set. - /// The strongly-typed model representing the property set, or the property set - /// itself it the factory has no model for that content type. - IPublishedElement CreateModel(IPublishedElement set); - - /// - /// Gets the model type map. - /// - Dictionary ModelTypeMap { get; } - - // fixme - // - // ModelFactory.Meta.Model("thing").ClrType (find the our post?) - // - // then - // make a plan to get NestedContent in - // and an equivalent of Vorto with different syntax - // - // then - // VARIANTS ARCHITECTURE FFS! - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs new file mode 100644 index 0000000000..9e488dd797 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides the published model creation service. + /// + public interface IPublishedModelFactory + { + /// + /// 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. + IPublishedElement CreateModel(IPublishedElement element); + + /// + /// Gets the model type map. + /// + /// The model type map maps element type aliases to actual Clr types. + Dictionary ModelTypeMap { get; } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs index 83cd9d0dfe..d78ced95a7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs @@ -1,7 +1,7 @@ namespace Umbraco.Core.Models.PublishedContent { /// - /// Represents a property of an IPublishedContent. + /// Represents a property of an IPublishedElement. /// public interface IPublishedProperty { diff --git a/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs b/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs index 0349807e27..5dc42cc542 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using System.Web; namespace Umbraco.Core.Models.PublishedContent { diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs index 93ba2e1788..3589643926 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs @@ -6,13 +6,15 @@ using System.Reflection; namespace Umbraco.Core.Models.PublishedContent { - // create a simple model type: - // ModelType.For("alias") - // use in a generic type: - // typeof (IEnumerable<>).MakeGenericType(ModelType.For("alias")) - // use in an array: - // ModelType.For("alias").MakeArrayType() - + /// + /// + /// Represents the Clr type of a model. + /// + /// + /// ModelType.For("alias") + /// typeof (IEnumerable{}).MakeGenericType(ModelType.For("alias")) + /// Model.For("alias").MakeArrayType() + /// public class ModelType : Type { private ModelType(string contentTypeAlias) @@ -21,14 +23,29 @@ namespace Umbraco.Core.Models.PublishedContent Name = "{" + ContentTypeAlias + "}"; } + /// + /// Gets the content type alias. + /// public string ContentTypeAlias { get; } + /// public override string ToString() => Name; + /// + /// Gets the model type for a published element type. + /// + /// The published element type alias. + /// The model type for the published element type. public static ModelType For(string alias) => new ModelType(alias); + /// + /// Gets the actual Clr type by replacing model types, if any. + /// + /// The type. + /// The model types map. + /// The actual Clr type. public static Type Map(Type type, Dictionary modelTypes) { if (type is ModelType modelType) @@ -47,12 +64,21 @@ namespace Umbraco.Core.Models.PublishedContent if (type.IsGenericType == false) return type; + var def = type.GetGenericTypeDefinition(); + if (def == null) + throw new InvalidOperationException("panic"); var args = type.GetGenericArguments().Select(x => Map(x, modelTypes)).ToArray(); - - return type.GetGenericTypeDefinition().MakeGenericType(args); + return def.MakeGenericType(args); } + /// + /// Gets a value indicating whether two instances are equal. + /// + /// The first instance. + /// The second instance. + /// A value indicating whether the two instances are equal. + /// Knows how to compare instances. public static bool Equals(Type t1, Type t2) { if (t1 == t2) @@ -80,104 +106,144 @@ namespace Umbraco.Core.Models.PublishedContent return true; } + /// protected override TypeAttributes GetAttributeFlagsImpl() => TypeAttributes.Class; + /// public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) => Array.Empty(); + /// protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) => null; + /// public override Type[] GetInterfaces() => Array.Empty(); + /// public override Type GetInterface(string name, bool ignoreCase) => null; + /// public override EventInfo[] GetEvents(BindingFlags bindingAttr) => Array.Empty(); + /// public override EventInfo GetEvent(string name, BindingFlags bindingAttr) => null; + /// public override Type[] GetNestedTypes(BindingFlags bindingAttr) => Array.Empty(); + /// public override Type GetNestedType(string name, BindingFlags bindingAttr) => null; + /// public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) => Array.Empty(); + /// protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) => null; + /// public override MethodInfo[] GetMethods(BindingFlags bindingAttr) => Array.Empty(); + /// protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) => null; + /// public override FieldInfo[] GetFields(BindingFlags bindingAttr) => Array.Empty(); + /// public override FieldInfo GetField(string name, BindingFlags bindingAttr) => null; + /// public override MemberInfo[] GetMembers(BindingFlags bindingAttr) => Array.Empty(); + /// public override object[] GetCustomAttributes(Type attributeType, bool inherit) => Array.Empty(); + /// public override object[] GetCustomAttributes(bool inherit) => Array.Empty(); + /// public override bool IsDefined(Type attributeType, bool inherit) => false; + /// public override Type GetElementType() => null; + /// protected override bool HasElementTypeImpl() => false; + /// protected override bool IsArrayImpl() => false; + /// protected override bool IsByRefImpl() => false; + /// protected override bool IsPointerImpl() => false; + /// protected override bool IsPrimitiveImpl() => false; + /// protected override bool IsCOMObjectImpl() => false; + /// public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) - { - throw new NotSupportedException(); - } + => throw new NotSupportedException(); + /// public override Type UnderlyingSystemType => this; + + /// public override Type BaseType => null; + /// public override string Name { get; } + + /// public override Guid GUID { get; } = Guid.NewGuid(); + + /// public override Module Module => throw new NotSupportedException(); + + /// public override Assembly Assembly => throw new NotSupportedException(); + + /// public override string FullName => Name; + + /// public override string Namespace => string.Empty; + + /// public override string AssemblyQualifiedName => Name; + /// public override Type MakeArrayType() - { - return new ModelTypeArrayType(this); - } + => new ModelTypeArrayType(this); } internal class ModelTypeArrayType : Type diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedContentModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedContentModelFactory.cs deleted file mode 100644 index 8264af9eb0..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedContentModelFactory.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Models.PublishedContent -{ - public class NoopPublishedContentModelFactory : IPublishedContentModelFactory - { - public IPublishedElement CreateModel(IPublishedElement set) - => set; - - public Dictionary ModelTypeMap { get; } = new Dictionary(); - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs new file mode 100644 index 0000000000..1ffbc04fe2 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents a no-operation factory. + public class NoopPublishedModelFactory : IPublishedModelFactory + { + /// + public IPublishedElement CreateModel(IPublishedElement element) => element; + + /// + public Dictionary ModelTypeMap { get; } = new Dictionary(); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PropertyResult.cs b/src/Umbraco.Core/Models/PublishedContent/PropertyResult.cs deleted file mode 100644 index c1a36d67bb..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PropertyResult.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Web; - -namespace Umbraco.Core.Models.PublishedContent -{ - // fixme - temp - internal class PropertyResult : IPublishedProperty, IHtmlString - { - private readonly IPublishedProperty _source; - private readonly string _alias; - private readonly object _value; - - internal PropertyResult(IPublishedProperty source, PropertyResultType type) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - - PropertyType = type; - _source = source; - } - - internal PropertyResult(string alias, object value, PropertyResultType type) - { - if (alias == null) throw new ArgumentNullException(nameof(alias)); - if (value == null) throw new ArgumentNullException(nameof(value)); - - PropertyType = type; - _alias = alias; - _value = value; - } - - internal PropertyResultType PropertyType { get; } - - public string PropertyTypeAlias => _source == null ? _alias : _source.PropertyTypeAlias; - public object SourceValue => _source == null ? _value : _source.SourceValue; - public bool HasValue => _source == null || _source.HasValue; - public object Value => _source == null ? _value : _source.Value; - public object XPathValue => Value?.ToString(); - - public string ToHtmlString() - { - var value = Value; - return value?.ToString() ?? string.Empty; - } - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PropertyResultType.cs b/src/Umbraco.Core/Models/PublishedContent/PropertyResultType.cs deleted file mode 100644 index 86310ea6e6..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PropertyResultType.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Umbraco.Core.Models.PublishedContent -{ - internal enum PropertyResultType - { - /// - /// The property resolved was a normal document property - /// - UserProperty, - - /// - /// The property resolved was a property defined as a member on the document object (IPublishedContent) itself - /// - ReflectedProperty, - - /// - /// The property was created manually for a custom purpose - /// - CustomProperty - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs index 3eca9f025d..7f42c022a5 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs @@ -20,13 +20,12 @@ namespace Umbraco.Core.Models.PublishedContent // get model // if factory returns nothing, throw - var model = Current.PublishedContentModelFactory.CreateModel(content); + var model = Current.PublishedModelFactory.CreateModel(content); if (model == null) throw new Exception("Factory returned null."); // if factory returns a different type, throw - var publishedContent = model as IPublishedContent; - if (publishedContent == null) + if (!(model is IPublishedContent publishedContent)) throw new Exception($"Factory returned model of type {model.GetType().FullName} which does not implement IPublishedContent."); return publishedContent; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelAttribute.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelAttribute.cs index 7e94636935..2e0b037a94 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelAttribute.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelAttribute.cs @@ -3,6 +3,7 @@ using Umbraco.Core.Exceptions; namespace Umbraco.Core.Models.PublishedContent { + /// /// /// Indicates that the class is a published content model for a specified content type. /// @@ -11,8 +12,9 @@ namespace Umbraco.Core.Models.PublishedContent [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class PublishedContentModelAttribute : Attribute { + /// /// - /// Initializes a new instance of the class with a content type alias. + /// Initializes a new instance of the class with a content type alias. /// /// The content type alias. public PublishedContentModelAttribute(string contentTypeAlias) @@ -24,6 +26,6 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets or sets the content type alias. /// - public string ContentTypeAlias { get; private set; } + public string ContentTypeAlias { get; } } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs index a7829371d9..e55fe66945 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs @@ -1,12 +1,18 @@ namespace Umbraco.Core.Models.PublishedContent { /// - /// The type of published content, ie whether it is a content or a media. + /// The type of published element. /// - public enum PublishedItemType + /// Can be a simple element, or a document, a media, a member. + public enum PublishedItemType // fixme - need to rename to PublishedElementType but then conflicts? { /// - /// A content, ie what was formerly known as a document. + /// Unknown. + /// + Unknown = 0, + + /// + /// A document. /// Content, diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs similarity index 85% rename from src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs rename to src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs index 898a575c02..4aebeb1a3e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs @@ -2,14 +2,13 @@ using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; -using System.Reflection.Emit; namespace Umbraco.Core.Models.PublishedContent { /// /// Implements a strongly typed content model factory /// - public class PublishedContentModelFactory : IPublishedContentModelFactory + public class PublishedModelFactory : IPublishedModelFactory { private readonly Dictionary _modelInfos; @@ -23,7 +22,7 @@ namespace Umbraco.Core.Models.PublishedContent public Dictionary ModelTypeMap { get; } /// - /// Initializes a new instance of the class with types. + /// Initializes a new instance of the class with types. /// /// The model types. /// @@ -36,9 +35,8 @@ namespace Umbraco.Core.Models.PublishedContent /// PublishedContentModelFactoryResolver.Current.SetFactory(factory); /// /// - public PublishedContentModelFactory(IEnumerable types) + public PublishedModelFactory(IEnumerable types) { - var ctorArgTypes = new[] { typeof(IPublishedElement) }; var modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase); var exprs = new List>>(); @@ -83,20 +81,20 @@ namespace Umbraco.Core.Models.PublishedContent _modelInfos = modelInfos.Count > 0 ? modelInfos : null; } - public IPublishedElement CreateModel(IPublishedElement set) + public IPublishedElement CreateModel(IPublishedElement element) { // fail fast if (_modelInfos == null) - return set; + return element; - if (_modelInfos.TryGetValue(set.ContentType.Alias, out ModelInfo modelInfo) == false) - return set; + if (_modelInfos.TryGetValue(element.ContentType.Alias, out var modelInfo) == false) + return element; // ReSharper disable once UseMethodIsInstanceOfType - if (modelInfo.ParameterType.IsAssignableFrom(set.GetType()) == false) - throw new InvalidOperationException($"Model {modelInfo.ModelType} expects argument of type {modelInfo.ParameterType.FullName}, but got {set.GetType().FullName}."); + 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}."); - return modelInfo.Ctor(set); + return modelInfo.Ctor(element); } } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 6e64822594..6f51dc4e0d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -256,8 +256,7 @@ namespace Umbraco.Core.Models.PublishedContent // else just return the inter value as a string or an XPathNavigator if (inter == null) return null; - var xElement = inter as XElement; - if (xElement != null) + if (inter is XElement xElement) return xElement.CreateNavigator(); return inter.ToString().Trim(); } @@ -281,7 +280,7 @@ namespace Umbraco.Core.Models.PublishedContent get { if (!_initialized) Initialize(); - return _clrType ?? (_clrType = ModelType.Map(_modelClrType, Current.PublishedContentModelFactory.ModelTypeMap)); + return _clrType ?? (_clrType = ModelType.Map(_modelClrType, Current.PublishedModelFactory.ModelTypeMap)); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs new file mode 100644 index 0000000000..cc7be8ad7f --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -0,0 +1,36 @@ +using System; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// + /// A published property base that uses a raw object value. + /// + /// Conversions results are stored within the property and will not + /// be refreshed, so this class is not suitable for cached properties. + internal class RawValueProperty : PublishedPropertyBase + { + private readonly object _propertyData; //the value in the db + private readonly Lazy _objectValue; + private readonly Lazy _xpathValue; + + public override object SourceValue => _propertyData; + + public override bool HasValue => _propertyData is string s ? !string.IsNullOrWhiteSpace(s) : _propertyData != null; + + public override object Value => _objectValue.Value; + + public override object XPathValue => _xpathValue.Value; + + public RawValueProperty(PublishedPropertyType propertyType, IPublishedElement content, object propertyData, bool isPreviewing = false) + : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored + { + _propertyData = propertyData; + + var interValue = new Lazy(() => PropertyType.ConvertSourceToInter(content, _propertyData, isPreviewing)); + _objectValue = new Lazy(() => PropertyType.ConvertInterToObject(content, PropertyCacheLevel.Unknown, interValue.Value, isPreviewing)); + _xpathValue = new Lazy(() => PropertyType.ConvertInterToXPath(content, PropertyCacheLevel.Unknown, interValue.Value, isPreviewing)); + } + } +} diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index 372f30d1f5..45d0b05cef 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -12,35 +12,6 @@ namespace Umbraco.Core /// public static class ReflectionUtilities { - private static Func GetPropertyGetter(PropertyInfo property) - { - var type = typeof(TInstance); - - var getMethod = property.GetMethod; - if (getMethod == null) - throw new InvalidOperationException($"Property {type}.{property.Name} : {property.PropertyType} does not have a getter."); - - var exprThis = Expression.Parameter(type, "this"); - var exprCall = Expression.Call(exprThis, getMethod); - var expr = Expression.Lambda>(exprCall, exprThis); - return expr.CompileToDelegate(); - } - - private static Action GetPropertySetter(PropertyInfo property) - { - var type = typeof(TInstance); - - var setMethod = property.SetMethod; - if (setMethod == null) - throw new InvalidOperationException($"Property {type}.{property.Name} : {property.PropertyType} does not have a setter."); - - var exprThis = Expression.Parameter(type, "this"); - var exprArg0 = Expression.Parameter(typeof(TValue), "value"); - var exprCall = Expression.Call(exprThis, setMethod, exprArg0); - var expr = Expression.Lambda>(exprCall, exprThis, exprArg0); - return expr.CompileToDelegate(); - } - public static Func GetPropertyGetter(string propertyName) { var type = typeof(TInstance); @@ -88,6 +59,20 @@ namespace Umbraco.Core return expr.CompileToDelegate(); } + public static Func GetCtor(Type type) + { + // get the constructor infos + var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, + null, Type.EmptyTypes, null); + + if (ctor == null) + throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); + + var exprNew = Expression.New(ctor); + var expr = Expression.Lambda>(exprNew); + return expr.CompileToDelegate(); + } + public static Func GetCtor() { var type = typeof(TInstance); @@ -155,6 +140,35 @@ namespace Umbraco.Core return GetMethod(method, methodName, type, parameterTypes, returnType); } + private static Func GetPropertyGetter(PropertyInfo property) + { + var type = typeof(TInstance); + + var getMethod = property.GetMethod; + if (getMethod == null) + throw new InvalidOperationException($"Property {type}.{property.Name} : {property.PropertyType} does not have a getter."); + + var exprThis = Expression.Parameter(type, "this"); + var exprCall = Expression.Call(exprThis, getMethod); + var expr = Expression.Lambda>(exprCall, exprThis); + return expr.CompileToDelegate(); + } + + private static Action GetPropertySetter(PropertyInfo property) + { + var type = typeof(TInstance); + + var setMethod = property.SetMethod; + if (setMethod == null) + throw new InvalidOperationException($"Property {type}.{property.Name} : {property.PropertyType} does not have a setter."); + + var exprThis = Expression.Parameter(type, "this"); + var exprArg0 = Expression.Parameter(typeof(TValue), "value"); + var exprCall = Expression.Call(exprThis, setMethod, exprArg0); + var expr = Expression.Lambda>(exprCall, exprThis, exprArg0); + return expr.CompileToDelegate(); + } + private static void GetMethodParms(out Type[] parameterTypes, out Type returnType) { var typeM = typeof(TMethod); @@ -260,6 +274,9 @@ namespace Umbraco.Core return expr.Compile(); } + // not sure we want this at all? + + /* public static object GetStaticProperty(this Type type, string propertyName, Func, PropertyInfo> filter = null) { var propertyInfo = GetPropertyInfo(type, propertyName, filter); @@ -418,6 +435,7 @@ namespace Umbraco.Core propInfo.SetValue(obj, val, null); } + */ public static Action CompileToDelegate(Expression expr) { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 4448bcfb4c..6ccf608161 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -644,21 +644,18 @@ - - + - - - + - + @@ -666,6 +663,7 @@ + diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs index 28d707c2a5..8d339824fe 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs @@ -61,7 +61,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var xmlStore = new XmlStore(() => _xml); var cacheProvider = new StaticCacheProvider(); var domainCache = new DomainCache(ServiceContext.DomainService); - var facade = new Facade( + var facade = new Umbraco.Web.PublishedCache.XmlPublishedCache.Facade( new PublishedContentCache(xmlStore, domainCache, cacheProvider, ContentTypesCache, null, null), new PublishedMediaCache(xmlStore, ServiceContext.MediaService, ServiceContext.UserService, cacheProvider, ContentTypesCache), new PublishedMemberCache(null, cacheProvider, Current.Services.MemberService, ContentTypesCache), diff --git a/src/Umbraco.Tests/Facade/NestedContentTests.cs b/src/Umbraco.Tests/Facade/NestedContentTests.cs new file mode 100644 index 0000000000..ff41f883c3 --- /dev/null +++ b/src/Umbraco.Tests/Facade/NestedContentTests.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LightInject; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web; +using Umbraco.Web.Models; +using Umbraco.Web.PropertyEditors.ValueConverters; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Tests.Facade +{ + [TestFixture] + public class NestedContentTests + { + private (PublishedContentType, PublishedContentType) CreateContentTypes() + { + Current.Reset(); + + var logger = Mock.Of(); + var profiler = Mock.Of(); + var proflog = new ProfilingLogger(logger, profiler); + + var container = new ServiceContainer(); + container.ConfigureUmbracoCore(); + + // fixme - temp needed by NestedContentHelper to cache preValues + container.RegisterSingleton(f => CacheHelper.NoCache); + + var dataTypeService = new Mock(); + + // fixme - temp both needed by NestedContentHelper to read preValues + container.RegisterSingleton(); + container.RegisterSingleton(f => dataTypeService.Object); + + // mocked dataservice returns nested content preValues + dataTypeService + .Setup(x => x.GetPreValuesCollectionByDataTypeId(It.IsAny())) + .Returns((int id) => + { + if (id == 1) + return new PreValueCollection(new Dictionary + { + { "minItems", new PreValue("1") }, + { "maxItems", new PreValue("1") }, + { "contentTypes", new PreValue("contentN1") } + }); + if (id == 2) + return new PreValueCollection(new Dictionary + { + { "minItems", new PreValue("1") }, + { "maxItems", new PreValue("99") }, + { "contentTypes", new PreValue("contentN1") } + }); + return null; + }); + + var publishedModelFactory = new Mock(); + + // fixme - temp needed by PublishedPropertyType + container.RegisterSingleton(f => publishedModelFactory.Object); + + // mocked model factory returns model type + publishedModelFactory + .Setup(x => x.ModelTypeMap) + .Returns(new Dictionary + { + { "contentN1", typeof (TestModel) } + }); + + // mocked model factory creates models + publishedModelFactory + .Setup(x => x.CreateModel(It.IsAny())) + .Returns((IPublishedElement element) => + { + if (element.ContentType.Alias.InvariantEquals("contentN1")) + return new TestModel(element); + return element; + }); + + var contentCache = new Mock(); + var facade = new Mock(); + + // mocked facade returns a content cache + facade + .Setup(x => x.ContentCache) + .Returns(contentCache.Object); + + var facadeAccessor = new Mock(); + //container.RegisterSingleton(f => facadeAccessor.Object); + + // mocked facade accessor returns a facade + facadeAccessor + .Setup(x => x.Facade) + .Returns(facade.Object); + + var facadeService = new Mock(); + //container.RegisterSingleton(f => facadeService.Object); + + // mocked facade service creates element properties + facadeService + .Setup(x => x.CreateElementProperty(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((PublishedPropertyType propertyType, IPublishedElement element, bool preview, PropertyCacheLevel referenceCacheLevel, object source) + => new TestPublishedProperty(propertyType, element, preview, referenceCacheLevel, source)); + + var converters = new PropertyValueConverterCollection(new IPropertyValueConverter[] + { + new NestedContentSingleValueConverter(facadeAccessor.Object, facadeService.Object, publishedModelFactory.Object, proflog), + new NestedContentManyValueConverter(facadeAccessor.Object, facadeService.Object, publishedModelFactory.Object, proflog), + }); + + var propertyType1 = new PublishedPropertyType("property1", 1, Constants.PropertyEditors.NestedContentAlias, converters); + var propertyType2 = new PublishedPropertyType("property2", 2, Constants.PropertyEditors.NestedContentAlias, converters); + var propertyTypeN1 = new PublishedPropertyType("propertyN1", Constants.PropertyEditors.TextboxAlias, converters); + + var contentType1 = new PublishedContentType(1, "content1", new[] { propertyType1 }); + var contentType2 = new PublishedContentType(2, "content2", new[] { propertyType2 }); + var contentTypeN1 = new PublishedContentType(2, "contentN1", new[] { propertyTypeN1 }); + + // mocked content cache returns content types + contentCache + .Setup(x => x.GetContentType(It.IsAny())) + .Returns((string alias) => + { + if (alias.InvariantEquals("contentN1")) return contentTypeN1; + return null; + }); + + return (contentType1, contentType2); + } + + [Test] + public void SingleNestedTest() + { + (var contentType1, _) = CreateContentTypes(); + + // nested single converter returns the proper value clr type TestModel, and cache level + Assert.AreEqual(typeof (TestModel), contentType1.GetPropertyType("property1").ClrType); + Assert.AreEqual(PropertyCacheLevel.Content, contentType1.GetPropertyType("property1").CacheLevel); + + var key = Guid.NewGuid(); + var keyA = Guid.NewGuid(); + var content = new TestPublishedContent(contentType1, key, new[] + { + new TestPublishedProperty(contentType1.GetPropertyType("property1"), $@"[ + {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }} + ]") + }); + var value = content.Value("property1"); + + // nested single converter returns proper TestModel value + Assert.IsInstanceOf(value); + var valueM = (TestModel) value; + Assert.AreEqual("foo", valueM.PropValue); + Assert.AreEqual(keyA, valueM.Key); + } + + [Test] + public void ManyNestedTest() + { + (_, var contentType2) = CreateContentTypes(); + + // nested many converter returns the proper value clr type IEnumerable, and cache level + Assert.AreEqual(typeof (IEnumerable), contentType2.GetPropertyType("property2").ClrType); + Assert.AreEqual(PropertyCacheLevel.Content, contentType2.GetPropertyType("property2").CacheLevel); + + var key = Guid.NewGuid(); + var keyA = Guid.NewGuid(); + var keyB = Guid.NewGuid(); + var content = new TestPublishedContent(contentType2, key, new[] + { + new TestPublishedProperty(contentType2.GetPropertyType("property2"), $@"[ + {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }}, + {{ ""key"": ""{keyB}"", ""propertyN1"": ""bar"", ""ncContentTypeAlias"": ""contentN1"" }} + ]") + }); + var value = content.Value("property2"); + + // nested many converter returns proper IEnumerable value + Assert.IsInstanceOf>(value); + Assert.IsInstanceOf>(value); + var valueM = ((IEnumerable) value).ToArray(); + Assert.AreEqual("foo", valueM[0].PropValue); + Assert.AreEqual(keyA, valueM[0].Key); + Assert.AreEqual("bar", valueM[1].PropValue); + Assert.AreEqual(keyB, valueM[1].Key); + } + + // note: this class needs to be public enough, for the converters to be able to instanciate it + public class TestModel : PublishedElementModel + { + public TestModel(IPublishedElement content) + : base(content) + { } + + public string PropValue => this.Value("propertyN1"); + } + + class TestPublishedProperty : PublishedPropertyBase + { + private readonly bool _preview; + private IPublishedElement _owner; + + public TestPublishedProperty(PublishedPropertyType propertyType, object source) + : base(propertyType, PropertyCacheLevel.Content) // initial reference cache level always is .Content + { + SourceValue = source; + HasValue = source != null && (!(source is string ssource) || !string.IsNullOrWhiteSpace(ssource)); + } + + public TestPublishedProperty(PublishedPropertyType propertyType, IPublishedElement element, bool preview, PropertyCacheLevel referenceCacheLevel, object source) + : base(propertyType, referenceCacheLevel) + { + SourceValue = source; + HasValue = source != null && (!(source is string ssource) || !string.IsNullOrWhiteSpace(ssource)); + _owner = element; + _preview = preview; + } + + private object InterValue => PropertyType.ConvertSourceToInter(null, SourceValue, false); + + internal void SetOwner(IPublishedElement owner) + { + _owner = owner; + } + + public override bool HasValue { get; } + public override object SourceValue { get; } + public override object Value => PropertyType.ConvertInterToObject(_owner, ReferenceCacheLevel, InterValue, _preview); + public override object XPathValue => throw new WontImplementException(); + } + + class TestPublishedContent : PublishedContentBase + { + public TestPublishedContent(PublishedContentType contentType, Guid key, IEnumerable properties) + { + ContentType = contentType; + Key = key; + var propertiesA = properties.ToArray(); + Properties = propertiesA; + foreach (var property in propertiesA) + property.SetOwner(this); + } + + // ReSharper disable UnassignedGetOnlyAutoProperty + public override PublishedItemType ItemType { get; } + public override bool IsDraft { get; } + public override IPublishedContent Parent { get; } + public override IEnumerable Children { get; } + public override PublishedContentType ContentType { get; } + // ReSharper restore UnassignedGetOnlyAutoProperty + + // ReSharper disable UnassignedGetOnlyAutoProperty + public override int Id { get; } + public override int TemplateId { get; } + public override int SortOrder { get; } + public override string Name { get; } + public override string UrlName { get; } + public override string DocumentTypeAlias { get; } + public override int DocumentTypeId { get; } + public override string WriterName { get; } + public override string CreatorName { get; } + public override int WriterId { get; } + public override int CreatorId { get; } + public override string Path { get; } + public override DateTime CreateDate { get; } + public override DateTime UpdateDate { get; } + public override Guid Version { get; } + public override int Level { get; } + public override Guid Key { get; } + // ReSharper restore UnassignedGetOnlyAutoProperty + + public override IEnumerable Properties { get; } + public override IPublishedProperty GetProperty(string alias) + { + return Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + } + } + } +} diff --git a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs index 60adf49bb5..bc5971a377 100644 --- a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs @@ -8,7 +8,6 @@ namespace Umbraco.Tests.FrontEnd [TestFixture] public class UmbracoHelperTests { - [Test] public void Truncate_Simple() { diff --git a/src/Umbraco.Tests/PublishedContent/ModelsAndConvertersTests.cs b/src/Umbraco.Tests/PublishedContent/ModelsAndConvertersTests.cs index 244d7f7e32..ee1b61f9f4 100644 --- a/src/Umbraco.Tests/PublishedContent/ModelsAndConvertersTests.cs +++ b/src/Umbraco.Tests/PublishedContent/ModelsAndConvertersTests.cs @@ -189,7 +189,7 @@ namespace Umbraco.Tests.PublishedContent .Append() .Append(); - IPublishedContentModelFactory factory = new PublishedContentModelFactory(new[] + IPublishedModelFactory factory = new PublishedModelFactory(new[] { typeof(TestSetModel1), typeof(TestSetModel2), typeof(TestContentModel1), typeof(TestContentModel2), @@ -405,7 +405,7 @@ namespace Umbraco.Tests.PublishedContent var facadeServiceMock = new Mock(); facadeServiceMock - .Setup(x => x.CreateSetProperty(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.CreateElementProperty(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((propertyType, set, previewing, refCacheLevel, value) => { // ReSharper disable AccessToModifiedClosure diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index e2991601c9..488a386529 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -142,8 +142,9 @@ namespace Umbraco.Tests.PublishedContent Properties = new Collection( new List() { - new PropertyResult("property1", "value" + indexVals, PropertyResultType.UserProperty), - new PropertyResult("property2", "value" + (indexVals + 1), PropertyResultType.UserProperty) + // PropertyResult is gone, this should be done differently + //new PropertyResult("property1", "value" + indexVals, PropertyResultType.UserProperty), + //new PropertyResult("property2", "value" + (indexVals + 1), PropertyResultType.UserProperty) }), Children = new List() }; @@ -156,15 +157,17 @@ namespace Umbraco.Tests.PublishedContent GetContent(false, indexVals + 9) }; } - if (!createChildren) - { - //create additional columns, used to test the different columns for child nodes - ((Collection)d.Properties).Add(new PropertyResult("property4", "value" + (indexVals + 2), PropertyResultType.UserProperty)); - } - else - { - ((Collection)d.Properties).Add(new PropertyResult("property3", "value" + (indexVals + 2), PropertyResultType.UserProperty)); - } + + // PropertyResult is gone, this should be done differently + //if (!createChildren) + //{ + // //create additional columns, used to test the different columns for child nodes + // ((Collection)d.Properties).Add(new PropertyResult("property4", "value" + (indexVals + 2), PropertyResultType.UserProperty)); + //} + //else + //{ + // ((Collection)d.Properties).Add(new PropertyResult("property3", "value" + (indexVals + 2), PropertyResultType.UserProperty)); + //} return d; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 2a85d0f66f..8a5c86a549 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -35,7 +35,7 @@ namespace Umbraco.Tests.PublishedContent { base.Compose(); - Container.RegisterSingleton(f => new PublishedContentModelFactory(f.GetInstance().GetTypes())); + Container.RegisterSingleton(f => new PublishedModelFactory(f.GetInstance().GetTypes())); } protected override TypeLoader CreatePluginManager(IServiceFactory f) diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 62de356714..7cdc8fd2c5 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -54,7 +54,7 @@ namespace Umbraco.Tests.PublishedContent { base.Compose(); - Container.RegisterSingleton(f => new PublishedContentModelFactory(f.GetInstance().GetTypes())); + Container.RegisterSingleton(f => new PublishedModelFactory(f.GetInstance().GetTypes())); } protected override TypeLoader CreatePluginManager(IServiceFactory f) diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 460df474cf..4f40efdef5 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -273,7 +273,7 @@ namespace Umbraco.Tests.Testing Container.RegisterSingleton(factory => new PhysicalFileSystem("MasterPages", "/masterpages"), "MasterpageFileSystem"); // no factory (noop) - Container.RegisterSingleton(); + Container.RegisterSingleton(); // register application stuff (database factory & context, services...) Container.RegisterCollectionBuilder() diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index cfcafe3d72..05d1d22175 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -168,6 +168,9 @@ + + ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + @@ -218,6 +221,7 @@ + diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index 7984e704b3..b75ca5912b 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -35,4 +35,5 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 86c4bb1678..275fc119b5 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -233,7 +233,7 @@ namespace Umbraco.Web.Composing internal static PropertyValueConverterCollection PropertyValueConverters => CoreCurrent.PropertyValueConverters; - internal static IPublishedContentModelFactory PublishedContentModelFactory => CoreCurrent.PublishedContentModelFactory; + internal static IPublishedModelFactory PublishedModelFactory => CoreCurrent.PublishedModelFactory; public static IServerMessenger ServerMessenger => CoreCurrent.ServerMessenger; diff --git a/src/Umbraco.Web/Models/DetachedPublishedContent.cs b/src/Umbraco.Web/Models/DetachedPublishedContent.cs deleted file mode 100644 index 5f2ee5e457..0000000000 --- a/src/Umbraco.Web/Models/DetachedPublishedContent.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web.Models -{ - public class DetachedPublishedContent : PublishedContentBase - { - private readonly PublishedContentType _contentType; - private readonly IEnumerable _properties; - private readonly IPublishedContent _containerNode; - - public DetachedPublishedContent( - Guid key, - string name, - PublishedContentType contentType, - IEnumerable properties, - IPublishedContent containerNode = null, - int sortOrder = 0, - bool isPreviewing = false) - { - Key = key; - Name = name; - _contentType = contentType; - _properties = properties; - SortOrder = sortOrder; - IsDraft = isPreviewing; - _containerNode = containerNode; - } - - public override Guid Key { get; } - - public override int Id => 0; - - public override string Name { get; } - - public override bool IsDraft { get; } - - public override PublishedItemType ItemType => PublishedItemType.Content; - - public override PublishedContentType ContentType => _contentType; - - public override string DocumentTypeAlias => _contentType.Alias; - - public override int DocumentTypeId => _contentType.Id; - - public override IEnumerable Properties => _properties.ToArray(); - - public override IPublishedProperty GetProperty(string alias) - => _properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); - - public override IPublishedProperty GetProperty(string alias, bool recurse) - { - if (recurse) - throw new NotSupportedException(); - - return GetProperty(alias); - } - - public override IPublishedContent Parent => null; - - public override IEnumerable Children => Enumerable.Empty(); - - public override int TemplateId => 0; - - public override int SortOrder { get; } - - public override string UrlName => null; - - public override string WriterName => _containerNode?.WriterName; - - public override string CreatorName => _containerNode?.CreatorName; - - public override int WriterId => _containerNode?.WriterId ?? 0; - - public override int CreatorId => _containerNode?.CreatorId ?? 0; - - public override string Path => null; - - public override DateTime CreateDate => _containerNode?.CreateDate ?? DateTime.MinValue; - - public override DateTime UpdateDate => _containerNode?.UpdateDate ?? DateTime.MinValue; - - public override Guid Version => _containerNode?.Version ?? Guid.Empty; - - public override int Level => 0; - } -} diff --git a/src/Umbraco.Web/Models/DetachedPublishedProperty.cs b/src/Umbraco.Web/Models/DetachedPublishedProperty.cs deleted file mode 100644 index 0a3d189afa..0000000000 --- a/src/Umbraco.Web/Models/DetachedPublishedProperty.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.Models -{ - internal class DetachedPublishedProperty : IPublishedProperty - { - private readonly PublishedPropertyType _propertyType; - private readonly object _sourceValue; - private readonly Lazy _objectValue; - private readonly Lazy _xpathValue; - private readonly bool _isPreview; - - public DetachedPublishedProperty(PublishedPropertyType propertyType, object value) - : this(propertyType, value, false) - { } - - public DetachedPublishedProperty(PublishedPropertyType propertyType, object value, bool isPreview) - { - _propertyType = propertyType; - _isPreview = isPreview; - - _sourceValue = value; - - IPublishedElement publishedElement = null; // fixme!! nested content needs complete refactoring! - - var interValue = new Lazy(() => _propertyType.ConvertSourceToInter(publishedElement, _sourceValue, _isPreview)); - _objectValue = new Lazy(() => _propertyType.ConvertInterToObject(publishedElement, PropertyCacheLevel.None, interValue.Value, _isPreview)); - _xpathValue = new Lazy(() => _propertyType.ConvertInterToXPath(publishedElement, PropertyCacheLevel.None, interValue.Value, _isPreview)); - } - - public string PropertyTypeAlias => _propertyType.PropertyTypeAlias; - - public bool HasValue => SourceValue != null && SourceValue.ToString().Trim().Length > 0; - - public object SourceValue => _sourceValue; - - public object Value => _objectValue.Value; - - public object XPathValue => _xpathValue.Value; - } -} diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs b/src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs index 32f8bd1b38..b3c066ee27 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs @@ -25,107 +25,17 @@ namespace Umbraco.Web.PropertyEditors string.Concat(CacheKeyPrefix, id)); } - public static string GetContentTypeAliasFromItem(JObject item) + public static string GetElementTypeAlias(JObject item) { - var contentTypeAliasProperty = item[NestedContentPropertyEditor.ContentTypeAliasPropertyKey]; - if (contentTypeAliasProperty == null) - { - return null; - } - - return contentTypeAliasProperty.ToObject(); + return item[NestedContentPropertyEditor.ContentTypeAliasPropertyKey]?.ToObject(); } - public static IContentType GetContentTypeFromItem(JObject item) + public static IContentType GetElementType(JObject item) { - var contentTypeAlias = GetContentTypeAliasFromItem(item); - if (string.IsNullOrEmpty(contentTypeAlias)) - { - return null; - } - - return Current.Services.ContentTypeService.Get(contentTypeAlias); + var contentTypeAlias = GetElementTypeAlias(item); + return string.IsNullOrEmpty(contentTypeAlias) + ? null + : Current.Services.ContentTypeService.Get(contentTypeAlias); } - - #region Conversion from v0.1.1 data formats - - public static void ConvertItemValueFromV011(JObject item, int dtdId, ref PreValueCollection preValues) - { - var contentTypeAlias = GetContentTypeAliasFromItem(item); - if (contentTypeAlias != null) - { - // the item is already in >v0.1.1 format - return; - } - - // old style (v0.1.1) data, let's attempt a conversion - // - get the prevalues (if they're not loaded already) - preValues = preValues ?? GetPreValuesCollectionByDataTypeId(dtdId); - - // - convert the prevalues (if necessary) - ConvertPreValueCollectionFromV011(preValues); - - // - get the content types prevalue as JArray - var preValuesAsDictionary = preValues.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); - if (!preValuesAsDictionary.ContainsKey(ContentTypesPreValueKey) || string.IsNullOrEmpty(preValuesAsDictionary[ContentTypesPreValueKey]) != false) - { - return; - } - - var preValueContentTypes = JArray.Parse(preValuesAsDictionary[ContentTypesPreValueKey]); - if (preValueContentTypes.Any()) - { - // the only thing we can really do is assume that the item is the first available content type - item[NestedContentPropertyEditor.ContentTypeAliasPropertyKey] = preValueContentTypes.First().Value("ncAlias"); - } - } - - public static void ConvertPreValueCollectionFromV011(PreValueCollection preValueCollection) - { - if (preValueCollection == null) - { - return; - } - - var persistedPreValuesAsDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); - - // do we have a "docTypeGuid" prevalue and no "contentTypes" prevalue? - if (persistedPreValuesAsDictionary.ContainsKey("docTypeGuid") == false || persistedPreValuesAsDictionary.ContainsKey(ContentTypesPreValueKey)) - { - // the prevalues are already in >v0.1.1 format - return; - } - - // attempt to parse the doc type guid - Guid guid; - if (Guid.TryParse(persistedPreValuesAsDictionary["docTypeGuid"], out guid) == false) - { - // this shouldn't happen... but just in case. - return; - } - - // find the content type - var contentType = Current.Services.ContentTypeService.Get(guid); - if (contentType == null) - { - return; - } - - // add a prevalue in the format expected by the new (>0.1.1) content type picker/configurator - preValueCollection.PreValuesAsDictionary[ContentTypesPreValueKey] = new PreValue( - string.Format(@"[{{""ncAlias"": ""{0}"", ""ncTabAlias"": ""{1}"", ""nameTemplate"": ""{2}"", }}]", - contentType.Alias, - persistedPreValuesAsDictionary["tabAlias"], - persistedPreValuesAsDictionary["nameTemplate"] - ) - ); - } - - private static string ContentTypesPreValueKey - { - get { return NestedContentPropertyEditor.NestedContentPreValueEditor.ContentTypesPreValueKey; } - } - - #endregion } } diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 00bd74149b..0a443b6888 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -76,14 +76,6 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("hideLabel", "Hide Label", "boolean", Description = "Set whether to hide the editor label and have the list take up the full width of the editor window.")] public string HideLabel { get; set; } - - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - // re-format old style (v0.1.1) pre values if necessary - NestedContentHelper.ConvertPreValueCollectionFromV011(persistedPreVals); - - return base.ConvertDbToEditor(defaultPreVals, persistedPreVals); - } } #endregion @@ -142,10 +134,7 @@ namespace Umbraco.Web.PropertyEditors var o = value[i]; var propValues = ((JObject)o); - // convert from old style (v0.1.1) data format if necessary - NestedContentHelper.ConvertItemValueFromV011(propValues, propertyType.DataTypeDefinitionId, ref preValues); - - var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); + var contentType = NestedContentHelper.GetElementType(propValues); if (contentType == null) { continue; @@ -217,10 +206,7 @@ namespace Umbraco.Web.PropertyEditors var o = value[i]; var propValues = ((JObject)o); - // convert from old style (v0.1.1) data format if necessary - NestedContentHelper.ConvertItemValueFromV011(propValues, propertyType.DataTypeDefinitionId, ref preValues); - - var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); + var contentType = NestedContentHelper.GetElementType(propValues); if (contentType == null) { continue; @@ -298,7 +284,7 @@ namespace Umbraco.Web.PropertyEditors var o = value[i]; var propValues = ((JObject)o); - var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); + var contentType = NestedContentHelper.GetElementType(propValues); if (contentType == null) { continue; @@ -368,7 +354,7 @@ namespace Umbraco.Web.PropertyEditors var o = value[i]; var propValues = (JObject) o; - var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); + var contentType = NestedContentHelper.GetElementType(propValues); if (contentType == null) { continue; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs index 3352816bdc..58e57ba247 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs @@ -1,41 +1,105 @@ using System; +using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors.ValueConverters { - public class NestedContentManyValueConverter : PropertyValueConverterBase + /// + /// + /// Provides an implementation for for nested content. + /// + public class NestedContentManyValueConverter : NestedContentValueConverterBase { - private readonly ILogger _logger; + private readonly ConcurrentDictionary> _listCtors = new ConcurrentDictionary>(); + private readonly ProfilingLogger _proflog; - public NestedContentManyValueConverter(ILogger logger) + /// + /// Initializes a new instance of the class. + /// + public NestedContentManyValueConverter(IFacadeAccessor facadeAccessor, IFacadeService facadeService, IPublishedModelFactory publishedModelFactory, ProfilingLogger proflog) + : base(facadeAccessor, facadeService, publishedModelFactory) { - _logger = logger; + _proflog = proflog; } + /// public override bool IsConverter(PublishedPropertyType propertyType) - => propertyType.IsNestedContentProperty() && !propertyType.IsSingleNestedContentProperty(); + => IsNestedMany(propertyType); + /// public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof(IEnumerable); + { + var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); + var contentTypes = preValueCollection.PreValuesAsDictionary["contentTypes"].Value; + return contentTypes.Contains(",") + ? typeof (IEnumerable) + : typeof (IEnumerable<>).MakeGenericType(ModelType.For(contentTypes)); + } + /// public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) => PropertyCacheLevel.Content; + /// public override object ConvertSourceToInter(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { - try - { - return propertyType.ConvertPropertyToNestedContent(source, preview); - } - catch (Exception e) - { - _logger.Error("Error converting value", e); - } + return source?.ToString(); + } - return null; + /// + public override object ConvertInterToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + using (_proflog.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataTypeId})")) + { + var value = (string)inter; + if (string.IsNullOrWhiteSpace(value)) return null; + + var objects = JsonConvert.DeserializeObject>(value); + if (objects.Count == 0) + return Enumerable.Empty(); + + // fixme do NOT do it here! + var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); + var contentTypes = preValueCollection.PreValuesAsDictionary["contentTypes"].Value; + IList elements; + if (contentTypes.Contains(",")) + { + elements = new List(); + } + else if (PublishedModelFactory.ModelTypeMap.TryGetValue(contentTypes, out var type)) + { + var ctor = _listCtors.GetOrAdd(type, t => + { + var listType = typeof(List<>).MakeGenericType(t); + return ReflectionUtilities.GetCtor(listType); + }); + + elements = (IList) ctor(); + } + else + { + // should we throw instead? + elements = new List(); + } + + foreach (var sourceObject in objects) + { + var element = ConvertToElement(sourceObject, referenceCacheLevel, preview); + if (element != null) + elements.Add(element); + } + + return elements; + } } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentPublishedPropertyTypeExtensions.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentPublishedPropertyTypeExtensions.cs deleted file mode 100644 index 4b0621db76..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentPublishedPropertyTypeExtensions.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Models; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - internal static class NestedContentPublishedPropertyTypeExtensions - { - public static bool IsNestedContentProperty(this PublishedPropertyType publishedProperty) - { - return publishedProperty.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.NestedContentAlias); - } - - public static bool IsSingleNestedContentProperty(this PublishedPropertyType publishedProperty) - { - if (!publishedProperty.IsNestedContentProperty()) - { - return false; - } - - var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(publishedProperty.DataTypeId); - var preValueDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); - - return preValueDictionary.ContainsKey("minItems") && - int.TryParse(preValueDictionary["minItems"], out var minItems) && minItems == 1 - && preValueDictionary.ContainsKey("maxItems") && - int.TryParse(preValueDictionary["maxItems"], out var maxItems) && maxItems == 1; - } - - public static object ConvertPropertyToNestedContent(this PublishedPropertyType propertyType, object source, bool preview) - { - using (Current.ProfilingLogger.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataTypeId})")) - { - if (source != null && !source.ToString().IsNullOrWhiteSpace()) - { - var rawValue = JsonConvert.DeserializeObject>(source.ToString()); - var processedValue = new List(); - - var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); - var preValueDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); - - for (var i = 0; i < rawValue.Count; i++) - { - var item = (JObject)rawValue[i]; - - // Convert from old style (v.0.1.1) data format if necessary - // - Please note: This call has virtually no impact on rendering performance for new style (>v0.1.1). - // Even so, this should be removed eventually, when it's safe to assume that there is - // no longer any need for conversion. - NestedContentHelper.ConvertItemValueFromV011(item, propertyType.DataTypeId, ref preValueCollection); - - var contentTypeAlias = NestedContentHelper.GetContentTypeAliasFromItem(item); - if (string.IsNullOrEmpty(contentTypeAlias)) - { - continue; - } - - var publishedContentType = Composing.Current.Facade.ContentCache.GetContentType(contentTypeAlias); - if (publishedContentType == null) - { - continue; - } - - var propValues = item.ToObject>(); - var properties = new List(); - - foreach (var jProp in propValues) - { - var propType = publishedContentType.GetPropertyType(jProp.Key); - if (propType != null) - { - properties.Add(new DetachedPublishedProperty(propType, jProp.Value, preview)); - } - } - - // Parse out the name manually - object nameObj = null; - if (propValues.TryGetValue("name", out nameObj)) - { - // Do nothing, we just want to parse out the name if we can - } - - object keyObj; - var key = Guid.Empty; - if (propValues.TryGetValue("key", out keyObj)) - { - key = Guid.Parse(keyObj.ToString()); - } - - // Get the current request node we are embedded in - var pcr = UmbracoContext.Current == null ? null : UmbracoContext.Current.PublishedContentRequest; - var containerNode = pcr != null && pcr.HasPublishedContent ? pcr.PublishedContent : null; - - // Create the model based on our implementation of IPublishedContent - IPublishedContent content = new DetachedPublishedContent( - key, - nameObj == null ? null : nameObj.ToString(), - publishedContentType, - properties.ToArray(), - containerNode, - i, - preview); - - if (Current.PublishedContentModelFactory != null) - { - // Let the current model factory create a typed model to wrap our model - content = (IPublishedContent) Current.PublishedContentModelFactory.CreateModel(content); - } - - // Add the (typed) model as a result - processedValue.Add(content); - } - - if (propertyType.IsSingleNestedContentProperty()) - { - return processedValue.FirstOrDefault(); - } - - return processedValue; - } - } - - return null; - } - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs index b75a3aa39f..cdaada3d99 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs @@ -1,40 +1,71 @@ using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors.ValueConverters { - public class NestedContentSingleValueConverter : PropertyValueConverterBase + /// + /// + /// Provides an implementation for for nested content. + /// + public class NestedContentSingleValueConverter : NestedContentValueConverterBase { - private readonly ILogger _logger; + private readonly ProfilingLogger _proflog; - public NestedContentSingleValueConverter(ILogger logger) + /// + /// Initializes a new instance of the class. + /// + public NestedContentSingleValueConverter(IFacadeAccessor facadeAccessor, IFacadeService facadeService, IPublishedModelFactory publishedModelFactory, ProfilingLogger proflog) + : base(facadeAccessor, facadeService, publishedModelFactory) { - _logger = logger; + _proflog = proflog; } + /// public override bool IsConverter(PublishedPropertyType propertyType) - => propertyType.IsSingleNestedContentProperty(); + => IsNestedSingle(propertyType); + /// public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof(IPublishedContent); + { + var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); + var contentTypes = preValueCollection.PreValuesAsDictionary["contentTypes"].Value; + return contentTypes.Contains(",") + ? typeof(IPublishedElement) + : ModelType.For(contentTypes); + } + /// public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) => PropertyCacheLevel.Content; + /// public override object ConvertSourceToInter(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { - try - { - return propertyType.ConvertPropertyToNestedContent(source, preview); - } - catch (Exception e) - { - _logger.Error("Error converting value", e); - } + return source?.ToString(); + } - return null; + /// + public override object ConvertInterToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + using (_proflog.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataTypeId})")) + { + var value = (string) inter; + if (string.IsNullOrWhiteSpace(value)) return null; + + var objects = JsonConvert.DeserializeObject>(value); + if (objects.Count == 0) + return null; + if (objects.Count > 1) + throw new InvalidOperationException(); + + return ConvertToElement(objects[0], referenceCacheLevel, preview); + } } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs new file mode 100644 index 0000000000..aa248158c1 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + public abstract class NestedContentValueConverterBase : PropertyValueConverterBase + { + private readonly IFacadeAccessor _facadeAccessor; + private readonly IFacadeService _facadeService; + + protected NestedContentValueConverterBase(IFacadeAccessor facadeAccessor, IFacadeService facadeService, IPublishedModelFactory publishedModelFactory) + { + _facadeAccessor = facadeAccessor; + _facadeService = facadeService; + PublishedModelFactory = publishedModelFactory; + } + + protected IPublishedModelFactory PublishedModelFactory { get; } + + public static bool IsNested(PublishedPropertyType publishedProperty) + { + return publishedProperty.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.NestedContentAlias); + } + + public static bool IsNestedSingle(PublishedPropertyType publishedProperty) + { + if (!IsNested(publishedProperty)) + return false; + + var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(publishedProperty.DataTypeId); + var preValueDictionary = preValueCollection.PreValuesAsDictionary; + + return preValueDictionary.TryGetValue("minItems", out var minItems) + && preValueDictionary.TryGetValue("maxItems", out var maxItems) + && int.TryParse(minItems.Value, out var minItemsValue) && minItemsValue == 1 + && int.TryParse(maxItems.Value, out var maxItemsValue) && maxItemsValue == 1; + } + + public static bool IsNestedMany(PublishedPropertyType publishedProperty) + { + return IsNested(publishedProperty) && !IsNestedSingle(publishedProperty); + } + + protected IPublishedElement ConvertToElement(JObject sourceObject, PropertyCacheLevel referenceCacheLevel, bool preview) + { + var elementTypeAlias = NestedContentHelper.GetElementTypeAlias(sourceObject); + if (string.IsNullOrEmpty(elementTypeAlias)) + return null; + + var publishedContentType = _facadeAccessor.Facade.ContentCache.GetContentType(elementTypeAlias); + if (publishedContentType == null) + return null; + + var propertyValues = sourceObject.ToObject>(); + + if (!propertyValues.TryGetValue("key", out var keyo) + || !Guid.TryParse(keyo.ToString(), out var key)) + key = Guid.Empty; + + // fixme - why does it need a facade service here? + IPublishedElement element = new PublishedElement(publishedContentType, key, propertyValues, preview, _facadeService, referenceCacheLevel); + + element = PublishedModelFactory.CreateModel(element); + return element; + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/FacadeServiceBase.cs b/src/Umbraco.Web/PublishedCache/FacadeServiceBase.cs index 48b1363187..40163dd2a8 100644 --- a/src/Umbraco.Web/PublishedCache/FacadeServiceBase.cs +++ b/src/Umbraco.Web/PublishedCache/FacadeServiceBase.cs @@ -28,13 +28,13 @@ namespace Umbraco.Web.PublishedCache public virtual IPublishedElement CreateSet(PublishedContentType contentType, Guid key, Dictionary values, bool previewing, PropertyCacheLevel referenceCacheLevel) { var set = new PublishedElement(contentType, key, values, previewing, this, referenceCacheLevel); - var model = Current.PublishedContentModelFactory.CreateModel(set); + var model = Current.PublishedModelFactory.CreateModel(set); if (model == null) throw new Exception("Factory returned null."); return model; } - public abstract IPublishedProperty CreateSetProperty(PublishedPropertyType propertyType, IPublishedElement set, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null); + public abstract IPublishedProperty CreateElementProperty(PublishedPropertyType propertyType, IPublishedElement element, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null); public abstract string EnterPreview(IUser user, int contentId); public abstract void RefreshPreview(string previewToken, int contentId); diff --git a/src/Umbraco.Web/PublishedCache/IFacade.cs b/src/Umbraco.Web/PublishedCache/IFacade.cs index 9af439b58e..f425f3ebcf 100644 --- a/src/Umbraco.Web/PublishedCache/IFacade.cs +++ b/src/Umbraco.Web/PublishedCache/IFacade.cs @@ -1,6 +1,4 @@ using System; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PublishedCache { diff --git a/src/Umbraco.Web/PublishedCache/IFacadeService.cs b/src/Umbraco.Web/PublishedCache/IFacadeService.cs index a82574e9bc..0059d8d0c1 100644 --- a/src/Umbraco.Web/PublishedCache/IFacadeService.cs +++ b/src/Umbraco.Web/PublishedCache/IFacadeService.cs @@ -152,10 +152,10 @@ namespace Umbraco.Web.PublishedCache #endregion - #region Property Set + #region Published Element /// - /// Creates a property set. + /// Creates a published element. /// /// The content type. /// The set key. @@ -166,15 +166,15 @@ namespace Umbraco.Web.PublishedCache IPublishedElement CreateSet(PublishedContentType contentType, Guid key, Dictionary values, bool previewing, PropertyCacheLevel referenceCacheLevel); /// - /// Creates a set property. + /// Creates a published property for a published element. /// /// The property type. - /// The set. + /// The published element. /// A value indicating whether previewing. /// The reference cache level. /// The source value. /// A set property. - IPublishedProperty CreateSetProperty(PublishedPropertyType propertyType, IPublishedElement set, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null); + IPublishedProperty CreateElementProperty(PublishedPropertyType propertyType, IPublishedElement element, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null); #endregion } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/FacadeService.cs b/src/Umbraco.Web/PublishedCache/NuCache/FacadeService.cs index d8afd32968..0f49bed0e4 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/FacadeService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/FacadeService.cs @@ -1534,11 +1534,11 @@ AND cmsContentNu.nodeId IS NULL #endregion - #region Property Set + #region Published Element - public override IPublishedProperty CreateSetProperty(PublishedPropertyType propertyType, IPublishedElement set, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null) + public override IPublishedProperty CreateElementProperty(PublishedPropertyType propertyType, IPublishedElement element, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null) { - return new PublishedElementProperty(FacadeAccessor, propertyType, set, previewing, referenceCacheLevel, sourceValue); + return new PublishedElementProperty(FacadeAccessor, propertyType, element, previewing, referenceCacheLevel, sourceValue); } #endregion diff --git a/src/Umbraco.Web/PublishedCache/PublishedElement.cs b/src/Umbraco.Web/PublishedCache/PublishedElement.cs index d39513a7c7..ee15db317b 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedElement.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedElement.cs @@ -7,12 +7,12 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PublishedCache { // notes: - // a property set does NOT manage any tree-like elements, neither the + // a published element does NOT manage any tree-like elements, neither the // original NestedContent (from Lee) nor the DetachedPublishedContent POC did. // // at the moment we do NOT support models for sets - that would require // an entirely new models factory + not even sure it makes sense at all since - // sets are created manually + // sets are created manually fixme yes it does! // internal class PublishedElement : IPublishedElement { @@ -31,7 +31,7 @@ namespace Umbraco.Web.PublishedCache .Select(propertyType => { values.TryGetValue(propertyType.PropertyTypeAlias, out object value); - return facadeService.CreateSetProperty(propertyType, this, previewing, referenceCacheLevel, value); + return facadeService.CreateElementProperty(propertyType, this, previewing, referenceCacheLevel, value); }) .ToArray(); } diff --git a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs index a95d367ba4..b32939ab45 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.PublishedCache private readonly object _locko = new object(); private readonly object _sourceValue; - protected readonly IPublishedElement Set; + protected readonly IPublishedElement Set; // fixme rename protected readonly bool IsPreviewing; protected readonly bool IsMember; diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index cfc89a1fbd..8f7bed8f23 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -25,9 +25,9 @@ namespace Umbraco.Web.PublishedCache _membershipUser = member; _publishedMemberType = publishedMemberType ?? throw new ArgumentNullException(nameof(publishedMemberType)); - _properties = PublishedProperty.MapProperties(_publishedMemberType.PropertyTypes, _member.Properties, - (t, v) => new RawValueProperty(t, this, v ?? string.Empty)) - .ToArray(); + var properties = PublishedProperty.MapProperties(_publishedMemberType.PropertyTypes, _member.Properties, + (t, v) => new RawValueProperty(t, this, v ?? string.Empty)); + _properties = WithMemberProperties(properties).ToArray(); } #region Membership provider member properties @@ -79,52 +79,51 @@ namespace Umbraco.Web.PublishedCache public override IPublishedProperty GetProperty(string alias) { - switch (alias.ToLowerInvariant()) - { - case "Email": - return new PropertyResult("Email", Email, PropertyResultType.CustomProperty); - case "UserName": - return new PropertyResult("UserName", UserName, PropertyResultType.CustomProperty); - case "PasswordQuestion": - return new PropertyResult("PasswordQuestion", PasswordQuestion, PropertyResultType.CustomProperty); - case "Comments": - return new PropertyResult("Comments", Email, PropertyResultType.CustomProperty); - case "IsApproved": - return new PropertyResult("IsApproved", IsApproved, PropertyResultType.CustomProperty); - case "IsLockedOut": - return new PropertyResult("IsLockedOut", IsLockedOut, PropertyResultType.CustomProperty); - case "LastLockoutDate": - return new PropertyResult("LastLockoutDate", LastLockoutDate, PropertyResultType.CustomProperty); - case "CreateDate": - return new PropertyResult("CreateDate", CreateDate, PropertyResultType.CustomProperty); - case "LastLoginDate": - return new PropertyResult("LastLoginDate", LastLoginDate, PropertyResultType.CustomProperty); - case "LastPasswordChangeDate": - return new PropertyResult("LastPasswordChangeDate", LastPasswordChangeDate, PropertyResultType.CustomProperty); - } - return _properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); } + private IEnumerable WithMemberProperties(IEnumerable properties) + { + var propertiesList = properties.ToList(); + var aliases = propertiesList.Select(x => x.PropertyTypeAlias).ToList(); + + if (!aliases.Contains("Email")) + propertiesList.Add(new RawValueProperty(ContentType.GetPropertyType("Email"), this, Email)); + if (!aliases.Contains("UserName")) + propertiesList.Add(new RawValueProperty(ContentType.GetPropertyType("UserName"), this, UserName)); + if (!aliases.Contains("PasswordQuestion")) + propertiesList.Add(new RawValueProperty(ContentType.GetPropertyType("PasswordQuestion"), this, PasswordQuestion)); + if (!aliases.Contains("Comments")) + propertiesList.Add(new RawValueProperty(ContentType.GetPropertyType("Comments"), this, Comments)); + if (!aliases.Contains("IsApproved")) + propertiesList.Add(new RawValueProperty(ContentType.GetPropertyType("IsApproved"), this, IsApproved)); + if (!aliases.Contains("IsLockedOut")) + propertiesList.Add(new RawValueProperty(ContentType.GetPropertyType("IsLockedOut"), this, IsLockedOut)); + if (!aliases.Contains("LastLockoutDate")) + propertiesList.Add(new RawValueProperty(ContentType.GetPropertyType("LastLockoutDate"), this, LastLockoutDate)); + if (!aliases.Contains("CreateDate")) + propertiesList.Add(new RawValueProperty(ContentType.GetPropertyType("CreateDate"), this, CreateDate)); + if (!aliases.Contains("LastLoginDate")) + propertiesList.Add(new RawValueProperty(ContentType.GetPropertyType("LastLoginDate"), this, LastLoginDate)); + if (!aliases.Contains("LastPasswordChangeDate")) + propertiesList.Add(new RawValueProperty(ContentType.GetPropertyType("LastPasswordChangeDate"), this, LastPasswordChangeDate)); + + return propertiesList; + } + public override PublishedContentType ContentType => _publishedMemberType; public override int Id => _member.Id; public override Guid Key => _member.Key; - public override int TemplateId - { - get { throw new NotSupportedException(); } - } + public override int TemplateId => throw new NotSupportedException(); public override int SortOrder => 0; public override string Name => _member.Name; - public override string UrlName - { - get { throw new NotSupportedException(); } - } + public override string UrlName => throw new NotSupportedException(); public override string DocumentTypeAlias => _member.ContentTypeAlias; diff --git a/src/Umbraco.Web/PublishedCache/RawValueProperty.cs b/src/Umbraco.Web/PublishedCache/RawValueProperty.cs deleted file mode 100644 index c12d41faf9..0000000000 --- a/src/Umbraco.Web/PublishedCache/RawValueProperty.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PublishedCache -{ - /// - /// A published property base that uses a raw object value. - /// - /// Conversions results are stored within the property and will not - /// be refreshed, so this class is not suitable for cached properties. - internal class RawValueProperty : PublishedPropertyBase - { - private readonly object _dbVal; //the value in the db - private readonly Lazy _objectValue; - private readonly Lazy _xpathValue; - - public override object SourceValue => _dbVal; - - public override bool HasValue => _dbVal != null && _dbVal.ToString().Trim().Length > 0; - - public override object Value => _objectValue.Value; - - public override object XPathValue => _xpathValue.Value; - - // note: propertyData cannot be null - public RawValueProperty(PublishedPropertyType propertyType, IPublishedContent content, object propertyData, bool isPreviewing = false) - : this(propertyType, content, isPreviewing) - { - _dbVal = propertyData ?? throw new ArgumentNullException(nameof(propertyData)); - } - - // note: maintaining two ctors to make sure we understand what we do when calling them - public RawValueProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing = false) - : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored - { - _dbVal = null; - var isPreviewing1 = isPreviewing; - - var sourceValue = new Lazy(() => PropertyType.ConvertSourceToInter(content, _dbVal, isPreviewing1)); - _objectValue = new Lazy(() => PropertyType.ConvertInterToObject(content, PropertyCacheLevel.Unknown, sourceValue.Value, isPreviewing1)); - _xpathValue = new Lazy(() => PropertyType.ConvertInterToXPath(content, PropertyCacheLevel.Unknown, sourceValue.Value, isPreviewing1)); - } - } -} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/FacadeService.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/FacadeService.cs index 26747d6ef2..82f3632317 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/FacadeService.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/FacadeService.cs @@ -240,7 +240,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache #region Property Set - public override IPublishedProperty CreateSetProperty(PublishedPropertyType propertyType, IPublishedElement set, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null) + public override IPublishedProperty CreateElementProperty(PublishedPropertyType propertyType, IPublishedElement element, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null) { // fixme - ouch? throw new NotImplementedException(); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 880be1d971..8e11ef5487 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -695,9 +695,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { if (i.Key.InvariantStartsWith("__")) { - // no type for that one, dunno how to convert - IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty); - _properties.Add(property); + // no type for that one, dunno how to convert, drop it + //IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty); + //_properties.Add(property); } else { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 4467495397..c8f4c9ee34 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -219,8 +219,6 @@ - - @@ -250,7 +248,7 @@ - + @@ -594,7 +592,6 @@ -