/// <summary> /// Returns the raw 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> /// <returns>The <see cref="object"/> representing the Umbraco value.</returns> /// <param name="instance">The instance to assign the value to.</param> private static object GetRawValue( IPublishedContent content, CultureInfo culture, PropertyInfo propertyInfo, object instance) { // Check the property for an associated value attribute, otherwise fall-back on expected behaviour. var valueAttr = propertyInfo.GetCustomAttribute <DittoValueResolverAttribute>(true) ?? new UmbracoPropertyAttribute(); // TODO: Only create one context and share between GetRawValue and SetTypedValue? var descriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name]; var context = new PublishedContentContext(content, descriptor); // Time custom value-resolver. using (DisposableTimer.DebugDuration <object>(string.Format("Custom ValueResolver ({0}, {1})", content.Id, propertyInfo.Name), "Complete")) { // Get the value from the custom attribute. // TODO: Cache these? var resolver = (DittoValueResolver)valueAttr.ResolverType.GetInstance(); 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> /// <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. if (culture == null) { culture = UmbracoContext.Current.PublishedContentRequest.Culture; } // 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); 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); } else { throw new InvalidOperationException("Type {0} has invalid constructor parameters"); } // 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).ToArray(); PropertyCache.TryAdd(type, properties); } var contentType = content.GetType(); foreach (var propertyInfo in properties) { using (DisposableTimer.DebugDuration(type, 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; } var umbracoPropertyName = propertyInfo.Name; var altUmbracoPropertyName = string.Empty; var recursive = false; object defaultValue = null; var umbracoPropertyAttr = propertyInfo.GetCustomAttribute <UmbracoPropertyAttribute>(); if (umbracoPropertyAttr != null) { umbracoPropertyName = umbracoPropertyAttr.PropertyName; altUmbracoPropertyName = umbracoPropertyAttr.AltPropertyName; recursive = umbracoPropertyAttr.Recursive; defaultValue = umbracoPropertyAttr.DefaultValue; } // Try fetching the value. var contentProperty = contentType.GetProperty(umbracoPropertyName); object propertyValue = contentProperty != null ? contentProperty.GetValue(content, null) : content.GetPropertyValue(umbracoPropertyName, recursive); // Try fetching the alt value. if ((propertyValue == null || propertyValue.ToString().IsNullOrWhiteSpace()) && !string.IsNullOrWhiteSpace(altUmbracoPropertyName)) { contentProperty = contentType.GetProperty(altUmbracoPropertyName); propertyValue = contentProperty != null ? contentProperty.GetValue(content, null) : content.GetPropertyValue(altUmbracoPropertyName, recursive); } // Try setting the default value. if ((propertyValue == null || propertyValue.ToString().IsNullOrWhiteSpace()) && defaultValue != null) { propertyValue = defaultValue; } // Process the value. if (propertyValue != null) { var propertyType = propertyInfo.PropertyType; var typeInfo = propertyType.GetTypeInfo(); var isEnumerableType = propertyType.IsEnumerableType() && typeInfo.GenericTypeArguments.Any(); // 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>() ?? (isEnumerableType ? typeInfo.GenericTypeArguments.First().GetCustomAttribute <TypeConverterAttribute>(true) : propertyType.GetCustomAttribute <TypeConverterAttribute>(true)); if (converterAttribute != null) { if (converterAttribute.ConverterTypeName != null) { // Time custom conversions. using (DisposableTimer.DebugDuration(type, string.Format("Custom TypeConverter ({0}, {1})", content.Id, propertyInfo.Name), "Complete")) { // Get the custom converter from the attribute and attempt to convert. var toConvert = Type.GetType(converterAttribute.ConverterTypeName); if (toConvert != null) { var converter = DependencyResolver.Current.GetService(toConvert) as TypeConverter; if (converter != null && converter.CanConvertFrom(propertyValue.GetType())) { // 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 PublishedContentContext(content, descriptor); object converted = converter.ConvertFrom(context, culture, propertyValue); // 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. if (isEnumerableType) { var parameterType = typeInfo.GenericTypeArguments.First(); // Some converters return an IEnumerable so we check again. if (!converted.GetType().IsEnumerableType()) { // Generate a method using 'Cast' to convert the type back to IEnumerable<T>. MethodInfo castMethod = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(parameterType); object enumerablePropertyValue = castMethod.Invoke(null, new object[] { converted.YieldSingleItem() }); propertyInfo.SetValue(instance, enumerablePropertyValue, null); } else { propertyInfo.SetValue(instance, converted, null); } } else { propertyInfo.SetValue(instance, converted, null); } } } } } } else if (propertyInfo.PropertyType == typeof(HtmlString)) { // Handle Html strings so we don't have to set the attribute. HtmlStringConverter converter = new HtmlStringConverter(); if (converter.CanConvertFrom(propertyValue.GetType())) { // This contains the IPublishedContent and the currently converting property descriptor. var descriptor = TypeDescriptor.GetProperties(instance)[propertyInfo.Name]; var context = new PublishedContentContext(content, descriptor); propertyInfo.SetValue(instance, converter.ConvertFrom(context, culture, propertyValue), null); } } else if (propertyInfo.PropertyType.IsInstanceOfType(propertyValue)) { // Simple types propertyInfo.SetValue(instance, propertyValue, null); } else { using (DisposableTimer.DebugDuration(type, string.Format("TypeConverter ({0}, {1})", content.Id, propertyInfo.Name), "Complete")) { var convert = propertyValue.TryConvertTo(propertyInfo.PropertyType); if (convert.Success) { propertyInfo.SetValue(instance, convert.Result, null); } } } } } } 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 GetTypedValue( 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) also. var propertyIsEnumerableType = propertyType.IsEnumerableType() && typeInfo.GenericTypeArguments.Any(); // 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 (DisposableTimer.DebugDuration <object>(string.Format("Custom TypeConverter ({0}, {1})", content.Id, propertyInfo.Name), "Complete")) { // 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 PublishedContentContext(content, 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 string. if (convertedType.IsEnumerableType() && convertedType.GenericTypeArguments.Any()) { // Use 'FirstOrDefault' to convert the type back to T. result = EnumerableInvocations.FirstOrDefault(propertyType, (IEnumerable)converted); } else { result = converted; } } } } } } } } else if (propertyInfo.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 PublishedContentContext(content, 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 (propertyInfo.PropertyType.IsInstanceOfType(propertyValue)) { // Simple types result = propertyValue; } else { using (DisposableTimer.DebugDuration <object>(string.Format("TypeConverter ({0}, {1})", content.Id, propertyInfo.Name), "Complete")) { var convert = propertyValue.TryConvertTo(propertyInfo.PropertyType); if (convert.Success) { result = convert.Result; } } } return(result); }