/// <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 defaultValue = DefaultValue; var recursive = Recursive; var propName = this.Context.PropertyDescriptor != null ? this.Context.PropertyDescriptor.Name : string.Empty; var altPropName = string.Empty; // Check for umbraco properties attribute on class if (this.Context.TargetType != null) { var classAttr = this.Context.TargetType .GetCustomAttribute <UmbracoPropertiesAttribute>(); if (classAttr != null) { // Apply the prefix if (!string.IsNullOrWhiteSpace(classAttr.Prefix)) { altPropName = propName; propName = classAttr.Prefix + propName; } // Apply global recursive setting recursive |= classAttr.Recursive; } } var umbracoPropertyName = PropertyName ?? propName; var altUmbracoPropertyName = AltPropertyName ?? altPropName; var content = this.Context.Content; if (content == null) { return(defaultValue); } var contentType = content.GetType(); object propertyValue = null; // Try fetching the value. if (!umbracoPropertyName.IsNullOrWhiteSpace()) { var contentProperty = contentType.GetProperty(umbracoPropertyName, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static); if (contentProperty != null) { // This is more than 2x as fast as propertyValue = contentProperty.GetValue(content, null); propertyValue = PropertyInfoInvocations.GetValue(contentProperty, content); } if (propertyValue == null) { propertyValue = content.GetPropertyValue(umbracoPropertyName, recursive); } } // Try fetching the alt value. if ((propertyValue == null || propertyValue.ToString().IsNullOrWhiteSpace()) && !string.IsNullOrWhiteSpace(altUmbracoPropertyName)) { var contentProperty = contentType.GetProperty(altUmbracoPropertyName, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static); if (contentProperty != null) { // This is more than 2x as fast as propertyValue = contentProperty.GetValue(content, null); propertyValue = PropertyInfoInvocations.GetValue(contentProperty, content); } if (propertyValue == null) { propertyValue = content.GetPropertyValue(altUmbracoPropertyName, recursive); } } // Try setting the default value. if ((propertyValue == null || propertyValue.ToString().IsNullOrWhiteSpace()) && defaultValue != null) { propertyValue = defaultValue; } return(propertyValue); }
/// <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> /// <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 <DittoProcessorContext> processorContexts = null, Action <DittoConversionHandlerContext> onConverting = null, Action <DittoConversionHandlerContext> onConverted = null) { // 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 && x.GetSetMethod() != null).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); } // Gets the default processor for this conversion. var defaultProcessorType = DittoProcessorRegistry.Instance.GetDefaultProcessorType(type); // 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>(() => GetProcessedValue(content, culture, type, deferredPropertyInfo, localInstance, defaultProcessorType, processorContexts))); } } // 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 var value = GetProcessedValue(content, culture, type, propertyInfo, instance, defaultProcessorType, processorContexts); // This is 2x as fast as propertyInfo.SetValue(instance, value, null); PropertyInfoInvocations.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, instance, onConverted); return(instance); }