/// <summary> /// Processes the value. /// </summary> /// <param name="value">The value.</param> /// <param name="context">The context.</param> /// <param name="chainContext">The chain context.</param> /// <returns> /// The <see cref="object" /> representing the processed value. /// </returns> internal virtual object ProcessValue( object value, DittoProcessorContext context, DittoChainContext chainContext) { if (value != null && this.ValueType.IsInstanceOfType(value) == false) { throw new ArgumentException($"Expected a value argument of type {this.ValueType} but got {value.GetType()}", "value"); } if (context == null) { throw new ArgumentNullException("context"); } if (this.ContextType.IsInstanceOfType(context) == false) { throw new ArgumentException($"Expected a context argument of type {this.ContextType} but got {context.GetType()}", "context"); } if (chainContext == null) { throw new ArgumentNullException("chainContext"); } this.Value = value; this.Context = context; this.ChainContext = chainContext; var ctx = new DittoCacheContext(this, context.Content, context.TargetType, context.PropertyInfo, context.Culture); return(this.GetCacheItem(ctx, this.ProcessValue)); }
/// <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> /// Processes the value. /// </summary> /// <param name="value">The value.</param> /// <param name="context">The context.</param> /// <returns> /// The <see cref="object" /> representing the processed value. /// </returns> internal virtual object ProcessValue( object value, DittoProcessorContext context) { if (value != null && !ValueType.IsInstanceOfType(value)) { throw new ArgumentException("Expected a value argument of type " + ValueType + " but got " + value.GetType(), "value"); } if (context == null) { throw new ArgumentNullException("context"); } if (!ContextType.IsInstanceOfType(context)) { throw new ArgumentException("Expected a context argument of type " + ContextType + " but got " + context.GetType(), "context"); } Value = value; Context = context; var ctx = new DittoCacheContext(this, context.Content, context.TargetType, context.PropertyDescriptor, context.Culture); return(this.GetCacheItem(ctx, this.ProcessValue)); }
/// <summary> /// Populates the core context fields. /// </summary> /// <param name="baseProcessorContext">The base processor content.</param> /// <returns>Returns the Ditto processors context.</returns> internal DittoProcessorContext Populate(DittoProcessorContext baseProcessorContext) { Content = baseProcessorContext.Content; TargetType = baseProcessorContext.TargetType; PropertyDescriptor = baseProcessorContext.PropertyDescriptor; Culture = baseProcessorContext.Culture; return(this); }
/// <summary> /// Gets or creates the <see cref="DittoProcessorContext" />. /// </summary> /// <param name="baseContext">The base context.</param> /// <param name="contextType">The object type of the content.</param> /// <returns>Returns the the <see cref="DittoProcessorContext" />.</returns> public DittoProcessorContext GetOrCreate(DittoProcessorContext baseContext, Type contextType) { // Get, clone and populate the relevant context for the given level var ctx = _processorContexts .GetOrAdd(contextType, type => (DittoProcessorContext)contextType.GetInstance()) .Clone() .Populate(baseContext); return(ctx); }
/// <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 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 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); }
/// <summary> /// Processes the value. /// </summary> /// <param name="value">The value.</param> /// <param name="context">The context.</param> /// <returns> /// The <see cref="object" /> representing the processed value. /// </returns> internal virtual object ProcessValue( object value, DittoProcessorContext context) { return(this.ProcessValue(value, context, new DittoChainContext(new[] { context }))); }
/// <summary> /// Adds a processor context to the collection chain. /// </summary> /// <param name="ctx">The processor context.</param> public void Add(DittoProcessorContext ctx) { _processorContexts.AddOrUpdate(ctx.GetType(), ctx, (type, ctx2) => ctx2); // Don't override if already exists }
/// <summary> /// Adds the context. /// </summary> /// <param name="context">The context.</param> public void AddContext(DittoProcessorContext context) { this.lookup.AddOrUpdate(context.GetType(), context.Populate(content, targetType, propertyDescriptor, culture), (type, ctx) => ctx); // Don't override if already exists }