diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 59cd36b3f8..0f843f9565 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -336,26 +336,6 @@ namespace Umbraco.Core.Models } } - ///// - ///// Creates a clone of the current entity - ///// - ///// - //public IContent Clone() - //{ - // var clone = (Content)this.MemberwiseClone(); - // clone.Key = Guid.Empty; - // clone.Version = Guid.NewGuid(); - // clone.ResetIdentity(); - - // foreach (var property in clone.Properties) - // { - // property.ResetIdentity(); - // property.Version = clone.Version; - // } - - // return clone; - //} - /// /// Indicates whether a specific property on the current entity is dirty. /// @@ -437,13 +417,12 @@ namespace Umbraco.Core.Models } /// - /// TODO: Remove this as it's really only a shallow clone and not thread safe - /// Creates a clone of the current entity + /// Creates a deep clone of the current entity with its identity and it's property identities reset /// /// public IContent Clone() { - var clone = (Content)this.MemberwiseClone(); + var clone = (Content)DeepClone(); clone.Key = Guid.Empty; clone.Version = Guid.NewGuid(); clone.ResetIdentity(); @@ -457,17 +436,17 @@ namespace Umbraco.Core.Models return clone; } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var asContent = (Content)(object)clone; + var asContent = (Content)clone; if (Template != null) { - asContent.Template = Template.DeepClone(); + asContent.Template = (ITemplate)Template.DeepClone(); } - asContent._contentType = ContentType.DeepClone(); + asContent._contentType = (IContentType)ContentType.DeepClone(); asContent.ResetDirtyProperties(true); return clone; diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 521afeee78..75af29263f 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -448,6 +448,7 @@ namespace Umbraco.Core.Models /// /// Returns a collection of the result of the last validation process, this collection contains all invalid properties. /// + [IgnoreDataMember] internal IEnumerable LastInvalidProperties { get { return _lastInvalidProperties; } @@ -470,13 +471,13 @@ namespace Umbraco.Core.Models } } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); //cast to this object to set the complex properties var asContentBase = (IContentBase)clone; - asContentBase.Properties = Properties.DeepClone(); + asContentBase.Properties = (PropertyCollection)Properties.DeepClone(); var tracksChanges = clone as TracksChangesEntityBase; if (tracksChanges != null) diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 2d6f180ee4..12c528278b 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -175,12 +175,12 @@ namespace Umbraco.Core.Models return clone; } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var contentType = (ContentType)(object)clone; - contentType.AllowedTemplates = AllowedTemplates.Select(t => t.DeepClone()).ToArray(); + var contentType = (ContentType)clone; + contentType.AllowedTemplates = AllowedTemplates.Select(t => (ITemplate)t.DeepClone()).ToArray(); contentType.ResetDirtyProperties(true); return clone; diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index b13e1d4115..dfa34fc226 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -578,16 +578,16 @@ namespace Umbraco.Core.Models } } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var contentType = (ContentTypeBase)(object)clone; + var contentType = (ContentTypeBase)clone; contentType._additionalData = new Dictionary(); contentType._additionalData.MergeLeft(_additionalData); - contentType.AllowedContentTypes = AllowedContentTypes.Select(x => x.DeepClone()).ToList(); - contentType.PropertyGroups = PropertyGroups.DeepClone(); - contentType.PropertyTypes = PropertyTypes.Select(x => x.DeepClone()).ToList(); + contentType.AllowedContentTypes = AllowedContentTypes.Select(x => (ContentTypeSort)x.DeepClone()).ToList(); + contentType.PropertyGroups = (PropertyGroupCollection)PropertyGroups.DeepClone(); + contentType.PropertyTypes = PropertyTypes.Select(x => (PropertyType)x.DeepClone()).ToList(); contentType.ResetDirtyProperties(true); diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 138f2cff45..b8b48e7eec 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -218,13 +218,13 @@ namespace Umbraco.Core.Models .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var contentType = (ContentTypeCompositionBase)(object)clone; + var contentType = (ContentTypeCompositionBase)clone; contentType.RemovedContentTypeKeyTracker = new List(); - contentType._contentTypeComposition = ContentTypeComposition.Select(x => x.DeepClone()).ToList(); + contentType._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); contentType.ResetDirtyProperties(true); return clone; diff --git a/src/Umbraco.Core/Models/ContentTypeSort.cs b/src/Umbraco.Core/Models/ContentTypeSort.cs index 431b1e2cbe..a52b5ae0f1 100644 --- a/src/Umbraco.Core/Models/ContentTypeSort.cs +++ b/src/Umbraco.Core/Models/ContentTypeSort.cs @@ -35,12 +35,12 @@ namespace Umbraco.Core.Models public string Alias { get; set; } - public T DeepClone() where T: IDeepCloneable + public object DeepClone() { var clone = (ContentTypeSort)MemberwiseClone(); var id = Id.Value; clone.Id = new Lazy(() => id); - return (T)(IDeepCloneable)clone; + return clone; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index 3d8d7b40b3..27dad979fc 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -96,7 +96,7 @@ namespace Umbraco.Core.Models.EntityBase } /// - /// /// Gets or sets the WasCancelled flag, which is used to track + /// Gets or sets the WasCancelled flag, which is used to track /// whether some action against an entity was cancelled through some event. /// This only exists so we have a way to check if an event was cancelled through /// the new api, which also needs to take effect in the legacy api. @@ -158,6 +158,7 @@ namespace Umbraco.Core.Models.EntityBase /// /// Indicates whether the current entity has an identity, eg. Id. /// + [DataMember] public virtual bool HasIdentity { get @@ -230,13 +231,13 @@ namespace Umbraco.Core.Models.EntityBase return _hash.Value; } - public virtual T DeepClone() where T : IDeepCloneable + public virtual object DeepClone() { //Memberwise clone on Entity will work since it doesn't have any deep elements // for any sub class this will work for standard properties as well that aren't complex object's themselves. var clone = MemberwiseClone(); ((TracksChangesEntityBase)clone).ResetDirtyProperties(true); - return (T)clone; + return clone; //Using data contract serializer - has issues diff --git a/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs b/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs index 9d64e22858..62d8ba7f77 100644 --- a/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs +++ b/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs @@ -2,6 +2,6 @@ namespace Umbraco.Core.Models.EntityBase { public interface IDeepCloneable { - T DeepClone() where T : IDeepCloneable; + object DeepClone(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index ff5dac1a96..49f2557d40 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -105,11 +105,11 @@ namespace Umbraco.Core.Models /// True if file is valid, otherwise false public abstract bool IsValid(); - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var asFile = (File)(object)clone; + var asFile = (File)clone; asFile._alias = Alias; asFile._name = Name; diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 45c2a8e617..04aa545612 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -143,12 +143,12 @@ namespace Umbraco.Core.Models return _propertyType.IsPropertyValueValid(value); } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var asProperty = (Property)(object)clone; - asProperty._propertyType = PropertyType.DeepClone(); + var asProperty = (Property)clone; + asProperty._propertyType = (PropertyType)PropertyType.DeepClone(); asProperty.ResetDirtyProperties(true); var tracksChanges = clone as TracksChangesEntityBase; diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index 79ae50bc05..0ff2e13484 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -201,14 +201,14 @@ namespace Umbraco.Core.Models /// Create a deep clone of this property collection /// /// - public T DeepClone() where T: IDeepCloneable + public object DeepClone() { var newList = new PropertyCollection(); foreach (var p in this) { - newList.Add(p.DeepClone()); + newList.Add((Property)p.DeepClone()); } - return (T)(object)newList; + return newList; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 73125f576c..000ae3959a 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -159,12 +159,12 @@ namespace Umbraco.Core.Models return clone; } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var propertyGroup = (PropertyGroup)(object)clone; - propertyGroup.PropertyTypes = PropertyTypes.DeepClone(); + var propertyGroup = (PropertyGroup)clone; + propertyGroup.PropertyTypes = (PropertyTypeCollection)PropertyTypes.DeepClone(); propertyGroup.ResetDirtyProperties(true); return clone; diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index 57fad535f1..6e1847856b 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -134,14 +134,14 @@ namespace Umbraco.Core.Models } } - public T DeepClone() where T : IDeepCloneable + public object DeepClone() { var newGroup = new PropertyGroupCollection(); foreach (var p in this) { - newGroup.Add(p.DeepClone()); + newGroup.Add((PropertyGroup)p.DeepClone()); } - return (T)(object)newGroup; + return newGroup; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 0c6b169c2b..28014ed2d6 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -419,11 +419,11 @@ namespace Umbraco.Core.Models return clone; } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var asPropertyType = (PropertyType)(object)clone; + var asPropertyType = (PropertyType)clone; if (PropertyGroupId != null) { diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 34588f2e80..ea153ccae4 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -136,14 +136,14 @@ namespace Umbraco.Core.Models } } - public T DeepClone() where T : IDeepCloneable + public object DeepClone() { var newGroup = new PropertyTypeCollection(); foreach (var p in this) { - newGroup.Add(p.DeepClone()); + newGroup.Add((PropertyType)p.DeepClone()); } - return (T)(object)newGroup; + return newGroup; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 5a152bd92b..808e4627b7 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -197,11 +197,11 @@ namespace Umbraco.Core.Models MasterTemplateId = new Lazy(() => masterTemplate.Id); } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var asTemplate = (Template)(object)clone; + var asTemplate = (Template)clone; asTemplate._alias = Alias; asTemplate._name = Name; diff --git a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs index 018ef43ff6..928511e407 100644 --- a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs +++ b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs @@ -420,9 +420,9 @@ namespace Umbraco.Tests.Models.Collections return _hash.Value; }*/ - public T DeepClone() where T : IDeepCloneable + public object DeepClone() { - return (T)this.MemberwiseClone(); + return this.MemberwiseClone(); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index f35818eb7e..f676569c5f 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -6,6 +6,7 @@ using System.Web; using Moq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -111,7 +112,7 @@ namespace Umbraco.Tests.Models [Test] - public void Can_Clone_Content() + public void Can_Clone_Content_With_Reset_Identity() { // Arrange var contentType = MockedContentTypes.CreateTextpageContentType(); @@ -120,7 +121,7 @@ namespace Umbraco.Tests.Models content.Key = new Guid("29181B97-CB8F-403F-86DE-5FEB497F4800"); // Act - var clone = content.DeepClone(); + var clone = content.Clone(); // Assert Assert.AreNotSame(clone, content); @@ -131,6 +132,99 @@ namespace Umbraco.Tests.Models Assert.AreNotSame(content.Properties, clone.Properties); } + [Test] + public void Can_Deep_Clone() + { + // Arrange + var contentType = MockedContentTypes.CreateTextpageContentType(); + contentType.Id = 99; + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); + var i = 200; + foreach (var property in content.Properties) + { + property.Id = ++i; + } + content.Id = 10; + content.CreateDate = DateTime.Now; + content.CreatorId = 22; + content.ExpireDate = DateTime.Now; + content.Key = Guid.NewGuid(); + content.Language = "en"; + content.Level = 3; + content.Path = "-1,4,10"; + content.ReleaseDate = DateTime.Now; + content.ChangePublishedState(PublishedState.Published); + content.SortOrder = 5; + content.Template = new Template("-1,2,3,4", "Test Template", "testTemplate") + { + Id = 88 + }; + content.Trashed = false; + content.UpdateDate = DateTime.Now; + content.Version = Guid.NewGuid(); + content.WriterId = 23; + + ((IUmbracoEntity)content).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)content).AdditionalData.Add("test2", "hello"); + + // Act + var clone = (Content)content.DeepClone(); + + // Assert + Assert.AreNotSame(clone, content); + Assert.AreEqual(clone, content); + Assert.AreEqual(clone.Id, content.Id); + Assert.AreEqual(clone.Version, content.Version); + Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)content).AdditionalData); + Assert.AreNotSame(clone.ContentType, content.ContentType); + Assert.AreEqual(clone.ContentType, content.ContentType); + Assert.AreEqual(clone.ContentType.PropertyGroups.Count, content.ContentType.PropertyGroups.Count); + for (var index = 0; index < content.ContentType.PropertyGroups.Count; index++) + { + Assert.AreNotSame(clone.ContentType.PropertyGroups[index], content.ContentType.PropertyGroups[index]); + Assert.AreEqual(clone.ContentType.PropertyGroups[index], content.ContentType.PropertyGroups[index]); + } + Assert.AreEqual(clone.ContentType.PropertyTypes.Count(), content.ContentType.PropertyTypes.Count()); + for (var index = 0; index < content.ContentType.PropertyTypes.Count(); index++) + { + Assert.AreNotSame(clone.ContentType.PropertyTypes.ElementAt(index), content.ContentType.PropertyTypes.ElementAt(index)); + Assert.AreEqual(clone.ContentType.PropertyTypes.ElementAt(index), content.ContentType.PropertyTypes.ElementAt(index)); + } + Assert.AreEqual(clone.ContentTypeId, content.ContentTypeId); + Assert.AreEqual(clone.CreateDate, content.CreateDate); + Assert.AreEqual(clone.CreatorId, content.CreatorId); + Assert.AreEqual(clone.ExpireDate, content.ExpireDate); + Assert.AreEqual(clone.Key, content.Key); + Assert.AreEqual(clone.Language, content.Language); + Assert.AreEqual(clone.Level, content.Level); + Assert.AreEqual(clone.Path, content.Path); + Assert.AreEqual(clone.ReleaseDate, content.ReleaseDate); + Assert.AreEqual(clone.Published, content.Published); + Assert.AreEqual(clone.PublishedState, content.PublishedState); + Assert.AreEqual(clone.SortOrder, content.SortOrder); + Assert.AreEqual(clone.PublishedState, content.PublishedState); + Assert.AreNotSame(clone.Template, content.Template); + Assert.AreEqual(clone.Template, content.Template); + Assert.AreEqual(clone.Trashed, content.Trashed); + Assert.AreEqual(clone.UpdateDate, content.UpdateDate); + Assert.AreEqual(clone.Version, content.Version); + Assert.AreEqual(clone.WriterId, content.WriterId); + Assert.AreNotSame(clone.Properties, content.Properties); + Assert.AreEqual(clone.Properties.Count(), content.Properties.Count()); + for (var index = 0; index < content.Properties.Count; index++) + { + Assert.AreNotSame(clone.Properties[index], content.Properties[index]); + Assert.AreEqual(clone.Properties[index], content.Properties[index]); + } + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(content, null)); + } + } + /*[Test] public void Cannot_Change_Property_With_Invalid_Value() {