diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index 1278ff0e33..c1f82b0d89 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -53,5 +53,6 @@ namespace Umbraco.Core.Cache public const string StylesheetPropertyCacheKey = "UmbracoStylesheetProperty"; public const string DataTypeCacheKey = "UmbracoDataTypeDefinition"; + public const string DataTypePreValuesCacheKey = "UmbracoPreVal"; } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs index 1776913880..474fa00a8b 100644 --- a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs +++ b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs @@ -41,6 +41,12 @@ namespace Umbraco.Core.Cache return result.Select(x => x.TryConvertTo().Result); } + public static IEnumerable GetCacheItemsByKeyExpression(this ICacheProvider provider, string regexString) + { + var result = provider.GetCacheItemsByKeyExpression(regexString); + return result.Select(x => x.TryConvertTo().Result); + } + public static T GetCacheItem(this ICacheProvider provider, string cacheKey) { var result = provider.GetCacheItem(cacheKey); diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs index cec4c2ffa3..e88a51021a 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs @@ -171,6 +171,26 @@ namespace Umbraco.Core.Cache select c.Value).ToList(); } + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + var found = new List(); + foreach (var item in DictionaryCache) + { + var c = new DictionaryItemWrapper(item); + var s = c.Key as string; + if (s != null) + { + var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix)); + if (Regex.IsMatch(withoutPrefix, regexString)) + { + found.Add(c.Value); + } + } + } + + return found; + } + /// /// Returns a cache item by key, does not update the cache if it isn't there. /// diff --git a/src/Umbraco.Core/Cache/ICacheProvider.cs b/src/Umbraco.Core/Cache/ICacheProvider.cs index ff947b0ae8..6cec619be8 100644 --- a/src/Umbraco.Core/Cache/ICacheProvider.cs +++ b/src/Umbraco.Core/Cache/ICacheProvider.cs @@ -16,7 +16,15 @@ namespace Umbraco.Core.Cache void ClearCacheByKeySearch(string keyStartsWith); void ClearCacheByKeyExpression(string regexString); IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); + IEnumerable GetCacheItemsByKeyExpression(string regexString); + + /// + /// Returns an item with a given key + /// + /// + /// object GetCacheItem(string cacheKey); + object GetCacheItem(string cacheKey, Func getCacheItem); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs index ba88637661..f4e449499b 100644 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ b/src/Umbraco.Core/Cache/NullCacheProvider.cs @@ -43,6 +43,11 @@ namespace Umbraco.Core.Cache return Enumerable.Empty(); } + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return Enumerable.Empty(); + } + public virtual object GetCacheItem(string cacheKey) { return default(object); diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index 8e8d61e9b4..7b1053c8a4 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -114,6 +114,13 @@ namespace Umbraco.Core.Cache select c.Value).ToList(); } + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return (from c in MemoryCache + where Regex.IsMatch(c.Key, regexString) + select c.Value).ToList(); + } + public virtual object GetCacheItem(string cacheKey) { var result = MemoryCache.Get(cacheKey); diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs index cd58fc3dde..9c448efa6a 100644 --- a/src/Umbraco.Core/Cache/StaticCacheProvider.cs +++ b/src/Umbraco.Core/Cache/StaticCacheProvider.cs @@ -59,6 +59,13 @@ namespace Umbraco.Core.Cache select c.Value).ToList(); } + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return (from KeyValuePair c in StaticCache + where Regex.IsMatch(c.Key, regexString) + select c.Value).ToList(); + } + public virtual object GetCacheItem(string cacheKey) { var result = StaticCache[cacheKey]; diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 1eecd5a877..dbfd446c39 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -149,7 +149,14 @@ namespace Umbraco.Core /// public void ConfigureDatabaseConnection(string connectionString) { - SaveConnectionString(connectionString, string.Empty); + var provider = DbConnectionExtensions.DetectProviderFromConnectionString(connectionString); + var databaseProvider = provider.ToString(); + var providerName = "System.Data.SqlClient"; + if (databaseProvider.ToLower().Contains("mysql")) + { + providerName = "MySql.Data.MySqlClient"; + } + SaveConnectionString(connectionString, providerName); Initialize(string.Empty); } @@ -169,7 +176,7 @@ namespace Umbraco.Core SaveConnectionString(connectionString, providerName); Initialize(providerName); } - + public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName) { providerName = "System.Data.SqlClient"; diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs index 0637864ceb..8439278b2e 100644 --- a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs +++ b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs @@ -36,42 +36,22 @@ namespace Umbraco.Core.Dynamics [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, IEnumerable needles) { - if (haystack == null) throw new ArgumentNullException("haystack"); - if (!string.IsNullOrEmpty(haystack) || needles.Any()) - { - return needles.Any(haystack.Contains); - } - return false; + return StringExtensions.ContainsAny(haystack, needles); } [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, params string[] needles) { - if (haystack == null) throw new ArgumentNullException("haystack"); - if (!string.IsNullOrEmpty(haystack) || needles.Length > 0) - { - return needles.Any(haystack.Contains); - } - return false; + return StringExtensions.ContainsAny(haystack, needles); } [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, StringComparison comparison, IEnumerable needles) { - if (haystack == null) throw new ArgumentNullException("haystack"); - if (!string.IsNullOrEmpty(haystack) || needles.Any()) - { - return needles.Any(value => haystack.IndexOf(value, comparison) >= 0); - } - return false; + return StringExtensions.ContainsAny(haystack, needles, comparison); } [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, StringComparison comparison, params string[] needles) { - if (haystack == null) throw new ArgumentNullException("haystack"); - if (!string.IsNullOrEmpty(haystack) || needles.Length > 0) - { - return needles.Any(value => haystack.IndexOf(value, comparison) >= 0); - } - return false; + return StringExtensions.ContainsAny(haystack, needles, comparison); } [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsInsensitive(this string haystack, string needle) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 2223d45cd8..05b78fe5dc 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -232,6 +232,7 @@ namespace Umbraco.Core.Models /// /// This Property is kept internal until localization is introduced. /// + [DataMember] internal string NodeName { get { return _nodeName; } @@ -248,6 +249,7 @@ namespace Umbraco.Core.Models /// /// Used internally to track if permissions have been changed during the saving process for this entity /// + [IgnoreDataMember] internal bool PermissionsChanged { get { return _permissionsChanged; } @@ -314,6 +316,7 @@ namespace Umbraco.Core.Models PublishedState = state; } + [DataMember] internal PublishedState PublishedState { get; set; } /// @@ -333,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. /// @@ -432,5 +415,37 @@ namespace Umbraco.Core.Models base.UpdatingEntity(); Version = Guid.NewGuid(); } + + /// + /// Creates a deep clone of the current entity with its identity and it's property identities reset + /// + /// + public IContent Clone() + { + var clone = (Content)DeepClone(); + clone.Key = Guid.Empty; + clone.Version = Guid.NewGuid(); + clone.ResetIdentity(); + + foreach (var property in clone.Properties) + { + property.ResetIdentity(); + property.Version = clone.Version; + } + + return clone; + } + + public override object DeepClone() + { + var clone = (Content)base.DeepClone(); + + //need to manually clone this since it's not settable + clone._contentType = (IContentType)ContentType.DeepClone(); + clone.ResetDirtyProperties(false); + + return clone; + + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index c18c7c3a43..e4cc256d1b 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -15,10 +15,13 @@ namespace Umbraco.Core.Models /// /// Represents an abstract class for base Content properties and methods /// + [Serializable] + [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] public abstract class ContentBase : Entity, IContentBase { protected IContentTypeComposition ContentTypeBase; + private Lazy _parentId; private string _name;//NOTE Once localization is introduced this will be the localized Name of the Content/Media. private int _sortOrder; @@ -257,6 +260,7 @@ namespace Umbraco.Core.Models } private readonly IDictionary _additionalData; + /// /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// @@ -445,6 +449,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; } @@ -466,5 +471,6 @@ namespace Umbraco.Core.Models prop.ResetDirtyProperties(rememberPreviouslyChangedProperties); } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 46bf9bdc18..710efb9e3a 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -122,6 +122,28 @@ namespace Umbraco.Core.Models } /// + /// Method to call when Entity is being saved + /// + /// Created date is set and a Unique key is assigned + internal override void AddingEntity() + { + base.AddingEntity(); + + if(Key == Guid.Empty) + Key = Guid.NewGuid(); + } + + /// + /// Method to call when Entity is being updated + /// + /// Modified Date is set and a new Version guid is set + internal override void UpdatingEntity() + { + base.UpdatingEntity(); + } + + /// + /// //TODO: REmove this as it's mostly just a shallow clone and not thread safe /// Creates a clone of the current entity /// /// @@ -153,25 +175,5 @@ namespace Umbraco.Core.Models return clone; } - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if(Key == Guid.Empty) - Key = Guid.NewGuid(); - } - - /// - /// Method to call when Entity is being updated - /// - /// Modified Date is set and a new Version guid is set - internal override void UpdatingEntity() - { - base.UpdatingEntity(); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index f22c72f34d..953ef5ccce 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -319,7 +319,7 @@ namespace Umbraco.Core.Models } } - private readonly IDictionary _additionalData; + private IDictionary _additionalData; /// /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 8e4583adb8..c73ae8ee95 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public abstract class ContentTypeCompositionBase : ContentTypeBase, IContentTypeComposition { - private readonly List _contentTypeComposition = new List(); + private List _contentTypeComposition = new List(); internal List RemovedContentTypeKeyTracker = new List(); protected ContentTypeCompositionBase(int parentId) : base(parentId) @@ -217,5 +217,17 @@ namespace Umbraco.Core.Models .Select(x => x.Id) .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); } + + public override object DeepClone() + { + var clone = (ContentTypeCompositionBase)base.DeepClone(); + + //need to manually assign since this is an internal field and will not be automatically mapped + clone.RemovedContentTypeKeyTracker = new List(); + clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); + clone.ResetDirtyProperties(false); + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentTypeSort.cs b/src/Umbraco.Core/Models/ContentTypeSort.cs index 6790f15fb0..5aa81d9db0 100644 --- a/src/Umbraco.Core/Models/ContentTypeSort.cs +++ b/src/Umbraco.Core/Models/ContentTypeSort.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Models /// /// Represents a POCO for setting sort order on a ContentType reference /// - public class ContentTypeSort : IValueObject + public class ContentTypeSort : IValueObject, IDeepCloneable { public ContentTypeSort() { @@ -33,5 +33,36 @@ namespace Umbraco.Core.Models /// Gets or sets the Alias of the ContentType /// public string Alias { get; set; } + + + public object DeepClone() + { + var clone = (ContentTypeSort)MemberwiseClone(); + var id = Id.Value; + clone.Id = new Lazy(() => id); + return clone; + } + + protected bool Equals(ContentTypeSort other) + { + return Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ContentTypeSort) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Id.GetHashCode()*397) ^ Alias.GetHashCode(); + } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs new file mode 100644 index 0000000000..aaee03f963 --- /dev/null +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Models +{ + public static class DeepCloneHelper + { + /// + /// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output') + /// + /// + /// + /// + public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable output) + { + var inputType = input.GetType(); + var outputType = output.GetType(); + + if (inputType != outputType) + { + throw new InvalidOperationException("Both the input and output types must be the same"); + } + + var refProperties = inputType.GetProperties() + .Where(x => + //reference type but not string + x.PropertyType.IsValueType == false && x.PropertyType != typeof (string) + //settable + && x.CanWrite + //non-indexed + && x.GetIndexParameters().Any() == false); + + foreach (var propertyInfo in refProperties) + { + if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)) + { + //this ref property is also deep cloneable so clone it + var result = (IDeepCloneable)propertyInfo.GetValue(input, null); + + if (result != null) + { + //set the cloned value to the property + propertyInfo.SetValue(output, result.DeepClone(), null); + } + } + else if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) + && TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) == false) + { + IList newList; + 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()); + newList = (IList)Activator.CreateInstance(genericType); + } + else 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 + newList = new List(); + } + else + { + //its a custom IEnumerable, we'll try to create it + try + { + var custom = Activator.CreateInstance(propertyInfo.PropertyType); + //if it's an IList we can work with it, otherwise we cannot + newList = custom as IList; + if (newList == null) + { + continue; + } + } + catch (Exception) + { + //could not create this type so we'll skip it + continue; + } + } + + var enumerable = (IEnumerable)propertyInfo.GetValue(input, null); + if (enumerable == null) continue; + + var isUsableType = true; + + //now clone each item + foreach (var o in enumerable) + { + //first check if the item is deep cloneable and copy that way + var dc = o as IDeepCloneable; + if (dc != null) + { + newList.Add(dc.DeepClone()); + } + else if (o is string || o.GetType().IsValueType) + { + //check if the item is a value type or a string, then we can just use it + newList.Add(o); + } + else + { + //this will occur if the item is not a string or value type or IDeepCloneable, in this case we cannot + // clone each element, we'll need to skip this property, people will have to manually clone this list + isUsableType = false; + break; + } + } + + //if this was not usable, skip this property + if (isUsableType == false) + { + continue; + } + + if (propertyInfo.PropertyType.IsArray) + { + //need to convert to array + var arr = (object[])Activator.CreateInstance(propertyInfo.PropertyType, newList.Count); + for (int i = 0; i < newList.Count; i++) + { + arr[i] = newList[i]; + } + //set the cloned collection + propertyInfo.SetValue(output, arr, null); + } + else + { + //set the cloned collection + propertyInfo.SetValue(output, newList, null); + } + + } + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index c670a75108..6c2ee47714 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; @@ -96,5 +97,6 @@ namespace Umbraco.Core.Models if(ParentId == Guid.Empty) _parentId = new Guid("41c7638d-f529-4bff-853e-59a0c2fb1bde"); } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index 5f8f5ebc25..f1c1af0eb7 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -65,5 +65,6 @@ namespace Umbraco.Core.Models }, _value, ValueSelector); } } + } } \ 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 8a9521c23c..b7d054c676 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -1,7 +1,10 @@ using System; using System.Diagnostics; +using System.IO; using System.Reflection; using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; namespace Umbraco.Core.Models.EntityBase { @@ -93,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. @@ -155,6 +158,7 @@ namespace Umbraco.Core.Models.EntityBase /// /// Indicates whether the current entity has an identity, eg. Id. /// + [DataMember] public virtual bool HasIdentity { get @@ -226,5 +230,62 @@ namespace Umbraco.Core.Models.EntityBase _hash = !HasIdentity ? new int?(base.GetHashCode()) : new int?(Id.GetHashCode() * 397 ^ GetType().GetHashCode()); return _hash.Value; } + + 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 = (Entity)MemberwiseClone(); + //Automatically deep clone ref properties that are IDeepCloneable + DeepCloneHelper.DeepCloneRefProperties(this, clone); + clone.ResetDirtyProperties(false); + return clone; + + //Using data contract serializer - has issues + + //var s = Serialize(this); + //var d = Deserialize(s, this.GetType()); + //return d; + + //Using binary serializer - has issues + + //using (var memoryStream = new MemoryStream(10)) + //{ + //IFormatter formatter = new BinaryFormatter(); + //formatter.Serialize(memoryStream, this); + //memoryStream.Seek(0, SeekOrigin.Begin); + //return formatter.Deserialize(memoryStream); + //} + } + + // serialize/deserialize with data contracts: + + //public static string Serialize(object obj) + //{ + // using (var memoryStream = new MemoryStream()) + // using (var reader = new StreamReader(memoryStream)) + // { + // var serializer = new DataContractSerializer(obj.GetType()); + // serializer.WriteObject(memoryStream, obj); + // memoryStream.Position = 0; + // return reader.ReadToEnd(); + // } + //} + + //public static object Deserialize(string xml, Type toType) + //{ + // using (Stream stream = new MemoryStream()) + // { + // using (var writer = new StreamWriter(stream, Encoding.UTF8)) + // { + // writer.Write(xml); + // //byte[] data = Encoding.UTF8.GetBytes(xml); + // //stream.Write(data, 0, data.Length); + // stream.Position = 0; + // var deserializer = new DataContractSerializer(toType); + // return deserializer.ReadObject(stream); + // } + // } + //} } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/IEntity.cs b/src/Umbraco.Core/Models/EntityBase/IEntity.cs index 9557d6fb0e..81f5f632ef 100644 --- a/src/Umbraco.Core/Models/EntityBase/IEntity.cs +++ b/src/Umbraco.Core/Models/EntityBase/IEntity.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Models.EntityBase /// /// The current database schema doesn't provide a modified date /// for all entities, so this will have to be changed at a later stage. - public interface IEntity + public interface IEntity : IDeepCloneable { /// /// The Id of the entity diff --git a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs index cf34296281..9a357b3b38 100644 --- a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs +++ b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs @@ -3,12 +3,15 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; namespace Umbraco.Core.Models.EntityBase { /// /// A base class for use to implement IRememberBeingDirty/ICanBeDirty /// + [Serializable] + [DataContract(IsReference = true)] public abstract class TracksChangesEntityBase : IRememberBeingDirty { /// diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index f520b63210..d337b82bbe 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -6,16 +6,6 @@ using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { - internal sealed class Folder : Entity - { - public Folder(string folderPath) - { - Path = folderPath; - } - - public string Path { get; set; } - } - /// /// Represents an abstract file which provides basic functionality for a File with an Alias and Name /// @@ -114,5 +104,18 @@ namespace Umbraco.Core.Models /// /// True if file is valid, otherwise false public abstract bool IsValid(); + + public override object DeepClone() + { + var clone = (File)base.DeepClone(); + + //need to manually assign since they are readonly properties + clone._alias = Alias; + clone._name = Name; + + clone.ResetDirtyProperties(false); + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Folder.cs b/src/Umbraco.Core/Models/Folder.cs new file mode 100644 index 0000000000..be8e9b6dc4 --- /dev/null +++ b/src/Umbraco.Core/Models/Folder.cs @@ -0,0 +1,14 @@ +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + internal sealed class Folder : Entity + { + public Folder(string folderPath) + { + Path = folderPath; + } + + public string Path { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IDeepCloneable.cs b/src/Umbraco.Core/Models/IDeepCloneable.cs new file mode 100644 index 0000000000..5f41f6d828 --- /dev/null +++ b/src/Umbraco.Core/Models/IDeepCloneable.cs @@ -0,0 +1,9 @@ +using System.Reflection; + +namespace Umbraco.Core.Models +{ + public interface IDeepCloneable + { + object DeepClone(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index cb41f2b7f8..465a6c3b58 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Member : ContentBase, IMember { - private readonly IMemberType _contentType; + private IMemberType _contentType; private readonly string _contentTypeAlias; private string _username; private string _email; @@ -153,7 +153,7 @@ namespace Umbraco.Core.Models /// /// Gets or sets the raw password value /// - [DataMember] + [IgnoreDataMember] public string RawPasswordValue { get { return _rawPasswordValue; } @@ -184,7 +184,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoMemberPasswordRetrievalQuestion /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public string PasswordQuestion { get @@ -244,7 +244,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoMemberComments /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public string Comments { get @@ -273,7 +273,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoMemberApproved /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public bool IsApproved { get @@ -308,7 +308,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoMemberLockedOut /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public bool IsLockedOut { get @@ -341,7 +341,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoMemberLastLogin /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public DateTime LastLoginDate { get @@ -374,7 +374,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoMemberLastPasswordChangeDate /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public DateTime LastPasswordChangeDate { get @@ -407,7 +407,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoMemberLastLockoutDate /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public DateTime LastLockoutDate { get @@ -441,7 +441,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoMemberFailedPasswordAttempts /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public int FailedPasswordAttempts { get @@ -563,11 +563,17 @@ namespace Umbraco.Core.Models /* Internal experiment - only used for mapping queries. * Adding these to have first level properties instead of the Properties collection. */ + [IgnoreDataMember] internal string LongStringPropertyValue { get; set; } + [IgnoreDataMember] internal string ShortStringPropertyValue { get; set; } + [IgnoreDataMember] internal int IntegerropertyValue { get; set; } + [IgnoreDataMember] internal bool BoolPropertyValue { get; set; } + [IgnoreDataMember] internal DateTime DateTimePropertyValue { get; set; } + [IgnoreDataMember] internal string PropertyTypeAlias { get; set; } private Attempt WarnIfPropertyTypeNotFoundOnGet(string propertyAlias, string propertyName, T defaultVal) @@ -637,5 +643,17 @@ namespace Umbraco.Core.Models return true; } + + public override object DeepClone() + { + var clone = (Member)base.DeepClone(); + + //need to manually clone this since it's not settable + clone._contentType = (IMemberType)ContentType.DeepClone(); + clone.ResetDirtyProperties(false); + + return clone; + + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 2010161df0..9c710f4375 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -7,7 +7,7 @@ using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Configuration; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; + namespace Umbraco.Core.Models.Membership { @@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.Membership /// [Serializable] [DataContract(IsReference = true)] - public class User : TracksChangesEntityBase, IUser + public class User : Entity, IUser { public User(IUserType userType) { @@ -57,8 +57,6 @@ namespace Umbraco.Core.Models.Membership } private IUserType _userType; - private bool _hasIdentity; - private int _id; private string _name; private Type _userTypeKey; private readonly List _addedSections; @@ -81,7 +79,6 @@ namespace Umbraco.Core.Models.Membership private static readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); private static readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); private static readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedSections); - private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); private static readonly PropertyInfo UserTypeKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKeyType); @@ -93,52 +90,8 @@ namespace Umbraco.Core.Models.Membership private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); private static readonly PropertyInfo DefaultPermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.DefaultPermissions); private static readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); - private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); private static readonly PropertyInfo UserTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.UserType); - - #region Implementation of IEntity - - [IgnoreDataMember] - public bool HasIdentity - { - get - { - return _hasIdentity; - } - protected set - { - SetPropertyValueAndDetectChanges(o => - { - _hasIdentity = value; - return _hasIdentity; - }, _hasIdentity, HasIdentitySelector); - } - } - - [DataMember] - public int Id - { - get - { - return _id; - } - set - { - SetPropertyValueAndDetectChanges(o => - { - _id = value; - HasIdentity = true; //set the has Identity - return _id; - }, _id, IdSelector); - } - } - - //this doesn't get used - [IgnoreDataMember] - public Guid Key { get; set; } - - #endregion - + #region Implementation of IMembershipUser [IgnoreDataMember] @@ -256,11 +209,7 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] public string RawPasswordAnswerValue { get; set; } [IgnoreDataMember] - public string Comments { get; set; } - [IgnoreDataMember] - public DateTime CreateDate { get; set; } - [IgnoreDataMember] - public DateTime UpdateDate { get; set; } + public string Comments { get; set; } [IgnoreDataMember] public DateTime LastLoginDate { get; set; } [IgnoreDataMember] @@ -491,7 +440,7 @@ namespace Umbraco.Core.Models.Membership _removedSections.Add(e.OldItems.Cast().First()); } } - + /// /// Internal class used to wrap the user in a profile /// @@ -515,6 +464,24 @@ namespace Umbraco.Core.Models.Membership get { return _user.Name; } set { _user.Name = value; } } + + protected bool Equals(UserProfile other) + { + return _user.Equals(other._user); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((UserProfile) obj); + } + + public override int GetHashCode() + { + return _user.GetHashCode(); + } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PreValue.cs b/src/Umbraco.Core/Models/PreValue.cs index a9e539a960..b12312130d 100644 --- a/src/Umbraco.Core/Models/PreValue.cs +++ b/src/Umbraco.Core/Models/PreValue.cs @@ -26,7 +26,7 @@ /// /// The value stored for the pre-value field /// - public string Value { get; private set; } + public string Value { get; set; } /// /// The database id for the pre-value field value diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 1721efe5de..f8696794b1 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Property : Entity { - private readonly PropertyType _propertyType; + private PropertyType _propertyType; private Guid _version; private object _value; private readonly PropertyTags _tagSupport = new PropertyTags(); @@ -156,5 +156,16 @@ namespace Umbraco.Core.Models { return _propertyType.IsPropertyValueValid(value); } + + public override object DeepClone() + { + var clone = (Property)base.DeepClone(); + + //need to manually assign since this is a readonly property + clone._propertyType = (PropertyType)PropertyType.DeepClone(); + clone.ResetDirtyProperties(false); + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index db90d5b42b..0ff2e13484 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -5,6 +5,7 @@ using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; using System.Threading; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -13,7 +14,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class PropertyCollection : KeyedCollection, INotifyCollectionChanged + public class PropertyCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); internal Action OnAdd; @@ -195,5 +196,19 @@ namespace Umbraco.Core.Models } } } + + /// + /// Create a deep clone of this property collection + /// + /// + public object DeepClone() + { + var newList = new PropertyCollection(); + foreach (var p in this) + { + newList.Add((Property)p.DeepClone()); + } + 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 52dc022a50..3158dfe6d6 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -109,23 +109,6 @@ namespace Umbraco.Core.Models } } - internal PropertyGroup Clone() - { - var clone = (PropertyGroup) this.MemberwiseClone(); - var collection = new PropertyTypeCollection(); - foreach (var propertyType in this.PropertyTypes) - { - var property = propertyType.Clone(); - property.ResetIdentity(); - property.ResetDirtyProperties(false); - collection.Add(property); - } - clone.PropertyTypes = collection; - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; - } - /// /// Sets the ParentId from the lazy integer id /// @@ -159,5 +142,24 @@ namespace Umbraco.Core.Models //Calculate the hash code for the product. return hashName ^ hashId; } + + //TODO: Remove this, its mostly a shallow clone and is not thread safe + internal PropertyGroup Clone() + { + var clone = (PropertyGroup)this.MemberwiseClone(); + var collection = new PropertyTypeCollection(); + foreach (var propertyType in this.PropertyTypes) + { + var property = propertyType.Clone(); + property.ResetIdentity(); + property.ResetDirtyProperties(false); + collection.Add(property); + } + clone.PropertyTypes = collection; + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index facb5b9e5c..6e1847856b 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -5,6 +5,7 @@ using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; using System.Threading; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -13,9 +14,10 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract] - public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged + public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); + internal Action OnAdd; internal PropertyGroupCollection() @@ -131,5 +133,15 @@ namespace Umbraco.Core.Models CollectionChanged(this, args); } } + + public object DeepClone() + { + var newGroup = new PropertyGroupCollection(); + foreach (var p in this) + { + newGroup.Add((PropertyGroup)p.DeepClone()); + } + 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 b275a6912f..83a8c09a99 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -419,14 +419,6 @@ namespace Umbraco.Core.Models return true; } - internal PropertyType Clone() - { - var clone = (PropertyType) MemberwiseClone(); - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; - } - public bool Equals(PropertyType other) { //Check whether the compared object is null. @@ -450,5 +442,30 @@ namespace Umbraco.Core.Models //Calculate the hash code for the product. return hashName ^ hashAlias; } + + //TODO: Remove this + internal PropertyType Clone() + { + var clone = (PropertyType)this.MemberwiseClone(); + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } + + public override object DeepClone() + { + var clone = (PropertyType)base.DeepClone(); + + //need to manually assign the Lazy value as it will not be automatically mapped + if (PropertyGroupId != null) + { + var propGroupId = PropertyGroupId.Value; + clone._propertyGroupId = new Lazy(() => propGroupId); + } + + clone.ResetDirtyProperties(false); + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 27e3926f09..ea153ccae4 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -5,6 +5,7 @@ using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; using System.Threading; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -13,9 +14,12 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract] - public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged + public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { + [IgnoreDataMember] private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); + + [IgnoreDataMember] internal Action OnAdd; internal PropertyTypeCollection() @@ -131,5 +135,15 @@ namespace Umbraco.Core.Models CollectionChanged(this, args); } } + + public object DeepClone() + { + var newGroup = new PropertyTypeCollection(); + foreach (var p in this) + { + newGroup.Add((PropertyType)p.DeepClone()); + } + return newGroup; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index ec7c3741a6..f08e4b147e 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -107,5 +107,6 @@ namespace Umbraco.Core.Models { get { return _relationType.Id; } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index ae20482229..5477628222 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -127,5 +127,6 @@ namespace Umbraco.Core.Models }, _childObjectType, ChildObjectTypeSelector); } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index dd352c2c86..7f43f5dfd2 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Reflection; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Mappers; @@ -34,7 +35,7 @@ namespace Umbraco.Core.Models { UpdateDate = updateDate; CreateDate = createDate; - Key = Id.ToString().EncodeAsGuid(); + Key = Id.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); Id = id; ServerAddress = serverAddress; ComputerName = computerName; @@ -51,7 +52,7 @@ namespace Umbraco.Core.Models { CreateDate = createDate; UpdateDate = createDate; - Key = 0.ToString().EncodeAsGuid(); + Key = 0.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); ServerAddress = serverAddress; ComputerName = computerName; } diff --git a/src/Umbraco.Core/Models/Task.cs b/src/Umbraco.Core/Models/Task.cs index 86370c00bb..0f58814eea 100644 --- a/src/Umbraco.Core/Models/Task.cs +++ b/src/Umbraco.Core/Models/Task.cs @@ -132,5 +132,6 @@ namespace Umbraco.Core.Models }, _comment, CommentSelector); } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 387a345039..a0bef57ae8 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -14,20 +15,18 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Template : File, ITemplate { - private readonly string _alias; - private readonly string _name; + private string _alias; + private string _name; private int _creatorId; private int _level; private int _sortOrder; private int _parentId; - private int _masterTemplateId; private string _masterTemplateAlias; private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - //private static readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateId); private static readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateAlias); public Template(string path, string name, string alias) @@ -189,5 +188,18 @@ namespace Umbraco.Core.Models { MasterTemplateId = new Lazy(() => masterTemplate.Id); } + + public override object DeepClone() + { + var clone = (Template)base.DeepClone(); + + //need to manually assign since they are readonly properties + clone._alias = Alias; + clone._name = Name; + + clone.ResetDirtyProperties(false); + + return clone; + } } } diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 24261b4989..92bdf3c790 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Reflection; using Umbraco.Core.Models.EntityBase; @@ -46,11 +47,13 @@ namespace Umbraco.Core.Models public UmbracoEntity() { AdditionalData = new Dictionary(); + UmbracoProperties = new List(); } public UmbracoEntity(bool trashed) { AdditionalData = new Dictionary(); + UmbracoProperties = new List(); Trashed = trashed; } @@ -58,6 +61,7 @@ namespace Umbraco.Core.Models public UmbracoEntity(UInt64 trashed) { AdditionalData = new Dictionary(); + UmbracoProperties = new List(); Trashed = trashed == 1; } @@ -287,11 +291,39 @@ namespace Umbraco.Core.Models /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// public IList UmbracoProperties { get; set; } - - internal class UmbracoProperty + + internal class UmbracoProperty : IDeepCloneable { public string PropertyEditorAlias { get; set; } public string Value { get; set; } + public 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(); + return clone; + } + + protected bool Equals(UmbracoProperty other) + { + return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && string.Equals(Value, other.Value); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((UmbracoProperty) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0); + } + } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs index 5544c74494..7572dcd5c5 100644 --- a/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs +++ b/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs @@ -37,7 +37,10 @@ namespace Umbraco.Core.Persistence.Caching var containsKey = _cache.ContainsKey(compositeKey); if (containsKey) { - return _cache[compositeKey]; + var result = _cache[compositeKey]; + + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + return (IEntity)result.DeepClone(); } return null; @@ -56,7 +59,12 @@ namespace Umbraco.Core.Persistence.Caching into key let containsKey = _cache.ContainsKey(key) where containsKey - select _cache[key]).ToList(); + select _cache[key] + into result + //don't return null objects + where result != null + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + select (IEntity)result.DeepClone()).ToList(); return list; } @@ -67,7 +75,14 @@ namespace Umbraco.Core.Persistence.Caching /// public IEnumerable GetAllByType(Type type) { - var list = _cache.Keys.Where(key => key.Contains(type.Name)).Select(key => _cache[key]).ToList(); + var list = _cache.Keys + .Where(key => key.Contains(type.Name)) + .Select(key => _cache[key]) + //don't return null objects + .Where(result => result != null) + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + .Select(result => (IEntity)result.DeepClone()) + .ToList(); return list; } @@ -78,6 +93,9 @@ namespace Umbraco.Core.Persistence.Caching /// public void Save(Type type, IEntity entity) { + //IMPORTANT: we must clone to store, see: http://issues.umbraco.org/issue/U4-4259 + entity = (IEntity)entity.DeepClone(); + _cache.AddOrUpdate(GetCompositeId(type, entity.Id), entity, (x, y) => entity); } diff --git a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs index 273d605bef..c2ca53a240 100644 --- a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -29,6 +29,9 @@ namespace Umbraco.Core.Persistence.Caching /// when there are async requests being made even in the context of a web request, the HttpContext.Current will be null but the HttpRuntime.Cache will /// always be available. /// + /// TODO: Each item that get's added to this cache will be a clone of the original with it's dirty properties reset, and every item that is resolved from the cache + /// is a clone of the item that is in there, otherwise we end up with thread safety issues since multiple thread would be working on the exact same entity at the same time. + /// /// internal sealed class RuntimeCacheProvider : IRepositoryCacheProvider { @@ -64,8 +67,11 @@ namespace Umbraco.Core.Persistence.Caching { //ensure the key doesn't exist anymore in the tracker _keyTracker.Remove(key); + return null; } - return result; + + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + return (IEntity)result.DeepClone(); } public IEnumerable GetByIds(Type type, List ids) @@ -85,7 +91,8 @@ namespace Umbraco.Core.Persistence.Caching } else { - collection.Add(result); + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + collection.Add((IEntity)result.DeepClone()); } } return collection; @@ -110,7 +117,8 @@ namespace Umbraco.Core.Persistence.Caching } else { - collection.Add(result); + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + collection.Add((IEntity)result.DeepClone()); } } } @@ -119,6 +127,9 @@ namespace Umbraco.Core.Persistence.Caching public void Save(Type type, IEntity entity) { + //IMPORTANT: we must clone to store, see: http://issues.umbraco.org/issue/U4-4259 + entity = (IEntity)entity.DeepClone(); + var key = GetCompositeId(type, entity.Id); _keyTracker.TryAdd(key); diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 3798bb53d9..dc73f3584a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; +using System.Threading; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; @@ -11,6 +13,8 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; +using NullCacheProvider = Umbraco.Core.Persistence.Caching.NullCacheProvider; namespace Umbraco.Core.Persistence.Repositories { @@ -19,16 +23,30 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class DataTypeDefinitionRepository : PetaPocoRepositoryBase, IDataTypeDefinitionRepository { - public DataTypeDefinitionRepository(IDatabaseUnitOfWork work) + private readonly CacheHelper _cacheHelper; + private readonly IContentTypeRepository _contentTypeRepository; + private readonly DataTypePreValueRepository _preValRepository; + + public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, + IContentTypeRepository contentTypeRepository) : base(work) { + _cacheHelper = cacheHelper; + _contentTypeRepository = contentTypeRepository; + _preValRepository = new DataTypePreValueRepository(work, NullCacheProvider.Current); } - public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) + public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, CacheHelper cacheHelper, + IContentTypeRepository contentTypeRepository) : base(work, cache) { + _cacheHelper = cacheHelper; + _contentTypeRepository = contentTypeRepository; + _preValRepository = new DataTypePreValueRepository(work, NullCacheProvider.Current); } + private readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + #region Overrides of RepositoryBase protected override IDataTypeDefinition PerformGet(int id) @@ -83,6 +101,37 @@ namespace Umbraco.Core.Persistence.Repositories } } + /// + /// Override the delete method so that we can ensure that all related content type's are updated as part of the overall transaction + /// + /// + public override void Delete(IDataTypeDefinition entity) + { + //Find ContentTypes using this IDataTypeDefinition on a PropertyType + var query = Query.Builder.Where(x => x.DataTypeDefinitionId == entity.Id); + var contentTypes = _contentTypeRepository.GetByQuery(query); + + //Loop through the list of results and remove the PropertyTypes that references the DataTypeDefinition that is being deleted + foreach (var contentType in contentTypes) + { + if (contentType == null) continue; + + foreach (var group in contentType.PropertyGroups) + { + var types = @group.PropertyTypes.Where(x => x.DataTypeDefinitionId == entity.Id).ToList(); + foreach (var propertyType in types) + { + @group.PropertyTypes.Remove(propertyType); + } + } + + _contentTypeRepository.AddOrUpdate(contentType); + } + + //call the base method to queue the deletion of this data type + base.Delete(entity); + } + #endregion #region Overrides of PetaPocoRepositoryBase @@ -117,6 +166,42 @@ namespace Umbraco.Core.Persistence.Repositories #region Unit of Work Implementation + public override void PersistUpdatedItem(IEntity entity) + { + if (entity is PreValue) + { + _preValRepository.PersistUpdatedItem(entity); + } + else + { + base.PersistUpdatedItem(entity); + } + } + + public override void PersistNewItem(IEntity entity) + { + if (entity is PreValue) + { + _preValRepository.PersistNewItem(entity); + } + else + { + base.PersistNewItem(entity); + } + } + + public override void PersistDeletedItem(IEntity entity) + { + if (entity is PreValue) + { + _preValRepository.PersistDeletedItem(entity); + } + else + { + base.PersistDeletedItem(entity); + } + } + protected override void PersistNewItem(IDataTypeDefinition entity) { ((DataTypeDefinition)entity).AddingEntity(); @@ -238,5 +323,321 @@ AND umbracoNode.id <> @id", } #endregion + + public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId) + { + using (var l = new UpgradeableReadLock(Locker)) + { + var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); + if (cached != null && cached.Any()) + { + //return from the cache + return cached.First(); + } + + l.UpgradeToWriteLock(); + + return GetAndCachePreValueCollection(dataTypeId); + } + } + + public string GetPreValueAsString(int preValueId) + { + using (var l = new UpgradeableReadLock(Locker)) + { + //We need to see if we can find the cached PreValueCollection based on the cache key above + + var regex = CacheKeys.DataTypePreValuesCacheKey + @"[\d]+-[,\d]*" + preValueId + @"[,\d$]*"; + + var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeyExpression(regex); + if (cached != null && cached.Any()) + { + //return from the cache + var collection = cached.First(); + var preVal = collection.PreValuesAsArray.Single(x => x.Id == preValueId); + return preVal.Value; + } + + l.UpgradeToWriteLock(); + + //go and find the data type id for the pre val id passed in + + var dto = Database.FirstOrDefault("WHERE id = @preValueId", new { preValueId = preValueId }); + if (dto == null) + { + return string.Empty; + } + // go cache the collection + var preVals = GetAndCachePreValueCollection(dto.DataTypeNodeId); + + //return the single value for this id + var pv = preVals.PreValuesAsArray.Single(x => x.Id == preValueId); + return pv.Value; + } + } + + public void AddOrUpdatePreValues(int dataTypeId, IDictionary values) + { + var dtd = Get(dataTypeId); + if (dtd == null) + { + throw new InvalidOperationException("No data type found with id " + dataTypeId); + } + AddOrUpdatePreValues(dtd, values); + } + + public void AddOrUpdatePreValues(IDataTypeDefinition dataType, IDictionary values) + { + var currentVals = new DataTypePreValueDto[]{}; + if (dataType.HasIdentity) + { + //first just get all pre-values for this data type so we can compare them to see if we need to insert or update or replace + var sql = new Sql().Select("*") + .From() + .Where(dto => dto.DataTypeNodeId == dataType.Id) + .OrderBy(dto => dto.SortOrder); + currentVals = Database.Fetch(sql).ToArray(); + } + + //already existing, need to be updated + var valueIds = values.Where(x => x.Value.Id > 0).Select(x => x.Value.Id).ToArray(); + var existingByIds = currentVals.Where(x => valueIds.Contains(x.Id)).ToArray(); + + //These ones need to be removed from the db, they no longer exist in the new values + var deleteById = currentVals.Where(x => valueIds.Contains(x.Id) == false); + + foreach (var d in deleteById) + { + _preValRepository.Delete(new PreValueEntity + { + Alias = d.Alias, + Id = d.Id, + Value = d.Value, + DataType = dataType, + SortOrder = d.SortOrder + }); + } + + var sortOrder = 1; + + foreach (var pre in values) + { + var existing = existingByIds.FirstOrDefault(valueDto => valueDto.Id == pre.Value.Id); + if (existing != null) + { + existing.Value = pre.Value.Value; + existing.SortOrder = sortOrder; + _preValRepository.AddOrUpdate(new PreValueEntity + { + Alias = existing.Alias, + Id = existing.Id, + SortOrder = existing.SortOrder, + Value = existing.Value, + DataType = dataType, + }); + } + else + { + _preValRepository.AddOrUpdate(new PreValueEntity + { + Alias = pre.Key, + SortOrder = sortOrder, + Value = pre.Value.Value, + DataType = dataType, + }); + } + + sortOrder++; + } + + } + + private string GetPrefixedCacheKey(int dataTypeId) + { + return CacheKeys.DataTypePreValuesCacheKey + dataTypeId + "-"; + } + + private PreValueCollection GetAndCachePreValueCollection(int dataTypeId) + { + //go get the data + var dtos = Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = dataTypeId }); + var list = dtos.Select(x => new Tuple(new PreValue(x.Id, x.Value), x.Alias, x.SortOrder)).ToList(); + var collection = PreValueConverter.ConvertToPreValuesCollection(list); + + //now create the cache key, this needs to include all pre-value ids so that we can use this cached item in the GetPreValuesAsString method + //the key will be: "UmbracoPreValDATATYPEID-CSVOFPREVALIDS + + var key = GetPrefixedCacheKey(dataTypeId) + + string.Join(",", collection.FormatAsDictionary().Select(x => x.Value.Id).ToArray()); + + //store into cache + _cacheHelper.RuntimeCache.InsertCacheItem(key, () => collection, + //30 mins + new TimeSpan(0, 0, 30), + //sliding is true + true); + + return collection; + } + + /// + /// Private class to handle pre-value crud based on units of work with transactions + /// + public class PreValueEntity : Entity, IAggregateRoot + { + public string Value { get; set; } + public string Alias { get; set; } + public IDataTypeDefinition DataType { get; set; } + public int SortOrder { get; set; } + } + + /// + /// Private class to handle pre-value crud based on standard principles and units of work with transactions + /// + private class DataTypePreValueRepository : PetaPocoRepositoryBase + { + public DataTypePreValueRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + { + } + + #region Not implemented (don't need to for the purposes of this repo) + protected override PreValueEntity PerformGet(int id) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + protected override Sql GetBaseQuery(bool isCount) + { + throw new NotImplementedException(); + } + + protected override string GetBaseWhereClause() + { + throw new NotImplementedException(); + } + + protected override IEnumerable GetDeleteClauses() + { + return new List(); + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + #endregion + + protected override void PersistDeletedItem(PreValueEntity entity) + { + Database.Execute( + "DELETE FROM cmsDataTypePreValues WHERE id=@Id", + new { Id = entity.Id }); + } + + protected override void PersistNewItem(PreValueEntity entity) + { + if (entity.DataType.HasIdentity == false) + { + throw new InvalidOperationException("Cannot insert a pre value for a data type that has no identity"); + } + + //Cannot add a duplicate alias + var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataTypePreValues +WHERE alias = @alias +AND datatypeNodeId = @dtdid", + new { alias = entity.Alias, dtdid = entity.DataType.Id }); + if (exists > 0) + { + throw new DuplicateNameException("A pre value with the alias " + entity.Alias + " already exists for this data type"); + } + + var dto = new DataTypePreValueDto + { + DataTypeNodeId = entity.DataType.Id, + Value = entity.Value, + SortOrder = entity.SortOrder, + Alias = entity.Alias + }; + Database.Insert(dto); + } + + protected override void PersistUpdatedItem(PreValueEntity entity) + { + if (entity.DataType.HasIdentity == false) + { + throw new InvalidOperationException("Cannot update a pre value for a data type that has no identity"); + } + + //Cannot change to a duplicate alias + var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataTypePreValues +WHERE alias = @alias +AND datatypeNodeId = @dtdid +AND id <> @id", + new { id = entity.Id, alias = entity.Alias, dtdid = entity.DataType.Id }); + if (exists > 0) + { + throw new DuplicateNameException("A pre value with the alias " + entity.Alias + " already exists for this data type"); + } + + var dto = new DataTypePreValueDto + { + DataTypeNodeId = entity.DataType.Id, + Id = entity.Id, + Value = entity.Value, + SortOrder = entity.SortOrder, + Alias = entity.Alias + }; + Database.Update(dto); + } + } + + internal static class PreValueConverter + { + /// + /// Converts the tuple to a pre-value collection + /// + /// + /// + internal static PreValueCollection ConvertToPreValuesCollection(IEnumerable> list) + { + //now we need to determine if they are dictionary based, otherwise they have to be array based + var dictionary = new Dictionary(); + + //need to check all of the keys, if there's only one and it is empty then it's an array + var keys = list.Select(x => x.Item2).Distinct().ToArray(); + if (keys.Length == 1 && keys[0].IsNullOrWhiteSpace()) + { + return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); + } + + foreach (var item in list + .OrderBy(x => x.Item3) //we'll order them first so we maintain the order index in the dictionary + .GroupBy(x => x.Item2)) //group by alias + { + if (item.Count() > 1) + { + //if there's more than 1 item per key, then it cannot be a dictionary, just return the array + return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); + } + + dictionary.Add(item.Key, item.First().Item1); + } + + return new PreValueCollection(dictionary); + } + } + } + + } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs index 7a39a2cdc1..f04b90c2e0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs @@ -1,9 +1,15 @@ -using Umbraco.Core.Models; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { public interface IDataTypeDefinitionRepository : IRepositoryQueryable { - + PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId); + string GetPreValueAsString(int preValueId); + + void AddOrUpdatePreValues(IDataTypeDefinition dataType, IDictionary values); + void AddOrUpdatePreValues(int dataTypeId, IDictionary values); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 0932cc89b2..de20cc4fe8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -72,7 +72,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Deletes the passed in entity /// /// - public void Delete(TEntity entity) + public virtual void Delete(TEntity entity) { if(_work != null) { diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index a381443561..de5f136567 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -81,14 +81,16 @@ namespace Umbraco.Core.Persistence return new ContentTypeRepository( uow, _disableAllCache ? (IRepositoryCacheProvider)NullCacheProvider.Current : RuntimeCacheProvider.Current, - new TemplateRepository(uow, NullCacheProvider.Current)); + CreateTemplateRepository(uow)); } public virtual IDataTypeDefinitionRepository CreateDataTypeDefinitionRepository(IDatabaseUnitOfWork uow) { return new DataTypeDefinitionRepository( uow, - NullCacheProvider.Current); + _disableAllCache ? (IRepositoryCacheProvider)NullCacheProvider.Current : RuntimeCacheProvider.Current, + _cacheHelper, + CreateContentTypeRepository(uow)); } public virtual IDictionaryRepository CreateDictionaryRepository(IDatabaseUnitOfWork uow) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 5eb516aa4b..d4915292a1 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -120,10 +120,11 @@ namespace Umbraco.Core.Services /// An enumerable list of string values public IEnumerable GetPreValuesByDataTypeId(int id) { - using (var uow = _uowProvider.GetUnitOfWork()) + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(_uowProvider.GetUnitOfWork())) { - var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new {Id = id}); - var list = dtos.Select(x => x.Value).ToList(); + var collection = repository.GetPreValuesCollectionByDataTypeId(id); + //now convert the collection to a string list + var list = collection.PreValuesAsArray.Select(x => x.Value).ToList(); return list; } } @@ -135,12 +136,9 @@ namespace Umbraco.Core.Services /// public PreValueCollection GetPreValuesCollectionByDataTypeId(int id) { - using (var uow = _uowProvider.GetUnitOfWork()) + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(_uowProvider.GetUnitOfWork())) { - var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = id }); - var list = dtos.Select(x => new Tuple(new PreValue(x.Id, x.Value, x.SortOrder), x.Alias, x.SortOrder)).ToList(); - - return PreValueConverter.ConvertToPreValuesCollection(list); + return repository.GetPreValuesCollectionByDataTypeId(id); } } @@ -151,10 +149,9 @@ namespace Umbraco.Core.Services /// PreValue as a string public string GetPreValueAsString(int id) { - using (var uow = _uowProvider.GetUnitOfWork()) + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(_uowProvider.GetUnitOfWork())) { - var dto = uow.Database.FirstOrDefault("WHERE id = @Id", new { Id = id }); - return dto != null ? dto.Value : string.Empty; + return repository.GetPreValueAsString(id); } } @@ -168,17 +165,14 @@ namespace Umbraco.Core.Services if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) return; - using (new WriteLock(Locker)) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) { - var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) - { - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - uow.Commit(); + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + uow.Commit(); - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); - } + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); } Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); @@ -194,31 +188,29 @@ namespace Umbraco.Core.Services if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinitions), this)) return; - using (new WriteLock(Locker)) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) { - var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) + foreach (var dataTypeDefinition in dataTypeDefinitions) { - foreach (var dataTypeDefinition in dataTypeDefinitions) - { - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - } - uow.Commit(); - - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinitions, false), this); + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); } + uow.Commit(); + + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinitions, false), this); } + Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); } /// /// Saves a list of PreValues for a given DataTypeDefinition /// - /// Id of the DataTypeDefinition to save PreValues for + /// Id of the DataTypeDefinition to save PreValues for /// List of string values to save [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] - public void SavePreValues(int id, IEnumerable values) + public void SavePreValues(int dataTypeId, IEnumerable values) { //TODO: Should we raise an event here since we are really saving values for the data type? @@ -230,7 +222,7 @@ namespace Umbraco.Core.Services { var sortOrderObj = uow.Database.ExecuteScalar( - "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = id }); + "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); int sortOrder; if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) { @@ -239,7 +231,7 @@ namespace Umbraco.Core.Services foreach (var value in values) { - var dto = new DataTypePreValueDto { DataTypeNodeId = id, Value = value, SortOrder = sortOrder }; + var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; uow.Database.Insert(dto); sortOrder++; } @@ -253,26 +245,40 @@ namespace Umbraco.Core.Services /// /// Saves/updates the pre-values /// - /// + /// /// /// /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors /// like 'dropdown list publishing keys' /// - public void SavePreValues(int id, IDictionary values) + public void SavePreValues(int dataTypeId, IDictionary values) + { + var dtd = this.GetDataTypeDefinitionById(dataTypeId); + if (dtd == null) + { + throw new InvalidOperationException("No data type found for id " + dataTypeId); + } + SavePreValues(dtd, values); + } + + /// + /// Saves/updates the pre-values + /// + /// + /// + /// + /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors + /// like 'dropdown list publishing keys' + /// + public void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values) { //TODO: Should we raise an event here since we are really saving values for the data type? - using (new WriteLock(Locker)) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) { - using (var uow = _uowProvider.GetUnitOfWork()) - { - using (var transaction = uow.Database.GetTransaction()) - { - AddOrUpdatePreValues(id, values, uow); - transaction.Complete(); - } - } + repository.AddOrUpdatePreValues(dataTypeDefinition, values); + uow.Commit(); } } @@ -287,73 +293,25 @@ namespace Umbraco.Core.Services if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) return; - using (new WriteLock(Locker)) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) { - var uow = (PetaPocoUnitOfWork)_uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) - { - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); + dataTypeDefinition.CreatorId = userId; - //complete the transaction, but run the delegate before the db transaction is finalized - uow.Commit(database => AddOrUpdatePreValues(dataTypeDefinition.Id, values, uow)); + //add/update the dtd + repository.AddOrUpdate(dataTypeDefinition); - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); - } + //add/update the prevalues + repository.AddOrUpdatePreValues(dataTypeDefinition, values); + + uow.Commit(); + + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); } Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); } - private void AddOrUpdatePreValues(int id, IDictionary preValueCollection, IDatabaseUnitOfWork uow) - { - //first just get all pre-values for this data type so we can compare them to see if we need to insert or update or replace - var sql = new Sql().Select("*") - .From() - .Where(dto => dto.DataTypeNodeId == id) - .OrderBy(dto => dto.SortOrder); - var currentVals = uow.Database.Fetch(sql).ToArray(); - - //already existing, need to be updated - var valueIds = preValueCollection.Where(x => x.Value.Id > 0).Select(x => x.Value.Id).ToArray(); - var existingByIds = currentVals.Where(x => valueIds.Contains(x.Id)).ToArray(); - - //These ones need to be removed from the db, they no longer exist in the new values - var deleteById = currentVals.Where(x => valueIds.Contains(x.Id) == false); - - foreach (var d in deleteById) - { - uow.Database.Execute( - "DELETE FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId AND id=@Id", - new { DataTypeId = id, Id = d.Id }); - } - - var sortOrder = 1; - - foreach (var pre in preValueCollection) - { - var existing = existingByIds.FirstOrDefault(valueDto => valueDto.Id == pre.Value.Id); - if (existing != null) - { - existing.Value = pre.Value.Value; - existing.SortOrder = sortOrder; - uow.Database.Update(existing); - } - else - { - var dto = new DataTypePreValueDto - { - DataTypeNodeId = id, - Value = pre.Value.Value, - SortOrder = sortOrder, - Alias = pre.Key - }; - uow.Database.Insert(dto); - } - - sortOrder++; - } - } /// /// Deletes an @@ -370,31 +328,9 @@ namespace Umbraco.Core.Services return; var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateContentTypeRepository(uow)) + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) { - //Find ContentTypes using this IDataTypeDefinition on a PropertyType - var query = Query.Builder.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id); - var contentTypes = repository.GetByQuery(query); - - //Loop through the list of results and remove the PropertyTypes that references the DataTypeDefinition that is being deleted - foreach (var contentType in contentTypes) - { - if (contentType == null) continue; - - foreach (var group in contentType.PropertyGroups) - { - var types = @group.PropertyTypes.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id).ToList(); - foreach (var propertyType in types) - { - @group.PropertyTypes.Remove(propertyType); - } - } - - repository.AddOrUpdate(contentType); - } - - var dataTypeRepository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow); - dataTypeRepository.Delete(dataTypeDefinition); + repository.Delete(dataTypeDefinition); uow.Commit(); @@ -447,40 +383,6 @@ namespace Umbraco.Core.Services public static event TypedEventHandler> Saved; #endregion - internal static class PreValueConverter - { - /// - /// Converts the tuple to a pre-value collection - /// - /// - /// - internal static PreValueCollection ConvertToPreValuesCollection(IEnumerable> list) - { - //now we need to determine if they are dictionary based, otherwise they have to be array based - var dictionary = new Dictionary(); - - //need to check all of the keys, if there's only one and it is empty then it's an array - var keys = list.Select(x => x.Item2).Distinct().ToArray(); - if (keys.Length == 1 && keys[0].IsNullOrWhiteSpace()) - { - return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); - } - - foreach (var item in list - .OrderBy(x => x.Item3) //we'll order them first so we maintain the order index in the dictionary - .GroupBy(x => x.Item2)) //group by alias - { - if (item.Count() > 1) - { - //if there's more than 1 item per key, then it cannot be a dictionary, just return the array - return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); - } - - dictionary.Add(item.Key, item.First().Item1); - } - - return new PreValueCollection(dictionary); - } - } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index e57d9016e4..7bf938aa25 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -103,17 +103,24 @@ namespace Umbraco.Core.Services /// /// Saves a list of PreValues for a given DataTypeDefinition /// - /// Id of the DataTypeDefinition to save PreValues for + /// Id of the DataTypeDefinition to save PreValues for /// List of string values to save [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] - void SavePreValues(int id, IEnumerable values); + void SavePreValues(int dataTypeId, IEnumerable values); /// /// Saves a list of PreValues for a given DataTypeDefinition /// - /// Id of the DataTypeDefinition to save PreValues for + /// Id of the DataTypeDefinition to save PreValues for /// List of key/value pairs to save - void SavePreValues(int id, IDictionary values); + void SavePreValues(int dataTypeId, IDictionary values); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// The DataTypeDefinition to save PreValues for + /// List of key/value pairs to save + void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values); /// /// Saves the data type and it's prevalues diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 3187fb9bdd..b3e5829306 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -922,11 +922,13 @@ namespace Umbraco.Core.Services .Select(x => x.Attribute("Value").Value); var valuesWithKeys = prevaluesElement.Elements("PreValue") - .Where(x => ((string) x.Attribute("Alias")).IsNullOrWhiteSpace() == false) - .ToDictionary(key => (string) key.Attribute("Alias"), val => new PreValue((string) val.Attribute("Value"))); + .Where(x => ((string) x.Attribute("Alias")).IsNullOrWhiteSpace() == false) + .ToDictionary( + key => (string) key.Attribute("Alias"), + val => new PreValue((string) val.Attribute("Value"))); //save the values with keys - _dataTypeService.SavePreValues(dataTypeDefinition.Id, valuesWithKeys); + _dataTypeService.SavePreValues(dataTypeDefinition, valuesWithKeys); //save the values without keys (this is legacy) _dataTypeService.SavePreValues(dataTypeDefinition.Id, valuesWithoutKeys); diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 93a35b95e2..8ce7d33c48 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -1248,5 +1248,24 @@ namespace Umbraco.Core */ } + public static bool ContainsAny(this string haystack, IEnumerable needles, StringComparison comparison = StringComparison.CurrentCulture) + { + if (haystack == null) throw new ArgumentNullException("haystack"); + if (string.IsNullOrEmpty(haystack) == false || needles.Any()) + { + return needles.Any(value => haystack.IndexOf(value) >= 0); + } + return false; + } + + public static bool CsvContains(this string csv, string value) + { + if (string.IsNullOrEmpty(csv)) + { + return false; + } + var idCheckList = csv.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + return idCheckList.Contains(value); + } } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index de8f2e17f6..6d3e4cf693 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -335,7 +335,10 @@ + + + diff --git a/src/Umbraco.Tests/Models/Collections/Item.cs b/src/Umbraco.Tests/Models/Collections/Item.cs new file mode 100644 index 0000000000..ff1789a850 --- /dev/null +++ b/src/Umbraco.Tests/Models/Collections/Item.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Tests.Models.Collections +{ + public abstract class Item : IEntity, ICanBeDirty + { + private bool _hasIdentity; + private int? _hash; + private int _id; + private Guid _key; + + protected Item() + { + _propertyChangedInfo = new Dictionary(); + } + + /// + /// Integer Id + /// + [DataMember] + public int Id + { + get + { + return _id; + } + set + { + _id = value; + HasIdentity = true; + } + } + + /// + /// Guid based Id + /// + /// The key is currectly used to store the Unique Id from the + /// umbracoNode table, which many of the entities are based on. + [DataMember] + public Guid Key + { + get + { + if (_key == Guid.Empty) + return _id.ToGuid(); + + return _key; + } + set { _key = value; } + } + + /// + /// Gets or sets the Created Date + /// + [DataMember] + public DateTime CreateDate { get; set; } + + /// + /// Gets or sets the Modified Date + /// + [DataMember] + public DateTime UpdateDate { get; set; } + + /// + /// 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. + /// + [IgnoreDataMember] + internal bool WasCancelled { get; set; } + + /// + /// Property changed event + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Method to call on a property setter. + /// + /// The property info. + protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) + { + _propertyChangedInfo[propertyInfo.Name] = true; + + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyInfo.Name)); + } + } + + internal virtual void ResetIdentity() + { + _hasIdentity = false; + _id = default(int); + } + + /// + /// Method to call on entity saved when first added + /// + internal virtual void AddingEntity() + { + CreateDate = DateTime.Now; + UpdateDate = DateTime.Now; + } + + /// + /// Method to call on entity saved/updated + /// + internal virtual void UpdatingEntity() + { + UpdateDate = DateTime.Now; + } + + /// + /// Tracks the properties that have changed + /// + //private readonly IDictionary _propertyChangedInfo = new Dictionary(); + private IDictionary _propertyChangedInfo; + + /// + /// Indicates whether a specific property on the current entity is dirty. + /// + /// Name of the property to check + /// True if Property is dirty, otherwise False + public virtual bool IsPropertyDirty(string propertyName) + { + return _propertyChangedInfo.Any(x => x.Key == propertyName); + } + + /// + /// Indicates whether the current entity is dirty. + /// + /// True if entity is dirty, otherwise False + public virtual bool IsDirty() + { + return _propertyChangedInfo.Any(); + } + + /// + /// Resets dirty properties by clearing the dictionary used to track changes. + /// + /// + /// Please note that resetting the dirty properties could potentially + /// obstruct the saving of a new or updated entity. + /// + public virtual void ResetDirtyProperties() + { + _propertyChangedInfo.Clear(); + } + + /// + /// Indicates whether the current entity has an identity, eg. Id. + /// + public virtual bool HasIdentity + { + get + { + return _hasIdentity; + } + protected set + { + _hasIdentity = value; + } + } + + public static bool operator ==(Item left, Item right) + { + /*if (ReferenceEquals(null, left)) + return false; + + if(ReferenceEquals(null, right)) + return false;*/ + + return ReferenceEquals(left, right); + + return left.Equals(right); + } + + public static bool operator !=(Item left, Item right) + { + return !(left == right); + } + + /*public virtual bool SameIdentityAs(IEntity other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return SameIdentityAs(other as Entity); + } + + public virtual bool Equals(Entity other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return SameIdentityAs(other); + } + + public virtual Type GetRealType() + { + return GetType(); + } + + public virtual bool SameIdentityAs(Entity other) + { + if (ReferenceEquals(null, other)) + return false; + + if (ReferenceEquals(this, other)) + return true; + + if (GetType() == other.GetRealType() && HasIdentity && other.HasIdentity) + return other.Id.Equals(Id); + + return false; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + + return SameIdentityAs(obj as IEntity); + } + + public override int GetHashCode() + { + if (!_hash.HasValue) + _hash = !HasIdentity ? new int?(base.GetHashCode()) : new int?(Id.GetHashCode() * 397 ^ GetType().GetHashCode()); + return _hash.Value; + }*/ + + public object DeepClone() + { + return this.MemberwiseClone(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/Collections/OrderItem.cs b/src/Umbraco.Tests/Models/Collections/OrderItem.cs new file mode 100644 index 0000000000..81c770db92 --- /dev/null +++ b/src/Umbraco.Tests/Models/Collections/OrderItem.cs @@ -0,0 +1,42 @@ +using System; + +namespace Umbraco.Tests.Models.Collections +{ + public class OrderItem : Item + { + public readonly int PartNumber; + public readonly string Description; + public readonly double UnitPrice; + + private int _quantity = 0; + + public OrderItem(int partNumber, string description, + int quantity, double unitPrice) + { + this.PartNumber = partNumber; + this.Description = description; + this.Quantity = quantity; + this.UnitPrice = unitPrice; + } + + public int Quantity + { + get { return _quantity; } + set + { + if (value < 0) + throw new ArgumentException("Quantity cannot be negative."); + + _quantity = value; + } + } + + public override string ToString() + { + return String.Format( + "{0,9} {1,6} {2,-12} at {3,8:#,###.00} = {4,10:###,###.00}", + PartNumber, _quantity, Description, UnitPrice, + UnitPrice * _quantity); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs index 67ef3a2bf5..d54e8727aa 100644 --- a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs +++ b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs @@ -1,15 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; +using System.Collections.Generic; using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -72,353 +65,4 @@ namespace Umbraco.Tests.Models.Collections Assert.That(contentType.PropertyGroups.Any(x => x.Name.InvariantEquals("Test")), Is.False); } } - - public class SimpleOrder : KeyedCollection, INotifyCollectionChanged - { - // The parameterless constructor of the base class creates a - // KeyedCollection with an internal dictionary. For this code - // example, no other constructors are exposed. - // - public SimpleOrder() : base() { } - - public SimpleOrder(IEnumerable properties) - { - Reset(properties); - } - - // This is the only method that absolutely must be overridden, - // because without it the KeyedCollection cannot extract the - // keys from the items. The input parameter type is the - // second generic type argument, in this case OrderItem, and - // the return value type is the first generic type argument, - // in this case int. - // - protected override int GetKeyForItem(OrderItem item) - { - // In this example, the key is the part number. - return item.PartNumber; - } - - internal void Reset(IEnumerable properties) - { - Clear(); - properties.ForEach(Add); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - protected override void SetItem(int index, OrderItem item) - { - base.SetItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); - } - - protected override void RemoveItem(int index) - { - var removed = this[index]; - base.RemoveItem(index); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); - } - - protected override void InsertItem(int index, OrderItem item) - { - base.InsertItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); - } - - protected override void ClearItems() - { - base.ClearItems(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - public new bool Contains(int partNumber) - { - return this.Any(x => x.PartNumber == partNumber); - } - - public event NotifyCollectionChangedEventHandler CollectionChanged; - - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - if (CollectionChanged != null) - { - CollectionChanged(this, args); - } - } - } - - public class OrderItem : Item - { - public readonly int PartNumber; - public readonly string Description; - public readonly double UnitPrice; - - private int _quantity = 0; - - public OrderItem(int partNumber, string description, - int quantity, double unitPrice) - { - this.PartNumber = partNumber; - this.Description = description; - this.Quantity = quantity; - this.UnitPrice = unitPrice; - } - - public int Quantity - { - get { return _quantity; } - set - { - if (value < 0) - throw new ArgumentException("Quantity cannot be negative."); - - _quantity = value; - } - } - - public override string ToString() - { - return String.Format( - "{0,9} {1,6} {2,-12} at {3,8:#,###.00} = {4,10:###,###.00}", - PartNumber, _quantity, Description, UnitPrice, - UnitPrice * _quantity); - } - } - - public abstract class Item : IEntity, ICanBeDirty - { - private bool _hasIdentity; - private int? _hash; - private int _id; - private Guid _key; - - protected Item() - { - _propertyChangedInfo = new Dictionary(); - } - - /// - /// Integer Id - /// - [DataMember] - public int Id - { - get - { - return _id; - } - set - { - _id = value; - HasIdentity = true; - } - } - - /// - /// Guid based Id - /// - /// The key is currectly used to store the Unique Id from the - /// umbracoNode table, which many of the entities are based on. - [DataMember] - public Guid Key - { - get - { - if (_key == Guid.Empty) - return _id.ToGuid(); - - return _key; - } - set { _key = value; } - } - - /// - /// Gets or sets the Created Date - /// - [DataMember] - public DateTime CreateDate { get; set; } - - /// - /// Gets or sets the Modified Date - /// - [DataMember] - public DateTime UpdateDate { get; set; } - - /// - /// 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. - /// - [IgnoreDataMember] - internal bool WasCancelled { get; set; } - - /// - /// Property changed event - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Method to call on a property setter. - /// - /// The property info. - protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) - { - _propertyChangedInfo[propertyInfo.Name] = true; - - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyInfo.Name)); - } - } - - internal virtual void ResetIdentity() - { - _hasIdentity = false; - _id = default(int); - } - - /// - /// Method to call on entity saved when first added - /// - internal virtual void AddingEntity() - { - CreateDate = DateTime.Now; - UpdateDate = DateTime.Now; - } - - /// - /// Method to call on entity saved/updated - /// - internal virtual void UpdatingEntity() - { - UpdateDate = DateTime.Now; - } - - /// - /// Tracks the properties that have changed - /// - //private readonly IDictionary _propertyChangedInfo = new Dictionary(); - private IDictionary _propertyChangedInfo; - - /// - /// Indicates whether a specific property on the current entity is dirty. - /// - /// Name of the property to check - /// True if Property is dirty, otherwise False - public virtual bool IsPropertyDirty(string propertyName) - { - return _propertyChangedInfo.Any(x => x.Key == propertyName); - } - - /// - /// Indicates whether the current entity is dirty. - /// - /// True if entity is dirty, otherwise False - public virtual bool IsDirty() - { - return _propertyChangedInfo.Any(); - } - - /// - /// Resets dirty properties by clearing the dictionary used to track changes. - /// - /// - /// Please note that resetting the dirty properties could potentially - /// obstruct the saving of a new or updated entity. - /// - public virtual void ResetDirtyProperties() - { - _propertyChangedInfo.Clear(); - } - - /// - /// Indicates whether the current entity has an identity, eg. Id. - /// - public virtual bool HasIdentity - { - get - { - return _hasIdentity; - } - protected set - { - _hasIdentity = value; - } - } - - public static bool operator ==(Item left, Item right) - { - /*if (ReferenceEquals(null, left)) - return false; - - if(ReferenceEquals(null, right)) - return false;*/ - - return ReferenceEquals(left, right); - - return left.Equals(right); - } - - public static bool operator !=(Item left, Item right) - { - return !(left == right); - } - - /*public virtual bool SameIdentityAs(IEntity other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return SameIdentityAs(other as Entity); - } - - public virtual bool Equals(Entity other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return SameIdentityAs(other); - } - - public virtual Type GetRealType() - { - return GetType(); - } - - public virtual bool SameIdentityAs(Entity other) - { - if (ReferenceEquals(null, other)) - return false; - - if (ReferenceEquals(this, other)) - return true; - - if (GetType() == other.GetRealType() && HasIdentity && other.HasIdentity) - return other.Id.Equals(Id); - - return false; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - if (ReferenceEquals(this, obj)) - return true; - - return SameIdentityAs(obj as IEntity); - } - - public override int GetHashCode() - { - if (!_hash.HasValue) - _hash = !HasIdentity ? new int?(base.GetHashCode()) : new int?(Id.GetHashCode() * 397 ^ GetType().GetHashCode()); - return _hash.Value; - }*/ - } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/Collections/SimpleOrder.cs b/src/Umbraco.Tests/Models/Collections/SimpleOrder.cs new file mode 100644 index 0000000000..82df104b64 --- /dev/null +++ b/src/Umbraco.Tests/Models/Collections/SimpleOrder.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using Umbraco.Core; + +namespace Umbraco.Tests.Models.Collections +{ + public class SimpleOrder : KeyedCollection, INotifyCollectionChanged + { + // The parameterless constructor of the base class creates a + // KeyedCollection with an internal dictionary. For this code + // example, no other constructors are exposed. + // + public SimpleOrder() : base() { } + + public SimpleOrder(IEnumerable properties) + { + Reset(properties); + } + + // This is the only method that absolutely must be overridden, + // because without it the KeyedCollection cannot extract the + // keys from the items. The input parameter type is the + // second generic type argument, in this case OrderItem, and + // the return value type is the first generic type argument, + // in this case int. + // + protected override int GetKeyForItem(OrderItem item) + { + // In this example, the key is the part number. + return item.PartNumber; + } + + internal void Reset(IEnumerable properties) + { + Clear(); + properties.ForEach(Add); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + protected override void SetItem(int index, OrderItem item) + { + base.SetItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + } + + protected override void RemoveItem(int index) + { + var removed = this[index]; + base.RemoveItem(index); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + } + + protected override void InsertItem(int index, OrderItem item) + { + base.InsertItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } + + protected override void ClearItems() + { + base.ClearItems(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + public new bool Contains(int partNumber) + { + return this.Any(x => x.PartNumber == partNumber); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + if (CollectionChanged != null) + { + CollectionChanged(this, args); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index e80fe8ba83..61eda9633d 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -6,6 +6,8 @@ using System.Web; using Moq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Serialization; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -159,7 +161,7 @@ namespace Umbraco.Tests.Models [Test] - public void Can_Clone_Content() + public void Can_Clone_Content_With_Reset_Identity() { // Arrange var contentType = MockedContentTypes.CreateTextpageContentType(); @@ -175,6 +177,143 @@ namespace Umbraco.Tests.Models Assert.AreNotSame(clone.Id, content.Id); Assert.AreNotSame(clone.Version, content.Version); Assert.That(clone.HasIdentity, Is.False); + + 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 Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + // 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"); + + var result = ss.ToStream(content); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); } /*[Test] diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs new file mode 100644 index 0000000000..af94f80fd4 --- /dev/null +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -0,0 +1,367 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Serialization; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class ContentTypeTests + { + [Test] + public void Can_Deep_Clone_Content_Type_Sort() + { + var contentType = new ContentTypeSort(new Lazy(() => 3), 4, "test"); + var clone = (ContentTypeSort) contentType.DeepClone(); + Assert.AreNotSame(clone, contentType); + Assert.AreEqual(clone, contentType); + Assert.AreEqual(clone.Id.Value, contentType.Id.Value); + Assert.AreEqual(clone.SortOrder, contentType.SortOrder); + Assert.AreEqual(clone.Alias, contentType.Alias); + + } + + [Test] + public void Can_Deep_Clone_Content_Type() + { + // Arrange + var contentType = MockedContentTypes.CreateTextpageContentType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.AllowedTemplates = new[] { new Template("-1,2", "Name", "name") { Id = 200 }, new Template("-1,3", "Name2", "name2") { Id = 201 } }; + contentType.AllowedContentTypes = new[] {new ContentTypeSort(new Lazy(() => 888), 8, "sub"), new ContentTypeSort(new Lazy(() => 889), 9, "sub2")}; + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.SetDefaultTemplate(new Template("-1,2,3,4", "Test Template", "testTemplate") + { + Id = 88 + }); + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + // Act + var clone = (ContentType)contentType.DeepClone(); + + // Assert + Assert.AreNotSame(clone, contentType); + Assert.AreEqual(clone, contentType); + Assert.AreEqual(clone.Id, contentType.Id); + Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)contentType).AdditionalData); + Assert.AreEqual(clone.AllowedTemplates.Count(), contentType.AllowedTemplates.Count()); + for (var index = 0; index < contentType.AllowedTemplates.Count(); index++) + { + Assert.AreNotSame(clone.AllowedTemplates.ElementAt(index), contentType.AllowedTemplates.ElementAt(index)); + Assert.AreEqual(clone.AllowedTemplates.ElementAt(index), contentType.AllowedTemplates.ElementAt(index)); + } + Assert.AreEqual(clone.PropertyGroups.Count, contentType.PropertyGroups.Count); + for (var index = 0; index < contentType.PropertyGroups.Count; index++) + { + Assert.AreNotSame(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + Assert.AreEqual(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + } + Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); + for (var index = 0; index < contentType.PropertyTypes.Count(); index++) + { + Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + Assert.AreEqual(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + } + + Assert.AreEqual(clone.CreateDate, contentType.CreateDate); + Assert.AreEqual(clone.CreatorId, contentType.CreatorId); + Assert.AreEqual(clone.Key, contentType.Key); + Assert.AreEqual(clone.Level, contentType.Level); + Assert.AreEqual(clone.Path, contentType.Path); + Assert.AreEqual(clone.SortOrder, contentType.SortOrder); + Assert.AreNotSame(clone.DefaultTemplate, contentType.DefaultTemplate); + Assert.AreEqual(clone.DefaultTemplate, contentType.DefaultTemplate); + Assert.AreEqual(clone.DefaultTemplateId, contentType.DefaultTemplateId); + Assert.AreEqual(clone.Trashed, contentType.Trashed); + Assert.AreEqual(clone.UpdateDate, contentType.UpdateDate); + Assert.AreEqual(clone.Thumbnail, contentType.Thumbnail); + Assert.AreEqual(clone.Icon, contentType.Icon); + Assert.AreEqual(clone.IsContainer, contentType.IsContainer); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); + } + } + + [Test] + public void Can_Serialize_Content_Type_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + // Arrange + var contentType = MockedContentTypes.CreateTextpageContentType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.AllowedTemplates = new[] { new Template("-1,2", "Name", "name") { Id = 200 }, new Template("-1,3", "Name2", "name2") { Id = 201 } }; + contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy(() => 888), 8, "sub"), new ContentTypeSort(new Lazy(() => 889), 9, "sub2") }; + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.SetDefaultTemplate(new Template("-1,2,3,4", "Test Template", "testTemplate") + { + Id = 88 + }); + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(contentType); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + + [Test] + public void Can_Deep_Clone_Media_Type() + { + // Arrange + var contentType = MockedContentTypes.CreateImageMediaType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + // Act + var clone = (MediaType)contentType.DeepClone(); + + // Assert + Assert.AreNotSame(clone, contentType); + Assert.AreEqual(clone, contentType); + Assert.AreEqual(clone.Id, contentType.Id); + Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)contentType).AdditionalData); + Assert.AreEqual(clone.PropertyGroups.Count, contentType.PropertyGroups.Count); + for (var index = 0; index < contentType.PropertyGroups.Count; index++) + { + Assert.AreNotSame(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + Assert.AreEqual(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + } + Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); + for (var index = 0; index < contentType.PropertyTypes.Count(); index++) + { + Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + Assert.AreEqual(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + } + Assert.AreEqual(clone.CreateDate, contentType.CreateDate); + Assert.AreEqual(clone.CreatorId, contentType.CreatorId); + Assert.AreEqual(clone.Key, contentType.Key); + Assert.AreEqual(clone.Level, contentType.Level); + Assert.AreEqual(clone.Path, contentType.Path); + Assert.AreEqual(clone.SortOrder, contentType.SortOrder); + Assert.AreEqual(clone.Trashed, contentType.Trashed); + Assert.AreEqual(clone.UpdateDate, contentType.UpdateDate); + Assert.AreEqual(clone.Thumbnail, contentType.Thumbnail); + Assert.AreEqual(clone.Icon, contentType.Icon); + Assert.AreEqual(clone.IsContainer, contentType.IsContainer); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); + } + } + + [Test] + public void Can_Serialize_Media_Type_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + // Arrange + var contentType = MockedContentTypes.CreateImageMediaType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(contentType); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + + [Test] + public void Can_Deep_Clone_Member_Type() + { + // Arrange + var contentType = MockedContentTypes.CreateSimpleMemberType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + contentType.SetMemberCanEditProperty("title", true); + contentType.SetMemberCanViewProperty("bodyText", true); + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + // Act + var clone = (MemberType)contentType.DeepClone(); + + // Assert + Assert.AreNotSame(clone, contentType); + Assert.AreEqual(clone, contentType); + Assert.AreEqual(clone.Id, contentType.Id); + Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)contentType).AdditionalData); + Assert.AreEqual(clone.PropertyGroups.Count, contentType.PropertyGroups.Count); + for (var index = 0; index < contentType.PropertyGroups.Count; index++) + { + Assert.AreNotSame(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + Assert.AreEqual(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + } + Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); + for (var index = 0; index < contentType.PropertyTypes.Count(); index++) + { + Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + Assert.AreEqual(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + } + Assert.AreEqual(clone.CreateDate, contentType.CreateDate); + Assert.AreEqual(clone.CreatorId, contentType.CreatorId); + Assert.AreEqual(clone.Key, contentType.Key); + Assert.AreEqual(clone.Level, contentType.Level); + Assert.AreEqual(clone.Path, contentType.Path); + Assert.AreEqual(clone.SortOrder, contentType.SortOrder); + Assert.AreEqual(clone.Trashed, contentType.Trashed); + Assert.AreEqual(clone.UpdateDate, contentType.UpdateDate); + Assert.AreEqual(clone.Thumbnail, contentType.Thumbnail); + Assert.AreEqual(clone.Icon, contentType.Icon); + Assert.AreEqual(clone.IsContainer, contentType.IsContainer); + Assert.AreEqual(clone.MemberTypePropertyTypes, contentType.MemberTypePropertyTypes); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); + } + } + + [Test] + public void Can_Serialize_Member_Type_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + // Arrange + var contentType = MockedContentTypes.CreateSimpleMemberType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + contentType.SetMemberCanEditProperty("title", true); + contentType.SetMemberCanViewProperty("bodyText", true); + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(contentType); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs b/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs new file mode 100644 index 0000000000..d066fbacaf --- /dev/null +++ b/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DataTypeDefinitionTests + { + [Test] + public void Can_Deep_Clone() + { + var dtd = new DataTypeDefinition(9, Guid.NewGuid()) + { + CreateDate = DateTime.Now, + CreatorId = 5, + DatabaseType = DataTypeDatabaseType.Nvarchar, + Id = 4, + Key = Guid.NewGuid(), + Level = 7, + Name = "Test", + ParentId = 9, + Path = "-1,2", + SortOrder = 8, + Trashed = true, + UpdateDate = DateTime.Now + }; + var clone = (DataTypeDefinition) dtd.DeepClone(); + + Assert.AreNotSame(clone, dtd); + Assert.AreEqual(clone, dtd); + Assert.AreEqual(clone.CreateDate, dtd.CreateDate); + Assert.AreEqual(clone.CreatorId, dtd.CreatorId); + Assert.AreEqual(clone.DatabaseType, dtd.DatabaseType); + Assert.AreEqual(clone.Id, dtd.Id); + Assert.AreEqual(clone.Key, dtd.Key); + Assert.AreEqual(clone.Level, dtd.Level); + Assert.AreEqual(clone.Name, dtd.Name); + Assert.AreEqual(clone.ParentId, dtd.ParentId); + Assert.AreEqual(clone.Path, dtd.Path); + Assert.AreEqual(clone.SortOrder, dtd.SortOrder); + Assert.AreEqual(clone.Trashed, dtd.Trashed); + Assert.AreEqual(clone.UpdateDate, dtd.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(dtd, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var dtd = new DataTypeDefinition(9, Guid.NewGuid()) + { + CreateDate = DateTime.Now, + CreatorId = 5, + DatabaseType = DataTypeDatabaseType.Nvarchar, + Id = 4, + Key = Guid.NewGuid(), + Level = 7, + Name = "Test", + ParentId = 9, + Path = "-1,2", + SortOrder = 8, + Trashed = true, + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(dtd); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DeepCloneHelperTests.cs b/src/Umbraco.Tests/Models/DeepCloneHelperTests.cs new file mode 100644 index 0000000000..d82d0d2ebd --- /dev/null +++ b/src/Umbraco.Tests/Models/DeepCloneHelperTests.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DeepCloneHelperTests + { + [Test] + public void Deep_Clone_Ref_Properties() + { + var test1 = new Test1() + { + MyTest1 = new Test1(), + MyTest2 = new Test2() + }; + + var clone = (Test1)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreNotSame(test1.MyTest1, clone.MyTest1); + Assert.AreSame(test1.MyTest2, clone.MyTest2); + } + + [Test] + public void Deep_Clone_Array_Property() + { + var test1 = new Test3() + { + MyTest1 = new object[] { new Test1(), new Test1() } + }; + + var clone = (Test3)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreEqual(test1.MyTest1.Count(), clone.MyTest1.Count()); + for (int i = 0; i < test1.MyTest1.Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.ElementAt(i), test1.MyTest1.ElementAt(i)); + } + } + + [Test] + public void Deep_Clone_Typed_Array_Property() + { + var test1 = new Test4() + { + MyTest1 = new[] { new Test1(), new Test1() } + }; + + var clone = (Test4)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreEqual(test1.MyTest1.Count(), clone.MyTest1.Count()); + for (int i = 0; i < test1.MyTest1.Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.ElementAt(i), test1.MyTest1.ElementAt(i)); + } + } + + [Test] + public void Deep_Clone_Enumerable_Property() + { + var test1 = new Test5() + { + MyTest1 = new[] { new Test1(), new Test1() } + }; + + var clone = (Test5)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + + Assert.AreEqual(test1.MyTest1.Cast().Count(), clone.MyTest1.Cast().Count()); + for (int i = 0; i < test1.MyTest1.Cast().Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.Cast().ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.Cast().ElementAt(i), test1.MyTest1.Cast().ElementAt(i)); + } + } + + [Test] + public void Deep_Clone_Typed_Enumerable_Property() + { + var test1 = new Test6() + { + MyTest1 = new[] { new Test1(), new Test1() } + }; + + var clone = (Test6)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreEqual(test1.MyTest1.Count(), clone.MyTest1.Count()); + for (int i = 0; i < test1.MyTest1.Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.ElementAt(i), test1.MyTest1.ElementAt(i)); + } + } + + [Test] + public void Deep_Clone_Custom_Enumerable_Property() + { + var test1 = new Test7() + { + MyTest1 = new List { new Test1(), new Test1() } + }; + + var clone = (Test7)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreEqual(test1.MyTest1.Count(), clone.MyTest1.Count()); + for (int i = 0; i < test1.MyTest1.Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.ElementAt(i), test1.MyTest1.ElementAt(i)); + } + } + + [Test] + public void Deep_Clone_Custom_Enumerable_Interface_Property() + { + var test1 = new Test8() + { + MyTest1 = new List { new Test1(), new Test1() } + }; + + var clone = (Test8)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreEqual(test1.MyTest1.Count(), clone.MyTest1.Count()); + for (int i = 0; i < test1.MyTest1.Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.ElementAt(i), test1.MyTest1.ElementAt(i)); + } + } + + [Test] + public void Cannot_Deep_Clone_Collection_Properties_That_Are_Not_Cloneable() + { + var test1 = new Test3() + { + MyTest1 = new object[] + { + new Test1(), "hello", + //not cloneable so this property will get skipped + new Test2() + } + }; + + var clone = (Test3)test1.DeepClone(); + + //it skipped this property so these will now be the same + Assert.AreSame(clone.MyTest1, test1.MyTest1); + + } + + public class Test1 : BaseCloneable + { + public string Name { get; set; } + public int Age { get; set; } + + public Test1 MyTest1 { get; set; } + public Test2 MyTest2 { get; set; } + + } + + public class Test2 + { + public string Name { get; set; } + public Test1 MyTest1 { get; set; } + } + + public class Test3 : BaseCloneable + { + public string Name { get; set; } + public object[] MyTest1 { get; set; } + + } + + public class Test4 : BaseCloneable + { + public string Name { get; set; } + public Test1[] MyTest1 { get; set; } + + } + + public class Test5 : BaseCloneable + { + public string Name { get; set; } + public IEnumerable MyTest1 { get; set; } + + } + + public class Test6 : BaseCloneable + { + public string Name { get; set; } + public IEnumerable MyTest1 { get; set; } + + } + + public class Test7 : BaseCloneable + { + public string Name { get; set; } + public List MyTest1 { get; set; } + + } + + public class Test8 : BaseCloneable + { + public string Name { get; set; } + public ICollection MyTest1 { get; set; } + + } + + public abstract class BaseCloneable : IDeepCloneable + { + public object DeepClone() + { + var clone = (IDeepCloneable)MemberwiseClone(); + DeepCloneHelper.DeepCloneRefProperties(this, clone); + return clone; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DictionaryItemTests.cs b/src/Umbraco.Tests/Models/DictionaryItemTests.cs new file mode 100644 index 0000000000..ff57b5eec6 --- /dev/null +++ b/src/Umbraco.Tests/Models/DictionaryItemTests.cs @@ -0,0 +1,136 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DictionaryItemTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new DictionaryItem("blah") + { + CreateDate = DateTime.Now, + Id = 8, + ItemKey = "blah", + Key = Guid.NewGuid(), + ParentId = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Translations = new[] + { + new DictionaryTranslation(new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 11, + IsoCode = "AU", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "colour") + { + CreateDate = DateTime.Now, + Id = 88, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, + new DictionaryTranslation(new Language("en-US") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 12, + IsoCode = "US", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "color") + { + CreateDate = DateTime.Now, + Id = 89, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, + } + }; + + var clone = (DictionaryItem)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.ItemKey, item.ItemKey); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.ParentId, item.ParentId); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + Assert.AreEqual(clone.Translations.Count(), item.Translations.Count()); + for (var i = 0; i < item.Translations.Count(); i++) + { + Assert.AreNotSame(clone.Translations.ElementAt(i), item.Translations.ElementAt(i)); + Assert.AreEqual(clone.Translations.ElementAt(i), item.Translations.ElementAt(i)); + } + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new DictionaryItem("blah") + { + CreateDate = DateTime.Now, + Id = 8, + ItemKey = "blah", + Key = Guid.NewGuid(), + ParentId = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Translations = new[] + { + new DictionaryTranslation(new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 11, + IsoCode = "AU", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "colour") + { + CreateDate = DateTime.Now, + Id = 88, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, + new DictionaryTranslation(new Language("en-US") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 12, + IsoCode = "US", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "color") + { + CreateDate = DateTime.Now, + Id = 89, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, + } + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs new file mode 100644 index 0000000000..d0a8c21581 --- /dev/null +++ b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs @@ -0,0 +1,76 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DictionaryTranslationTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new DictionaryTranslation(new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 11, + IsoCode = "AU", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "colour") + { + CreateDate = DateTime.Now, + Id = 88, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var clone = (DictionaryTranslation) item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + Assert.AreNotSame(clone.Language, item.Language); + Assert.AreEqual(clone.Language, item.Language); + Assert.AreEqual(clone.Value, item.Value); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new DictionaryTranslation(new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 11, + IsoCode = "AU", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "colour") + { + CreateDate = DateTime.Now, + Id = 88, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/LanguageTests.cs b/src/Umbraco.Tests/Models/LanguageTests.cs new file mode 100644 index 0000000000..5d79364fb5 --- /dev/null +++ b/src/Umbraco.Tests/Models/LanguageTests.cs @@ -0,0 +1,62 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class LanguageTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "AU", + Id = 11, + IsoCode = "en", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var clone = (Language) item.DeepClone(); + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.CultureName, item.CultureName); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.IsoCode, item.IsoCode); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "AU", + Id = 11, + IsoCode = "en", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/MemberGroupTests.cs b/src/Umbraco.Tests/Models/MemberGroupTests.cs new file mode 100644 index 0000000000..d39ddc93f3 --- /dev/null +++ b/src/Umbraco.Tests/Models/MemberGroupTests.cs @@ -0,0 +1,73 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class MemberGroupTests + { + [Test] + public void Can_Deep_Clone() + { + // Arrange + var group = new MemberGroup() + { + CreateDate = DateTime.Now, + CreatorId = 4, + Id = 6, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Name = "asdf" + }; + group.AdditionalData.Add("test1", 123); + group.AdditionalData.Add("test2", "hello"); + + // Act + var clone = (MemberGroup)group.DeepClone(); + + // Assert + Assert.AreNotSame(clone, group); + Assert.AreEqual(clone, group); + Assert.AreEqual(clone.Id, group.Id); + Assert.AreEqual(clone.AdditionalData, group.AdditionalData); + Assert.AreEqual(clone.AdditionalData.Count, group.AdditionalData.Count); + Assert.AreEqual(clone.CreateDate, group.CreateDate); + Assert.AreEqual(clone.CreatorId, group.CreatorId); + Assert.AreEqual(clone.Key, group.Key); + Assert.AreEqual(clone.UpdateDate, group.UpdateDate); + Assert.AreEqual(clone.Name, group.Name); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(group, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var group = new MemberGroup() + { + CreateDate = DateTime.Now, + CreatorId = 4, + Id = 6, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Name = "asdf" + }; + group.AdditionalData.Add("test1", 123); + group.AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(group); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs new file mode 100644 index 0000000000..62e3602232 --- /dev/null +++ b/src/Umbraco.Tests/Models/MemberTests.cs @@ -0,0 +1,156 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Serialization; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class MemberTests + { + [Test] + public void Can_Deep_Clone() + { + // Arrange + var memberType = MockedContentTypes.CreateSimpleMemberType("memberType", "Member Type"); + memberType.Id = 99; + var member = MockedMember.CreateSimpleMember(memberType, "Name", "email@email.com", "pass", "user", Guid.NewGuid()); + var i = 200; + foreach (var property in member.Properties) + { + property.Id = ++i; + } + member.Id = 10; + member.CreateDate = DateTime.Now; + member.CreatorId = 22; + member.Comments = "comments"; + member.Key = Guid.NewGuid(); + member.FailedPasswordAttempts = 22; + member.Level = 3; + member.Path = "-1,4,10"; + member.Groups = new[] {"group1", "group2"}; + member.IsApproved = true; + member.IsLockedOut = true; + member.LastLockoutDate = DateTime.Now; + member.LastLoginDate = DateTime.Now; + member.LastPasswordChangeDate = DateTime.Now; + member.PasswordQuestion = "question"; + member.ProviderUserKey = Guid.NewGuid(); + member.RawPasswordAnswerValue = "raw answer"; + member.RawPasswordValue = "raw pass"; + member.SortOrder = 5; + member.Trashed = false; + member.UpdateDate = DateTime.Now; + member.Version = Guid.NewGuid(); + ((IUmbracoEntity)member).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)member).AdditionalData.Add("test2", "hello"); + + // Act + var clone = (Member)member.DeepClone(); + + // Assert + Assert.AreNotSame(clone, member); + Assert.AreEqual(clone, member); + Assert.AreEqual(clone.Id, member.Id); + Assert.AreEqual(clone.Version, member.Version); + Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)member).AdditionalData); + Assert.AreNotSame(clone.ContentType, member.ContentType); + Assert.AreEqual(clone.ContentType, member.ContentType); + Assert.AreEqual(clone.ContentType.PropertyGroups.Count, member.ContentType.PropertyGroups.Count); + for (var index = 0; index < member.ContentType.PropertyGroups.Count; index++) + { + Assert.AreNotSame(clone.ContentType.PropertyGroups[index], member.ContentType.PropertyGroups[index]); + Assert.AreEqual(clone.ContentType.PropertyGroups[index], member.ContentType.PropertyGroups[index]); + } + Assert.AreEqual(clone.ContentType.PropertyTypes.Count(), member.ContentType.PropertyTypes.Count()); + for (var index = 0; index < member.ContentType.PropertyTypes.Count(); index++) + { + Assert.AreNotSame(clone.ContentType.PropertyTypes.ElementAt(index), member.ContentType.PropertyTypes.ElementAt(index)); + Assert.AreEqual(clone.ContentType.PropertyTypes.ElementAt(index), member.ContentType.PropertyTypes.ElementAt(index)); + } + Assert.AreEqual(clone.ContentTypeId, member.ContentTypeId); + Assert.AreEqual(clone.CreateDate, member.CreateDate); + Assert.AreEqual(clone.CreatorId, member.CreatorId); + Assert.AreEqual(clone.Comments, member.Comments); + Assert.AreEqual(clone.Key, member.Key); + Assert.AreEqual(clone.FailedPasswordAttempts, member.FailedPasswordAttempts); + Assert.AreEqual(clone.Level, member.Level); + Assert.AreEqual(clone.Path, member.Path); + Assert.AreEqual(clone.Groups, member.Groups); + Assert.AreEqual(clone.Groups.Count(), member.Groups.Count()); + Assert.AreEqual(clone.IsApproved, member.IsApproved); + Assert.AreEqual(clone.IsLockedOut, member.IsLockedOut); + Assert.AreEqual(clone.SortOrder, member.SortOrder); + Assert.AreEqual(clone.LastLockoutDate, member.LastLockoutDate); + Assert.AreNotSame(clone.LastLoginDate, member.LastLoginDate); + Assert.AreEqual(clone.LastPasswordChangeDate, member.LastPasswordChangeDate); + Assert.AreEqual(clone.Trashed, member.Trashed); + Assert.AreEqual(clone.UpdateDate, member.UpdateDate); + Assert.AreEqual(clone.Version, member.Version); + Assert.AreEqual(clone.PasswordQuestion, member.PasswordQuestion); + Assert.AreEqual(clone.ProviderUserKey, member.ProviderUserKey); + Assert.AreEqual(clone.RawPasswordAnswerValue, member.RawPasswordAnswerValue); + Assert.AreEqual(clone.RawPasswordValue, member.RawPasswordValue); + Assert.AreNotSame(clone.Properties, member.Properties); + Assert.AreEqual(clone.Properties.Count(), member.Properties.Count()); + for (var index = 0; index < member.Properties.Count; index++) + { + Assert.AreNotSame(clone.Properties[index], member.Properties[index]); + Assert.AreEqual(clone.Properties[index], member.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(member, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var memberType = MockedContentTypes.CreateSimpleMemberType("memberType", "Member Type"); + memberType.Id = 99; + var member = MockedMember.CreateSimpleMember(memberType, "Name", "email@email.com", "pass", "user", Guid.NewGuid()); + var i = 200; + foreach (var property in member.Properties) + { + property.Id = ++i; + } + member.Id = 10; + member.CreateDate = DateTime.Now; + member.CreatorId = 22; + member.Comments = "comments"; + member.Key = Guid.NewGuid(); + member.FailedPasswordAttempts = 22; + member.Level = 3; + member.Path = "-1,4,10"; + member.Groups = new[] { "group1", "group2" }; + member.IsApproved = true; + member.IsLockedOut = true; + member.LastLockoutDate = DateTime.Now; + member.LastLoginDate = DateTime.Now; + member.LastPasswordChangeDate = DateTime.Now; + member.PasswordQuestion = "question"; + member.ProviderUserKey = Guid.NewGuid(); + member.RawPasswordAnswerValue = "raw answer"; + member.RawPasswordValue = "raw pass"; + member.SortOrder = 5; + member.Trashed = false; + member.UpdateDate = DateTime.Now; + member.Version = Guid.NewGuid(); + ((IUmbracoEntity)member).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)member).AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(member); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyGroupTests.cs b/src/Umbraco.Tests/Models/PropertyGroupTests.cs new file mode 100644 index 0000000000..b6b15c83e8 --- /dev/null +++ b/src/Umbraco.Tests/Models/PropertyGroupTests.cs @@ -0,0 +1,148 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class PropertyGroupTests + { + [Test] + public void Can_Deep_Clone() + { + var pg = new PropertyGroup( + new PropertyTypeCollection(new[] + { + new PropertyType("TestPropertyEditor", DataTypeDatabaseType.Nvarchar) + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 5, + DataTypeId = Guid.NewGuid(), + Description = "testing", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test", + PropertyGroupId = new Lazy(() => 11), + SortOrder = 9, + UpdateDate = DateTime.Now, + ValidationRegExp = "xxxx", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + }, + new PropertyType("TestPropertyEditor", DataTypeDatabaseType.Nvarchar) + { + Id = 4, + Alias = "test2", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 6, + DataTypeId = Guid.NewGuid(), + Description = "testing2", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test2", + PropertyGroupId = new Lazy(() => 12), + SortOrder = 10, + UpdateDate = DateTime.Now, + ValidationRegExp = "yyyy", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + } + })) + { + Id = 77, + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + Name = "Group1", + SortOrder = 555, + UpdateDate = DateTime.Now, + ParentId = 9 + }; + + var clone = (PropertyGroup)pg.DeepClone(); + + Assert.AreNotSame(clone, pg); + Assert.AreEqual(clone, pg); + Assert.AreEqual(clone.Id, pg.Id); + Assert.AreEqual(clone.CreateDate, pg.CreateDate); + Assert.AreEqual(clone.Key, pg.Key); + Assert.AreEqual(clone.Name, pg.Name); + Assert.AreEqual(clone.SortOrder, pg.SortOrder); + Assert.AreEqual(clone.UpdateDate, pg.UpdateDate); + Assert.AreEqual(clone.ParentId, pg.ParentId); + Assert.AreNotSame(clone.PropertyTypes, pg.PropertyTypes); + Assert.AreEqual(clone.PropertyTypes, pg.PropertyTypes); + Assert.AreEqual(clone.PropertyTypes.Count, pg.PropertyTypes.Count); + for (var i = 0; i < pg.PropertyTypes.Count; i++) + { + Assert.AreNotSame(clone.PropertyTypes[i], pg.PropertyTypes[i]); + Assert.AreEqual(clone.PropertyTypes[i], pg.PropertyTypes[i]); + } + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(pg, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var pg = new PropertyGroup( + new PropertyTypeCollection(new[] + { + new PropertyType("TestPropertyEditor", DataTypeDatabaseType.Nvarchar) + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 5, + DataTypeId = Guid.NewGuid(), + Description = "testing", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test", + PropertyGroupId = new Lazy(() => 11), + SortOrder = 9, + UpdateDate = DateTime.Now, + ValidationRegExp = "xxxx", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + }, + new PropertyType("TestPropertyEditor2", DataTypeDatabaseType.Nvarchar) + { + Id = 4, + Alias = "test2", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 6, + DataTypeId = Guid.NewGuid(), + Description = "testing2", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test2", + PropertyGroupId = new Lazy(() => 12), + SortOrder = 10, + UpdateDate = DateTime.Now, + ValidationRegExp = "yyyy", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + } + })) + { + Id = 77, + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + Name = "Group1", + SortOrder = 555, + UpdateDate = DateTime.Now, + ParentId = 9 + }; + + var result = ss.ToStream(pg); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyTypeTests.cs b/src/Umbraco.Tests/Models/PropertyTypeTests.cs new file mode 100644 index 0000000000..e051791faf --- /dev/null +++ b/src/Umbraco.Tests/Models/PropertyTypeTests.cs @@ -0,0 +1,88 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class PropertyTypeTests + { + [Test] + public void Can_Deep_Clone() + { + var pt = new PropertyType("TestPropertyEditor", DataTypeDatabaseType.Nvarchar) + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 5, + DataTypeId = Guid.NewGuid(), + Description = "testing", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test", + PropertyGroupId = new Lazy(() => 11), + SortOrder = 9, + UpdateDate = DateTime.Now, + ValidationRegExp = "xxxx", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + }; + + var clone = (PropertyType)pt.DeepClone(); + + Assert.AreNotSame(clone, pt); + Assert.AreEqual(clone, pt); + Assert.AreEqual(clone.Id, pt.Id); + Assert.AreEqual(clone.Alias, pt.Alias); + Assert.AreEqual(clone.CreateDate, pt.CreateDate); + Assert.AreEqual(clone.DataTypeDefinitionId, pt.DataTypeDefinitionId); + Assert.AreEqual(clone.DataTypeId, pt.DataTypeId); + Assert.AreEqual(clone.Description, pt.Description); + Assert.AreEqual(clone.Key, pt.Key); + Assert.AreEqual(clone.Mandatory, pt.Mandatory); + Assert.AreEqual(clone.Name, pt.Name); + Assert.AreEqual(clone.PropertyGroupId.Value, pt.PropertyGroupId.Value); + Assert.AreEqual(clone.SortOrder, pt.SortOrder); + Assert.AreEqual(clone.UpdateDate, pt.UpdateDate); + Assert.AreEqual(clone.ValidationRegExp, pt.ValidationRegExp); + Assert.AreEqual(clone.DataTypeDatabaseType, pt.DataTypeDatabaseType); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(pt, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var pt = new PropertyType("TestPropertyEditor", DataTypeDatabaseType.Nvarchar) + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 5, + DataTypeId = Guid.NewGuid(), + Description = "testing", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test", + PropertyGroupId = new Lazy(() => 11), + SortOrder = 9, + UpdateDate = DateTime.Now, + ValidationRegExp = "xxxx", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + }; + + var result = ss.ToStream(pt); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/RelationTests.cs b/src/Umbraco.Tests/Models/RelationTests.cs new file mode 100644 index 0000000000..fdc3ae874b --- /dev/null +++ b/src/Umbraco.Tests/Models/RelationTests.cs @@ -0,0 +1,71 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class RelationTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new Relation(9, 8, new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + { + Id = 66 + }) + { + Comment = "test comment", + CreateDate = DateTime.Now, + Id = 4, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var clone = (Relation) item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.ChildId, item.ChildId); + Assert.AreEqual(clone.Comment, item.Comment); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.ParentId, item.ParentId); + Assert.AreNotSame(clone.RelationType, item.RelationType); + Assert.AreEqual(clone.RelationType, item.RelationType); + Assert.AreEqual(clone.RelationTypeId, item.RelationTypeId); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new Relation(9, 8, new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + { + Id = 66 + }) + { + Comment = "test comment", + CreateDate = DateTime.Now, + Id = 4, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/RelationTypeTests.cs b/src/Umbraco.Tests/Models/RelationTypeTests.cs new file mode 100644 index 0000000000..2022ab912d --- /dev/null +++ b/src/Umbraco.Tests/Models/RelationTypeTests.cs @@ -0,0 +1,65 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class RelationTypeTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + { + Id = 66, + CreateDate = DateTime.Now, + IsBidirectional = true, + Key = Guid.NewGuid(), + Name = "Test", + UpdateDate = DateTime.Now + }; + + var clone = (RelationType)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.Alias, item.Alias); + Assert.AreEqual(clone.ChildObjectType, item.ChildObjectType); + Assert.AreEqual(clone.IsBidirectional, item.IsBidirectional); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.Name, item.Name); + Assert.AreNotSame(clone.ParentObjectType, item.ParentObjectType); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + { + Id = 66, + CreateDate = DateTime.Now, + IsBidirectional = true, + Key = Guid.NewGuid(), + Name = "Test", + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/StylesheetTests.cs b/src/Umbraco.Tests/Models/StylesheetTests.cs index 7d65a9130d..d45aed9cae 100644 --- a/src/Umbraco.Tests/Models/StylesheetTests.cs +++ b/src/Umbraco.Tests/Models/StylesheetTests.cs @@ -1,6 +1,8 @@ -using System.Linq; +using System; +using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -128,5 +130,22 @@ namespace Umbraco.Tests.Models Assert.That(properties, Is.Not.Null); Assert.That(properties.Any(), Is.True); } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var stylesheet = new Stylesheet("/css/styles.css"); + stylesheet.Content = @"@media screen and (min-width: 600px) and (min-width: 900px) { + .class { + background: #666; + } + }"; + + var result = ss.ToStream(stylesheet); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TaskTests.cs b/src/Umbraco.Tests/Models/TaskTests.cs new file mode 100644 index 0000000000..377746cbaf --- /dev/null +++ b/src/Umbraco.Tests/Models/TaskTests.cs @@ -0,0 +1,77 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class TaskTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new Task(new TaskType("test") {Id = 3}) + { + AssigneeUserId = 4, + Closed = true, + Comment = "blah", + CreateDate = DateTime.Now, + EntityId = 99, + Id = 2, + Key = Guid.NewGuid(), + OwnerUserId = 89, + TaskType = new TaskType("asdf") {Id = 99}, + UpdateDate = DateTime.Now + }; + + var clone = (Task) item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + Assert.AreEqual(clone.AssigneeUserId, item.AssigneeUserId); + Assert.AreEqual(clone.Closed, item.Closed); + Assert.AreEqual(clone.Comment, item.Comment); + Assert.AreEqual(clone.EntityId, item.EntityId); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.OwnerUserId, item.OwnerUserId); + Assert.AreNotSame(clone.TaskType, item.TaskType); + Assert.AreEqual(clone.TaskType, item.TaskType); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new Task(new TaskType("test") { Id = 3 }) + { + AssigneeUserId = 4, + Closed = true, + Comment = "blah", + CreateDate = DateTime.Now, + EntityId = 99, + Id = 2, + Key = Guid.NewGuid(), + OwnerUserId = 89, + TaskType = new TaskType("asdf") { Id = 99 }, + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TaskTypeTests.cs b/src/Umbraco.Tests/Models/TaskTypeTests.cs new file mode 100644 index 0000000000..e83f8dc3cf --- /dev/null +++ b/src/Umbraco.Tests/Models/TaskTypeTests.cs @@ -0,0 +1,61 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class TaskTypeTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new TaskType("test") + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var clone = (TaskType)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.Alias, item.Alias); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new TaskType("test") + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TemplateTests.cs b/src/Umbraco.Tests/Models/TemplateTests.cs new file mode 100644 index 0000000000..bf63387d61 --- /dev/null +++ b/src/Umbraco.Tests/Models/TemplateTests.cs @@ -0,0 +1,80 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class TemplateTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new Template("-1,2,3", "Test", "test") + { + Id = 3, + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Content = "blah", + CreatorId = 66, + Level = 55, + ParentId = 2, + SortOrder = 99, + MasterTemplateAlias = "master", + MasterTemplateId = new Lazy(() => 88) + }; + + var clone = (Template)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Alias, item.Alias); + Assert.AreEqual(clone.CreatorId, item.CreatorId); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.Level, item.Level); + Assert.AreEqual(clone.MasterTemplateAlias, item.MasterTemplateAlias); + Assert.AreEqual(clone.MasterTemplateId.Value, item.MasterTemplateId.Value); + Assert.AreEqual(clone.Name, item.Name); + Assert.AreEqual(clone.ParentId, item.ParentId); + Assert.AreEqual(clone.SortOrder, item.SortOrder); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new Template("-1,2,3", "Test", "test") + { + Id = 3, + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Content = "blah", + CreatorId = 66, + Level = 55, + ParentId = 2, + SortOrder = 99, + MasterTemplateAlias = "master", + MasterTemplateId = new Lazy(() => 88) + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index a24b5f1c35..ac70364e67 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -19,5 +20,125 @@ namespace Umbraco.Tests.Models Assert.IsTrue(trashedWithBool.Trashed); Assert.IsTrue(trashedWithInt.Trashed); } + + [Test] + public void Can_Deep_Clone() + { + var item = new UmbracoEntity() + { + Id = 3, + ContentTypeAlias = "test1", + CreatorId = 4, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + ParentId = 5, + SortOrder = 6, + Path = "-1,23", + Level = 7, + ContentTypeIcon = "icon", + ContentTypeThumbnail = "thumb", + HasChildren = true, + HasPendingChanges = true, + IsDraft = true, + IsPublished = true, + NodeObjectTypeId = Guid.NewGuid() + }; + item.AdditionalData.Add("test1", 3); + item.AdditionalData.Add("test2", "valuie"); + item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() + { + Value = "test", + PropertyEditorAlias = "TestPropertyEditor" + }); + item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() + { + Value = "test2", + PropertyEditorAlias = "TestPropertyEditor2" + }); + + var clone = (UmbracoEntity)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.ContentTypeAlias, item.ContentTypeAlias); + Assert.AreEqual(clone.CreatorId, item.CreatorId); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.Level, item.Level); + Assert.AreEqual(clone.Name, item.Name); + Assert.AreEqual(clone.ParentId, item.ParentId); + Assert.AreEqual(clone.SortOrder, item.SortOrder); + Assert.AreEqual(clone.Path, item.Path); + Assert.AreEqual(clone.ContentTypeIcon, item.ContentTypeIcon); + Assert.AreEqual(clone.ContentTypeThumbnail, item.ContentTypeThumbnail); + Assert.AreEqual(clone.HasChildren, item.HasChildren); + Assert.AreEqual(clone.HasPendingChanges, item.HasPendingChanges); + Assert.AreEqual(clone.IsDraft, item.IsDraft); + Assert.AreEqual(clone.IsPublished, item.IsPublished); + Assert.AreEqual(clone.NodeObjectTypeId, item.NodeObjectTypeId); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + Assert.AreEqual(clone.AdditionalData.Count, item.AdditionalData.Count); + Assert.AreEqual(clone.AdditionalData, item.AdditionalData); + Assert.AreEqual(clone.UmbracoProperties.Count, item.UmbracoProperties.Count); + for (var i = 0; i < clone.UmbracoProperties.Count; i++) + { + Assert.AreNotSame(clone.UmbracoProperties[i], item.UmbracoProperties[i]); + Assert.AreEqual(clone.UmbracoProperties[i], item.UmbracoProperties[i]); + } + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new UmbracoEntity() + { + Id = 3, + ContentTypeAlias = "test1", + CreatorId = 4, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + ParentId = 5, + SortOrder = 6, + Path = "-1,23", + Level = 7, + ContentTypeIcon = "icon", + ContentTypeThumbnail = "thumb", + HasChildren = true, + HasPendingChanges = true, + IsDraft = true, + IsPublished = true, + NodeObjectTypeId = Guid.NewGuid() + }; + item.AdditionalData.Add("test1", 3); + item.AdditionalData.Add("test2", "valuie"); + item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() + { + Value = "test", + PropertyEditorAlias = "TestPropertyEditor" + }); + item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() + { + Value = "test2", + PropertyEditorAlias = "TestPropertyEditor2" + }); + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs new file mode 100644 index 0000000000..84b0fe317d --- /dev/null +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -0,0 +1,96 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class UserTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new User(new UserType(){Id = 3}) + { + Id = 3, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + Comments = "comments", + DefaultPermissions = new[]{"a","b","c"}, + DefaultToLiveEditing = false, + Email = "test@test.com", + Language = "en", + FailedPasswordAttempts = 3, + IsApproved = true, + IsLockedOut = true, + LastLockoutDate = DateTime.Now, + LastLoginDate = DateTime.Now, + LastPasswordChangeDate = DateTime.Now, + //Password = "test pass", + //PasswordAnswer = "answer", + PasswordQuestion = "question", + //ProviderUserKey = "user key", + SessionTimeout = 5, + StartContentId = 3, + StartMediaId = 8, + Username = "username" + }; + + var clone = (User)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + + Assert.AreNotSame(clone.UserType, item.UserType); + Assert.AreEqual(clone.UserType, item.UserType); + + //Verify normal properties with reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new User(new UserType() { Id = 3 }) + { + Id = 3, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + Comments = "comments", + DefaultPermissions = new[] { "a", "b", "c" }, + DefaultToLiveEditing = false, + Email = "test@test.com", + Language = "en", + FailedPasswordAttempts = 3, + IsApproved = true, + IsLockedOut = true, + LastLockoutDate = DateTime.Now, + LastLoginDate = DateTime.Now, + LastPasswordChangeDate = DateTime.Now, + //Password = "test pass", + //PasswordAnswer = "answer", + PasswordQuestion = "question", + //ProviderUserKey = "user key", + SessionTimeout = 5, + StartContentId = 3, + StartMediaId = 8, + Username = "username" + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UserTypeTests.cs b/src/Umbraco.Tests/Models/UserTypeTests.cs new file mode 100644 index 0000000000..01e4d6fd89 --- /dev/null +++ b/src/Umbraco.Tests/Models/UserTypeTests.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class UserTypeTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new UserType() + { + Id = 3, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + Alias = "test", + Permissions = new[] {"a", "b", "c"} + }; + + var clone = (UserType)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + + //Verify normal properties with reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new UserType() + { + Id = 3, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + Alias = "test", + Permissions = new[] { "a", "b", "c" } + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index d2f1578d87..e59945ac91 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -3,14 +3,16 @@ using System.Data; using System.Linq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Tests.TestHelpers; +using NullCacheProvider = Umbraco.Core.Persistence.Caching.NullCacheProvider; namespace Umbraco.Tests.Persistence.Repositories { @@ -26,7 +28,10 @@ namespace Umbraco.Tests.Persistence.Repositories private DataTypeDefinitionRepository CreateRepository(IDatabaseUnitOfWork unitOfWork) { - var dataTypeDefinitionRepository = new DataTypeDefinitionRepository(unitOfWork, NullCacheProvider.Current); + var dataTypeDefinitionRepository = new DataTypeDefinitionRepository( + unitOfWork, NullCacheProvider.Current, CacheHelper.CreateDisabledCacheHelper(), + new ContentTypeRepository(unitOfWork, NullCacheProvider.Current, + new TemplateRepository(unitOfWork, NullCacheProvider.Current))); return dataTypeDefinitionRepository; } @@ -304,6 +309,137 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Get_Pre_Value_Collection() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + + int dtid; + using (var repository = CreateRepository(unitOfWork)) + { + var dataTypeDefinition = new DataTypeDefinition(-1, new Guid(Constants.PropertyEditors.RadioButtonList)) { Name = "test" }; + repository.AddOrUpdate(dataTypeDefinition); + unitOfWork.Commit(); + dtid = dataTypeDefinition.Id; + } + + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtid, SortOrder = 0, Value = "test1"}); + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtid, SortOrder = 1, Value = "test2" }); + + using (var repository = CreateRepository(unitOfWork)) + { + var collection = repository.GetPreValuesCollectionByDataTypeId(dtid); + Assert.AreEqual(2, collection.PreValuesAsArray.Count()); + } + } + + [Test] + public void Can_Get_Pre_Value_As_String() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + + int dtid; + using (var repository = CreateRepository(unitOfWork)) + { + var dataTypeDefinition = new DataTypeDefinition(-1, new Guid(Constants.PropertyEditors.RadioButtonList)) { Name = "test" }; + repository.AddOrUpdate(dataTypeDefinition); + unitOfWork.Commit(); + dtid = dataTypeDefinition.Id; + } + + var id = DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtid, SortOrder = 0, Value = "test1" }); + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtid, SortOrder = 1, Value = "test2" }); + + using (var repository = CreateRepository(unitOfWork)) + { + var val = repository.GetPreValueAsString(Convert.ToInt32(id)); + Assert.AreEqual("test1", val); + } + } + + [Test] + public void Can_Get_Pre_Value_Collection_With_Cache() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + + var cache = new CacheHelper(new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new StaticCacheProvider()); + + Func creator = () => new DataTypeDefinitionRepository( + unitOfWork, NullCacheProvider.Current, cache, + new ContentTypeRepository(unitOfWork, NullCacheProvider.Current, + new TemplateRepository(unitOfWork, NullCacheProvider.Current))); + + DataTypeDefinition dtd; + using (var repository = creator()) + { + dtd = new DataTypeDefinition(-1, new Guid(Constants.PropertyEditors.RadioButtonList)) { Name = "test" }; + repository.AddOrUpdate(dtd); + unitOfWork.Commit(); + } + + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtd.Id, SortOrder = 0, Value = "test1" }); + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtd.Id, SortOrder = 1, Value = "test2" }); + + //this will cache the result + using (var repository = creator()) + { + var collection = repository.GetPreValuesCollectionByDataTypeId(dtd.Id); + } + + var cached = cache.RuntimeCache.GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); + + Assert.IsNotNull(cached); + Assert.AreEqual(1, cached.Count()); + Assert.AreEqual(2, cached.Single().FormatAsDictionary().Count); + } + + [Test] + public void Can_Get_Pre_Value_As_String_With_Cache() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + + var cache = new CacheHelper(new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new StaticCacheProvider()); + + Func creator = () => new DataTypeDefinitionRepository( + unitOfWork, NullCacheProvider.Current, cache, + new ContentTypeRepository(unitOfWork, NullCacheProvider.Current, + new TemplateRepository(unitOfWork, NullCacheProvider.Current))); + + DataTypeDefinition dtd; + using (var repository = creator()) + { + dtd = new DataTypeDefinition(-1, new Guid(Constants.PropertyEditors.RadioButtonList)) { Name = "test" }; + repository.AddOrUpdate(dtd); + unitOfWork.Commit(); + } + + var id = DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtd.Id, SortOrder = 0, Value = "test1" }); + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtd.Id, SortOrder = 1, Value = "test2" }); + + //this will cache the result + using (var repository = creator()) + { + var val = repository.GetPreValueAsString(Convert.ToInt32(id)); + } + + var cached = cache.RuntimeCache.GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); + + Assert.IsNotNull(cached); + Assert.AreEqual(1, cached.Count()); + Assert.AreEqual(2, cached.Single().FormatAsDictionary().Count); + + using (var repository = creator()) + { + //ensure it still gets resolved! + var val = repository.GetPreValueAsString(Convert.ToInt32(id)); + Assert.AreEqual("test1", val); + } + } + [TearDown] public override void TearDown() { diff --git a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs index 4ba5a05688..073d7af406 100644 --- a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs @@ -75,6 +75,36 @@ namespace Umbraco.Tests.Services var dataTypeService = ServiceContext.DataTypeService; var textfieldId = new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"); + // Act + IDataTypeDefinition dataTypeDefinition = new DataTypeDefinition(-1, textfieldId) { Name = "Testing prevals", DatabaseType = DataTypeDatabaseType.Ntext }; + dataTypeService.Save(dataTypeDefinition); + dataTypeService.SavePreValues(dataTypeDefinition, new Dictionary + { + {"preVal1", new PreValue("Hello")}, + {"preVal2", new PreValue("World")} + }); + //re-get + dataTypeDefinition = dataTypeService.GetDataTypeDefinitionById(dataTypeDefinition.Id); + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + // Assert + Assert.That(dataTypeDefinition, Is.Not.Null); + Assert.That(dataTypeDefinition.HasIdentity, Is.True); + Assert.AreEqual(true, preVals.IsDictionaryBased); + Assert.AreEqual(2, preVals.PreValuesAsDictionary.Keys.Count); + Assert.AreEqual("preVal1", preVals.PreValuesAsDictionary.Keys.First()); + Assert.AreEqual("preVal2", preVals.PreValuesAsDictionary.Keys.Last()); + Assert.AreEqual("Hello", preVals.PreValuesAsDictionary["preVal1"].Value); + Assert.AreEqual("World", preVals.PreValuesAsDictionary["preVal2"].Value); + } + + [Test] + public void DataTypeService_Can_Persist_Dtd_And_Dictionary_Based_Pre_Values() + { + // Arrange + var dataTypeService = ServiceContext.DataTypeService; + var textfieldId = new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"); + // Act IDataTypeDefinition dataTypeDefinition = new DataTypeDefinition(-1, textfieldId) { Name = "Testing prevals", DatabaseType = DataTypeDatabaseType.Ntext }; dataTypeService.SaveDataTypeAndPreValues(dataTypeDefinition, new Dictionary @@ -97,6 +127,77 @@ namespace Umbraco.Tests.Services Assert.AreEqual("World", preVals.PreValuesAsDictionary["preVal2"].Value); } + [Test] + public void DataTypeService_Can_Update_Pre_Values() + { + // Arrange + var dataTypeService = ServiceContext.DataTypeService; + var textfieldId = new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"); + + // Act + IDataTypeDefinition dataTypeDefinition = new DataTypeDefinition(-1, textfieldId) { Name = "Testing prevals", DatabaseType = DataTypeDatabaseType.Ntext }; + dataTypeService.SaveDataTypeAndPreValues(dataTypeDefinition, new Dictionary + { + {"preVal1", new PreValue("Hello")}, + {"preVal2", new PreValue("World")} + }); + //re-get + dataTypeDefinition = dataTypeService.GetDataTypeDefinitionById(dataTypeDefinition.Id); + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + //update them (ensure Ids are there!) + var asDictionary = preVals.FormatAsDictionary(); + asDictionary["preVal1"].Value = "Hello2"; + asDictionary["preVal2"].Value = "World2"; + + dataTypeService.SavePreValues(dataTypeDefinition, asDictionary); + + var preValsAgain = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + // Assert + + Assert.AreEqual(preVals.PreValuesAsDictionary.Values.First().Id, preValsAgain.PreValuesAsDictionary.Values.First().Id); + Assert.AreEqual(preVals.PreValuesAsDictionary.Values.Last().Id, preValsAgain.PreValuesAsDictionary.Values.Last().Id); + Assert.AreEqual("preVal1", preValsAgain.PreValuesAsDictionary.Keys.First()); + Assert.AreEqual("preVal2", preValsAgain.PreValuesAsDictionary.Keys.Last()); + Assert.AreEqual("Hello2", preValsAgain.PreValuesAsDictionary["preVal1"].Value); + Assert.AreEqual("World2", preValsAgain.PreValuesAsDictionary["preVal2"].Value); + } + + [Test] + public void DataTypeService_Can_Remove_Pre_Value() + { + // Arrange + var dataTypeService = ServiceContext.DataTypeService; + var textfieldId = new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"); + + // Act + IDataTypeDefinition dataTypeDefinition = new DataTypeDefinition(-1, textfieldId) { Name = "Testing prevals", DatabaseType = DataTypeDatabaseType.Ntext }; + dataTypeService.SaveDataTypeAndPreValues(dataTypeDefinition, new Dictionary + { + {"preVal1", new PreValue("Hello")}, + {"preVal2", new PreValue("World")} + }); + //re-get + dataTypeDefinition = dataTypeService.GetDataTypeDefinitionById(dataTypeDefinition.Id); + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + //update them (ensure Ids are there!) + var asDictionary = preVals.FormatAsDictionary(); + asDictionary.Remove("preVal2"); + + dataTypeService.SavePreValues(dataTypeDefinition, asDictionary); + + var preValsAgain = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + // Assert + + Assert.AreEqual(1, preValsAgain.FormatAsDictionary().Count); + Assert.AreEqual(preVals.PreValuesAsDictionary.Values.First().Id, preValsAgain.PreValuesAsDictionary.Values.First().Id); + Assert.AreEqual("preVal1", preValsAgain.PreValuesAsDictionary.Keys.First()); + + } + [Test] public void DataTypeService_Can_Persist_Array_Based_Pre_Values() { diff --git a/src/Umbraco.Tests/Services/PreValueConverterTests.cs b/src/Umbraco.Tests/Services/PreValueConverterTests.cs index 63149a28fe..b4eb34ee9f 100644 --- a/src/Umbraco.Tests/Services/PreValueConverterTests.cs +++ b/src/Umbraco.Tests/Services/PreValueConverterTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Services; namespace Umbraco.Tests.Services @@ -21,7 +22,7 @@ namespace Umbraco.Tests.Services new Tuple(new PreValue(13, "value4"), "key4", 1) }; - var result = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + var result = DataTypeDefinitionRepository.PreValueConverter.ConvertToPreValuesCollection(list); Assert.Throws(() => { @@ -47,7 +48,7 @@ namespace Umbraco.Tests.Services new Tuple(new PreValue(13, "value4"), "", 1) }; - var result = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + var result = DataTypeDefinitionRepository.PreValueConverter.ConvertToPreValuesCollection(list); Assert.Throws(() => { @@ -73,7 +74,7 @@ namespace Umbraco.Tests.Services new Tuple(new PreValue(13, "value4"), "key4", 1) }; - var result = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + var result = DataTypeDefinitionRepository.PreValueConverter.ConvertToPreValuesCollection(list); Assert.Throws(() => { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 6d772332ec..b36d94f3e1 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -179,7 +179,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js index 5ac118e1f4..5b3e174930 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -4,7 +4,8 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $scope.dbs = [ {name: 'Microsoft SQL Server Compact (SQL CE)', id: 0}, {name: 'Microsoft SQL Server', id: 1}, - {name: 'MySQL', id: 2}, + { name: 'Microsoft SQL Azure', id: 3 }, + { name: 'MySQL', id: 2 }, {name: 'Custom connection string', id: -1}]; if(installerService.status.current.model.dbType === undefined){ diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index f8c6de39cc..f91c08b455 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -129,11 +129,15 @@ namespace Umbraco.Web.Cache payloads.ForEach(payload => { //clear both the Id and Unique Id cache since we cache both in the legacy classes :( - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch( - string.Format("{0}{1}", CacheKeys.DataTypeCacheKey, payload.Id)); - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch( + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + string.Format("{0}{1}", CacheKeys.DataTypeCacheKey, payload.Id)); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( string.Format("{0}{1}", CacheKeys.DataTypeCacheKey, payload.UniqueId)); + //clears the prevalue cache + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + string.Format("{0}{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); + PublishedContentType.ClearDataType(payload.Id); }); diff --git a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs index 1d88b9b9a2..6041e23ca5 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs @@ -55,7 +55,8 @@ namespace Umbraco.Web.Install.Controllers public bool PostValidateDatabaseConnection(DatabaseModel model) { var dbHelper = new DatabaseHelper(); - return dbHelper.CheckConnection(model, ApplicationContext); + var canConnect = dbHelper.CheckConnection(model, ApplicationContext); + return canConnect; } ///