/// <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)); } } }
public static DittoTypeInfo GetOrAdd(Type type) { if (_cache.TryGetValue(type, out DittoTypeInfo config) == false) { config = DittoTypeInfo.Create(type); _cache.TryAdd(type, config); } return(config); }
/// <summary>Fires off the various on converted events.</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 culture.</param> /// <param name="instance">The instance to assign the value to.</param> /// <param name="callback">The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.</param> private static void OnConverted( IPublishedContent content, DittoTypeInfo config, CultureInfo culture, object instance, Action <DittoConversionHandlerContext> callback) { OnConvert <DittoOnConvertedAttribute>( DittoConversionHandlerType.OnConverted, content, config, culture, instance, callback); }
/// <summary>Convenience method for calling converting/converter handlers.</summary> /// <typeparam name="TAttributeType">The type of the attribute type.</typeparam> /// <param name="conversionType">Type of the conversion.</param> /// <param name="content">The content.</param> /// <param name="config">The Ditto configuration for the type.</param> /// <param name="culture">The culture.</param> /// <param name="instance">The instance.</param> /// <param name="callback">The callback.</param> private static void OnConvert <TAttributeType>( DittoConversionHandlerType conversionType, IPublishedContent content, DittoTypeInfo config, CultureInfo culture, object instance, Action <DittoConversionHandlerContext> callback) where TAttributeType : Attribute { // Trigger conversion handlers var conversionCtx = new DittoConversionHandlerContext { Content = content, Culture = culture, ModelType = config.TargetType, Model = instance }; // Run the registered handlers foreach (var handler in config.ConversionHandlers) { handler.Run(conversionCtx, conversionType); } var methods = conversionType == DittoConversionHandlerType.OnConverting ? config.ConvertingMethods : config.ConvertedMethods; if (methods.Any()) { foreach (var method in methods) { // TODO: Review this, `Invoke` could be CPU heavy?! method.Invoke(instance, new object[] { conversionCtx }); // Could we use a RuntimeMethodHandle? // https://web.archive.org/web/20150118044646/http://msdn.microsoft.com:80/en-us/magazine/cc163759.aspx#S8 } } // Check for a callback function callback?.Invoke(conversionCtx); }
public static DittoTypeInfo Create(Type type) { var config = new DittoTypeInfo { TargetType = type }; // constructor // // Check the validity of the mapped type constructor as early as possible. var constructorParams = type.GetConstructorParameters(); if (constructorParams != null) { // Is it a PublishedContent or similar? if (constructorParams.Length == 1 && constructorParams[0].ParameterType == typeof(IPublishedContent)) { config.ConstructorHasPublishedContentParameter = true; } if (constructorParams.Length == 0 || config.ConstructorHasPublishedContentParameter) { config.ConstructorIsValid = true; } } // No valid constructor, but see if the value can be cast to the type if (type.IsAssignableFrom(typeof(IPublishedContent))) { config.IsOfTypePublishedContent = true; config.ConstructorIsValid = true; } // attributes // config.CustomAttributes = type.GetCustomAttributes(); // cacheable // var conversionHandlers = new List <DittoConversionHandler>(); foreach (var attr in config.CustomAttributes) { if (attr is DittoCacheAttribute) { config.IsCacheable = true; config.CacheInfo = (DittoCacheAttribute)attr; } // Check for class level DittoConversionHandlerAttribute if (attr is DittoConversionHandlerAttribute) { conversionHandlers.Add(((DittoConversionHandlerAttribute)attr).HandlerType.GetInstance <DittoConversionHandler>()); } } // properties (lazy & eager) // var lazyProperties = new List <DittoTypePropertyInfo>(); var lazyPropertyNames = new List <string>(); var eagerProperties = new List <DittoTypePropertyInfo>(); // Collect all the properties of the given type and loop through writable ones. foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (property.CanWrite == false) { continue; } if (property.GetSetMethod() == null) { continue; } var attributes = new List <Attribute>(property.GetCustomAttributes()); if (attributes.Any(x => x is DittoIgnoreAttribute)) { continue; } var propertyType = property.PropertyType; var propertyConfig = new DittoTypePropertyInfo { CustomAttributes = attributes, PropertyInfo = property, }; // Check the property for any explicit processor attributes var processors = attributes.Where(x => x is DittoProcessorAttribute).Cast <DittoProcessorAttribute>().ToList(); if (processors.Count == 0) { // Adds the default processor for this conversion var defaultProcessor = DittoProcessorRegistry.Instance.GetDefaultProcessorFor(type); // Forces the default processor to be the very first processor defaultProcessor.Order = -1; processors.Add(defaultProcessor); } // Check for registered processors on the property's type processors.AddRange(propertyType.GetCustomAttributes <DittoProcessorAttribute>(true)); // Check any type arguments in generic enumerable types. // This should return false against typeof(string) etc also. if (propertyType.IsCastableEnumerableType()) { propertyConfig.IsEnumerable = true; propertyConfig.EnumerableType = propertyType.GenericTypeArguments[0]; processors.AddRange(propertyConfig.EnumerableType.GetCustomAttributes <DittoProcessorAttribute>(true)); } // Sort the order of the processors processors.Sort((x, y) => x.Order.CompareTo(y.Order)); // Check for globally registered processors processors.AddRange(DittoProcessorRegistry.Instance.GetRegisteredProcessorAttributesFor(propertyType)); // Add any core processors onto the end processors.AddRange(DittoProcessorRegistry.Instance.GetPostProcessorAttributes()); propertyConfig.Processors = processors; var propertyCache = attributes.Where(x => x is DittoCacheAttribute).Cast <DittoCacheAttribute>().FirstOrDefault(); if (propertyCache != null) { propertyConfig.IsCacheable = true; propertyConfig.CacheInfo = propertyCache; } // detect if the property should be lazy-loaded if (property.ShouldAttemptLazyLoad()) { lazyProperties.Add(propertyConfig); lazyPropertyNames.Add(property.Name); } else { eagerProperties.Add(propertyConfig); } } if (lazyProperties.Count > 0) { config.ConstructorRequiresProxyType = true; config.HasLazyProperties = true; config.LazyPropertyNames = lazyPropertyNames; // lazyProperties.Select(x => x.PropertyInfo.Name); config.LazyProperties = lazyProperties; } if (eagerProperties.Count > 0) { config.HasEagerProperties = true; config.EagerProperties = eagerProperties; } // conversion handlers // // Check for globaly registered handlers foreach (var handlerType in DittoConversionHandlerRegistry.Instance.GetRegisteredHandlerTypesFor(type)) { conversionHandlers.Add(handlerType.GetInstance <DittoConversionHandler>()); } config.ConversionHandlers = conversionHandlers; var before = new List <MethodInfo>(); var after = new List <MethodInfo>(); // Check for method level DittoOnConvert[ing|ed]Attribute foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { var ing = method.GetCustomAttribute <DittoOnConvertingAttribute>(); var ed = method.GetCustomAttribute <DittoOnConvertedAttribute>(); if (ing == null && ed == null) { continue; } var p = method.GetParameters(); if (p.Length == 1 && p[0].ParameterType == typeof(DittoConversionHandlerContext)) { if (ing != null) { before.Add(method); } if (ed != null) { after.Add(method); } } } config.ConvertingMethods = before; config.ConvertedMethods = after; return(config); }
/// <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); }
public static void Add(Type type) { _cache.TryAdd(type, DittoTypeInfo.Create(type)); }