/// <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> /// 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="defaultProcessorType">The default processor type.</param> /// <param name="processorContexts">A collection of <see cref="DittoProcessorContext" /> entities to use whilst processing values.</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, Type defaultProcessorType, IEnumerable <DittoProcessorContext> processorContexts = null) { // Time custom value-processor. using (DittoDisposableTimer.DebugDuration <object>(string.Format("Custom ValueProcessor ({0}, {1})", content.Id, propertyInfo.Name))) { // Get the target property description var propertyDescriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name]; // 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, culture, targetType, propertyInfo, propertyDescriptor, defaultProcessorType, processorContexts))); } else { return(DoGetProcessedValue(content, culture, targetType, propertyInfo, propertyDescriptor, defaultProcessorType, processorContexts)); } } }
/// <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> /// <param name="chainContext"> /// The <see cref="DittoChainContext"/> for the current processor chain. /// </param> /// <returns> /// The converted <see cref="Object"/> as the given type. /// </returns> public static object As( this IPublishedContent content, Type type, CultureInfo culture = null, object instance = null, IEnumerable <DittoProcessorContext> processorContexts = null, Action <DittoConversionHandlerContext> onConverting = null, Action <DittoConversionHandlerContext> onConverted = null, DittoChainContext chainContext = null) { // Ensure content if (content == null) { return(null); } // Ensure instance is of target type if (instance != null && !type.IsInstanceOfType(instance)) { throw new ArgumentException($"The instance parameter does not implement Type '{type.Name}'", nameof(instance)); } // Get the context accessor (for access to ApplicationContext, UmbracoContext, et al) var contextAccessor = (IDittoContextAccessor)Ditto.GetContextAccessorType().GetInstance(); // Check if the culture has been set, otherwise use from Umbraco, or fallback to a default if (culture == null) { culture = contextAccessor.UmbracoContext?.PublishedContentRequest != null ? contextAccessor.UmbracoContext.PublishedContentRequest.Culture : CultureInfo.CurrentCulture; } // Ensure a chain context if (chainContext == null) { chainContext = new DittoChainContext(); } // Populate prcessor contexts collection with any passed in contexts chainContext.ProcessorContexts.AddRange(processorContexts); // Convert using (DittoDisposableTimer.DebugDuration <Ditto>($"As<{type.Name}>({content.DocumentTypeAlias} {content.Id})")) { var cacheAttr = type.GetCustomAttribute <DittoCacheAttribute>(true); if (cacheAttr != null) { var ctx = new DittoCacheContext(cacheAttr, content, type, culture); return(cacheAttr.GetCacheItem(ctx, () => ConvertContent(content, type, contextAccessor, culture, instance, onConverting, onConverted, chainContext))); } else { return(ConvertContent(content, type, contextAccessor, culture, instance, onConverting, onConverted, chainContext)); } } }
/// <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> /// <param name="chainContext">The <see cref="DittoChainContext"/> for the current processor chain.</param> /// <returns>The converted <see cref="Object"/> as the given type.</returns> public static object As( this IPublishedContent content, Type type, CultureInfo culture = null, object instance = null, IEnumerable <DittoProcessorContext> processorContexts = null, Action <DittoConversionHandlerContext> onConverting = null, Action <DittoConversionHandlerContext> onConverted = null, DittoChainContext chainContext = null) { // Ensure content if (content == null) { return(null); } // Ensure instance is of target type if (instance != null && type.IsInstanceOfType(instance) == false) { throw new ArgumentException($"The instance parameter does not implement Type '{type.Name}'", nameof(instance)); } // Check if the culture has been set, otherwise use from Umbraco, or fallback to a default if (culture == null) { culture = ContextAccessor?.UmbracoContext?.PublishedContentRequest?.Culture ?? CultureInfo.CurrentCulture; } // Ensure a chain context if (chainContext == null) { chainContext = new DittoChainContext(); } // Populate prcessor contexts collection with any passed in contexts chainContext.ProcessorContexts.AddRange(processorContexts); // Convert using (DittoDisposableTimer.DebugDuration(typeof(Ditto), $"As<{type.Name}>({content.DocumentTypeAlias} {content.Id})")) { var config = DittoTypeInfoCache.GetOrAdd(type); if (config.IsCacheable) { var ctx = new DittoCacheContext(config.CacheInfo, content, type, culture); return(config.CacheInfo.GetCacheItem(ctx, () => ConvertContent(content, config, culture, instance, onConverting, onConverted, chainContext))); } else { return(ConvertContent(content, config, culture, instance, onConverting, onConverted, chainContext)); } } }
/// <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> public static object As( this IPublishedContent content, Type type, CultureInfo culture = null, object instance = null, IEnumerable <DittoProcessorContext> processorContexts = null, Action <DittoConversionHandlerContext> onConverting = null, Action <DittoConversionHandlerContext> onConverted = null) { // Ensure content if (content == null) { return(null); } // Ensure instance is of target type if (instance != null && !type.IsInstanceOfType(instance)) { throw new ArgumentException(string.Format("The instance parameter does not implement Type '{0}'", type.Name), "instance"); } // 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; } } // Convert using (DittoDisposableTimer.DebugDuration <object>(string.Format("IPublishedContent As ({0})", content.DocumentTypeAlias))) { var cacheAttr = type.GetCustomAttribute <DittoCacheAttribute>(true); if (cacheAttr != null) { var ctx = new DittoCacheContext(cacheAttr, content, type, culture); return(cacheAttr.GetCacheItem(ctx, () => ConvertContent(content, type, culture, instance, processorContexts, onConverting, onConverted))); } else { return(ConvertContent(content, type, culture, instance, processorContexts, onConverting, onConverted)); } } }
/// <summary> /// Gets a collection of the given type from the given <see cref="IEnumerable{IPublishedContent}"/>. /// </summary> /// <param name="items"> /// The <see cref="IEnumerable{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="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 resolved <see cref="IEnumerable{T}"/>. /// </returns> public static IEnumerable <object> As( this IEnumerable <IPublishedContent> items, Type type, CultureInfo culture = null, IEnumerable <DittoValueResolverContext> valueResolverContexts = null, Action <DittoConversionHandlerContext> onConverting = null, Action <DittoConversionHandlerContext> onConverted = null) { using (DittoDisposableTimer.DebugDuration <IEnumerable <object> >("IEnumerable As")) { var typedItems = items.Select(x => x.As(type, culture, null, valueResolverContexts, onConverting, onConverted)); // We need to cast back here as nothing is strong typed anymore. return((IEnumerable <object>)EnumerableInvocations.Cast(type, typedItems)); } }
/// <summary> /// Gets a collection of the given type from the given <see cref="IEnumerable{IPublishedContent}"/>. /// </summary> /// <param name="items"> /// The <see cref="IEnumerable{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="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> /// <param name="chainContext"> /// The <see cref="DittoChainContext"/> for the current processor chain. /// </param> /// <returns> /// The converted <see cref="IEnumerable{T}"/>. /// </returns> public static IEnumerable <object> As( this IEnumerable <IPublishedContent> items, Type type, CultureInfo culture = null, IEnumerable <DittoProcessorContext> processorContexts = null, Action <DittoConversionHandlerContext> onConverting = null, Action <DittoConversionHandlerContext> onConverted = null, DittoChainContext chainContext = null) { using (DittoDisposableTimer.DebugDuration <Ditto>($"IEnumerable As<{type.Name}>")) { var typedItems = items.Select(x => x.As(type, culture, null, processorContexts, onConverting, onConverted, chainContext)); // We need to cast back here as nothing is strong typed anymore. return((IEnumerable <object>)EnumerableInvocations.Cast(type, typedItems)); } }
/// <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 given instance of <see cref="RenderModel"/> as the specified type. /// </summary> /// <param name="model"> /// The <see cref="RenderModel"/> to convert. /// </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> /// <typeparam name="T"> /// The <see cref="Type"/> of items to return. /// </typeparam> /// <returns> /// The resolved generic <see cref="Type"/>. /// </returns> public static T As <T>( this RenderModel model, IEnumerable <DittoValueResolverContext> valueResolverContexts = null, Action <DittoConversionHandlerContext> onConverting = null, Action <DittoConversionHandlerContext> onConverted = null) where T : class { if (model == null) { return(default(T)); } using (DittoDisposableTimer.DebugDuration <T>(string.Format("RenderModel As ({0})", model.Content.DocumentTypeAlias))) { return(model.Content.As <T>(model.CurrentCulture, null, valueResolverContexts, onConverting, onConverted)); } }
/// <summary>Returns the given instance of <see cref="RenderModel"/> as the specified type.</summary> /// <param name="model">The <see cref="RenderModel"/> to convert.</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> /// <typeparam name="T">The <see cref="Type"/> of items to return.</typeparam> /// <returns>The resolved generic <see cref="Type"/>.</returns> public static T As <T>( this RenderModel model, IEnumerable <DittoProcessorContext> processorContexts = null, Action <DittoConversionHandlerContext> onConverting = null, Action <DittoConversionHandlerContext> onConverted = null) where T : class { if (model == null || model.Content == null) { return(default(T)); } using (DittoDisposableTimer.DebugDuration <T>($"RenderModel As ({model.Content.DocumentTypeAlias})")) { return(model.Content.As <T>(model.CurrentCulture, null, processorContexts, onConverting, onConverted)); } }
/// <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 result = Value; if (Value != null && !Context.PropertyDescriptor.PropertyType.IsInstanceOfType(Value)) { // TODO: Maybe support enumerables? using (DittoDisposableTimer.DebugDuration <TryConvertToAttribute>(string.Format("TryConvertTo ({0}, {1})", Context.Content.Id, Context.PropertyDescriptor.Name))) { var convert = Value.TryConvertTo(Context.PropertyDescriptor.PropertyType); if (convert.Success) { result = convert.Result; } } } return(result); }
/// <summary> /// Processes the value. /// </summary> /// <returns> /// The <see cref="object" /> representing the processed value. /// </returns> public override object ProcessValue() { var result = this.Value; if (this.Value != null && this.Context.PropertyInfo.PropertyType.IsInstanceOfType(this.Value) == false) { // TODO: Maybe support enumerables? using (DittoDisposableTimer.DebugDuration <TryConvertToAttribute>($"TryConvertTo '{this.Context.PropertyInfo.Name}' ({this.Context.Content.Id})")) { var convert = this.Value.TryConvertTo(this.Context.PropertyInfo.PropertyType); if (convert.Success) { result = convert.Result; } } } return(result); }
/// <summary> /// Processes the value. /// </summary> /// <returns> /// The <see cref="object" /> representing the processed value. /// </returns> public override object ProcessValue() { foreach (var processorAttr in this.Attributes) { using (DittoDisposableTimer.DebugDuration <DittoMultiProcessorAttribute>($"Processor '{processorAttr.GetType().Name}' ({this.Context.Content.Id})")) { // Get the right context type var newCtx = this.ChainContext.ProcessorContexts.GetOrCreate(this.Context, processorAttr.ContextType); // Populate UmbracoContext & ApplicationContext processorAttr.UmbracoContext = this.UmbracoContext; processorAttr.ApplicationContext = this.ApplicationContext; // Process value this.Value = processorAttr.ProcessValue(this.Value, newCtx, this.ChainContext); } } return(this.Value); }
/// <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> public static object As( this IPublishedContent content, Type type, CultureInfo culture = null, object instance = null, IEnumerable <DittoValueResolverContext> valueResolverContexts = null, Action <DittoConversionHandlerContext> onConverting = null, Action <DittoConversionHandlerContext> onConverted = null) { if (content == null) { return(null); } if (instance != null && !type.IsInstanceOfType(instance)) { throw new ArgumentException(string.Format("The instance parameter does not implement Type '{0}'", type.Name), "instance"); } using (DittoDisposableTimer.DebugDuration <object>(string.Format("IPublishedContent As ({0})", content.DocumentTypeAlias))) { return(ConvertContent(content, type, culture, instance, valueResolverContexts, onConverting, onConverted)); } }
/// <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 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> /// Set the typed value to the given instance. /// </summary> /// <param name="content">The <see cref="IPublishedContent"/> to convert.</param> /// <param name="culture">The <see cref="CultureInfo"/></param> /// <param name="propertyInfo">The <see cref="PropertyInfo"/> property info associated with the type.</param> /// <param name="propertyValue">The property value.</param> /// <param name="instance">The instance to assign the value to.</param> /// <returns>The strong typed converted value for the given property.</returns> private static object GetConvertedValue( IPublishedContent content, CultureInfo culture, PropertyInfo propertyInfo, object propertyValue, object instance) { // Process the value. object result = null; var propertyType = propertyInfo.PropertyType; var typeInfo = propertyType.GetTypeInfo(); // This should return false against typeof(string) etc also. var propertyIsEnumerableType = propertyType.IsCastableEnumerableType(); // Try any custom type converters first. // 1: Check the property. // 2: Check any type arguments in generic enumerable types. // 3: Check the type itself. var converterAttribute = propertyInfo.GetCustomAttribute <TypeConverterAttribute>() ?? (propertyIsEnumerableType ? typeInfo.GenericTypeArguments.First().GetCustomAttribute <TypeConverterAttribute>(true) : propertyType.GetCustomAttribute <TypeConverterAttribute>(true)); if (converterAttribute != null && converterAttribute.ConverterTypeName != null) { // Time custom conversions. using (DittoDisposableTimer.DebugDuration <object>(string.Format("Custom TypeConverter ({0}, {1})", content.Id, propertyInfo.Name))) { // Get the custom converter from the attribute and attempt to convert. var converterType = Type.GetType(converterAttribute.ConverterTypeName); if (converterType != null) { var converter = converterType.GetDependencyResolvedInstance() as TypeConverter; if (converter != null) { // Create context to pass to converter implementations. // This contains the IPublishedContent and the currently converting property descriptor. var descriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name]; var context = new DittoTypeConverterContext { ConversionType = instance.GetType(), Instance = content, PropertyDescriptor = descriptor }; Type propertyValueType = null; if (propertyValue != null) { propertyValueType = propertyValue.GetType(); } // We're deliberately passing null. // ReSharper disable once AssignNullToNotNullAttribute if (converter.CanConvertFrom(context, propertyValueType)) { object converted = converter.ConvertFrom(context, culture, propertyValue); if (converted != null) { // Handle Typeconverters returning single objects when we want an IEnumerable. // Use case: Someone selects a folder of images rather than a single image with the media picker. var convertedType = converted.GetType(); if (propertyIsEnumerableType) { var parameterType = typeInfo.GenericTypeArguments.First(); // Some converters return an IEnumerable so we check again. if (!convertedType.IsEnumerableType()) { // Using 'Cast' to convert the type back to IEnumerable<T>. object enumerablePropertyValue = EnumerableInvocations.Cast( parameterType, converted.YieldSingleItem()); result = enumerablePropertyValue; } else { // Nothing is strong typed anymore. result = EnumerableInvocations.Cast(parameterType, (IEnumerable)converted); } } else { // Return single expected items from converters returning an IEnumerable. // Check for key/value pairs and strings. if (convertedType.IsEnumerableType() && !convertedType.IsEnumerableOfKeyValueType() && !(convertedType == typeof(string) && propertyType == typeof(string))) { // Use 'FirstOrDefault' to convert the type back to T. result = EnumerableInvocations.FirstOrDefault( propertyType, (IEnumerable)converted); } else { result = converted; } } } else { // Ensure we pass back an empty enumerable if the expected output is an enumerable. // and null has been returned by the type converter. if (propertyIsEnumerableType) { result = EnumerableInvocations.Empty(typeInfo.GenericTypeArguments.First()); } } } else if (propertyType.IsInstanceOfType(propertyValue)) { // If the TypeConverter's `CanConvertFrom` has returned false, // then we can check if the value is the same type as the target type. result = propertyValue; } } } } } else if (propertyType == typeof(HtmlString)) { // Handle Html strings so we don't have to set the attribute. var converterType = typeof(DittoHtmlStringConverter); var converter = converterType.GetDependencyResolvedInstance() as TypeConverter; if (converter != null) { // This contains the IPublishedContent and the currently converting property descriptor. var descriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name]; var context = new DittoTypeConverterContext { ConversionType = instance.GetType(), Instance = content, PropertyDescriptor = descriptor }; Type propertyValueType = null; if (propertyValue != null) { propertyValueType = propertyValue.GetType(); } // We're deliberately passing null. // ReSharper disable once AssignNullToNotNullAttribute if (converter.CanConvertFrom(context, propertyValueType)) { result = converter.ConvertFrom(context, culture, propertyValue); } } } else if (propertyType.IsInstanceOfType(propertyValue)) { // Simple types result = propertyValue; } else if (propertyValue is IPublishedContent && propertyType.IsClass) { // If the property value is an IPublishedContent, then we can use Ditto to map to the target type. result = ((IPublishedContent)propertyValue).As(propertyType); } else if (propertyValue != null && propertyValue.GetType().IsEnumerableOfType(typeof(IPublishedContent)) && propertyType.IsEnumerable() && propertyType.GetEnumerableType() != null && propertyType.GetEnumerableType().IsClass) { // If the property value is IEnumerable<IPublishedContent>, then we can use Ditto to map to the target type. result = ((IEnumerable <IPublishedContent>)propertyValue).As(propertyType.GetEnumerableType()); } else { using (DittoDisposableTimer.DebugDuration <object>(string.Format("TypeConverter ({0}, {1})", content.Id, propertyInfo.Name))) { var convert = propertyValue.TryConvertTo(propertyType); if (convert.Success) { result = convert.Result; } } } return(result); }
/// <summary> /// Returns the resolved 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="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="valueResolverContexts"> /// A collection of <see cref="DittoValueResolverContext"/> entities to use whilst resolving values. /// </param> /// <returns>The <see cref="object"/> representing the Umbraco value.</returns> private static object GetResolvedValue( IPublishedContent content, CultureInfo culture, PropertyInfo propertyInfo, object instance, IEnumerable <DittoValueResolverContext> valueResolverContexts = null) { // Check the property for an associated value attribute, otherwise fall-back on expected behaviour. var valueAttr = propertyInfo.GetCustomAttribute <DittoValueResolverAttribute>(true); if (valueAttr == null) { // Check for globally registered resolver valueAttr = DittoValueResolverRegistry.Instance.GetRegisteredResolverAttributeFor(propertyInfo.PropertyType); } if (valueAttr == null) { // Default to umbraco property attribute valueAttr = new UmbracoPropertyAttribute(); } // Time custom value-resolver. using (DittoDisposableTimer.DebugDuration <object>(string.Format("Custom ValueResolver ({0}, {1})", content.Id, propertyInfo.Name))) { var resolver = (DittoValueResolver)valueAttr.ResolverType.GetInstance(); DittoValueResolverContext context = null; // Get the value from the custom attribute. // TODO: Cache these? var resolverTypeInstances = valueAttr.ResolverType.GetGenericTypeImplementations(typeof(DittoValueResolver <,>)).ToArray(); if (resolverTypeInstances.Length == 1) { var contextType = resolverTypeInstances[0].GetGenericArguments().FirstOrDefault(x => typeof(DittoValueResolverContext).IsAssignableFrom(x)); if (contextType != null) { var resolverContext = valueResolverContexts != null?valueResolverContexts.FirstOrDefault(x => x.GetType() == contextType) : null; if (resolverContext != null) { context = resolverContext; } else { context = (DittoValueResolverContext)contextType.GetInstance(); } } } // No context found so create a default one if (context == null) { context = new DittoValueResolverContext(); } // Populate internal context properties context.ConversionType = instance.GetType(); context.Instance = content; context.PropertyDescriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name]; // Resolve value return(resolver.ResolveValue(context, valueAttr, culture)); } }
/// <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 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); }