/// <summary>Returns the processed value for the given type and property.</summary>
        /// <param name="content">The <see cref="IPublishedContent" /> to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo" /></param>
        /// <param name="config">The Ditto configuration for the type.</param>
        /// <param name="mappableProperty">Information about the mappable property.</param>
        /// <param name="instance">The instance to assign the value to.</param>
        /// <param name="chainContext">The <see cref="DittoChainContext"/> for the current processor chain.</param>
        /// <returns>The <see cref="object" /> representing the Umbraco value.</returns>
        private static object GetProcessedValue(
            IPublishedContent content,
            CultureInfo culture,
            DittoTypeInfo config,
            DittoTypeInfo.DittoTypePropertyInfo mappableProperty,
            object instance,
            DittoChainContext chainContext)
        {
            using (DittoDisposableTimer.DebugDuration(typeof(Ditto), $"Processing '{mappableProperty.PropertyInfo.Name}' ({content.Id})"))
            {
                // Create a base processor context for this current chain level
                var baseProcessorContext = new DittoProcessorContext
                {
                    Content      = content,
                    TargetType   = config.TargetType,
                    PropertyInfo = mappableProperty.PropertyInfo,
                    Culture      = culture
                };

                // Check for cache attribute
                if (mappableProperty.IsCacheable)
                {
                    var ctx = new DittoCacheContext(mappableProperty.CacheInfo, content, config.TargetType, mappableProperty.PropertyInfo, culture);
                    return(mappableProperty.CacheInfo.GetCacheItem(ctx, () => DoGetProcessedValue(content, mappableProperty, baseProcessorContext, chainContext)));
                }
                else
                {
                    return(DoGetProcessedValue(content, mappableProperty, baseProcessorContext, chainContext));
                }
            }
        }
Example #2
0
        /// <summary>
        /// Returns the processed value for the given type and property.
        /// </summary>
        /// <param name="content">The <see cref="IPublishedContent" /> to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo" /></param>
        /// <param name="targetType">The target type.</param>
        /// <param name="propertyInfo">The <see cref="PropertyInfo" /> property info associated with the type.</param>
        /// <param name="instance">The instance to assign the value to.</param>
        /// <param name="defaultProcessorType">The default processor type.</param>
        /// <param name="processorContexts">A collection of <see cref="DittoProcessorContext" /> entities to use whilst processing values.</param>
        /// <returns>
        /// The <see cref="object" /> representing the Umbraco value.
        /// </returns>
        private static object GetProcessedValue(
            IPublishedContent content,
            CultureInfo culture,
            Type targetType,
            PropertyInfo propertyInfo,
            object instance,
            Type defaultProcessorType,
            IEnumerable <DittoProcessorContext> processorContexts = null)
        {
            // Time custom value-processor.
            using (DittoDisposableTimer.DebugDuration <object>(string.Format("Custom ValueProcessor ({0}, {1})", content.Id, propertyInfo.Name)))
            {
                // Get the target property description
                var propertyDescriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name];

                // Check for cache attribute
                var cacheAttr = propertyInfo.GetCustomAttribute <DittoCacheAttribute>(true);
                if (cacheAttr != null)
                {
                    var ctx = new DittoCacheContext(cacheAttr, content, targetType, propertyDescriptor, culture);
                    return(cacheAttr.GetCacheItem(ctx, () => DoGetProcessedValue(content, culture, targetType, propertyInfo, propertyDescriptor, defaultProcessorType, processorContexts)));
                }
                else
                {
                    return(DoGetProcessedValue(content, culture, targetType, propertyInfo, propertyDescriptor, defaultProcessorType, processorContexts));
                }
            }
        }
        /// <summary>
        /// Returns an object representing the given <see cref="Type"/>.
        /// </summary>
        /// <param name="content">
        /// The <see cref="IPublishedContent"/> to convert.
        /// </param>
        /// <param name="type">
        /// The <see cref="Type"/> of items to return.
        /// </param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/>
        /// </param>
        /// <param name="instance">
        /// An existing instance of T to populate
        /// </param>
        /// <param name="processorContexts">
        /// A collection of <see cref="DittoProcessorContext"/> entities to use whilst processing values.
        /// </param>
        /// <param name="onConverting">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.
        /// </param>
        /// <param name="onConverted">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.
        /// </param>
        /// <param name="chainContext">
        /// The <see cref="DittoChainContext"/> for the current processor chain.
        /// </param>
        /// <returns>
        /// The converted <see cref="Object"/> as the given type.
        /// </returns>
        public static object As(
            this IPublishedContent content,
            Type type,
            CultureInfo culture = null,
            object instance     = null,
            IEnumerable <DittoProcessorContext> processorContexts = null,
            Action <DittoConversionHandlerContext> onConverting   = null,
            Action <DittoConversionHandlerContext> onConverted    = null,
            DittoChainContext chainContext = null)
        {
            // Ensure content
            if (content == null)
            {
                return(null);
            }

            // Ensure instance is of target type
            if (instance != null && !type.IsInstanceOfType(instance))
            {
                throw new ArgumentException($"The instance parameter does not implement Type '{type.Name}'", nameof(instance));
            }

            // Get the context accessor (for access to ApplicationContext, UmbracoContext, et al)
            var contextAccessor = (IDittoContextAccessor)Ditto.GetContextAccessorType().GetInstance();

            // Check if the culture has been set, otherwise use from Umbraco, or fallback to a default
            if (culture == null)
            {
                culture = contextAccessor.UmbracoContext?.PublishedContentRequest != null
                    ? contextAccessor.UmbracoContext.PublishedContentRequest.Culture
                    : CultureInfo.CurrentCulture;
            }

            // Ensure a chain context
            if (chainContext == null)
            {
                chainContext = new DittoChainContext();
            }

            // Populate prcessor contexts collection with any passed in contexts
            chainContext.ProcessorContexts.AddRange(processorContexts);

            // Convert
            using (DittoDisposableTimer.DebugDuration <Ditto>($"As<{type.Name}>({content.DocumentTypeAlias} {content.Id})"))
            {
                var cacheAttr = type.GetCustomAttribute <DittoCacheAttribute>(true);
                if (cacheAttr != null)
                {
                    var ctx = new DittoCacheContext(cacheAttr, content, type, culture);
                    return(cacheAttr.GetCacheItem(ctx, () => ConvertContent(content, type, contextAccessor, culture, instance, onConverting, onConverted, chainContext)));
                }
                else
                {
                    return(ConvertContent(content, type, contextAccessor, culture, instance, onConverting, onConverted, chainContext));
                }
            }
        }
        /// <summary>Returns an object representing the given <see cref="Type"/>.</summary>
        /// <param name="content">The <see cref="IPublishedContent"/> to convert.</param>
        /// <param name="type">The <see cref="Type"/> of items to return.</param>
        /// <param name="culture">The <see cref="CultureInfo"/></param>
        /// <param name="instance">An existing instance of T to populate</param>
        /// <param name="processorContexts">A collection of <see cref="DittoProcessorContext"/> entities to use whilst processing values.</param>
        /// <param name="onConverting">The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.</param>
        /// <param name="onConverted">The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.</param>
        /// <param name="chainContext">The <see cref="DittoChainContext"/> for the current processor chain.</param>
        /// <returns>The converted <see cref="Object"/> as the given type.</returns>
        public static object As(
            this IPublishedContent content,
            Type type,
            CultureInfo culture = null,
            object instance     = null,
            IEnumerable <DittoProcessorContext> processorContexts = null,
            Action <DittoConversionHandlerContext> onConverting   = null,
            Action <DittoConversionHandlerContext> onConverted    = null,
            DittoChainContext chainContext = null)
        {
            // Ensure content
            if (content == null)
            {
                return(null);
            }

            // Ensure instance is of target type
            if (instance != null && type.IsInstanceOfType(instance) == false)
            {
                throw new ArgumentException($"The instance parameter does not implement Type '{type.Name}'", nameof(instance));
            }

            // Check if the culture has been set, otherwise use from Umbraco, or fallback to a default
            if (culture == null)
            {
                culture = ContextAccessor?.UmbracoContext?.PublishedContentRequest?.Culture ?? CultureInfo.CurrentCulture;
            }

            // Ensure a chain context
            if (chainContext == null)
            {
                chainContext = new DittoChainContext();
            }

            // Populate prcessor contexts collection with any passed in contexts
            chainContext.ProcessorContexts.AddRange(processorContexts);

            // Convert
            using (DittoDisposableTimer.DebugDuration(typeof(Ditto), $"As<{type.Name}>({content.DocumentTypeAlias} {content.Id})"))
            {
                var config = DittoTypeInfoCache.GetOrAdd(type);

                if (config.IsCacheable)
                {
                    var ctx = new DittoCacheContext(config.CacheInfo, content, type, culture);
                    return(config.CacheInfo.GetCacheItem(ctx, () => ConvertContent(content, config, culture, instance, onConverting, onConverted, chainContext)));
                }
                else
                {
                    return(ConvertContent(content, config, culture, instance, onConverting, onConverted, chainContext));
                }
            }
        }
Example #5
0
        /// <summary>
        /// Returns an object representing the given <see cref="Type"/>.
        /// </summary>
        /// <param name="content">
        /// The <see cref="IPublishedContent"/> to convert.
        /// </param>
        /// <param name="type">
        /// The <see cref="Type"/> of items to return.
        /// </param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/>
        /// </param>
        /// <param name="instance">
        /// An existing instance of T to populate
        /// </param>
        /// <param name="processorContexts">
        /// A collection of <see cref="DittoProcessorContext"/> entities to use whilst processing values.
        /// </param>
        /// <param name="onConverting">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.
        /// </param>
        /// <param name="onConverted">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.
        /// </param>
        /// <returns>
        /// The converted <see cref="Object"/> as the given type.
        /// </returns>
        public static object As(
            this IPublishedContent content,
            Type type,
            CultureInfo culture = null,
            object instance     = null,
            IEnumerable <DittoProcessorContext> processorContexts = null,
            Action <DittoConversionHandlerContext> onConverting   = null,
            Action <DittoConversionHandlerContext> onConverted    = null)
        {
            // Ensure content
            if (content == null)
            {
                return(null);
            }

            // Ensure instance is of target type
            if (instance != null && !type.IsInstanceOfType(instance))
            {
                throw new ArgumentException(string.Format("The instance parameter does not implement Type '{0}'", type.Name), "instance");
            }

            // Check if the culture has been set, otherwise use from Umbraco, or fallback to a default
            if (culture == null)
            {
                if (UmbracoContext.Current != null && UmbracoContext.Current.PublishedContentRequest != null)
                {
                    culture = UmbracoContext.Current.PublishedContentRequest.Culture;
                }
                else
                {
                    // Fallback
                    culture = CultureInfo.CurrentCulture;
                }
            }

            // Convert
            using (DittoDisposableTimer.DebugDuration <object>(string.Format("IPublishedContent As ({0})", content.DocumentTypeAlias)))
            {
                var cacheAttr = type.GetCustomAttribute <DittoCacheAttribute>(true);
                if (cacheAttr != null)
                {
                    var ctx = new DittoCacheContext(cacheAttr, content, type, culture);
                    return(cacheAttr.GetCacheItem(ctx, () => ConvertContent(content, type, culture, instance, processorContexts, onConverting, onConverted)));
                }
                else
                {
                    return(ConvertContent(content, type, culture, instance, processorContexts, onConverting, onConverted));
                }
            }
        }
Example #6
0
        /// <summary>
        /// Gets a collection of the given type from the given <see cref="IEnumerable{IPublishedContent}"/>.
        /// </summary>
        /// <param name="items">
        /// The <see cref="IEnumerable{IPublishedContent}"/> to convert.
        /// </param>
        /// <param name="type">
        /// The <see cref="Type"/> of items to return.
        /// </param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/>.
        /// </param>
        /// <param name="valueResolverContexts">
        /// A collection of <see cref="DittoValueResolverContext"/> entities to use whilst resolving values.
        /// </param>
        /// <param name="onConverting">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.
        /// </param>
        /// <param name="onConverted">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.
        /// </param>
        /// <returns>
        /// The resolved <see cref="IEnumerable{T}"/>.
        /// </returns>
        public static IEnumerable <object> As(
            this IEnumerable <IPublishedContent> items,
            Type type,
            CultureInfo culture = null,
            IEnumerable <DittoValueResolverContext> valueResolverContexts = null,
            Action <DittoConversionHandlerContext> onConverting           = null,
            Action <DittoConversionHandlerContext> onConverted            = null)
        {
            using (DittoDisposableTimer.DebugDuration <IEnumerable <object> >("IEnumerable As"))
            {
                var typedItems = items.Select(x => x.As(type, culture, null, valueResolverContexts, onConverting, onConverted));

                // We need to cast back here as nothing is strong typed anymore.
                return((IEnumerable <object>)EnumerableInvocations.Cast(type, typedItems));
            }
        }
        /// <summary>
        /// Gets a collection of the given type from the given <see cref="IEnumerable{IPublishedContent}"/>.
        /// </summary>
        /// <param name="items">
        /// The <see cref="IEnumerable{IPublishedContent}"/> to convert.
        /// </param>
        /// <param name="type">
        /// The <see cref="Type"/> of items to return.
        /// </param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/>.
        /// </param>
        /// <param name="processorContexts">
        /// A collection of <see cref="DittoProcessorContext"/> entities to use whilst processing values.
        /// </param>
        /// <param name="onConverting">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.
        /// </param>
        /// <param name="onConverted">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.
        /// </param>
        /// <param name="chainContext">
        /// The <see cref="DittoChainContext"/> for the current processor chain.
        /// </param>
        /// <returns>
        /// The converted <see cref="IEnumerable{T}"/>.
        /// </returns>
        public static IEnumerable <object> As(
            this IEnumerable <IPublishedContent> items,
            Type type,
            CultureInfo culture = null,
            IEnumerable <DittoProcessorContext> processorContexts = null,
            Action <DittoConversionHandlerContext> onConverting   = null,
            Action <DittoConversionHandlerContext> onConverted    = null,
            DittoChainContext chainContext = null)
        {
            using (DittoDisposableTimer.DebugDuration <Ditto>($"IEnumerable As<{type.Name}>"))
            {
                var typedItems = items.Select(x => x.As(type, culture, null, processorContexts, onConverting, onConverted, chainContext));

                // We need to cast back here as nothing is strong typed anymore.
                return((IEnumerable <object>)EnumerableInvocations.Cast(type, typedItems));
            }
        }
        /// <summary>Returns the processed value for the given type and property.</summary>
        /// <param name="content">The content.</param>
        /// <param name="mappableProperty">Information about the mappable property.</param>
        /// <param name="baseProcessorContext">The base processor context.</param>
        /// <param name="chainContext">The <see cref="DittoChainContext"/> for the current processor chain.</param>
        /// <returns>Returns the processed value.</returns>
        private static object DoGetProcessedValue(
            IPublishedContent content,
            DittoTypeInfo.DittoTypePropertyInfo mappableProperty,
            DittoProcessorContext baseProcessorContext,
            DittoChainContext chainContext)
        {
            // Create holder for value as it's processed
            object currentValue = content;

            // Process attributes
            foreach (var processorAttr in mappableProperty.Processors)
            {
                using (DittoDisposableTimer.DebugDuration(typeof(Ditto), $"Processor '{processorAttr}' ({content.Id})"))
                {
                    // Get the right context type
                    var ctx = chainContext.ProcessorContexts.GetOrCreate(baseProcessorContext, processorAttr.ContextType);

                    // Populate UmbracoContext & ApplicationContext
                    processorAttr.UmbracoContext     = ContextAccessor.UmbracoContext;
                    processorAttr.ApplicationContext = ContextAccessor.ApplicationContext;

                    // Process value
                    currentValue = processorAttr.ProcessValue(currentValue, ctx, chainContext);
                }
            }

            // The following has to happen after all the processors.
            if (mappableProperty.IsEnumerable && currentValue != null && currentValue.Equals(Enumerable.Empty <object>()))
            {
                if (mappableProperty.PropertyInfo.PropertyType.IsInterface)
                {
                    // You cannot set an enumerable of type from an empty object array.
                    currentValue = EnumerableInvocations.Cast(mappableProperty.EnumerableType, (IEnumerable)currentValue);
                }
                else
                {
                    // This should allow the casting back of IEnumerable<T> to an empty List<T> Collection<T> etc.
                    // I cant think of any that don't have an empty constructor
                    currentValue = mappableProperty.PropertyInfo.PropertyType.GetInstance();
                }
            }

            return(currentValue == null && mappableProperty.PropertyInfo.PropertyType.IsValueType
                ? mappableProperty.PropertyInfo.PropertyType.GetInstance() // Set to default instance of value type
                : currentValue);
        }
        /// <summary>
        /// Returns the given instance of <see cref="RenderModel"/> as the specified type.
        /// </summary>
        /// <param name="model">
        /// The <see cref="RenderModel"/> to convert.
        /// </param>
        /// <param name="valueResolverContexts">
        /// A collection of <see cref="DittoValueResolverContext"/> entities to use whilst resolving values.
        /// </param>
        /// <param name="onConverting">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.
        /// </param>
        /// <param name="onConverted">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.
        /// </param>
        /// <typeparam name="T">
        /// The <see cref="Type"/> of items to return.
        /// </typeparam>
        /// <returns>
        /// The resolved generic <see cref="Type"/>.
        /// </returns>
        public static T As <T>(
            this RenderModel model,
            IEnumerable <DittoValueResolverContext> valueResolverContexts = null,
            Action <DittoConversionHandlerContext> onConverting           = null,
            Action <DittoConversionHandlerContext> onConverted            = null)
            where T : class
        {
            if (model == null)
            {
                return(default(T));
            }

            using (DittoDisposableTimer.DebugDuration <T>(string.Format("RenderModel As ({0})", model.Content.DocumentTypeAlias)))
            {
                return(model.Content.As <T>(model.CurrentCulture, null, valueResolverContexts, onConverting, onConverted));
            }
        }
Example #10
0
        /// <summary>Returns the given instance of <see cref="RenderModel"/> as the specified type.</summary>
        /// <param name="model">The <see cref="RenderModel"/> to convert.</param>
        /// <param name="processorContexts">A collection of <see cref="DittoProcessorContext"/> entities to use whilst processing values.</param>
        /// <param name="onConverting">The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.</param>
        /// <param name="onConverted">The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.</param>
        /// <typeparam name="T">The <see cref="Type"/> of items to return.</typeparam>
        /// <returns>The resolved generic <see cref="Type"/>.</returns>
        public static T As <T>(
            this RenderModel model,
            IEnumerable <DittoProcessorContext> processorContexts = null,
            Action <DittoConversionHandlerContext> onConverting   = null,
            Action <DittoConversionHandlerContext> onConverted    = null)
            where T : class
        {
            if (model == null || model.Content == null)
            {
                return(default(T));
            }

            using (DittoDisposableTimer.DebugDuration <T>($"RenderModel As ({model.Content.DocumentTypeAlias})"))
            {
                return(model.Content.As <T>(model.CurrentCulture, null, processorContexts, onConverting, onConverted));
            }
        }
        /// <summary>
        /// Processes the value.
        /// </summary>
        /// <returns>
        /// The <see cref="object" /> representing the processed value.
        /// </returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public override object ProcessValue()
        {
            var result = Value;

            if (Value != null && !Context.PropertyDescriptor.PropertyType.IsInstanceOfType(Value))
            {
                // TODO: Maybe support enumerables?
                using (DittoDisposableTimer.DebugDuration <TryConvertToAttribute>(string.Format("TryConvertTo ({0}, {1})", Context.Content.Id, Context.PropertyDescriptor.Name)))
                {
                    var convert = Value.TryConvertTo(Context.PropertyDescriptor.PropertyType);
                    if (convert.Success)
                    {
                        result = convert.Result;
                    }
                }
            }

            return(result);
        }
Example #12
0
        /// <summary>
        /// Processes the value.
        /// </summary>
        /// <returns>
        /// The <see cref="object" /> representing the processed value.
        /// </returns>
        public override object ProcessValue()
        {
            var result = this.Value;

            if (this.Value != null && this.Context.PropertyInfo.PropertyType.IsInstanceOfType(this.Value) == false)
            {
                // TODO: Maybe support enumerables?
                using (DittoDisposableTimer.DebugDuration <TryConvertToAttribute>($"TryConvertTo '{this.Context.PropertyInfo.Name}' ({this.Context.Content.Id})"))
                {
                    var convert = this.Value.TryConvertTo(this.Context.PropertyInfo.PropertyType);
                    if (convert.Success)
                    {
                        result = convert.Result;
                    }
                }
            }

            return(result);
        }
Example #13
0
        /// <summary>
        /// Processes the value.
        /// </summary>
        /// <returns>
        /// The <see cref="object" /> representing the processed value.
        /// </returns>
        public override object ProcessValue()
        {
            foreach (var processorAttr in this.Attributes)
            {
                using (DittoDisposableTimer.DebugDuration <DittoMultiProcessorAttribute>($"Processor '{processorAttr.GetType().Name}' ({this.Context.Content.Id})"))
                {
                    // Get the right context type
                    var newCtx = this.ChainContext.ProcessorContexts.GetOrCreate(this.Context, processorAttr.ContextType);

                    // Populate UmbracoContext & ApplicationContext
                    processorAttr.UmbracoContext     = this.UmbracoContext;
                    processorAttr.ApplicationContext = this.ApplicationContext;

                    // Process value
                    this.Value = processorAttr.ProcessValue(this.Value, newCtx, this.ChainContext);
                }
            }

            return(this.Value);
        }
Example #14
0
        /// <summary>
        /// Returns an object representing the given <see cref="Type"/>.
        /// </summary>
        /// <param name="content">
        /// The <see cref="IPublishedContent"/> to convert.
        /// </param>
        /// <param name="type">
        /// The <see cref="Type"/> of items to return.
        /// </param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/>
        /// </param>
        /// <param name="instance">
        /// An existing instance of T to populate
        /// </param>
        /// <param name="valueResolverContexts">
        /// A collection of <see cref="DittoValueResolverContext"/> entities to use whilst resolving values.
        /// </param>
        /// <param name="onConverting">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.
        /// </param>
        /// <param name="onConverted">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.
        /// </param>
        /// <returns>
        /// The converted <see cref="Object"/> as the given type.
        /// </returns>
        public static object As(
            this IPublishedContent content,
            Type type,
            CultureInfo culture = null,
            object instance     = null,
            IEnumerable <DittoValueResolverContext> valueResolverContexts = null,
            Action <DittoConversionHandlerContext> onConverting           = null,
            Action <DittoConversionHandlerContext> onConverted            = null)
        {
            if (content == null)
            {
                return(null);
            }

            if (instance != null && !type.IsInstanceOfType(instance))
            {
                throw new ArgumentException(string.Format("The instance parameter does not implement Type '{0}'", type.Name), "instance");
            }

            using (DittoDisposableTimer.DebugDuration <object>(string.Format("IPublishedContent As ({0})", content.DocumentTypeAlias)))
            {
                return(ConvertContent(content, type, culture, instance, valueResolverContexts, onConverting, onConverted));
            }
        }
        /// <summary>
        /// Returns the processed value for the given type and property.
        /// </summary>
        /// <param name="content">The <see cref="IPublishedContent" /> to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo" /></param>
        /// <param name="targetType">The target type.</param>
        /// <param name="propertyInfo">The <see cref="PropertyInfo" /> property info associated with the type.</param>
        /// <param name="instance">The instance to assign the value to.</param>
        /// <param name="contextAccessor">The context accessor.</param>
        /// <param name="chainContext">The <see cref="DittoChainContext"/> for the current processor chain.</param>
        /// <returns>
        /// The <see cref="object" /> representing the Umbraco value.
        /// </returns>
        private static object GetProcessedValue(
            IPublishedContent content,
            CultureInfo culture,
            Type targetType,
            PropertyInfo propertyInfo,
            object instance,
            IDittoContextAccessor contextAccessor,
            DittoChainContext chainContext)
        {
            using (DittoDisposableTimer.DebugDuration <Ditto>($"Processing '{propertyInfo.Name}' ({content.Id})"))
            {
                // Get the target property description
                var propertyDescriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name];

                // Create a base processor context for this current chain level
                var baseProcessorContext = new DittoProcessorContext
                {
                    Content            = content,
                    TargetType         = targetType,
                    PropertyDescriptor = propertyDescriptor,
                    Culture            = culture
                };

                // Check for cache attribute
                var cacheAttr = propertyInfo.GetCustomAttribute <DittoCacheAttribute>(true);
                if (cacheAttr != null)
                {
                    var ctx = new DittoCacheContext(cacheAttr, content, targetType, propertyDescriptor, culture);
                    return(cacheAttr.GetCacheItem(ctx, () => DoGetProcessedValue(content, propertyInfo, contextAccessor, baseProcessorContext, chainContext)));
                }
                else
                {
                    return(DoGetProcessedValue(content, propertyInfo, contextAccessor, baseProcessorContext, chainContext));
                }
            }
        }
        /// <summary>
        /// Returns an object representing the given <see cref="Type"/>.
        /// </summary>
        /// <param name="content">
        /// The <see cref="IPublishedContent"/> to convert.
        /// </param>
        /// <param name="type">
        /// The <see cref="Type"/> of items to return.
        /// </param>
        /// <param name="contextAccessor">
        /// The context accessor.
        /// </param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/>
        /// </param>
        /// <param name="instance">
        /// An existing instance of T to populate
        /// </param>
        /// <param name="onConverting">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.
        /// </param>
        /// <param name="onConverted">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.
        /// </param>
        /// <param name="chainContext">
        /// The <see cref="DittoChainContext"/> for the current processor chain.
        /// </param>
        /// <returns>
        /// The converted <see cref="Object"/> as the given type.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// Thrown if the given type has invalid constructors.
        /// </exception>
        private static object ConvertContent(
            IPublishedContent content,
            Type type,
            IDittoContextAccessor contextAccessor,
            CultureInfo culture,
            object instance,
            Action <DittoConversionHandlerContext> onConverting,
            Action <DittoConversionHandlerContext> onConverted,
            DittoChainContext chainContext)
        {
            // Collect all the properties of the given type and loop through writable ones.
            PropertyInfo[] properties;
            PropertyCache.TryGetValue(type, out properties);

            if (properties == null)
            {
                properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                             .Where(x => x.CanWrite && x.GetSetMethod() != null).ToArray();

                PropertyCache.TryAdd(type, properties);
            }

            // Check the validity of the mpped type constructor as early as possible.
            ParameterInfo[] constructorParams = type.GetConstructorParameters();
            bool            validConstructor  = false;
            bool            hasParameter      = false;
            bool            isType            = false;
            bool            hasLazy           = false;

            if (constructorParams != null)
            {
                // Is it PublishedContentmModel or similar?
                if (constructorParams.Length == 1 && constructorParams[0].ParameterType == typeof(IPublishedContent))
                {
                    hasParameter = true;
                }

                if (constructorParams.Length == 0 || hasParameter)
                {
                    validConstructor = true;
                }
            }

            // No valid constructor, but see if the value can be cast to the type
            if (type.IsInstanceOfType(content))
            {
                isType           = true;
                validConstructor = true;
            }

            if (!validConstructor)
            {
                throw new InvalidOperationException(
                          $"Cannot convert IPublishedContent to {type} as it has no valid constructor. " +
                          "A valid constructor is either an empty one, or one accepting a single IPublishedContent parameter.");
            }

            PropertyInfo[] lazyProperties = null;

            // If not already an instance, create an instance of the object
            if (instance == null)
            {
                // We can only proxy new instances.
                lazyProperties = properties.Where(x => x.ShouldAttemptLazyLoad()).ToArray();

                if (lazyProperties.Any())
                {
                    hasLazy = true;

                    var factory = new ProxyFactory();
                    instance = hasParameter
                        ? factory.CreateProxy(type, lazyProperties.Select(x => x.Name), content)
                        : factory.CreateProxy(type, lazyProperties.Select(x => x.Name));
                }
                else if (isType)
                {
                    instance = content;
                }
                else
                {
                    // 1: This extension method is about 7x faster than the native implementation.
                    // 2: Internally this uses Activator.CreateInstance which is heavily optimized.
                    instance = hasParameter
                        ? type.GetInstance(content) // 1
                        : type.GetInstance();       // 2
                }
            }

            // We have the instance object but haven't yet populated properties
            // so fire the on converting event handlers
            OnConverting(content, type, culture, instance, onConverting);

            if (hasLazy)
            {
                // A dictionary to store lazily invoked values.
                var lazyMappings = new Dictionary <string, Lazy <object> >();
                foreach (var propertyInfo in lazyProperties)
                {
                    // Configure lazy properties
                    using (DittoDisposableTimer.DebugDuration <Ditto>($"Lazy Property ({content.Id} {propertyInfo.Name})"))
                    {
                        // Ensure it's a virtual property (Only relevant to property level lazy loads)
                        if (!propertyInfo.IsVirtualAndOverridable())
                        {
                            throw new InvalidOperationException($"Lazy property '{propertyInfo.Name}' of type '{type.AssemblyQualifiedName}' must be declared virtual in order to be lazy loadable.");
                        }

                        lazyMappings.Add(propertyInfo.Name, new Lazy <object>(() => GetProcessedValue(content, culture, type, propertyInfo, instance, contextAccessor, chainContext)));
                    }
                }

                ((IProxy)instance).Interceptor = new LazyInterceptor(lazyMappings);
            }

            // Process any non lazy properties
            foreach (var propertyInfo in properties.Where(x => !x.ShouldAttemptLazyLoad()))
            {
                // Check for the ignore attribute.
                if (propertyInfo.HasCustomAttribute <DittoIgnoreAttribute>())
                {
                    continue;
                }

                // Set the value normally.
                var value = GetProcessedValue(content, culture, type, propertyInfo, instance, contextAccessor, chainContext);

                // This over 4x faster as propertyInfo.SetValue(instance, value, null);
                FastPropertyAccessor.SetValue(propertyInfo, instance, value);
            }

            // We have now finished populating the instance object so go ahead
            // and fire the on converted event handlers
            OnConverted(content, type, culture, instance, onConverted);

            return(instance);
        }
Example #17
0
        /// <summary>
        /// Set the typed value to the given instance.
        /// </summary>
        /// <param name="content">The <see cref="IPublishedContent"/> to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/></param>
        /// <param name="propertyInfo">The <see cref="PropertyInfo"/> property info associated with the type.</param>
        /// <param name="propertyValue">The property value.</param>
        /// <param name="instance">The instance to assign the value to.</param>
        /// <returns>The strong typed converted value for the given property.</returns>
        private static object GetConvertedValue(
            IPublishedContent content,
            CultureInfo culture,
            PropertyInfo propertyInfo,
            object propertyValue,
            object instance)
        {
            // Process the value.
            object result       = null;
            var    propertyType = propertyInfo.PropertyType;
            var    typeInfo     = propertyType.GetTypeInfo();

            // This should return false against typeof(string) etc also.
            var propertyIsEnumerableType = propertyType.IsCastableEnumerableType();

            // Try any custom type converters first.
            // 1: Check the property.
            // 2: Check any type arguments in generic enumerable types.
            // 3: Check the type itself.
            var converterAttribute =
                propertyInfo.GetCustomAttribute <TypeConverterAttribute>()
                ?? (propertyIsEnumerableType ? typeInfo.GenericTypeArguments.First().GetCustomAttribute <TypeConverterAttribute>(true)
                                             : propertyType.GetCustomAttribute <TypeConverterAttribute>(true));

            if (converterAttribute != null && converterAttribute.ConverterTypeName != null)
            {
                // Time custom conversions.
                using (DittoDisposableTimer.DebugDuration <object>(string.Format("Custom TypeConverter ({0}, {1})", content.Id, propertyInfo.Name)))
                {
                    // Get the custom converter from the attribute and attempt to convert.
                    var converterType = Type.GetType(converterAttribute.ConverterTypeName);
                    if (converterType != null)
                    {
                        var converter = converterType.GetDependencyResolvedInstance() as TypeConverter;

                        if (converter != null)
                        {
                            // Create context to pass to converter implementations.
                            // This contains the IPublishedContent and the currently converting property descriptor.
                            var descriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name];
                            var context    = new DittoTypeConverterContext
                            {
                                ConversionType     = instance.GetType(),
                                Instance           = content,
                                PropertyDescriptor = descriptor
                            };

                            Type propertyValueType = null;
                            if (propertyValue != null)
                            {
                                propertyValueType = propertyValue.GetType();
                            }

                            // We're deliberately passing null.
                            // ReSharper disable once AssignNullToNotNullAttribute
                            if (converter.CanConvertFrom(context, propertyValueType))
                            {
                                object converted = converter.ConvertFrom(context, culture, propertyValue);

                                if (converted != null)
                                {
                                    // Handle Typeconverters returning single objects when we want an IEnumerable.
                                    // Use case: Someone selects a folder of images rather than a single image with the media picker.
                                    var convertedType = converted.GetType();

                                    if (propertyIsEnumerableType)
                                    {
                                        var parameterType = typeInfo.GenericTypeArguments.First();

                                        // Some converters return an IEnumerable so we check again.
                                        if (!convertedType.IsEnumerableType())
                                        {
                                            // Using 'Cast' to convert the type back to IEnumerable<T>.
                                            object enumerablePropertyValue = EnumerableInvocations.Cast(
                                                parameterType,
                                                converted.YieldSingleItem());

                                            result = enumerablePropertyValue;
                                        }
                                        else
                                        {
                                            // Nothing is strong typed anymore.
                                            result = EnumerableInvocations.Cast(parameterType, (IEnumerable)converted);
                                        }
                                    }
                                    else
                                    {
                                        // Return single expected items from converters returning an IEnumerable.
                                        // Check for key/value pairs and strings.
                                        if (convertedType.IsEnumerableType() &&
                                            !convertedType.IsEnumerableOfKeyValueType() &&
                                            !(convertedType == typeof(string) && propertyType == typeof(string)))
                                        {
                                            // Use 'FirstOrDefault' to convert the type back to T.
                                            result = EnumerableInvocations.FirstOrDefault(
                                                propertyType,
                                                (IEnumerable)converted);
                                        }
                                        else
                                        {
                                            result = converted;
                                        }
                                    }
                                }
                                else
                                {
                                    // Ensure we pass back an empty enumerable if the expected output is an enumerable.
                                    // and null has been returned by the type converter.
                                    if (propertyIsEnumerableType)
                                    {
                                        result = EnumerableInvocations.Empty(typeInfo.GenericTypeArguments.First());
                                    }
                                }
                            }
                            else if (propertyType.IsInstanceOfType(propertyValue))
                            {
                                // If the TypeConverter's `CanConvertFrom` has returned false,
                                // then we can check if the value is the same type as the target type.
                                result = propertyValue;
                            }
                        }
                    }
                }
            }
            else if (propertyType == typeof(HtmlString))
            {
                // Handle Html strings so we don't have to set the attribute.
                var converterType = typeof(DittoHtmlStringConverter);
                var converter     = converterType.GetDependencyResolvedInstance() as TypeConverter;

                if (converter != null)
                {
                    // This contains the IPublishedContent and the currently converting property descriptor.
                    var descriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name];
                    var context    = new DittoTypeConverterContext
                    {
                        ConversionType     = instance.GetType(),
                        Instance           = content,
                        PropertyDescriptor = descriptor
                    };

                    Type propertyValueType = null;
                    if (propertyValue != null)
                    {
                        propertyValueType = propertyValue.GetType();
                    }

                    // We're deliberately passing null.
                    // ReSharper disable once AssignNullToNotNullAttribute
                    if (converter.CanConvertFrom(context, propertyValueType))
                    {
                        result = converter.ConvertFrom(context, culture, propertyValue);
                    }
                }
            }
            else if (propertyType.IsInstanceOfType(propertyValue))
            {
                // Simple types
                result = propertyValue;
            }
            else if (propertyValue is IPublishedContent && propertyType.IsClass)
            {
                // If the property value is an IPublishedContent, then we can use Ditto to map to the target type.
                result = ((IPublishedContent)propertyValue).As(propertyType);
            }
            else if (propertyValue != null &&
                     propertyValue.GetType().IsEnumerableOfType(typeof(IPublishedContent)) &&
                     propertyType.IsEnumerable() &&
                     propertyType.GetEnumerableType() != null &&
                     propertyType.GetEnumerableType().IsClass)
            {
                // If the property value is IEnumerable<IPublishedContent>, then we can use Ditto to map to the target type.
                result = ((IEnumerable <IPublishedContent>)propertyValue).As(propertyType.GetEnumerableType());
            }
            else
            {
                using (DittoDisposableTimer.DebugDuration <object>(string.Format("TypeConverter ({0}, {1})", content.Id, propertyInfo.Name)))
                {
                    var convert = propertyValue.TryConvertTo(propertyType);
                    if (convert.Success)
                    {
                        result = convert.Result;
                    }
                }
            }

            return(result);
        }
Example #18
0
        /// <summary>
        /// Returns the resolved value for the given type and property.
        /// </summary>
        /// <param name="content">The <see cref="IPublishedContent"/> to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/></param>
        /// <param name="propertyInfo">The <see cref="PropertyInfo"/> property info associated with the type.</param>
        /// <param name="instance">The instance to assign the value to.</param>
        /// <param name="valueResolverContexts">
        /// A collection of <see cref="DittoValueResolverContext"/> entities to use whilst resolving values.
        /// </param>
        /// <returns>The <see cref="object"/> representing the Umbraco value.</returns>
        private static object GetResolvedValue(
            IPublishedContent content,
            CultureInfo culture,
            PropertyInfo propertyInfo,
            object instance,
            IEnumerable <DittoValueResolverContext> valueResolverContexts = null)
        {
            // Check the property for an associated value attribute, otherwise fall-back on expected behaviour.
            var valueAttr = propertyInfo.GetCustomAttribute <DittoValueResolverAttribute>(true);

            if (valueAttr == null)
            {
                // Check for globally registered resolver
                valueAttr = DittoValueResolverRegistry.Instance.GetRegisteredResolverAttributeFor(propertyInfo.PropertyType);
            }

            if (valueAttr == null)
            {
                // Default to umbraco property attribute
                valueAttr = new UmbracoPropertyAttribute();
            }

            // Time custom value-resolver.
            using (DittoDisposableTimer.DebugDuration <object>(string.Format("Custom ValueResolver ({0}, {1})", content.Id, propertyInfo.Name)))
            {
                var resolver = (DittoValueResolver)valueAttr.ResolverType.GetInstance();

                DittoValueResolverContext context = null;

                // Get the value from the custom attribute.
                // TODO: Cache these?
                var resolverTypeInstances = valueAttr.ResolverType.GetGenericTypeImplementations(typeof(DittoValueResolver <,>)).ToArray();
                if (resolverTypeInstances.Length == 1)
                {
                    var contextType = resolverTypeInstances[0].GetGenericArguments().FirstOrDefault(x => typeof(DittoValueResolverContext).IsAssignableFrom(x));
                    if (contextType != null)
                    {
                        var resolverContext = valueResolverContexts != null?valueResolverContexts.FirstOrDefault(x => x.GetType() == contextType) : null;

                        if (resolverContext != null)
                        {
                            context = resolverContext;
                        }
                        else
                        {
                            context = (DittoValueResolverContext)contextType.GetInstance();
                        }
                    }
                }

                // No context found so create a default one
                if (context == null)
                {
                    context = new DittoValueResolverContext();
                }

                // Populate internal context properties
                context.ConversionType     = instance.GetType();
                context.Instance           = content;
                context.PropertyDescriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name];

                // Resolve value
                return(resolver.ResolveValue(context, valueAttr, culture));
            }
        }
Example #19
0
        /// <summary>
        /// Returns an object representing the given <see cref="Type"/>.
        /// </summary>
        /// <param name="content">
        /// The <see cref="IPublishedContent"/> to convert.
        /// </param>
        /// <param name="type">
        /// The <see cref="Type"/> of items to return.
        /// </param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/>
        /// </param>
        /// <param name="instance">
        /// An existing instance of T to populate
        /// </param>
        /// <param name="valueResolverContexts">
        /// A collection of <see cref="DittoValueResolverContext"/> entities to use whilst resolving values.
        /// </param>
        /// <param name="onConverting">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.
        /// </param>
        /// <param name="onConverted">
        /// The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.
        /// </param>
        /// <returns>
        /// The converted <see cref="Object"/> as the given type.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// Thrown if the given type has invalid constructors.
        /// </exception>
        private static object ConvertContent(
            IPublishedContent content,
            Type type,
            CultureInfo culture = null,
            object instance     = null,
            IEnumerable <DittoValueResolverContext> valueResolverContexts = null,
            Action <DittoConversionHandlerContext> onConverting           = null,
            Action <DittoConversionHandlerContext> onConverted            = null)
        {
            // Check if the culture has been set, otherwise use from Umbraco, or fallback to a default
            if (culture == null)
            {
                if (UmbracoContext.Current != null && UmbracoContext.Current.PublishedContentRequest != null)
                {
                    culture = UmbracoContext.Current.PublishedContentRequest.Culture;
                }
                else
                {
                    // Fallback
                    culture = CultureInfo.CurrentCulture;
                }
            }

            // Get the default constructor, parameters and create an instance of the type.
            // Try and return from the cache first. TryGetValue is faster than GetOrAdd.
            ParameterInfo[] constructorParams;
            ConstructorCache.TryGetValue(type, out constructorParams);
            bool hasParameter = false;

            if (constructorParams == null)
            {
                var constructor = type.GetConstructors().OrderBy(x => x.GetParameters().Length).FirstOrDefault();
                if (constructor != null)
                {
                    constructorParams = constructor.GetParameters();
                    ConstructorCache.TryAdd(type, constructorParams);
                }
            }

            // If not already an instance, create an instance of the object
            if (instance == null)
            {
                if (constructorParams != null && constructorParams.Length == 0)
                {
                    // Internally this uses Activator.CreateInstance which is heavily optimized.
                    instance = type.GetInstance();
                }
                else if (constructorParams != null && constructorParams.Length == 1 & constructorParams[0].ParameterType == typeof(IPublishedContent))
                {
                    // This extension method is about 7x faster than the native implementation.
                    instance     = type.GetInstance(content);
                    hasParameter = true;
                }
                else
                {
                    // No valid constructor, but see if the value can be cast to the type
                    if (type.IsInstanceOfType(content))
                    {
                        instance = content;
                    }
                    else
                    {
                        throw new InvalidOperationException(string.Format("Can't convert IPublishedContent to {0} as it has no valid constructor. A valid constructor is either an empty one, or one accepting a single IPublishedContent parameter.", type));
                    }
                }
            }

            // Collect all the properties of the given type and loop through writable ones.
            PropertyInfo[] virtualProperties;
            PropertyInfo[] nonVirtualProperties;
            VirtualPropertyCache.TryGetValue(type, out virtualProperties);
            PropertyCache.TryGetValue(type, out nonVirtualProperties);
            if (virtualProperties == null && nonVirtualProperties == null)
            {
                var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                 .Where(x => x.CanWrite).ToArray();

                // Split out the properties.
                virtualProperties    = properties.Where(p => p.IsVirtualAndOverridable()).ToArray();
                nonVirtualProperties = properties.Except(virtualProperties).ToArray();
                VirtualPropertyCache.TryAdd(type, virtualProperties);
                PropertyCache.TryAdd(type, nonVirtualProperties);
            }

            // A dictionary to store lazily invoked values.
            var lazyProperties = new Dictionary <string, Lazy <object> >();

            // If there are any virtual properties we want to lazily invoke them.
            if (virtualProperties != null && virtualProperties.Any())
            {
                foreach (var propertyInfo in virtualProperties)
                {
                    using (DittoDisposableTimer.DebugDuration <object>(string.Format("ForEach Virtual Property ({1} {0})", propertyInfo.Name, content.Id)))
                    {
                        // Check for the ignore attribute.
                        var ignoreAttr = propertyInfo.GetCustomAttribute <DittoIgnoreAttribute>();
                        if (ignoreAttr != null)
                        {
                            continue;
                        }

                        // Create a Lazy<object> to deferr returning our value.
                        var deferredPropertyInfo = propertyInfo;
                        var localInstance        = instance;
                        lazyProperties.Add(
                            propertyInfo.Name,
                            new Lazy <object>(
                                () =>
                        {
                            // Get the value from Umbraco.
                            object propertyValue = GetResolvedValue(content, culture, deferredPropertyInfo, localInstance, valueResolverContexts);
                            return(GetConvertedValue(content, culture, deferredPropertyInfo, propertyValue, localInstance));
                        }));
                    }
                }

                // Create a proxy instance to replace our object.
                var interceptor = new LazyInterceptor(instance, lazyProperties);
                var factory     = new ProxyFactory();

                instance = hasParameter
                    ? factory.CreateProxy(type, interceptor, content)
                    : factory.CreateProxy(type, interceptor);
            }

            // We have the instance object but haven't yet populated properties
            // so fire the on converting event handlers
            OnConverting(content, type, instance, onConverting);

            // Now loop through and convert non-virtual properties.
            if (nonVirtualProperties != null && nonVirtualProperties.Any())
            {
                foreach (var propertyInfo in nonVirtualProperties)
                {
                    using (DittoDisposableTimer.DebugDuration <object>(string.Format("ForEach Property ({1} {0})", propertyInfo.Name, content.Id)))
                    {
                        // Check for the ignore attribute.
                        var ignoreAttr = propertyInfo.GetCustomAttribute <DittoIgnoreAttribute>();
                        if (ignoreAttr != null)
                        {
                            continue;
                        }

                        // Set the value normally.
                        // ReSharper disable once PossibleMultipleEnumeration
                        object propertyValue = GetResolvedValue(content, culture, propertyInfo, instance, valueResolverContexts);
                        object result        = GetConvertedValue(content, culture, propertyInfo, propertyValue, instance);

                        propertyInfo.SetValue(instance, result, null);
                    }
                }
            }

            // We have now finished populating the instance object so go ahead
            // and fire the on converted event handlers
            OnConverted(content, type, instance, onConverted);

            return(instance);
        }
        /// <summary>
        /// Returns the processed value for the given type and property.
        /// </summary>
        /// <param name="content">The content.</param>
        /// <param name="propertyInfo">The property information.</param>
        /// <param name="contextAccessor">The context accessor.</param>
        /// <param name="baseProcessorContext">The base processor context.</param>
        /// <param name="chainContext">The <see cref="DittoChainContext"/> for the current processor chain.</param>
        /// <returns>Returns the processed value.</returns>
        private static object DoGetProcessedValue(
            IPublishedContent content,
            PropertyInfo propertyInfo,
            IDittoContextAccessor contextAccessor,
            DittoProcessorContext baseProcessorContext,
            DittoChainContext chainContext)
        {
            // Check the property for any explicit processor attributes
            var processorAttrs = propertyInfo.GetCustomAttributes <DittoProcessorAttribute>(true)
                                 .OrderBy(x => x.Order)
                                 .ToList();

            if (!processorAttrs.Any())
            {
                // Adds the default processor for this conversion
                processorAttrs.Add(DittoProcessorRegistry.Instance.GetDefaultProcessorFor(baseProcessorContext.TargetType));
            }

            var propertyType = propertyInfo.PropertyType;

            // Check for type registered processors
            processorAttrs.AddRange(propertyType
                                    .GetCustomAttributes <DittoProcessorAttribute>(true)
                                    .OrderBy(x => x.Order));

            // Check any type arguments in generic enumerable types.
            // This should return false against typeof(string) etc also.
            var  typeInfo     = propertyType.GetTypeInfo();
            bool isEnumerable = false;
            Type typeArg      = null;

            if (propertyType.IsCastableEnumerableType())
            {
                typeArg = typeInfo.GenericTypeArguments.First();
                processorAttrs.AddRange(typeInfo
                                        .GenericTypeArguments
                                        .First()
                                        .GetCustomAttributes <DittoProcessorAttribute>(true)
                                        .OrderBy(x => x.Order)
                                        .ToList());

                isEnumerable = true;
            }

            // Check for globally registered processors
            processorAttrs.AddRange(DittoProcessorRegistry.Instance.GetRegisteredProcessorAttributesFor(propertyInfo.PropertyType));

            // Add any core processors onto the end
            processorAttrs.AddRange(DittoProcessorRegistry.Instance.GetPostProcessorAttributes());

            // Create holder for value as it's processed
            object currentValue = content;

            // Process attributes
            foreach (var processorAttr in processorAttrs)
            {
                using (DittoDisposableTimer.DebugDuration <Ditto>($"Processor '{processorAttr.GetType().Name}' ({content.Id})"))
                {
                    // Get the right context type
                    var ctx = chainContext.ProcessorContexts.GetOrCreate(baseProcessorContext, processorAttr.ContextType);

                    // Populate UmbracoContext & ApplicationContext
                    processorAttr.UmbracoContext     = contextAccessor.UmbracoContext;
                    processorAttr.ApplicationContext = contextAccessor.ApplicationContext;

                    // Process value
                    currentValue = processorAttr.ProcessValue(currentValue, ctx, chainContext);
                }
            }

            // The following has to happen after all the processors.
            if (isEnumerable && currentValue != null && currentValue.Equals(Enumerable.Empty <object>()))
            {
                if (propertyType.IsInterface)
                {
                    // You cannot set an enumerable of type from an empty object array.
                    currentValue = EnumerableInvocations.Cast(typeArg, (IEnumerable)currentValue);
                }
                else
                {
                    // This should allow the casting back of IEnumerable<T> to an empty List<T> Collection<T> etc.
                    // I cant think of any that don't have an empty constructor
                    currentValue = propertyType.GetInstance();
                }
            }

            return((currentValue == null && propertyType.IsValueType)
                ? propertyInfo.PropertyType.GetInstance() // Set to default instance of value type
                : currentValue);
        }