/// <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); }