/// <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>
        /// Gets the cache item.
        /// </summary>
        /// <typeparam name="TOuputType">The type of the output type.</typeparam>
        /// <param name="cacheContext">The cache context.</param>
        /// <param name="refresher">The refresher.</param>
        /// <returns>Returns the output type.</returns>
        /// <exception cref="ApplicationException">Expected a cache key builder of type DittoProcessorCacheKeyBuilder but got CacheKeyBuilderType.</exception>
        internal TOuputType GetCacheItem <TOuputType>(DittoCacheContext cacheContext, Func <TOuputType> refresher)
        {
            // TODO: [LK:2018-01-18] Review this, does `cacheContext` need to be passed in?
            // Given that the values are available on the instance.

            // If no cache duration set, (and in debug mode AND NOT a unit-test), then just run the refresher
            if (this.CacheDuration == 0 || (Ditto.IsDebuggingEnabled && Ditto.IsRunningInUnitTest == false))
            {
                return(refresher());
            }

            // Get the cache key builder type
            var cacheKeyBuilderType = this.CacheKeyBuilderType ?? typeof(DittoDefaultCacheKeyBuilder);

            // Check the cache key builder type
            if (typeof(DittoCacheKeyBuilder).IsAssignableFrom(cacheKeyBuilderType) == false)
            {
                throw new ApplicationException($"Expected a cache key builder of type {typeof(DittoCacheKeyBuilder)} but got {cacheKeyBuilderType}");
            }

            // TODO: [LK:2018-01-18] Review this, a new instance is being created per call
            // Construct the cache key builder
            var builder  = cacheKeyBuilderType.GetInstance <DittoCacheKeyBuilder>();
            var cacheKey = builder.BuildCacheKey(cacheContext);

            // TODO: Review this. Is there a way we can remove the use of the `ApplicationContext` singleton?
            // Get and cache the result
            return((TOuputType)ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem(
                       cacheKey,
                       () => refresher(),
                       priority: CacheItemPriority.NotRemovable, // Same as Umbraco macros
                       timeout: new TimeSpan(0, 0, 0, this.CacheDuration)));
        }
        /// <summary>
        /// Processes the value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="context">The context.</param>
        /// <param name="chainContext">The chain context.</param>
        /// <returns>
        /// The <see cref="object" /> representing the processed value.
        /// </returns>
        internal virtual object ProcessValue(
            object value,
            DittoProcessorContext context,
            DittoChainContext chainContext)
        {
            if (value != null && this.ValueType.IsInstanceOfType(value) == false)
            {
                throw new ArgumentException($"Expected a value argument of type {this.ValueType} but got {value.GetType()}", "value");
            }

            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            if (this.ContextType.IsInstanceOfType(context) == false)
            {
                throw new ArgumentException($"Expected a context argument of type {this.ContextType} but got {context.GetType()}", "context");
            }

            if (chainContext == null)
            {
                throw new ArgumentNullException("chainContext");
            }

            this.Value        = value;
            this.Context      = context;
            this.ChainContext = chainContext;

            var ctx = new DittoCacheContext(this, context.Content, context.TargetType, context.PropertyInfo, context.Culture);

            return(this.GetCacheItem(ctx, this.ProcessValue));
        }
        /// <summary>
        /// Gets the cache item.
        /// </summary>
        /// <typeparam name="TOuputType">The type of the output type.</typeparam>
        /// <param name="cacheContext">The cache context.</param>
        /// <param name="refresher">The refresher.</param>
        /// <returns>Returns the output type.</returns>
        /// <exception cref="System.ApplicationException">Expected a cache key builder of type  + typeof(DittoProcessorCacheKeyBuilder) +  but got  + CacheKeyBuilderType</exception>
        internal TOuputType GetCacheItem <TOuputType>(DittoCacheContext cacheContext, Func <TOuputType> refresher)
        {
            // If no cache duration set, just run the refresher
            if (this.CacheDuration == 0 || Ditto.IsDebuggingEnabled)
            {
                return(refresher());
            }

            // Get the cache key builder type
            var cacheKeyBuilderType = this.CacheKeyBuilderType ?? typeof(DittoDefaultCacheKeyBuilder);

            // Check the cache key builder type
            if (!typeof(DittoCacheKeyBuilder).IsAssignableFrom(cacheKeyBuilderType))
            {
                throw new ApplicationException($"Expected a cache key builder of type {typeof(DittoCacheKeyBuilder)} but got {this.CacheKeyBuilderType}");
            }

            // Construct the cache key builder
            var builder  = (DittoCacheKeyBuilder)cacheKeyBuilderType.GetInstance();
            var cacheKey = builder.BuildCacheKey(cacheContext);

            // Get and cache the result
            return((TOuputType)ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem(
                       cacheKey,
                       () => refresher(),
                       priority: CacheItemPriority.NotRemovable, // Same as Umbraco macros
                       timeout: new TimeSpan(0, 0, 0, this.CacheDuration)));
        }
        /// <summary>
        /// Processes the value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="context">The context.</param>
        /// <returns>
        /// The <see cref="object" /> representing the processed value.
        /// </returns>
        internal virtual object ProcessValue(
            object value,
            DittoProcessorContext context)
        {
            if (value != null && !ValueType.IsInstanceOfType(value))
            {
                throw new ArgumentException("Expected a value argument of type " + ValueType + " but got " + value.GetType(), "value");
            }

            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            if (!ContextType.IsInstanceOfType(context))
            {
                throw new ArgumentException("Expected a context argument of type " + ContextType + " but got " + context.GetType(), "context");
            }

            Value   = value;
            Context = context;

            var ctx = new DittoCacheContext(this, context.Content, context.TargetType, context.PropertyDescriptor, context.Culture);

            return(this.GetCacheItem(ctx, this.ProcessValue));
        }
Example #7
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 #10
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));
                }
            }
        }
        /// <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));
                }
            }
        }