Beispiel #1
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 an object representing the given <see cref="Type"/>.</summary>
        /// <param name="content">The <see cref="IPublishedContent"/> to convert.</param>
        /// <param name="config">The Ditto configuration for the type.</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,
            DittoTypeInfo config,
            CultureInfo culture,
            object instance,
            Action <DittoConversionHandlerContext> onConverting,
            Action <DittoConversionHandlerContext> onConverted,
            DittoChainContext chainContext)
        {
            // If not already an instance, create an instance of the object.
            if (instance == null)
            {
                // Check the validity of the mapped type constructor.
                if (config.ConstructorIsValid == false)
                {
                    throw new InvalidOperationException(
                              $"Cannot convert IPublishedContent to {config.TargetType} as it has no valid constructor. " +
                              "A valid constructor is either empty, or one accepting a single IPublishedContent parameter.");
                }

                // We can only proxy new instances.
                if (config.ConstructorRequiresProxyType)
                {
                    var factory = new ProxyFactory();
                    instance = config.ConstructorHasPublishedContentParameter
                        ? factory.CreateProxy(config.TargetType, config.LazyPropertyNames, content)
                        : factory.CreateProxy(config.TargetType, config.LazyPropertyNames);
                }
                else if (config.IsOfTypePublishedContent)
                {
                    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 = config.ConstructorHasPublishedContentParameter
                                                                 // TODO: Review this, as we could get the Constructor metadata from the "type-cache"?
                        ? config.TargetType.GetInstance(content) // 1
                        : config.TargetType.GetInstance();       // 2
                }
            }

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

            if (config.HasLazyProperties)
            {
                // A dictionary to store lazily invoked values.
                var lazyMappings = new Dictionary <string, Lazy <object> >();
                foreach (var lazyProperty in config.LazyProperties)
                {
                    // Configure lazy properties
                    lazyMappings.Add(lazyProperty.PropertyInfo.Name, new Lazy <object>(() => GetProcessedValue(content, culture, config, lazyProperty, instance, chainContext)));
                }

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

            // Process any eager properties
            if (config.HasEagerProperties)
            {
                foreach (var eagerProperty in config.EagerProperties)
                {
                    // Set the value normally.
                    var value = GetProcessedValue(content, culture, config, eagerProperty, instance, chainContext);

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

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

            return(instance);
        }
        /// <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);
        }
        /// <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>
        /// <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 GetTypedProperty(
            IPublishedContent content,
            Type type,
            CultureInfo culture = 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).First();
                constructorParams = constructor.GetParameters();
                ConstructorCache.TryAdd(type, constructorParams);
            }

            object instance;

            if (constructorParams.Length == 0)
            {
                // Internally this uses Activator.CreateInstance which is heavily optimized.
                instance = type.GetInstance();
            }
            else if (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
            {
                throw new InvalidOperationException("Type {0} has invalid constructor parameters");
            }

            // 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 (DisposableTimer.DebugDuration <object>(string.Format("ForEach Virtual Property ({1} {0})", propertyInfo.Name, content.Id), "Complete"))
                    {
                        // 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 = GetRawValue(content, culture, deferredPropertyInfo, localInstance);
                            return(GetTypedValue(content, culture, deferredPropertyInfo, propertyValue, localInstance));
                        }));
                    }
                }

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

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

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

                        // Set the value normally.
                        object propertyValue = GetRawValue(content, culture, propertyInfo, instance);
                        var    result        = GetTypedValue(content, culture, propertyInfo, propertyValue, instance);
                        propertyInfo.SetValue(instance, result, null);
                    }
                }
            }

            return(instance);
        }