Ejemplo n.º 1
0
        /// <summary>
        /// Gets a property value from the given content objects class properties
        /// </summary>
        /// <param name="content"></param>
        /// <param name="umbracoPropertyName"></param>
        /// <returns></returns>
        private object GetClassPropertyValue(IPublishedContent content, string umbracoPropertyName)
        {
            var contentType     = content.GetType();
            var contentProperty = contentType.GetProperty(umbracoPropertyName, Ditto.MappablePropertiesBindingFlags);

            if (contentProperty != null && contentProperty.IsMappable())
            {
                if (Ditto.IsDebuggingEnabled &&
                    PropertySource == PropertySource.InstanceThenUmbracoProperties &&
                    Ditto.IPublishedContentProperties.Any(x => x.Name.InvariantEquals(umbracoPropertyName)) &&
                    content.HasProperty(umbracoPropertyName))
                {
                    // Property is an IPublishedContent property and an umbraco property exists so warn the user
                    LogHelper.Warn <UmbracoPropertyAttribute>($"The property {umbracoPropertyName} being mapped from content type {contentType.Name}'s instance properties hides a property in the Umbraco properties collection of the same name. It is recommended that you avoid using Umbraco property aliases that conflict with IPublishedContent instance property names, but if you can't avoid this and you require access to the hidden property you can use the PropertySource parameter of the processors attribute to override the order in which properties are checked.");
                }

                // This is over 4x faster than propertyValue = contentProperty.GetValue(content, null);
                return(FastPropertyAccessor.GetValue(contentProperty, content));
            }

            return(null);
        }
        /// <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="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);
        }