/// <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>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));
                }
            }
        }
        /// <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));
        }
예제 #4
0
        /// <summary>
        /// Populates the core context fields.
        /// </summary>
        /// <param name="baseProcessorContext">The base processor content.</param>
        /// <returns>Returns the Ditto processors context.</returns>
        internal DittoProcessorContext Populate(DittoProcessorContext baseProcessorContext)
        {
            Content            = baseProcessorContext.Content;
            TargetType         = baseProcessorContext.TargetType;
            PropertyDescriptor = baseProcessorContext.PropertyDescriptor;
            Culture            = baseProcessorContext.Culture;

            return(this);
        }
예제 #5
0
        /// <summary>
        /// Gets or creates the <see cref="DittoProcessorContext" />.
        /// </summary>
        /// <param name="baseContext">The base context.</param>
        /// <param name="contextType">The object type of the content.</param>
        /// <returns>Returns the the <see cref="DittoProcessorContext" />.</returns>
        public DittoProcessorContext GetOrCreate(DittoProcessorContext baseContext, Type contextType)
        {
            // Get, clone and populate the relevant context for the given level
            var ctx = _processorContexts
                      .GetOrAdd(contextType, type => (DittoProcessorContext)contextType.GetInstance())
                      .Clone()
                      .Populate(baseContext);

            return(ctx);
        }
        /// <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 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 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);
        }
 /// <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)
 {
     return(this.ProcessValue(value, context, new DittoChainContext(new[] { context })));
 }
예제 #10
0
 /// <summary>
 /// Adds a processor context to the collection chain.
 /// </summary>
 /// <param name="ctx">The processor context.</param>
 public void Add(DittoProcessorContext ctx)
 {
     _processorContexts.AddOrUpdate(ctx.GetType(), ctx, (type, ctx2) => ctx2); // Don't override if already exists
 }
예제 #11
0
 /// <summary>
 /// Adds the context.
 /// </summary>
 /// <param name="context">The context.</param>
 public void AddContext(DittoProcessorContext context)
 {
     this.lookup.AddOrUpdate(context.GetType(), context.Populate(content, targetType, propertyDescriptor, culture), (type, ctx) => ctx); // Don't override if already exists
 }