diff --git a/.gitignore b/.gitignore index d1c7557..ae71574 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,12 @@ _ReSharper.* *.userprefs *.cache *.orig -*.hash Thumbs.db .DS_Store +*.hash +*.list +.wyam/ artifacts/ src/packages/ +docs/ \ No newline at end of file diff --git a/README.md b/README.md index 1b81fc2..1cb4b43 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ There is absolutely zero intention of generating Document-Types from your view-m ## Documentation and notes -> Ditto has been developed against **Umbraco v6.2.5** and will support that version and above. +> Ditto has been developed against **Umbraco v7.3.2** and will support that version and above. +> Support for earlier versions of Umbraco (v6.2.5 and above) please use Ditto version v0.9.0. Ditto can be installed from either Our Umbraco or NuGet package repositories, or build manually from the source-code. diff --git a/appveyor.yml b/appveyor.yml index 2a02799..ecf1343 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ # version format -version: 0.9.0.{build} +version: 0.10.0.{build} # UMBRACO_PACKAGE_PRERELEASE_SUFFIX if a rtm release build this should be blank, otherwise if empty will default to alpha # example UMBRACO_PACKAGE_PRERELEASE_SUFFIX=beta @@ -24,7 +24,7 @@ deploy: # MyGet (Ditto feed) Deployment for builds & releases - provider: NuGet server: https://www.myget.org/F/umbraco-ditto/ - symbol_server: https://nuget.symbolsource.org/MyGet/umbraco-ditto + symbol_server: https://www.myget.org/F/umbraco-ditto/symbols/api/v2/package api_key: secure: Q1/4K8VSwr7BjwmKDTef8y5lOc7S+jK9ELuWy67y6OVRpjxmnF9M3Gfs1kT+ir8x artifact: /.*\.nupkg/ @@ -34,7 +34,7 @@ deploy: # MyGet (Umbraco Community feed) Deployment for builds & releases - provider: NuGet server: https://www.myget.org/F/umbraco-packages/ - symbol_server: https://nuget.symbolsource.org/MyGet/umbraco-packages + symbol_server: https://www.myget.org/F/umbraco-packages/symbols/api/v2/package api_key: secure: Q1/4K8VSwr7BjwmKDTef8y5lOc7S+jK9ELuWy67y6OVRpjxmnF9M3Gfs1kT+ir8x artifact: /.*\.nupkg/ diff --git a/build/Ditto.proj b/build/Ditto.proj deleted file mode 100644 index 223bc40..0000000 --- a/build/Ditto.proj +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - $(MSBuildProjectDirectory)\tools\MSBuildCommunityTasks - $(MSBuildProjectDirectory)\tools\MSBuildUmbracoTasks - $(MSBuildProjectDirectory)\tools\MSBuildNugetTasks - $(MSBuildProjectDirectory)\tools\AppVeyorUmbraco - - - - - - - - - - Ditto - 6.2.5 - Ditto - the friendly POCO mapper for Umbraco - Lee Kelleher, Matt Brailsford, James Jackson-South - https://github.com/leekelleher/umbraco-ditto/graphs/contributors - MIT license - http://opensource.org/licenses/MIT - https://github.com/leekelleher/umbraco-ditto - - - - - Our.Umbraco.Ditto - Copyright © 2014 Umbrella Inc Ltd, Our Umbraco and other contributors - Umbrella Inc Ltd - https://raw.githubusercontent.com/leekelleher/umbraco-ditto/master/docs/assets/img/logo.png - umbraco poco mapping strongly-typed - en-GB - false - - - - - $(APPVEYOR_BUILD_VERSION) - - - - - false - - - - - true - - - - - - - - true - - - - - false - - - - - - - Release - $(MSBuildProjectDirectory)\.. - $(MSBuildProjectDirectory)\_core - $(BuildDir)\_umbraco - $(BuildDir)\_nuget - $(RootDir)\artifacts - $(RootDir)\src\Our.Umbraco.Ditto - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/package.proj b/build/package.proj index 1f0c862..94fbf74 100644 --- a/build/package.proj +++ b/build/package.proj @@ -1,9 +1,202 @@  - + + + + + $(MSBuildProjectDirectory)\tools\MSBuildCommunityTasks + $(MSBuildProjectDirectory)\tools\MSBuildUmbracoTasks + $(MSBuildProjectDirectory)\tools\MSBuildNugetTasks + $(MSBuildProjectDirectory)\tools\AppVeyorUmbraco + + + + + + + + + + Ditto + 7.3.2 + Ditto - the friendly POCO mapper for Umbraco + Lee Kelleher, Matt Brailsford, James Jackson-South + https://github.com/leekelleher/umbraco-ditto/graphs/contributors + MIT license + http://opensource.org/licenses/MIT + https://github.com/leekelleher/umbraco-ditto + + + + + Our.Umbraco.Ditto + Copyright © 2014 Umbrella Inc Ltd, Our Umbraco and other contributors + Umbrella Inc Ltd + https://raw.githubusercontent.com/leekelleher/umbraco-ditto/master/docs/assets/img/logo.png + umbraco poco mapping strongly-typed + en-GB + false + + + + + $(APPVEYOR_BUILD_VERSION) + + + + + false + + + + + true + + + + + + + + true + + + + + false + + + + + + + Release + $(MSBuildProjectDirectory)\.. + $(MSBuildProjectDirectory)\_core + $(BuildDir)\_umbraco + $(BuildDir)\_nuget + $(RootDir)\artifacts + $(RootDir)\src\Our.Umbraco.Ditto + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto.PerformanceTests/App.config b/src/Our.Umbraco.Ditto.PerformanceTests/App.config deleted file mode 100644 index d1428ad..0000000 --- a/src/Our.Umbraco.Ditto.PerformanceTests/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/Our.Umbraco.Ditto.sln b/src/Our.Umbraco.Ditto.sln index d214f55..1e5d009 100644 --- a/src/Our.Umbraco.Ditto.sln +++ b/src/Our.Umbraco.Ditto.sln @@ -17,7 +17,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build Package", "Build Pack ..\appveyor.yml = ..\appveyor.yml ..\build-appveyor.cmd = ..\build-appveyor.cmd ..\build.cmd = ..\build.cmd - ..\build\Ditto.proj = ..\build\Ditto.proj ..\build\package.nuspec = ..\build\package.nuspec ..\build\package.proj = ..\build\package.proj ..\build\package.xml = ..\build\package.xml @@ -43,7 +42,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentat ..\docs\usage-basic.md = ..\docs\usage-basic.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Ditto.PerformanceTests", "Our.Umbraco.Ditto.PerformanceTests\Our.Umbraco.Ditto.PerformanceTests.csproj", "{7409ADFA-56F9-4A18-9208-0EEBC696F46B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Ditto.PerformanceTests", "..\tests\Our.Umbraco.Ditto.PerformanceTests\Our.Umbraco.Ditto.PerformanceTests.csproj", "{7409ADFA-56F9-4A18-9208-0EEBC696F46B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Our.Umbraco.Ditto/Common/CachedInvocations.cs b/src/Our.Umbraco.Ditto/Common/CachedInvocations.cs index 622eb15..af784e2 100644 --- a/src/Our.Umbraco.Ditto/Common/CachedInvocations.cs +++ b/src/Our.Umbraco.Ditto/Common/CachedInvocations.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; namespace Our.Umbraco.Ditto { - using System.Collections.Concurrent; - using System.Runtime.CompilerServices; - + /// + /// Internal methods for cached invocations. + /// internal class CachedInvocations { /// diff --git a/src/Our.Umbraco.Ditto/Common/DittoDisposableTimer.cs b/src/Our.Umbraco.Ditto/Common/DittoDisposableTimer.cs index 42c1a6e..9e10b5e 100644 --- a/src/Our.Umbraco.Ditto/Common/DittoDisposableTimer.cs +++ b/src/Our.Umbraco.Ditto/Common/DittoDisposableTimer.cs @@ -3,17 +3,30 @@ namespace Our.Umbraco.Ditto { + /// + /// Ditto's internal disposable timer for debugging and profiling purposes. + /// internal class DittoDisposableTimer : DisposableTimer { + /// + /// Initializes a new instance of the class. + /// + /// A callback method. public DittoDisposableTimer(Action callback) : base(callback) { } - public static new DisposableTimer DebugDuration(string startMessage) + /// + /// Profiles and tracks how long a code fragment takes, until it is disposed. + /// + /// The type of the calling class. + /// The starting message for the profiler. + /// Returns an instance of the disposable timer. + public new static DisposableTimer DebugDuration(string startMessage) { - if (Ditto.IsDebuggingEnabled) + if (Ditto.IsDebuggingEnabled && ApplicationContext.Current != null && ApplicationContext.Current.ProfilingLogger != null) { - return DisposableTimer.DebugDuration(startMessage); + return ApplicationContext.Current.ProfilingLogger.DebugDuration(startMessage); } return new DittoDisposableTimer((x) => { }); diff --git a/src/Our.Umbraco.Ditto/Common/PropertyInfoInvocations.cs b/src/Our.Umbraco.Ditto/Common/PropertyInfoInvocations.cs index 65bb464..b210af4 100644 --- a/src/Our.Umbraco.Ditto/Common/PropertyInfoInvocations.cs +++ b/src/Our.Umbraco.Ditto/Common/PropertyInfoInvocations.cs @@ -6,7 +6,7 @@ /// /// Provides a way to set a properties value using delegates. - /// This is much faster than as it avois the normal overheads of reflection. + /// This is much faster than as it avoids the normal overheads of reflection. /// Once a method is invoked for a given type then it is cached so that subsequent calls do not require /// any overhead compilation costs. /// diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoCacheAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoCacheAttribute.cs index ae7d293..37f3372 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoCacheAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoCacheAttribute.cs @@ -5,7 +5,7 @@ namespace Our.Umbraco.Ditto /// /// Represents cache configuration data in order to tell Ditto how to cache this property/class /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false)] + [AttributeUsage(Ditto.ProcessorAttributeTargets)] public sealed class DittoCacheAttribute : DittoCacheableAttribute { } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoCacheableAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoCacheableAttribute.cs index 93959f5..b3a2e21 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoCacheableAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoCacheableAttribute.cs @@ -9,6 +9,15 @@ namespace Our.Umbraco.Ditto /// public abstract class DittoCacheableAttribute : Attribute { + /// + /// Initializes a new instance of the class. + /// + protected DittoCacheableAttribute() + { + this.CacheBy = Ditto.DefaultCacheBy; + this.CacheDuration = 0; + } + /// /// Gets or sets the type of the cache key builder. /// @@ -33,35 +42,29 @@ public abstract class DittoCacheableAttribute : Attribute /// public int CacheDuration { get; set; } - /// - /// Initializes a new instance of the class. - /// - protected DittoCacheableAttribute() - { - CacheBy = Ditto.DefaultCacheBy; - CacheDuration = 0; - } - /// /// Gets the cache item. /// - /// The type of the ouput type. + /// The type of the output type. /// The cache context. /// The refresher. - /// + /// Returns the output type. /// Expected a cache key builder of type + typeof(DittoProcessorCacheKeyBuilder) + but got + CacheKeyBuilderType internal TOuputType GetCacheItem(DittoCacheContext cacheContext, Func refresher) { // If no cache duration set, just run the refresher - if (CacheDuration == 0 || Ditto.IsDebuggingEnabled) return refresher(); + if (this.CacheDuration == 0 || Ditto.IsDebuggingEnabled) + { + return refresher(); + } // Get the cache key builder type - var cacheKeyBuilderType = CacheKeyBuilderType ?? typeof(DittoDefaultCacheKeyBuilder); + var cacheKeyBuilderType = this.CacheKeyBuilderType ?? typeof(DittoDefaultCacheKeyBuilder); // Check the cache key builder type if (!typeof(DittoCacheKeyBuilder).IsAssignableFrom(cacheKeyBuilderType)) { - throw new ApplicationException("Expected a cache key builder of type " + typeof(DittoCacheKeyBuilder) + " but got " + CacheKeyBuilderType); + throw new ApplicationException("Expected a cache key builder of type " + typeof(DittoCacheKeyBuilder) + " but got " + this.CacheKeyBuilderType); } // Construct the cache key builder @@ -69,10 +72,11 @@ internal TOuputType GetCacheItem(DittoCacheContext cacheContext, Fun var cacheKey = builder.BuildCacheKey(cacheContext); // Get and cache the result - return (TOuputType)ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem(cacheKey, - () => refresher(), + return (TOuputType)ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( + cacheKey, + () => refresher(), priority: CacheItemPriority.NotRemovable, // Same as Umbraco macros - timeout: new TimeSpan(0, 0, 0, CacheDuration)); + timeout: new TimeSpan(0, 0, 0, this.CacheDuration)); } } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoDefaultProcessorAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoDefaultProcessorAttribute.cs index 55a383b..88b6f56 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoDefaultProcessorAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoDefaultProcessorAttribute.cs @@ -19,7 +19,7 @@ public DittoDefaultProcessorAttribute(Type processorType) } /// - /// Gets the type of the processor. + /// Gets or sets the type of the processor. /// /// /// The type of the processor. diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoMultiProcessorAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoMultiProcessorAttribute.cs index 3f3e8e1..1b7b664 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoMultiProcessorAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoMultiProcessorAttribute.cs @@ -1,32 +1,42 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Our.Umbraco.Ditto { /// /// Represents a multi-ditto processor capable of wrapping multiple attributes into a single attribute definition /// - [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)] + [AttributeUsage(Ditto.ProcessorAttributeTargets, AllowMultiple = true, Inherited = false)] [DittoProcessorMetaData(ValueType = typeof(object), ContextType = typeof(DittoMultiProcessorContext))] public abstract class DittoMultiProcessorAttribute : DittoProcessorAttribute { /// - /// Gets or sets the attributes. + /// Initializes a new instance of the class. /// - /// - /// The attributes. - /// - public List Attributes { get; set; } + protected DittoMultiProcessorAttribute() + { + this.Attributes = new List(); + } /// /// Initializes a new instance of the class. /// /// The attributes. protected DittoMultiProcessorAttribute(IEnumerable attributes) + : this() { - this.Attributes = new List(attributes); + this.Attributes.AddRange(attributes); } + /// + /// Gets or sets the attributes. + /// + /// + /// The attributes. + /// + protected List Attributes { get; set; } + /// /// Processes the value. /// @@ -35,19 +45,18 @@ protected DittoMultiProcessorAttribute(IEnumerable attr /// public override object ProcessValue() { - var ctx = (DittoMultiProcessorContext)Context; + var ctx = (DittoMultiProcessorContext)this.Context; - foreach (var processorAttr in Attributes) + foreach (var processorAttr in this.Attributes) { // Get the right context type var newCtx = ctx.ContextCache.GetOrCreateContext(processorAttr.ContextType); // Process value - Value = processorAttr.ProcessValue(Value, newCtx); + this.Value = processorAttr.ProcessValue(this.Value, newCtx); } - return Value; + return this.Value; } } - } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoProcessorAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoProcessorAttribute.cs index 6350037..63eb334 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoProcessorAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoProcessorAttribute.cs @@ -9,41 +9,40 @@ namespace Our.Umbraco.Ditto /// /// The abstract base class for all Ditto processor attributes /// - [AttributeUsage(Ditto.ProcessorAttributeTargets)] + [AttributeUsage(Ditto.ProcessorAttributeTargets, AllowMultiple = true)] [DittoProcessorMetaData(ValueType = typeof(object), ContextType = typeof(DittoProcessorContext))] public abstract class DittoProcessorAttribute : DittoCacheableAttribute { /// - /// Gets or sets the context. + /// Initializes a new instance of the class. /// - /// - /// The context. - /// - public object Value { get; internal set; } + protected DittoProcessorAttribute() + { + var metaData = this.GetType().GetCustomAttribute(true); + if (metaData == null) + { + throw new ApplicationException("Ditto processor attributes require a DittoProcessorMetaData attribute to be applied to the class but none was found"); + } - /// - /// Gets or sets the type of the value. - /// - /// - /// The type of the value. - /// - internal Type ValueType { get; set; } + this.ValueType = metaData.ValueType; + this.ContextType = metaData.ContextType; + } /// - /// Gets or sets the context. + /// Gets the context. /// /// /// The context. /// - public DittoProcessorContext Context { get; internal set; } + public object Value { get; protected set; } /// - /// Gets or sets the type of the context. + /// Gets the context. /// /// - /// The type of the value. + /// The context. /// - internal Type ContextType { get; set; } + public DittoProcessorContext Context { get; protected set; } /// /// Gets or sets the order. @@ -54,19 +53,20 @@ public abstract class DittoProcessorAttribute : DittoCacheableAttribute public int Order { get; set; } /// - /// Initializes a new instance of the class. + /// Gets or sets the type of the value. /// - protected DittoProcessorAttribute() - { - var metaData = this.GetType().GetCustomAttribute(true); - if (metaData == null) - { - throw new ApplicationException("Ditto processor attributes require a DittoProcessorMetaData attribute to be applied to the class but none was found"); - } + /// + /// The type of the value. + /// + internal Type ValueType { get; set; } - ValueType = metaData.ValueType; - ContextType = metaData.ContextType; - } + /// + /// Gets or sets the type of the context. + /// + /// + /// The type of the value. + /// + internal Type ContextType { get; set; } /// /// Processes the value. @@ -85,12 +85,12 @@ protected DittoProcessorAttribute() /// The representing the processed value. /// internal virtual object ProcessValue( - object value, + object value, DittoProcessorContext context) { - if (value != null && !ValueType.IsInstanceOfType(value)) + if (value != null && !this.ValueType.IsInstanceOfType(value)) { - throw new ArgumentException("Expected a value argument of type " + ValueType + " but got " + value.GetType(), "value"); + throw new ArgumentException("Expected a value argument of type " + this.ValueType + " but got " + value.GetType(), "value"); } if (context == null) @@ -98,13 +98,13 @@ internal virtual object ProcessValue( throw new ArgumentNullException("context"); } - if (!ContextType.IsInstanceOfType(context)) + if (!this.ContextType.IsInstanceOfType(context)) { - throw new ArgumentException("Expected a context argument of type " + ContextType + " but got " + context.GetType(), "context"); + throw new ArgumentException("Expected a context argument of type " + this.ContextType + " but got " + context.GetType(), "context"); } - Value = value; - Context = context; + this.Value = value; + this.Context = context; var ctx = new DittoCacheContext(this, context.Content, context.TargetType, context.PropertyDescriptor, context.Culture); return this.GetCacheItem(ctx, this.ProcessValue); diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoProcessorMetaDataAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoProcessorMetaDataAttribute.cs index e60d882..5f726b1 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoProcessorMetaDataAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Attributes/DittoProcessorMetaDataAttribute.cs @@ -9,6 +9,15 @@ namespace Our.Umbraco.Ditto [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class DittoProcessorMetaDataAttribute : Attribute { + /// + /// Initializes a new instance of the class. + /// + public DittoProcessorMetaDataAttribute() + { + this.ValueType = typeof(object); + this.ContextType = typeof(DittoProcessorContext); + } + /// /// Gets or sets the value type. /// @@ -18,21 +27,11 @@ public class DittoProcessorMetaDataAttribute : Attribute public Type ValueType { get; set; } /// - /// Gets the type of the context. + /// Gets or sets the type of the context. /// /// /// The type of the context. /// public Type ContextType { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public DittoProcessorMetaDataAttribute() - { - ValueType = typeof(object); - ContextType = typeof(DittoProcessorContext); - } } - } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheBy.cs b/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheBy.cs index c685443..717efa4 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheBy.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheBy.cs @@ -11,13 +11,11 @@ public enum DittoCacheBy /// /// The content identifier /// - /// ContentId = 1, /// /// The content version /// - /// ContentVersion = 2, /// diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheContext.cs b/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheContext.cs index 43612e9..12a423b 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheContext.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheContext.cs @@ -10,6 +10,43 @@ namespace Our.Umbraco.Ditto /// public class DittoCacheContext { + /// + /// Initializes a new instance of the class. + /// + /// The attribute. + /// The content. + /// Type of the target. + /// The culture. + internal DittoCacheContext( + DittoCacheableAttribute attribute, + IPublishedContent content, + Type targetType, + CultureInfo culture) + : this(attribute, content, targetType, null, culture) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The attribute. + /// The content. + /// Type of the target. + /// The property descriptor. + /// The culture. + internal DittoCacheContext( + DittoCacheableAttribute attribute, + IPublishedContent content, + Type targetType, + PropertyDescriptor propertyDescriptor, + CultureInfo culture) + { + this.Attribute = attribute; + this.Content = content; + this.TargetType = targetType; + this.Culture = culture; + this.PropertyDescriptor = propertyDescriptor; + } + /// /// Gets the content. /// @@ -49,40 +86,5 @@ public class DittoCacheContext /// The attribute. /// public DittoCacheableAttribute Attribute { get; internal set; } - - /// - /// Initializes a new instance of the class. - /// - /// The attribute. - /// The content. - /// Type of the target. - /// The culture. - internal DittoCacheContext(DittoCacheableAttribute attribute, - IPublishedContent content, - Type targetType, - CultureInfo culture) - : this(attribute, content, targetType, null, culture) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The attribute. - /// The content. - /// Type of the target. - /// The property descriptor. - /// The culture. - internal DittoCacheContext(DittoCacheableAttribute attribute, - IPublishedContent content, - Type targetType, - PropertyDescriptor propertyDescriptor, - CultureInfo culture) - { - Attribute = attribute; - Content = content; - TargetType = targetType; - Culture = culture; - PropertyDescriptor = propertyDescriptor; - } } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheKeyBuilder.cs b/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheKeyBuilder.cs index b4ed0ce..0528446 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheKeyBuilder.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Caching/DittoCacheKeyBuilder.cs @@ -9,7 +9,7 @@ public abstract class DittoCacheKeyBuilder /// Builds the cache key. /// /// The context. - /// + /// Returns a string representing the cache key. public abstract string BuildCacheKey(DittoCacheContext context); } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/AltUmbracoPropertyAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/AltUmbracoPropertyAttribute.cs index 2df0b59..c34b1e9 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/AltUmbracoPropertyAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/AltUmbracoPropertyAttribute.cs @@ -26,16 +26,16 @@ public AltUmbracoPropertyAttribute(string propertyName, bool recursive = false, /// public override object ProcessValue() { - if (Value.IsNullOrEmptyString()) + if (this.Value.IsNullOrEmptyString()) { // Reset value to published content - Value = Context.Content; + this.Value = this.Context.Content; // Run base processor return base.ProcessValue(); } - return Value; + return this.Value; } } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/AppSettingAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/AppSettingAttribute.cs index 5639151..80e64e8 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/AppSettingAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/AppSettingAttribute.cs @@ -9,11 +9,6 @@ namespace Our.Umbraco.Ditto /// public class AppSettingAttribute : DittoProcessorAttribute { - /// - /// Gets or sets the app setting key. - /// - public string AppSettingKey { get; protected set; } - /// /// Initializes a new instance of the class. /// @@ -23,6 +18,11 @@ public AppSettingAttribute(string appSettingKey) this.AppSettingKey = appSettingKey; } + /// + /// Gets or sets the app setting key. + /// + public string AppSettingKey { get; protected set; } + /// /// Processes the value. /// @@ -31,7 +31,7 @@ public AppSettingAttribute(string appSettingKey) /// public override object ProcessValue() { - var appSettingKey = AppSettingKey ?? (this.Context.PropertyDescriptor != null ? this.Context.PropertyDescriptor.Name : string.Empty); + var appSettingKey = this.AppSettingKey ?? (this.Context.PropertyDescriptor != null ? this.Context.PropertyDescriptor.Name : string.Empty); if (string.IsNullOrWhiteSpace(appSettingKey)) { diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoMultiProcessorContext.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoMultiProcessorContext.cs index c7ca88c..ecd7456 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoMultiProcessorContext.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoMultiProcessorContext.cs @@ -1,7 +1,7 @@ namespace Our.Umbraco.Ditto { /// - /// Represents the context for a DitoMultiProcessorAttribute + /// Represents the context for a DittoMultiProcessorAttribute /// internal class DittoMultiProcessorContext : DittoProcessorContext { diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoProcessorContext.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoProcessorContext.cs index d1b0d60..daa8e85 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoProcessorContext.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoProcessorContext.cs @@ -49,17 +49,17 @@ public class DittoProcessorContext /// Type of the target. /// The property descriptor. /// The culture. - /// + /// Returns the Ditto processors context. internal DittoProcessorContext Populate( - IPublishedContent content, - Type targetType, - PropertyDescriptor propertyDescriptor, + IPublishedContent content, + Type targetType, + PropertyDescriptor propertyDescriptor, CultureInfo culture) { - Content = content; - TargetType = targetType; - PropertyDescriptor = propertyDescriptor; - Culture = culture; + this.Content = content; + this.TargetType = targetType; + this.PropertyDescriptor = propertyDescriptor; + this.Culture = culture; return this; } diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoProcessorContextCache.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoProcessorContextCache.cs index 711f891..d2c6f7f 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoProcessorContextCache.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/Contexts/DittoProcessorContextCache.cs @@ -44,9 +44,10 @@ internal class DittoProcessorContextCache /// Type of the target. /// The property descriptor. /// The culture. - public DittoProcessorContextCache(IPublishedContent content, - Type targetType, - PropertyDescriptor propertyDescriptor, + public DittoProcessorContextCache( + IPublishedContent content, + Type targetType, + PropertyDescriptor propertyDescriptor, CultureInfo culture) { this.content = content; @@ -65,14 +66,15 @@ public DittoProcessorContextCache(IPublishedContent content, /// The property descriptor. /// The culture. /// The contexts. - public DittoProcessorContextCache(IPublishedContent content, - Type targetType, - PropertyDescriptor propertyDescriptor, - CultureInfo culture, + public DittoProcessorContextCache( + IPublishedContent content, + Type targetType, + PropertyDescriptor propertyDescriptor, + CultureInfo culture, IEnumerable contexts) : this(content, targetType, propertyDescriptor, culture) { - AddContexts(contexts); + this.AddContexts(contexts); } /// @@ -81,7 +83,10 @@ public DittoProcessorContextCache(IPublishedContent content, /// The contexts. public void AddContexts(IEnumerable contexts) { - if (contexts == null) return; + if (contexts == null) + { + return; + } foreach (var ctx in contexts) { @@ -95,17 +100,18 @@ public void AddContexts(IEnumerable contexts) /// The context. 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 + this.lookup.AddOrUpdate(context.GetType(), context.Populate(this.content, this.targetType, this.propertyDescriptor, this.culture), (type, ctx) => ctx); // Don't override if already exists } /// - /// Gets the or create. + /// Gets or creates the processor context. /// - /// Type of the contex. - /// + /// Type of the context. + /// Returns the Ditto processor context. public DittoProcessorContext GetOrCreateContext(Type contexType) { - return this.lookup.GetOrAdd(contexType, + return this.lookup.GetOrAdd( + contexType, type => ((DittoProcessorContext)contexType.GetInstance()) .Populate(this.content, this.targetType, this.propertyDescriptor, this.culture)); } diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/CurrentContentAsAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/CurrentContentAsAttribute.cs index b4ba4c3..44705a7 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/CurrentContentAsAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/CurrentContentAsAttribute.cs @@ -15,7 +15,6 @@ public class CurrentContentAsAttribute : DittoProcessorAttribute /// /// The representing the processed value. /// - /// public override object ProcessValue() { // NOTE: [LK] In order to prevent an infinite loop / stack-overflow, we check if the @@ -28,7 +27,7 @@ public override object ProcessValue() this.Context.PropertyDescriptor.PropertyType.Name)); } - return Value; + return this.Context.Content; } } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/DittoDocTypeFactoryAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/DittoDocTypeFactoryAttribute.cs new file mode 100644 index 0000000..7478640 --- /dev/null +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/DittoDocTypeFactoryAttribute.cs @@ -0,0 +1,30 @@ +using Umbraco.Core.Models; + +namespace Our.Umbraco.Ditto +{ + /// + /// Convenient processor wrapper for resolving a type, based on a content item's Doc Type alias. + /// + public class DittoDocTypeFactoryAttribute : DittoFactoryAttribute + { + /// + /// Gets or sets a prefix string to prepend onto the resolved type name. + /// + public string Prefix { get; set; } + + /// + /// Gets or sets a suffix string to append onto the resolved type name. + /// + public string Suffix { get; set; } + + /// + /// Fetches the type name from the current content item's Doc Type alias. + /// + /// The current published content. + /// The name. + public override string ResolveTypeName(IPublishedContent currentContent) + { + return string.Concat(this.Prefix, currentContent.DocumentTypeAlias, this.Suffix); + } + } +} \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/DittoFactoryAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/DittoFactoryAttribute.cs new file mode 100644 index 0000000..6b16973 --- /dev/null +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/DittoFactoryAttribute.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Our.Umbraco.Ditto +{ + /// + /// Factory processor for dynamically typing items based on properties of the item itself. + /// + [AttributeUsage(Ditto.ProcessorAttributeTargets | AttributeTargets.Interface, AllowMultiple = true)] + public abstract class DittoFactoryAttribute : DittoProcessorAttribute + { + /// + /// Resolves a type name based upon the current content item. + /// + /// The current published content. + /// The name. + public abstract string ResolveTypeName(IPublishedContent currentContent); + + /// + /// Processes the value. + /// + /// + /// The representing the processed value. + /// + public override object ProcessValue() + { + var propType = this.Context.PropertyDescriptor.PropertyType; + var propTypeIsEnumerable = propType.IsEnumerableType(); + var baseType = propTypeIsEnumerable ? propType.GetEnumerableType() : propType; + + // We have an enumerable processor that runs at the end of every conversion + // converting individual instances to IEnumerables and vica versa, so we + // won't worry about returning in the right way, rather we'll just ensure + // that the IPublishedContent's are converted to the right types + // and let the enumerable processor handle the rest. + + // TODO: Validate the base type more? + + // Find the appropreate types + // There is no non generic version of ResolveTypes so we have to + // call it via reflection. + var method = typeof(PluginManager).GetMethod("ResolveTypes"); + var generic = method.MakeGenericMethod(baseType); + var types = (IEnumerable)generic.Invoke(PluginManager.Current, new object[] { true, null }); + + // Check for IEnumerable value + var enumerableValue = this.Value as IEnumerable; + if (enumerableValue != null) + { + var items = enumerableValue.Select(x => + { + var typeName = this.ResolveTypeName(x); + var type = types.FirstOrDefault(y => y.Name.InvariantEquals(typeName)); + + return type != null ? x.As(type) : null; + }); + + return EnumerableInvocations.Cast(baseType, items); + } + + // Check for IPublishedContent value + var ipublishedContentValue = this.Value as IPublishedContent; + if (ipublishedContentValue != null) + { + var typeName = this.ResolveTypeName(ipublishedContentValue); + var type = types.FirstOrDefault(y => y.Name.InvariantEquals(typeName)); + return type != null ? ipublishedContentValue.As(type) : null; + } + + // No other possible options + return null; + } + } +} \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/DittoProcessorRegistry.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/DittoProcessorRegistry.cs index f6e4a44..8893d3e 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/DittoProcessorRegistry.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/DittoProcessorRegistry.cs @@ -15,11 +15,6 @@ internal class DittoProcessorRegistry /// private static readonly Dictionary> Cache = new Dictionary>(); - /// - /// The default processor type, (defaults to `UmbracoProperty`). - /// - private Type DefaultProcessorType = typeof(UmbracoPropertyAttribute); - /// /// Static holder for singleton instance. /// @@ -30,6 +25,11 @@ internal class DittoProcessorRegistry /// private static readonly object CacheLock = new object(); + /// + /// The default processor type, (defaults to `UmbracoProperty`). + /// + private Type defaultProcessorType = typeof(UmbracoPropertyAttribute); + /// /// Prevents a default instance of the class from being created. /// @@ -52,13 +52,13 @@ public static DittoProcessorRegistry Instance } /// - /// Registeres the default processor attribute. + /// Registers the default processor attribute. /// - /// + /// The processor attribute type. public void RegisterDefaultProcessorType() where TProcessorAttributeType : DittoProcessorAttribute, new() { - this.DefaultProcessorType = typeof(TProcessorAttributeType); + this.defaultProcessorType = typeof(TProcessorAttributeType); } /// @@ -109,7 +109,7 @@ public Type GetDefaultProcessorType(Type objectType) return attr.ProcessorType; } - return this.DefaultProcessorType; + return this.defaultProcessorType; } /// diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/EnumAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/EnumAttribute.cs index e35bbdb..6a9bd7a 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/EnumAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/EnumAttribute.cs @@ -16,26 +16,25 @@ public class EnumAttribute : DittoProcessorAttribute /// /// The representing the processed value. /// - /// public override object ProcessValue() { // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (Context == null || Context.PropertyDescriptor == null) + if (this.Context == null || this.Context.PropertyDescriptor == null) { return null; } - var propertyType = Context.PropertyDescriptor.PropertyType; + var propertyType = this.Context.PropertyDescriptor.PropertyType; - if (Value.IsNullOrEmptyString()) + if (this.Value.IsNullOrEmptyString()) { // Value types return default instance. return propertyType.GetInstance(); } - if (Value is string) + if (this.Value is string) { - string strValue = (string)Value; + string strValue = (string)this.Value; if (strValue.IndexOf(',') != -1) { long convertedValue = 0; @@ -45,7 +44,7 @@ public override object ProcessValue() foreach (string v in values) { // OR assignment. Stolen from ComponentModel EnumConverter. - convertedValue |= Convert.ToInt64((Enum)Enum.Parse(propertyType, v, true), Context.Culture); + convertedValue |= Convert.ToInt64((Enum)Enum.Parse(propertyType, v, true), this.Context.Culture); } return Enum.ToObject(propertyType, convertedValue); @@ -54,35 +53,35 @@ public override object ProcessValue() return Enum.Parse(propertyType, strValue, true); } - if (Value is int) + if (this.Value is int) { // Should handle most cases. - if (Enum.IsDefined(propertyType, Value)) + if (Enum.IsDefined(propertyType, this.Value)) { - return Enum.ToObject(propertyType, Value); + return Enum.ToObject(propertyType, this.Value); } } - if (Value != null) + if (this.Value != null) { - var valueType = Value.GetType(); + var valueType = this.Value.GetType(); if (valueType.IsEnum) { // This should work for most cases where enums base type is int. - return Enum.ToObject(propertyType, Convert.ToInt64(Value, Context.Culture)); + return Enum.ToObject(propertyType, Convert.ToInt64(this.Value, this.Context.Culture)); } if (valueType.IsEnumerableOfType(typeof(string))) { long convertedValue = 0; - var enumerable = ((IEnumerable)Value).ToList(); + var enumerable = ((IEnumerable)this.Value).ToList(); if (enumerable.Any()) { // ReSharper disable once LoopCanBeConvertedToQuery foreach (string v in enumerable) { - convertedValue |= Convert.ToInt64((Enum)Enum.Parse(propertyType, v, true), Context.Culture); + convertedValue |= Convert.ToInt64((Enum)Enum.Parse(propertyType, v, true), this.Context.Culture); } return Enum.ToObject(propertyType, convertedValue); @@ -92,14 +91,15 @@ public override object ProcessValue() } } - var enums = Value as Enum[]; + var enums = this.Value as Enum[]; if (enums != null) { long convertedValue = 0; + // ReSharper disable once LoopCanBeConvertedToQuery foreach (Enum e in enums) { - convertedValue |= Convert.ToInt64(e, Context.Culture); + convertedValue |= Convert.ToInt64(e, this.Context.Culture); } return Enum.ToObject(propertyType, convertedValue); diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/EnumerableConverterAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/EnumerableConverterAttribute.cs index 7dbd753..95bda8d 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/EnumerableConverterAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/EnumerableConverterAttribute.cs @@ -5,41 +5,56 @@ namespace Our.Umbraco.Ditto { /// - /// An enumerable Ditto processor that converts values to/from enumerables based + /// An enumerable Ditto processor that converts values to/from an enumerable based /// upon the properties target type - /// NB: It won't try to cast the inner values, just convert the enumerables so this - /// should ideally already have occured + /// NB: It won't try to cast the inner values, just convert an enumerable so this + /// should ideally already have occurred /// - internal class EnumerableConverterAttribute : DittoProcessorAttribute + public class EnumerableConverterAttribute : DittoProcessorAttribute { + /// + /// Direction of the enumerable conversion + /// + public EnumerableConvertionDirection Direction { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public EnumerableConverterAttribute() + { + // Default to automatic + Direction = EnumerableConvertionDirection.Automatic; + } + /// /// Processes the value. /// /// /// The representing the processed value. /// - /// public override object ProcessValue() { - object result = Value; + object result = this.Value; - var propertyIsEnumerableType = Context.PropertyDescriptor.PropertyType.IsEnumerableType() - && !Context.PropertyDescriptor.PropertyType.IsEnumerableOfKeyValueType() - && !(Context.PropertyDescriptor.PropertyType == typeof(string)); + var propertyIsEnumerableType = Direction == EnumerableConvertionDirection.Automatic + ? this.Context.PropertyDescriptor.PropertyType.IsEnumerableType() + && !this.Context.PropertyDescriptor.PropertyType.IsEnumerableOfKeyValueType() + && !(this.Context.PropertyDescriptor.PropertyType == typeof(string)) + : Direction == EnumerableConvertionDirection.ToEnumerable; - if (Value != null) + if (this.Value != null) { - var valueIsEnumerableType = Value.GetType().IsEnumerableType() - && !Value.GetType().IsEnumerableOfKeyValueType() - && !(Value is string); + var valueIsEnumerableType = this.Value.GetType().IsEnumerableType() + && !this.Value.GetType().IsEnumerableOfKeyValueType() + && !(this.Value is string); if (propertyIsEnumerableType) { if (!valueIsEnumerableType) { // Property is enumerable, but value isn't, so make enumerable - var arr = Array.CreateInstance(Value.GetType(), 1); - arr.SetValue(Value, 0); + var arr = Array.CreateInstance(this.Value.GetType(), 1); + arr.SetValue(this.Value, 0); result = arr; } } @@ -48,7 +63,7 @@ public override object ProcessValue() if (valueIsEnumerableType) { // Property is not enumerable, but value is, so grab first item - var enumerator = ((IEnumerable)Value).GetEnumerator(); + var enumerator = ((IEnumerable)this.Value).GetEnumerator(); result = enumerator.MoveNext() ? enumerator.Current : null; } } @@ -58,11 +73,32 @@ public override object ProcessValue() if (propertyIsEnumerableType) { // Value is null, but property is enumerable, so return empty enumerable - result = EnumerableInvocations.Empty(Context.PropertyDescriptor.PropertyType.GenericTypeArguments.First()); + result = EnumerableInvocations.Empty(this.Context.PropertyDescriptor.PropertyType.GenericTypeArguments.First()); } } return result; } } + + /// + /// Determines the direction of the numerable conversion + /// + public enum EnumerableConvertionDirection + { + /// + /// Automatically convert the value based on the target property type + /// + Automatic, + + /// + /// Convert to enumerable + /// + ToEnumerable, + + /// + /// Convert from enumerable + /// + FromEnumerable + } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/HtmlStringAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/HtmlStringAttribute.cs index 803317f..5a36541 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/HtmlStringAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/HtmlStringAttribute.cs @@ -17,16 +17,16 @@ public class HtmlStringAttribute : DittoProcessorAttribute /// public override object ProcessValue() { - if (typeof(IHtmlString).IsAssignableFrom(Context.PropertyDescriptor.PropertyType)) + if (typeof(IHtmlString).IsAssignableFrom(this.Context.PropertyDescriptor.PropertyType)) { - if (Value.IsNullOrEmptyString()) + if (this.Value.IsNullOrEmptyString()) { return null; } - if (Value is string) + if (this.Value is string) { - var text = Value.ToString(); + var text = this.Value.ToString(); if (!string.IsNullOrWhiteSpace(text)) { @@ -36,9 +36,9 @@ public override object ProcessValue() return new HtmlString(text); } - if (Value is HtmlString) + if (this.Value is HtmlString) { - var html = Value.ToString(); + var html = this.Value.ToString(); if (!string.IsNullOrWhiteSpace(html)) { @@ -48,13 +48,13 @@ public override object ProcessValue() return new HtmlString(html); } - if (Value is DynamicXml) + if (this.Value is DynamicXml) { - return ((DynamicXml)Value).ToHtml(); + return ((DynamicXml)this.Value).ToHtml(); } } - return Value; + return this.Value; } } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/RecursiveDittoAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/RecursiveDittoAttribute.cs index 93370dd..f8dc51e 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/RecursiveDittoAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/RecursiveDittoAttribute.cs @@ -15,26 +15,25 @@ internal class RecursiveDittoAttribute : DittoProcessorAttribute /// /// The representing the processed value. /// - /// public override object ProcessValue() { - var result = Value; + var result = this.Value; // If we aren't already the right type, try recursing if the type is IPublishedContent - if (Value != null && !Context.PropertyDescriptor.PropertyType.IsInstanceOfType(Value)) + if (this.Value != null && !this.Context.PropertyDescriptor.PropertyType.IsInstanceOfType(this.Value)) { - if (Value is IPublishedContent && Context.PropertyDescriptor.PropertyType.IsClass) + if (this.Value is IPublishedContent && this.Context.PropertyDescriptor.PropertyType.IsClass) { // If the property value is an IPublishedContent, then we can use Ditto to map to the target type. - result = ((IPublishedContent)Value).As(Context.PropertyDescriptor.PropertyType); + result = ((IPublishedContent)this.Value).As(this.Context.PropertyDescriptor.PropertyType); } - else if (Value != null && Value.GetType().IsEnumerableOfType(typeof(IPublishedContent)) - && Context.PropertyDescriptor.PropertyType.IsEnumerable() - && Context.PropertyDescriptor.PropertyType.GetEnumerableType() != null - && Context.PropertyDescriptor.PropertyType.GetEnumerableType().IsClass) + else if (this.Value != null && this.Value.GetType().IsEnumerableOfType(typeof(IPublishedContent)) + && this.Context.PropertyDescriptor.PropertyType.IsEnumerable() + && this.Context.PropertyDescriptor.PropertyType.GetEnumerableType() != null + && this.Context.PropertyDescriptor.PropertyType.GetEnumerableType().IsClass) { // If the property value is IEnumerable, then we can use Ditto to map to the target type. - result = ((IEnumerable)Value).As(Context.PropertyDescriptor.PropertyType.GetEnumerableType()); + result = ((IEnumerable)this.Value).As(this.Context.PropertyDescriptor.PropertyType.GetEnumerableType()); } } diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/TryConvertToAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/TryConvertToAttribute.cs index 02ff909..24ebc5a 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/TryConvertToAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/TryConvertToAttribute.cs @@ -13,17 +13,16 @@ internal class TryConvertToAttribute : DittoProcessorAttribute /// /// The representing the processed value. /// - /// public override object ProcessValue() { - var result = Value; + var result = this.Value; - if (Value != null && !Context.PropertyDescriptor.PropertyType.IsInstanceOfType(Value)) + if (this.Value != null && !this.Context.PropertyDescriptor.PropertyType.IsInstanceOfType(this.Value)) { // TODO: Maybe support enumerables? - using (DittoDisposableTimer.DebugDuration(string.Format("TryConvertTo ({0}, {1})", Context.Content.Id, Context.PropertyDescriptor.Name))) + using (DittoDisposableTimer.DebugDuration(string.Format("TryConvertTo ({0}, {1})", this.Context.Content.Id, this.Context.PropertyDescriptor.Name))) { - var convert = Value.TryConvertTo(Context.PropertyDescriptor.PropertyType); + var convert = this.Value.TryConvertTo(this.Context.PropertyDescriptor.PropertyType); if (convert.Success) { result = convert.Result; diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/UltimatePickerAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/UltimatePickerAttribute.cs index ce1011f..d1bbbf4 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/UltimatePickerAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/UltimatePickerAttribute.cs @@ -10,6 +10,7 @@ namespace Our.Umbraco.Ditto { /// /// Provides a unified way of converting ultimate picker properties to strong typed collections. + /// TODO: [JMJS] Do we need this anymore since we are not supporting Umbraco 6? /// public class UltimatePickerAttribute : DittoProcessorAttribute { @@ -22,19 +23,19 @@ public class UltimatePickerAttribute : DittoProcessorAttribute public override object ProcessValue() { // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (Context == null || Context.PropertyDescriptor == null) + if (this.Context == null || this.Context.PropertyDescriptor == null) { // There's no way to determine the type here. return null; } - var propertyType = Context.PropertyDescriptor.PropertyType; + var propertyType = this.Context.PropertyDescriptor.PropertyType; var isGenericType = propertyType.IsGenericType; var targetType = isGenericType ? propertyType.GenericTypeArguments.First() : propertyType; - if (Value.IsNullOrEmptyString()) + if (this.Value.IsNullOrEmptyString()) { if (isGenericType) { @@ -45,28 +46,28 @@ public override object ProcessValue() } // If a single item is selected, this comes back as an int, not a string. - if (Value is int) + if (this.Value is int) { - var id = (int)Value; + var id = (int)this.Value; // CheckBoxList, ListBox if (targetType != null) { - return this.ConvertContentFromInt(id, targetType, Context.Culture).YieldSingleItem(); + return this.ConvertContentFromInt(id, targetType, this.Context.Culture).YieldSingleItem(); } // AutoComplete, DropDownList, RadioButton - return this.ConvertContentFromInt(id, propertyType, Context.Culture); + return this.ConvertContentFromInt(id, propertyType, this.Context.Culture); } - if (Value != null) + if (this.Value != null) { - string s = Value as string ?? Value.ToString(); + string s = this.Value as string ?? this.Value.ToString(); if (!string.IsNullOrWhiteSpace(s)) { int n; var nodeIds = s.ToDelimitedList() - .Select(x => int.TryParse(x, NumberStyles.Any, Context.Culture, out n) ? n : -1) + .Select(x => int.TryParse(x, NumberStyles.Any, this.Context.Culture, out n) ? n : -1) .Where(x => x > 0) .ToArray(); @@ -88,11 +89,11 @@ public override object ProcessValue() // CheckBoxList, ListBox if (isGenericType) { - return ultimatePicker.As(targetType, Context.Culture); + return ultimatePicker.As(targetType, this.Context.Culture); } // AutoComplete, DropDownList, RadioButton - return ultimatePicker.As(targetType, Context.Culture).FirstOrDefault(); + return ultimatePicker.As(targetType, this.Context.Culture).FirstOrDefault(); } } } diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoDictionaryAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoDictionaryAttribute.cs index 0916e7c..e9b8f40 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoDictionaryAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoDictionaryAttribute.cs @@ -1,24 +1,10 @@ using System; +using System.Diagnostics.CodeAnalysis; using Umbraco.Core.Models; using Umbraco.Web; namespace Our.Umbraco.Ditto { - /// - /// An Umbraco dictionary. - /// - internal static class UmbracoDictionaryHelper - { - /// - /// Gets the value from the Umbraco dictionary. - /// - /// The dictionary key. - /// Returns the value. - internal static Func GetValue = (key) => UmbracoContext.Current != null - ? new UmbracoHelper(UmbracoContext.Current).GetDictionaryValue(key) - : null; - } - /// /// The Umbraco dictionary value processor attribute. /// Used for providing Umbraco with additional information about a dictionary item to aid property value processing. @@ -26,11 +12,6 @@ internal static class UmbracoDictionaryHelper [DittoProcessorMetaData(ValueType = typeof(IPublishedContent))] public class UmbracoDictionaryAttribute : DittoProcessorAttribute { - /// - /// Gets or sets the dictionary key. - /// - public string DictionaryKey { get; set; } - /// /// Initializes a new instance of the class. /// @@ -46,17 +27,21 @@ public UmbracoDictionaryAttribute(string dictionaryKey) this.DictionaryKey = dictionaryKey; } + /// + /// Gets or sets the dictionary key. + /// + public string DictionaryKey { get; set; } + /// /// Processes the value. /// /// /// The representing the processed value. /// - /// public override object ProcessValue() { - var dictionaryKey = DictionaryKey - ?? (Context.PropertyDescriptor != null ? Context.PropertyDescriptor.Name : string.Empty); + var dictionaryKey = this.DictionaryKey + ?? (this.Context.PropertyDescriptor != null ? this.Context.PropertyDescriptor.Name : string.Empty); if (string.IsNullOrWhiteSpace(dictionaryKey)) { @@ -72,4 +57,21 @@ public override object ProcessValue() return UmbracoDictionaryHelper.GetValue(dictionaryKey); } } + + /// + /// An Umbraco dictionary. + /// + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed.")] + internal static class UmbracoDictionaryHelper + { + /// + /// Gets the value from the Umbraco dictionary using a key. + /// + internal static Func GetValue = (key) => + { + return UmbracoContext.Current != null + ? new UmbracoHelper(UmbracoContext.Current).GetDictionaryValue(key) + : null; + }; + } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPickerAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPickerAttribute.cs index ac59541..f22b8b1 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPickerAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPickerAttribute.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using Umbraco.Core; @@ -9,7 +10,7 @@ namespace Our.Umbraco.Ditto { - /// + /// /// A picker processor for handling the various types of Umbraco pickers /// public class UmbracoPickerAttribute : DittoProcessorAttribute @@ -20,29 +21,28 @@ public class UmbracoPickerAttribute : DittoProcessorAttribute /// /// The representing the processed value. /// - /// public override object ProcessValue() { // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (Context == null || Context.PropertyDescriptor == null || Value.IsNullOrEmptyString()) + if (this.Context == null || this.Context.PropertyDescriptor == null || this.Value.IsNullOrEmptyString()) { return Enumerable.Empty(); } // Single IPublishedContent - IPublishedContent content = Value as IPublishedContent; + IPublishedContent content = this.Value as IPublishedContent; if (content != null) { return content; } // ReSharper disable once PossibleNullReferenceException - var type = Value.GetType(); + var type = this.Value.GetType(); // Multiple IPublishedContent if (type.IsEnumerableOfType(typeof(IPublishedContent))) { - return ((IEnumerable)Value); + return (IEnumerable)this.Value; } int[] nodeIds = { }; @@ -53,28 +53,28 @@ public override object ProcessValue() if (type.IsEnumerableOfType(typeof(string))) { int n; - nodeIds = ((IEnumerable)Value) - .Select(x => int.TryParse(x, NumberStyles.Any, Context.Culture, out n) ? n : -1) + nodeIds = ((IEnumerable)this.Value) + .Select(x => int.TryParse(x, NumberStyles.Any, this.Context.Culture, out n) ? n : -1) .ToArray(); } if (type.IsEnumerableOfType(typeof(int))) { - nodeIds = ((IEnumerable)Value).ToArray(); + nodeIds = ((IEnumerable)this.Value).ToArray(); } } // Now csv strings. if (!nodeIds.Any()) { - var s = Value as string ?? Value.ToString(); + var s = this.Value as string ?? this.Value.ToString(); if (!string.IsNullOrWhiteSpace(s)) { int n; nodeIds = XmlHelper.CouldItBeXml(s) ? s.GetXmlIds() : s.ToDelimitedList() - .Select(x => int.TryParse(x, NumberStyles.Any, Context.Culture, out n) ? n : -1) + .Select(x => int.TryParse(x, NumberStyles.Any, this.Context.Culture, out n) ? n : -1) .Where(x => x > 0) .ToArray(); } @@ -83,7 +83,7 @@ public override object ProcessValue() if (nodeIds.Any()) { var umbracoContext = UmbracoContext.Current; - var membershipHelper = new MembershipHelper(umbracoContext); + var membershipHelper = UmbracoPickerHelper.GetMembershipHelper(umbracoContext); var objectType = UmbracoObjectTypes.Unknown; var multiPicker = new List(); @@ -138,4 +138,18 @@ private IPublishedContent GetPublishedContent(int nodeId, ref UmbracoObjectTypes return content; } } + + /// + /// A helper for UmbracoPicker processor. + /// + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed.")] + internal static class UmbracoPickerHelper + { + /// + /// Gets the MembershipHelper for the UmbracoPicker processor. + /// + internal static Func GetMembershipHelper = (ctx) => UmbracoContext.Current != null + ? new MembershipHelper(ctx) + : null; + } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPropertiesAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPropertiesAttribute.cs index f04c30e..272d55f 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPropertiesAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPropertiesAttribute.cs @@ -8,6 +8,14 @@ namespace Our.Umbraco.Ditto [AttributeUsage(AttributeTargets.Class)] public class UmbracoPropertiesAttribute : Attribute { + /// + /// Initializes a new instance of the class. + /// + public UmbracoPropertiesAttribute() + { + PropertySource = Ditto.DefaultPropertySource; + } + /// /// Gets or sets the prefix. /// @@ -20,5 +28,10 @@ public class UmbracoPropertiesAttribute : Attribute /// true if [recursive]; otherwise, false. /// public bool Recursive { get; set; } + + /// + /// Gets or sets the property source from which to map values from + /// + public PropertySource PropertySource { get; set; } } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPropertyAttribute.cs b/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPropertyAttribute.cs index 0cd1bb0..bf4cd26 100644 --- a/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPropertyAttribute.cs +++ b/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPropertyAttribute.cs @@ -1,5 +1,9 @@ -using System.Reflection; +using System; +using System.Linq; +using System.Reflection; using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Web; namespace Our.Umbraco.Ditto @@ -9,6 +13,35 @@ namespace Our.Umbraco.Ditto /// public class UmbracoPropertyAttribute : DittoProcessorAttribute { + /// + /// Initializes a new instance of the class. + /// + public UmbracoPropertyAttribute() + { + PropertySource = Ditto.DefaultPropertySource; + } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the property. + /// Name of the alternative property. + /// If set to true, a recursive lookup is performed. + /// The default value. + public UmbracoPropertyAttribute( + string propertyName, + string altPropertyName = null, + bool recursive = false, + object defaultValue = null) + { + this.PropertyName = propertyName; + this.AltPropertyName = altPropertyName; + this.Recursive = recursive; + this.DefaultValue = defaultValue; + + PropertySource = Ditto.DefaultPropertySource; + } + /// /// Gets or sets the name of the property. /// @@ -42,30 +75,9 @@ public class UmbracoPropertyAttribute : DittoProcessorAttribute public object DefaultValue { get; set; } /// - /// Initializes a new instance of the class. - /// - public UmbracoPropertyAttribute() - { - } - - /// - /// Initializes a new instance of the class. + /// Gets or sets the property source from which to map values from /// - /// Name of the property. - /// Name of the alternative property. - /// if set to true recurse. - /// The default value. - public UmbracoPropertyAttribute( - string propertyName, - string altPropertyName = null, - bool recursive = false, - object defaultValue = null) - { - this.PropertyName = propertyName; - this.AltPropertyName = altPropertyName; - this.Recursive = recursive; - this.DefaultValue = defaultValue; - } + public PropertySource PropertySource { get; set; } /// /// Processes the value. @@ -73,12 +85,11 @@ public UmbracoPropertyAttribute( /// /// The representing the processed value. /// - /// public override object ProcessValue() { - var defaultValue = DefaultValue; + var defaultValue = this.DefaultValue; - var recursive = Recursive; + var recursive = this.Recursive; var propName = this.Context.PropertyDescriptor != null ? this.Context.PropertyDescriptor.Name : string.Empty; var altPropName = string.Empty; @@ -98,54 +109,41 @@ public override object ProcessValue() // Apply global recursive setting recursive |= classAttr.Recursive; + + // Apply property source only if it's different from the default, + // and the current value is the default. We only do it this + // way because if they change it at the property level, we + // want that to take precedence over the class level. + if (classAttr.PropertySource != Ditto.DefaultPropertySource + && PropertySource == Ditto.DefaultPropertySource) + { + PropertySource = classAttr.PropertySource; + } } } - var umbracoPropertyName = PropertyName ?? propName; - var altUmbracoPropertyName = AltPropertyName ?? altPropName; + var umbracoPropertyName = this.PropertyName ?? propName; + var altUmbracoPropertyName = this.AltPropertyName ?? altPropName; - var content = this.Context.Content; + var content = this.Value as IPublishedContent; if (content == null) { return defaultValue; } - var contentType = content.GetType(); object propertyValue = null; // Try fetching the value. if (!umbracoPropertyName.IsNullOrWhiteSpace()) { - var contentProperty = contentType.GetProperty(umbracoPropertyName, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static); - - if (contentProperty != null) - { - // This is more than 2x as fast as propertyValue = contentProperty.GetValue(content, null); - propertyValue = PropertyInfoInvocations.GetValue(contentProperty, content); - } - - if (propertyValue == null) - { - propertyValue = content.GetPropertyValue(umbracoPropertyName, recursive); - } + propertyValue = GetPropertyValue(content, umbracoPropertyName, recursive); } // Try fetching the alt value. if ((propertyValue == null || propertyValue.ToString().IsNullOrWhiteSpace()) && !string.IsNullOrWhiteSpace(altUmbracoPropertyName)) { - var contentProperty = contentType.GetProperty(altUmbracoPropertyName, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static); - - if (contentProperty != null) - { - // This is more than 2x as fast as propertyValue = contentProperty.GetValue(content, null); - propertyValue = PropertyInfoInvocations.GetValue(contentProperty, content); - } - - if (propertyValue == null) - { - propertyValue = content.GetPropertyValue(altUmbracoPropertyName, recursive); - } + propertyValue = GetPropertyValue(content, altUmbracoPropertyName, recursive); } // Try setting the default value. @@ -157,5 +155,111 @@ public override object ProcessValue() return propertyValue; } + + /// + /// Gets a property value from the given content object + /// + /// + /// + /// + /// + private object GetPropertyValue(IPublishedContent content, string umbracoPropertyName, bool recursive) + { + object propertyValue = null; + + if (PropertySource == PropertySource.InstanceProperties || PropertySource == PropertySource.InstanceThenUmbracoProperties) + { + propertyValue = GetClassPropertyValue(content, umbracoPropertyName); + } + + if ((propertyValue == null || propertyValue.ToString().IsNullOrWhiteSpace()) + && (PropertySource != PropertySource.InstanceProperties)) + { + propertyValue = GetUmbracoPropertyValue(content, umbracoPropertyName, recursive); + } + + if ((propertyValue == null || propertyValue.ToString().IsNullOrWhiteSpace()) + && PropertySource == PropertySource.UmbracoThenInstanceProperties) + { + propertyValue = GetClassPropertyValue(content, umbracoPropertyName); + } + + return propertyValue; + } + + /// + /// Gets a property value from the given content objects class properties + /// + /// + /// + /// + private object GetClassPropertyValue(IPublishedContent content, string umbracoPropertyName) + { + var contentType = content.GetType(); + var contentProperty = contentType.GetProperty(umbracoPropertyName, Ditto.MappablePropertiesBindingFlags); + if (contentProperty != null && contentProperty.IsMappable()) + { + if (Ditto.IsDebuggingEnabled + && PropertySource == PropertySource.InstanceThenUmbracoProperties + && Ditto.IPublishedContentProperties.Any(x => x.Name.InvariantEquals(umbracoPropertyName)) + && content.HasProperty(umbracoPropertyName)) + { + // Property is an IPublishedContent property and an umbraco property exists so warn the user + LogHelper.Warn("The property " + umbracoPropertyName + " being mapped from content type " + contentType.Name + "'s instance properties hides a property in the umbraco properties collection of the same name. It is recommended that you avoid using umbraco property aliases that conflict with IPublishedContent instance property names, but if you can't avoid this and you require access to the hidden property you can use the PropertySource parameter of the processors attribute to override the order in which properties are checked."); + } + + // This is more than 2x as fast as propertyValue = contentProperty.GetValue(content, null); + return PropertyInfoInvocations.GetValue(contentProperty, content); + } + + return null; + } + + /// + /// Gets a property value from the given content objects umbraco properties collection + /// + /// + /// + /// + /// + private object GetUmbracoPropertyValue(IPublishedContent content, string umbracoPropertyName, bool recursive) + { + if (Ditto.IsDebuggingEnabled + && PropertySource == PropertySource.UmbracoThenInstanceProperties + && Ditto.IPublishedContentProperties.Any(x => x.Name.InvariantEquals(umbracoPropertyName)) + && content.HasProperty(umbracoPropertyName)) + { + // Property is an IPublishedContent property and an umbraco property exists so warn the user + LogHelper.Warn("The property " + umbracoPropertyName + " being mapped from the umbraco properties collection hides an instance property of the same name on content type " + content.GetType().Name + ". It is recommended that you avoid using umbraco property aliases that conflict with IPublishedContent instance property names, but if you can't avoid this and you require access to the hidden property you can use the PropertySource parameter of the processors attribute to override the order in which properties are checked."); + } + + return content.GetPropertyValue(umbracoPropertyName, recursive); + } + } + + /// + /// Defines the source from which the should map values from + /// + public enum PropertySource + { + /// + /// Properties declared on the content instance only + /// + InstanceProperties, + + /// + /// Properties declared in the umbraco properties collection only + /// + UmbracoProperties, + + /// + /// Properties declared on the content instance, followed by properties in the umbraco properties collection if no value is found + /// + InstanceThenUmbracoProperties, + + /// + /// Properties declared in the umbraco properties collection, followed by the content instance properties if no value is found + /// + UmbracoThenInstanceProperties } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/Ditto.cs b/src/Our.Umbraco.Ditto/Ditto.cs index a2d50f1..4852e7c 100644 --- a/src/Our.Umbraco.Ditto/Ditto.cs +++ b/src/Our.Umbraco.Ditto/Ditto.cs @@ -1,9 +1,13 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Configuration; +using System.Linq; +using System.Reflection; using System.Web; using System.Web.Configuration; using Umbraco.Core; +using Umbraco.Core.Models; namespace Our.Umbraco.Ditto { @@ -13,15 +17,70 @@ namespace Our.Umbraco.Ditto public class Ditto { /// - /// The ditto processor attribute targets + /// The Ditto processor attribute targets /// - public const AttributeTargets ProcessorAttributeTargets = AttributeTargets.Property | AttributeTargets.Class; + public const AttributeTargets ProcessorAttributeTargets = AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Enum; /// /// The default processor cache by flags /// public static DittoCacheBy DefaultCacheBy = DittoCacheBy.ContentId | DittoCacheBy.ContentVersion | DittoCacheBy.PropertyName | DittoCacheBy.Culture; + /// + /// The default source for umbraco property mappings + /// + public static PropertySource DefaultPropertySource = PropertySource.InstanceThenUmbracoProperties; + + /// + /// The property bindings for mappable properties + /// + internal const BindingFlags MappablePropertiesBindingFlags = BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static; + + /// + /// A list of mappable properties defined on the IPublishedContent interface + /// + internal static readonly IEnumerable IPublishedContentProperties = typeof(IPublishedContent).GetProperties(MappablePropertiesBindingFlags) + .Where(x => x.IsMappable()) + .ToList(); + + /// + /// Gets a value indicating whether application is running in debug mode. + /// + /// true if debug mode; otherwise, false. + internal static bool IsDebuggingEnabled + { + get + { + try + { + // + // TODO: [LK:2016-08-12] Consider setting the value to a private field, + // so that we don't need to access the config objects for subsequent checks. + // + + // Check for app setting first + if (!ConfigurationManager.AppSettings["Ditto:DebugEnabled"].IsNullOrWhiteSpace()) + { + return ConfigurationManager.AppSettings["Ditto:DebugEnabled"].InvariantEquals("true"); + } + + // Check the HTTP Context + if (HttpContext.Current != null) + { + return HttpContext.Current.IsDebuggingEnabled; + } + + // Go and get it from config directly + var section = ConfigurationManager.GetSection("system.web/compilation") as CompilationSection; + return section != null && section.Debug; + } + catch + { + return false; + } + } + } + /// /// Registers a global conversion handler. /// @@ -76,38 +135,5 @@ public static void RegisterTypeConverter() { TypeDescriptor.AddAttributes(typeof(TObjectType), new TypeConverterAttribute(typeof(TConverterType))); } - - /// - /// Gets a value indicating whether application is running in debug mode. - /// - /// true if debug mode; otherwise, false. - internal static bool IsDebuggingEnabled - { - get - { - try - { - // Check for app setting first - if (!ConfigurationManager.AppSettings["Ditto:DebugEnabled"].IsNullOrWhiteSpace()) - { - return ConfigurationManager.AppSettings["Ditto:DebugEnabled"].InvariantEquals("true"); - } - - // Check the HTTP Context - if (HttpContext.Current != null) - { - return HttpContext.Current.IsDebuggingEnabled; - } - - // Go and get it from config directly - var section = ConfigurationManager.GetSection("system.web/compilation") as CompilationSection; - return section != null && section.Debug; - } - catch - { - return false; - } - } - } } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/Extensions/Internal/PropertyInfoExtensions.cs b/src/Our.Umbraco.Ditto/Extensions/Internal/PropertyInfoExtensions.cs index 68460be..96fbec8 100644 --- a/src/Our.Umbraco.Ditto/Extensions/Internal/PropertyInfoExtensions.cs +++ b/src/Our.Umbraco.Ditto/Extensions/Internal/PropertyInfoExtensions.cs @@ -36,5 +36,27 @@ public static bool IsVirtualAndOverridable(this PropertyInfo source) var method = source.GetGetMethod(); return method.IsVirtual && !method.IsFinal; } + + /// + /// Checks to see if a model property is mappable by Ditto + /// + /// + /// The source . + /// + /// + /// True if the is mappable; otherwise, false. + /// + public static bool IsMappable(this PropertyInfo source) + { + // Make sure source is readable + if (!source.CanRead) return false; + + // Check to make sure the get method has no parameters + var hasParams = source.GetIndexParameters().GetLength(0) > 0; + if (hasParams) return false; + + // All checks have passed so allow it to be mapped + return true; + } } } \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/Extensions/Internal/TypeInferenceExtensions.cs b/src/Our.Umbraco.Ditto/Extensions/Internal/TypeInferenceExtensions.cs index 502e736..4706f36 100644 --- a/src/Our.Umbraco.Ditto/Extensions/Internal/TypeInferenceExtensions.cs +++ b/src/Our.Umbraco.Ditto/Extensions/Internal/TypeInferenceExtensions.cs @@ -17,7 +17,7 @@ internal static class TypeInferenceExtensions /// The type. /// The generic type argument. /// - /// True if the type is an enumerable of the given argument type otherwise; false. + /// True if the type is an enumerable of the given argument type; otherwise, false. /// public static bool IsEnumerableOfType(this Type type, Type typeArgument) { @@ -29,7 +29,7 @@ public static bool IsEnumerableOfType(this Type type, Type typeArgument) /// Determines whether the specified type is a collection type. /// /// The type. - /// True if the type is a collection type otherwise; false. + /// True if the type is a collection type; otherwise, false. public static bool IsCollectionType(this Type type) { return type.TryGetElementType(typeof(ICollection<>)) != null; @@ -39,7 +39,7 @@ public static bool IsCollectionType(this Type type) /// Determines whether the specified type is an enumerable type. /// /// The type. - /// True if the type is an enumerable type otherwise; false. + /// True if the type is an enumerable type; otherwise, false. public static bool IsEnumerableType(this Type type) { return type.TryGetElementType(typeof(IEnumerable<>)) != null; @@ -56,7 +56,7 @@ public static bool IsEnumerableType(this Type type) /// The type. /// /// True if the type is an enumerable type with the generic parameter of a key/value - /// pair otherwise; false. + /// pair; otherwise, false. public static bool IsEnumerableOfKeyValueType(this Type type) { return type.TryGetElementType(typeof(IDictionary<,>)) != null || @@ -73,11 +73,11 @@ public static bool IsEnumerableOfKeyValueType(this Type type) /// /// /// The type. - /// True if the type is a cast-safe, enumerable type otherwise; false. + /// True if the type is a cast-safe, enumerable type; otherwise, false. public static bool IsCastableEnumerableType(this Type type) { // String, though enumerable have no generic arguments. - // Types with more than one generic argument cnnot be cast. + // Types with more than one generic argument cannot be cast. // Dictionary, though enumerable, requires linq to convert and shouldn't be attempted anyway. return type.IsEnumerableType() && type.GenericTypeArguments.Any() && type.GenericTypeArguments.Length == 1 @@ -168,4 +168,4 @@ public static Type GetEnumerableType(this Type type) .Select(i => i.GetGenericArguments()[0]).FirstOrDefault(); } } -} \ No newline at end of file +} diff --git a/src/Our.Umbraco.Ditto/Extensions/Internal/TypeInitializationExtensions.cs b/src/Our.Umbraco.Ditto/Extensions/Internal/TypeInitializationExtensions.cs index 90662d5..75e063f 100644 --- a/src/Our.Umbraco.Ditto/Extensions/Internal/TypeInitializationExtensions.cs +++ b/src/Our.Umbraco.Ditto/Extensions/Internal/TypeInitializationExtensions.cs @@ -6,11 +6,12 @@ using System.Web.Mvc; namespace Our.Umbraco.Ditto -{ /// - /// Extensions methods for for creating instances of types faster than - /// using reflection. Modified from the original class at. - /// - /// +{ + /// + /// Extensions methods for for creating instances of types faster than + /// using reflection. Modified from the original class at. + /// + /// internal static class TypeInitializationExtensions { /// diff --git a/src/Our.Umbraco.Ditto/Extensions/PublishedContentExtensions.cs b/src/Our.Umbraco.Ditto/Extensions/PublishedContentExtensions.cs index 11ff37e..439e9a3 100644 --- a/src/Our.Umbraco.Ditto/Extensions/PublishedContentExtensions.cs +++ b/src/Our.Umbraco.Ditto/Extensions/PublishedContentExtensions.cs @@ -10,6 +10,8 @@ namespace Our.Umbraco.Ditto { + using System.Collections; + /// /// Encapsulates extension methods for . /// @@ -448,7 +450,7 @@ private static object GetProcessedValue( /// The property descriptor. /// The default processor type. /// The processor contexts. - /// + /// Returns the processed value. private static object DoGetProcessedValue( IPublishedContent content, CultureInfo culture, @@ -469,9 +471,26 @@ private static object DoGetProcessedValue( processorAttrs.Add((DittoProcessorAttribute)defaultProcessorType.GetInstance()); } + var propertyType = propertyInfo.PropertyType; + // Check for type registered processors - processorAttrs.AddRange(propertyInfo.PropertyType.GetCustomAttributes(true) - .OrderBy(x => x.Order)); + processorAttrs.AddRange(propertyType.GetCustomAttributes(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(true) + .OrderBy(x => x.Order) + .ToList()); + + isEnumerable = true; + } // Check for globally registered processors processorAttrs.AddRange(DittoProcessorRegistry.Instance.GetRegisteredProcessorAttributesFor(propertyInfo.PropertyType)); @@ -501,7 +520,23 @@ private static object DoGetProcessedValue( currentValue = processorAttr.ProcessValue(currentValue, ctx); } - return (currentValue == null && propertyInfo.PropertyType.IsValueType) + // The following has to happen after all the processors. + if (isEnumerable && currentValue != null && currentValue.Equals(Enumerable.Empty())) + { + 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 to an empty List Collection 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; } @@ -521,7 +556,6 @@ private static void OnConverting( object instance, Action callback = null) { - OnConvert( DittoConversionHandlerType.OnConverting, content, diff --git a/src/Our.Umbraco.Ditto/Models/DittoTransferModel.cs b/src/Our.Umbraco.Ditto/Models/DittoTransferModel.cs new file mode 100644 index 0000000..be38ba0 --- /dev/null +++ b/src/Our.Umbraco.Ditto/Models/DittoTransferModel.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; + +namespace Our.Umbraco.Ditto +{ + /// + /// Temporary model for transferring values from a controller to the view + /// + internal class DittoTransferModel + { + /// + /// Initializes a new instance of the class. + /// + /// The model. + public DittoTransferModel(object model) + { + this.Model = model; + this.ProcessorContexts = new List(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The model. + /// The processor contexts. + public DittoTransferModel(object model, IEnumerable processorContexts) + { + this.Model = model; + this.ProcessorContexts = new List(processorContexts); + } + + /// + /// Gets or sets the model. + /// + /// + /// The model. + /// + public object Model { get; set; } + + /// + /// Gets or sets the processor contexts. + /// + /// + /// The processor contexts. + /// + public List ProcessorContexts { get; set; } + } +} \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/Models/DittoViewModel.cs b/src/Our.Umbraco.Ditto/Models/DittoViewModel.cs new file mode 100644 index 0000000..dd10682 --- /dev/null +++ b/src/Our.Umbraco.Ditto/Models/DittoViewModel.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.Globalization; +using Umbraco.Core.Models; +using Umbraco.Web.Models; + +namespace Our.Umbraco.Ditto +{ + /// + /// Base class for a DittoViewModel + /// + public abstract class BaseDittoViewModel : RenderModel, IDittoViewModel + { + /// + /// Initializes a new instance of the class. + /// + /// The content. + /// The culture. + /// The processor contexts. + protected BaseDittoViewModel( + IPublishedContent content, + CultureInfo culture = null, + IEnumerable processorContexts = null) + : base(content, culture) + { + this.ProcessorContexts = processorContexts ?? new List(); + } + + /// + /// Gets the current page. + /// + /// + /// The current page. + /// + public IPublishedContent CurrentPage { get { return this.Content; } } + + /// + /// Gets or sets the processor contexts. + /// + /// + /// The processor contexts. + /// + internal IEnumerable ProcessorContexts { get; set; } + } + + /// + /// Model for a DittoView + /// + /// The type of the view model. + public class DittoViewModel : BaseDittoViewModel + where TViewModel : class + { + /// + /// The view-model type. + /// + private TViewModel view; + + /// + /// Initializes a new instance of the class. + /// + /// The content. + /// The culture. + /// The processor contexts. + /// The view model. + public DittoViewModel( + IPublishedContent content, + CultureInfo culture = null, + IEnumerable processorContexts = null, + TViewModel viewModel = null) + : base(content, culture, processorContexts) + { + if (viewModel != null) + { + this.View = viewModel; + } + } + + /// + /// Gets the view model. + /// + /// + /// The view. + /// + public TViewModel View + { + get + { + if (this.view == null) + { + if (this.Content is TViewModel) + { + this.view = this.Content as TViewModel; + } + else + { + this.view = this.Content.As(processorContexts: this.ProcessorContexts); + } + } + + return this.view; + } + + internal set + { + this.view = value; + } + } + } + + /// + /// Model for a DittoView + /// + public class DittoViewModel : DittoViewModel + { + /// + /// Initializes a new instance of the class. + /// + /// The content. + /// The culture. + /// The processor contexts. + protected DittoViewModel( + IPublishedContent content, + CultureInfo culture = null, + IEnumerable processorContexts = null) + : base(content, culture, processorContexts) + { } + } +} \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/Models/IDittoViewModel.cs b/src/Our.Umbraco.Ditto/Models/IDittoViewModel.cs new file mode 100644 index 0000000..3cf9b7e --- /dev/null +++ b/src/Our.Umbraco.Ditto/Models/IDittoViewModel.cs @@ -0,0 +1,19 @@ +using Umbraco.Core.Models; +using Umbraco.Web.Models; + +namespace Our.Umbraco.Ditto +{ + /// + /// Interface for a DittoViewModel + /// + public interface IDittoViewModel : IRenderModel + { + /// + /// Gets the current page. + /// + /// + /// The current page. + /// + IPublishedContent CurrentPage { get; } + } +} \ No newline at end of file diff --git a/src/Our.Umbraco.Ditto/Our.Umbraco.Ditto.csproj b/src/Our.Umbraco.Ditto/Our.Umbraco.Ditto.csproj index 01fcfb1..2c2addd 100644 --- a/src/Our.Umbraco.Ditto/Our.Umbraco.Ditto.csproj +++ b/src/Our.Umbraco.Ditto/Our.Umbraco.Ditto.csproj @@ -43,41 +43,34 @@ False - - ..\packages\UmbracoCms.Core.6.2.5\lib\interfaces.dll - False - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll False - - ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - False - - + False - ..\packages\Microsoft.AspNet.WebApi.Core.4.0.30506.0\lib\net40\System.Web.Http.dll + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll False - + + False + ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - + False - ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - + False - ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.dll - ..\packages\UmbracoCms.Core.6.2.5\lib\umbraco.dll + ..\packages\UmbracoCms.Core.7.3.2\lib\umbraco.dll False - ..\packages\UmbracoCms.Core.6.2.5\lib\Umbraco.Core.dll + ..\packages\UmbracoCms.Core.7.3.2\lib\Umbraco.Core.dll False @@ -98,6 +91,8 @@ + + @@ -132,6 +127,9 @@ + + + @@ -141,8 +139,11 @@ + + + @@ -154,6 +155,11 @@ + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/lib/Umbraco.Tests.6.2.5/Umbraco.Tests.pdb b/tests/lib/Umbraco.Tests.6.2.5/Umbraco.Tests.pdb deleted file mode 100644 index fa2f6d4..0000000 Binary files a/tests/lib/Umbraco.Tests.6.2.5/Umbraco.Tests.pdb and /dev/null differ diff --git a/tests/lib/Umbraco.Tests.7.3.2/Umbraco.Tests.dll b/tests/lib/Umbraco.Tests.7.3.2/Umbraco.Tests.dll new file mode 100644 index 0000000..35f857e Binary files /dev/null and b/tests/lib/Umbraco.Tests.7.3.2/Umbraco.Tests.dll differ diff --git a/tests/lib/Umbraco.Tests.7.3.2/Umbraco.Tests.dll.config b/tests/lib/Umbraco.Tests.7.3.2/Umbraco.Tests.dll.config new file mode 100644 index 0000000..a5845de --- /dev/null +++ b/tests/lib/Umbraco.Tests.7.3.2/Umbraco.Tests.dll.config @@ -0,0 +1,206 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/lib/Umbraco.Tests.7.3.2/Umbraco.Tests.pdb b/tests/lib/Umbraco.Tests.7.3.2/Umbraco.Tests.pdb new file mode 100644 index 0000000..bd762d9 Binary files /dev/null and b/tests/lib/Umbraco.Tests.7.3.2/Umbraco.Tests.pdb differ