diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs
index 7523555c24..c1b45f63ce 100644
--- a/src/Umbraco.Core/Models/DeepCloneHelper.cs
+++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs
@@ -9,10 +9,30 @@ namespace Umbraco.Core.Models
{
public static class DeepCloneHelper
{
+ ///
+ /// Stores the metadata for the properties for a given type so we know how to create them
+ ///
+ private struct ClonePropertyInfo
+ {
+ public ClonePropertyInfo(PropertyInfo propertyInfo) : this()
+ {
+ if (propertyInfo == null) throw new ArgumentNullException("propertyInfo");
+ PropertyInfo = propertyInfo;
+ }
+
+ public PropertyInfo PropertyInfo { get; private set; }
+ public bool IsDeepCloneable { get; set; }
+ public Type GenericListType { get; set; }
+ public bool IsList
+ {
+ get { return GenericListType != null; }
+ }
+ }
+
///
/// Used to avoid constant reflection (perf)
///
- private static readonly ConcurrentDictionary PropCache = new ConcurrentDictionary();
+ private static readonly ConcurrentDictionary PropCache = new ConcurrentDictionary();
///
/// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output')
@@ -30,81 +50,99 @@ namespace Umbraco.Core.Models
throw new InvalidOperationException("Both the input and output types must be the same");
}
+ //get the property metadata from cache so we only have to figure this out once per type
var refProperties = PropCache.GetOrAdd(inputType, type =>
inputType.GetProperties()
- .Where(x =>
- //is not attributed with the ignore clone attribute
- x.GetCustomAttribute() == null
+ .Select(propertyInfo =>
+ {
+ if (
+ //is not attributed with the ignore clone attribute
+ propertyInfo.GetCustomAttribute() != null
//reference type but not string
- && x.PropertyType.IsValueType == false && x.PropertyType != typeof (string)
+ || propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof (string)
//settable
- && x.CanWrite
+ || propertyInfo.CanWrite == false
//non-indexed
- && x.GetIndexParameters().Any() == false)
+ || propertyInfo.GetIndexParameters().Any())
+ {
+ return null;
+ }
+
+
+ if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType))
+ {
+ return new ClonePropertyInfo(propertyInfo) { IsDeepCloneable = true };
+ }
+
+ if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)
+ && TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) == false)
+ {
+ if (propertyInfo.PropertyType.IsGenericType
+ && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
+ || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
+ || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)))
+ {
+ //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<>
+ var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments());
+ return new ClonePropertyInfo(propertyInfo) { GenericListType = genericType };
+ }
+ if (propertyInfo.PropertyType.IsArray
+ || (propertyInfo.PropertyType.IsInterface && propertyInfo.PropertyType.IsGenericType == false))
+ {
+ //if its an array, we'll create a list to work with first and then convert to array later
+ //otherwise if its just a regular derivitave of IEnumerable, we can use a list too
+ return new ClonePropertyInfo(propertyInfo) { GenericListType = typeof(List