Merge pull request #1908 from umbraco/temp-scope-events-stale-entities

POC to normalize all event entities when we track events so that all …
This commit is contained in:
Shannon Deminick
2017-04-30 19:28:19 +10:00
committed by GitHub
34 changed files with 933 additions and 199 deletions
@@ -1,85 +1,177 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Security.Permissions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Events
{
/// <summary>
/// Event args for a strongly typed object that can support cancellation
/// </summary>
/// <typeparam name="T"></typeparam>
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
public class CancellableObjectEventArgs<T> : CancellableEventArgs, IEquatable<CancellableObjectEventArgs<T>>
{
public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
/// <summary>
/// Used as a base class for the generic type CancellableObjectEventArgs{T} so that we can get direct 'object' access to the underlying EventObject
/// </summary>
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
public abstract class CancellableObjectEventArgs : CancellableEventArgs
{
protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
: base(canCancel, messages, additionalData)
{
{
EventObject = eventObject;
}
public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages)
protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages eventMessages)
: base(canCancel, eventMessages)
{
EventObject = eventObject;
}
public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages)
protected CancellableObjectEventArgs(object eventObject, EventMessages eventMessages)
: this(eventObject, true, eventMessages)
{
}
protected CancellableObjectEventArgs(object eventObject, bool canCancel)
: base(canCancel)
{
EventObject = eventObject;
}
protected CancellableObjectEventArgs(object eventObject)
: this(eventObject, true)
{
}
/// <summary>
/// Returns the object relating to the event
/// </summary>
/// <remarks>
/// This is protected so that inheritors can expose it with their own name
/// </remarks>
internal object EventObject { get; set; }
}
/// <summary>
/// Event args for a strongly typed object that can support cancellation
/// </summary>
/// <typeparam name="T"></typeparam>
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
public class CancellableObjectEventArgs<T> : CancellableObjectEventArgs, IEquatable<CancellableObjectEventArgs<T>>
{
public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
: base(eventObject, canCancel, messages, additionalData)
{
}
public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages)
: base(eventObject, canCancel, eventMessages)
{
}
public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages)
: base(eventObject, eventMessages)
{
}
public CancellableObjectEventArgs(T eventObject, bool canCancel)
: base(canCancel)
{
EventObject = eventObject;
}
: base(eventObject, canCancel)
{
}
public CancellableObjectEventArgs(T eventObject)
: this(eventObject, true)
{
}
public CancellableObjectEventArgs(T eventObject)
: base(eventObject)
{
}
/// <summary>
/// Returns the object relating to the event
/// </summary>
/// <remarks>
/// This is protected so that inheritors can expose it with their own name
/// </remarks>
protected T EventObject { get; set; }
/// <summary>
/// Returns the object relating to the event
/// </summary>
/// <remarks>
/// This is protected so that inheritors can expose it with their own name
/// </remarks>
protected new T EventObject
{
get { return (T) base.EventObject; }
set { base.EventObject = value; }
}
public bool Equals(CancellableObjectEventArgs<T> other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return base.Equals(other) && EqualityComparer<T>.Default.Equals(EventObject, other.EventObject);
}
public bool Equals(CancellableObjectEventArgs<T> other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return base.Equals(other) && EqualityComparer<T>.Default.Equals(EventObject, other.EventObject);
}
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((CancellableObjectEventArgs<T>) obj);
}
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((CancellableObjectEventArgs<T>)obj);
}
public override int GetHashCode()
{
unchecked
{
return (base.GetHashCode() * 397) ^ EqualityComparer<T>.Default.GetHashCode(EventObject);
}
}
public override int GetHashCode()
{
unchecked
{
return (base.GetHashCode() * 397) ^ EqualityComparer<T>.Default.GetHashCode(EventObject);
}
}
public static bool operator ==(CancellableObjectEventArgs<T> left, CancellableObjectEventArgs<T> right)
{
return Equals(left, right);
}
public static bool operator ==(CancellableObjectEventArgs<T> left, CancellableObjectEventArgs<T> right)
{
return Equals(left, right);
}
public static bool operator !=(CancellableObjectEventArgs<T> left, CancellableObjectEventArgs<T> right)
{
return !Equals(left, right);
}
}
public static bool operator !=(CancellableObjectEventArgs<T> left, CancellableObjectEventArgs<T> right)
{
return !Equals(left, right);
}
}
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
public class CancellableEnumerableObjectEventArgs<T> : CancellableObjectEventArgs<IEnumerable<T>>, IEquatable<CancellableEnumerableObjectEventArgs<T>>
{
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
: base(eventObject, canCancel, messages, additionalData)
{ }
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel, EventMessages eventMessages)
: base(eventObject, canCancel, eventMessages)
{ }
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, EventMessages eventMessages)
: base(eventObject, eventMessages)
{ }
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel)
: base(eventObject, canCancel)
{ }
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject)
: base(eventObject)
{ }
public bool Equals(CancellableEnumerableObjectEventArgs<T> other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return EventObject.SequenceEqual(other.EventObject);
}
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((CancellableEnumerableObjectEventArgs<T>)obj);
}
public override int GetHashCode()
{
return HashCodeHelper.GetHashCode(EventObject);
}
}
}
+7 -2
View File
@@ -1,9 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Events
{
public class DeleteEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<DeleteEventArgs<TEntity>>, IDeletingMediaFilesEventArgs
[SupersedeEvent(typeof(SaveEventArgs<>))]
[SupersedeEvent(typeof(PublishEventArgs<>))]
[SupersedeEvent(typeof(MoveEventArgs<>))]
[SupersedeEvent(typeof(CopyEventArgs<>))]
public class DeleteEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>, IEquatable<DeleteEventArgs<TEntity>>, IDeletingMediaFilesEventArgs
{
/// <summary>
/// Constructor accepting multiple entities that are used in the delete operation
@@ -106,7 +111,7 @@ namespace Umbraco.Core.Events
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return base.Equals(other) && MediaFilesToDelete.Equals(other.MediaFilesToDelete);
return base.Equals(other) && MediaFilesToDelete.SequenceEqual(other.MediaFilesToDelete);
}
public override bool Equals(object obj)
+1 -1
View File
@@ -4,7 +4,7 @@ using System.Xml.Linq;
namespace Umbraco.Core.Events
{
public class ImportEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<ImportEventArgs<TEntity>>
public class ImportEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>, IEquatable<ImportEventArgs<TEntity>>
{
/// <summary>
/// Constructor accepting an XElement with the xml being imported
@@ -4,7 +4,7 @@ using Umbraco.Core.Packaging.Models;
namespace Umbraco.Core.Events
{
internal class ImportPackageEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<ImportPackageEventArgs<TEntity>>
internal class ImportPackageEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>, IEquatable<ImportPackageEventArgs<TEntity>>
{
private readonly MetaData _packageMetaData;
@@ -32,7 +32,8 @@ namespace Umbraco.Core.Events
public bool Equals(ImportPackageEventArgs<TEntity> other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(this, other)) return true;
//TODO: MetaData for package metadata has no equality operators :/
return base.Equals(other) && _packageMetaData.Equals(other._packageMetaData);
}
+1 -1
View File
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Umbraco.Core.Events
{
public class PublishEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<PublishEventArgs<TEntity>>
public class PublishEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>, IEquatable<PublishEventArgs<TEntity>>
{
/// <summary>
/// Constructor accepting multiple entities that are used in the publish operation
+2 -3
View File
@@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Events
{
public class SaveEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
public class SaveEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>
{
/// <summary>
/// Constructor accepting multiple entities that are used in the saving operation
@@ -116,7 +117,5 @@ namespace Umbraco.Core.Events
{
get { return EventObject; }
}
}
}
@@ -1,6 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Events
{
@@ -15,6 +17,7 @@ namespace Umbraco.Core.Events
/// </remarks>
public abstract class ScopeEventDispatcherBase : IEventDispatcher
{
//events will be enlisted in the order they are raised
private List<IEventDefinition> _events;
private readonly bool _raiseCancelable;
@@ -73,29 +76,263 @@ namespace Umbraco.Core.Events
{
if (_events == null)
return Enumerable.Empty<IEventDefinition>();
switch (filter)
{
case EventDefinitionFilter.All:
return _events;
return FilterSupersededAndUpdateToLatestEntity(_events);
case EventDefinitionFilter.FirstIn:
var l1 = new OrderedHashSet<IEventDefinition>();
foreach (var e in _events)
{
l1.Add(e);
}
return l1;
return FilterSupersededAndUpdateToLatestEntity(l1);
case EventDefinitionFilter.LastIn:
var l2 = new OrderedHashSet<IEventDefinition>(keepOldest: false);
foreach (var e in _events)
{
l2.Add(e);
}
return l2;
return FilterSupersededAndUpdateToLatestEntity(l2);
default:
throw new ArgumentOutOfRangeException("filter", filter, null);
}
}
private class EventDefinitionTypeData
{
public IEventDefinition EventDefinition { get; set; }
public Type EventArgType { get; set; }
public SupersedeEventAttribute[] SupersedeAttributes { get; set; }
}
/// <summary>
/// This will iterate over the events (latest first) and filter out any events or entities in event args that are included
/// in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want
/// to raise the Saved event (well actually we just don't want to include it in the args for that saved event)
/// </summary>
/// <param name="events"></param>
/// <returns></returns>
private static IEnumerable<IEventDefinition> FilterSupersededAndUpdateToLatestEntity(IReadOnlyList<IEventDefinition> events)
{
//used to keep the 'latest' entity and associated event definition data
var allEntities = new List<Tuple<IEntity, EventDefinitionTypeData>>();
//tracks all CancellableObjectEventArgs instances in the events which is the only type of args we can work with
var cancelableArgs = new List<CancellableObjectEventArgs>();
var result = new List<IEventDefinition>();
//This will eagerly load all of the event arg types and their attributes so we don't have to continuously look this data up
var allArgTypesWithAttributes = events.Select(x => x.Args.GetType())
.Distinct()
.ToDictionary(x => x, x => x.GetCustomAttributes<SupersedeEventAttribute>(false).ToArray());
//Iterate all events and collect the actual entities in them and relates them to their corresponding EventDefinitionTypeData
//we'll process the list in reverse because events are added in the order they are raised and we want to filter out
//any entities from event args that are not longer relevant
//(i.e. if an item is Deleted after it's Saved, we won't include the item in the Saved args)
for (var index = events.Count - 1; index >= 0; index--)
{
var eventDefinition = events[index];
var argType = eventDefinition.Args.GetType();
var attributes = allArgTypesWithAttributes[eventDefinition.Args.GetType()];
var meta = new EventDefinitionTypeData
{
EventDefinition = eventDefinition,
EventArgType = argType,
SupersedeAttributes = attributes
};
var args = eventDefinition.Args as CancellableObjectEventArgs;
if (args != null)
{
var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
if (list == null)
{
//extract the event object
var obj = args.EventObject as IEntity;
if (obj != null)
{
//Now check if this entity already exists in other event args that supersede this current event arg type
if (IsFiltered(obj, meta, allEntities) == false)
{
//if it's not filtered we can adde these args to the response
cancelableArgs.Add(args);
result.Add(eventDefinition);
//track the entity
allEntities.Add(Tuple.Create(obj, meta));
}
}
else
{
//Can't retrieve the entity so cant' filter or inspect, just add to the output
result.Add(eventDefinition);
}
}
else
{
var toRemove = new List<IEntity>();
foreach (var entity in list)
{
//extract the event object
var obj = entity as IEntity;
if (obj != null)
{
//Now check if this entity already exists in other event args that supersede this current event arg type
if (IsFiltered(obj, meta, allEntities))
{
//track it to be removed
toRemove.Add(obj);
}
else
{
//track the entity, it's not filtered
allEntities.Add(Tuple.Create(obj, meta));
}
}
else
{
//we don't need to do anything here, we can't cast to IEntity so we cannot filter, so it will just remain in the list
}
}
//remove anything that has been filtered
foreach (var entity in toRemove)
{
list.Remove(entity);
}
//track the event and include in the response if there's still entities remaining in the list
if (list.Count > 0)
{
if (toRemove.Count > 0)
{
//re-assign if the items have changed
args.EventObject = list;
}
cancelableArgs.Add(args);
result.Add(eventDefinition);
}
}
}
else
{
//it's not a cancelable event arg so we just include it in the result
result.Add(eventDefinition);
}
}
//Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
UpdateToLatestEntities(allEntities, cancelableArgs);
//we need to reverse the result since we've been adding by latest added events first!
result.Reverse();
return result;
}
private static void UpdateToLatestEntities(IEnumerable<Tuple<IEntity, EventDefinitionTypeData>> allEntities, IEnumerable<CancellableObjectEventArgs> cancelableArgs)
{
//Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
var latestEntities = new OrderedHashSet<IEntity>(keepOldest: true);
foreach (var entity in allEntities.OrderByDescending(entity => entity.Item1.UpdateDate))
{
latestEntities.Add(entity.Item1);
}
foreach (var args in cancelableArgs)
{
var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
if (list == null)
{
//try to find the args entity in the latest entity - based on the equality operators, this will
//match by Id since that is the default equality checker for IEntity. If one is found, than it is
//the most recent entity instance so update the args with that instance so we don't emit a stale instance.
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, args.EventObject));
if (foundEntity != null)
{
args.EventObject = foundEntity;
}
}
else
{
var updated = false;
for (int i = 0; i < list.Count; i++)
{
//try to find the args entity in the latest entity - based on the equality operators, this will
//match by Id since that is the default equality checker for IEntity. If one is found, than it is
//the most recent entity instance so update the args with that instance so we don't emit a stale instance.
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, list[i]));
if (foundEntity != null)
{
list[i] = foundEntity;
updated = true;
}
}
if (updated)
{
args.EventObject = list;
}
}
}
}
/// <summary>
/// This will check against all of the processed entity/events (allEntities) to see if this entity already exists in
/// event args that supersede the event args being passed in and if so returns true.
/// </summary>
/// <param name="entity"></param>
/// <param name="eventDef"></param>
/// <param name="allEntities"></param>
/// <returns></returns>
private static bool IsFiltered(
IEntity entity,
EventDefinitionTypeData eventDef,
List<Tuple<IEntity, EventDefinitionTypeData>> allEntities)
{
var argType = eventDef.EventDefinition.Args.GetType();
//check if the entity is found in any processed event data that could possible supersede this one
var foundByEntity = allEntities
.Where(x => x.Item2.SupersedeAttributes.Length > 0
//if it's the same arg type than it cannot supersede
&& x.Item2.EventArgType != argType
&& Equals(x.Item1, entity))
.ToArray();
//no args have been processed with this entity so it should not be filtered
if (foundByEntity.Length == 0)
return false;
if (argType.IsGenericType)
{
var supercededBy = foundByEntity
.FirstOrDefault(x =>
x.Item2.SupersedeAttributes.Any(y =>
//if the attribute type is a generic type def then compare with the generic type def of the event arg
(y.SupersededEventArgsType.IsGenericTypeDefinition && y.SupersededEventArgsType == argType.GetGenericTypeDefinition())
//if the attribute type is not a generic type def then compare with the normal type of the event arg
|| (y.SupersededEventArgsType.IsGenericTypeDefinition == false && y.SupersededEventArgsType == argType)));
return supercededBy != null;
}
else
{
var supercededBy = foundByEntity
.FirstOrDefault(x =>
x.Item2.SupersedeAttributes.Any(y =>
//since the event arg type is not a generic type, then we just compare type 1:1
y.SupersededEventArgsType == argType));
return supercededBy != null;
}
}
public void ScopeExit(bool completed)
{
@@ -0,0 +1,20 @@
using System;
namespace Umbraco.Core.Events
{
/// <summary>
/// This is used to know if the event arg attributed should supersede another event arg type when
/// tracking events for the same entity. If one event args supercedes another then the event args that have been superseded
/// will mean that the event will not be dispatched or the args will be filtered to exclude the entity.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
internal class SupersedeEventAttribute : Attribute
{
public Type SupersededEventArgsType { get; private set; }
public SupersedeEventAttribute(Type supersededEventArgsType)
{
SupersededEventArgsType = supersededEventArgsType;
}
}
}
+74 -75
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -7,32 +6,32 @@ using System.Text;
namespace Umbraco.Core
{
/// <summary>
/// Used to create a hash code from multiple objects.
/// </summary>
/// <remarks>
/// .Net has a class the same as this: System.Web.Util.HashCodeCombiner and of course it works for all sorts of things
/// which we've not included here as we just need a quick easy class for this in order to create a unique
/// hash of directories/files to see if they have changed.
/// </remarks>
internal class HashCodeCombiner
{
private long _combinedHash = 5381L;
/// <summary>
/// Used to create a hash code from multiple objects.
/// </summary>
/// <remarks>
/// .Net has a class the same as this: System.Web.Util.HashCodeCombiner and of course it works for all sorts of things
/// which we've not included here as we just need a quick easy class for this in order to create a unique
/// hash of directories/files to see if they have changed.
/// </remarks>
internal class HashCodeCombiner
{
private long _combinedHash = 5381L;
internal void AddInt(int i)
{
_combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i;
}
internal void AddInt(int i)
{
_combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i;
}
internal void AddObject(object o)
{
AddInt(o.GetHashCode());
}
internal void AddObject(object o)
{
AddInt(o.GetHashCode());
}
internal void AddDateTime(DateTime d)
{
AddInt(d.GetHashCode());
}
internal void AddDateTime(DateTime d)
{
AddInt(d.GetHashCode());
}
internal void AddString(string s)
{
@@ -40,61 +39,61 @@ namespace Umbraco.Core
AddInt((StringComparer.InvariantCulture).GetHashCode(s));
}
internal void AddCaseInsensitiveString(string s)
{
if (s != null)
AddInt((StringComparer.InvariantCultureIgnoreCase).GetHashCode(s));
}
internal void AddCaseInsensitiveString(string s)
{
if (s != null)
AddInt((StringComparer.InvariantCultureIgnoreCase).GetHashCode(s));
}
internal void AddFileSystemItem(FileSystemInfo f)
{
//if it doesn't exist, don't proceed.
if (!f.Exists)
return;
internal void AddFileSystemItem(FileSystemInfo f)
{
//if it doesn't exist, don't proceed.
if (!f.Exists)
return;
AddCaseInsensitiveString(f.FullName);
AddDateTime(f.CreationTimeUtc);
AddDateTime(f.LastWriteTimeUtc);
//check if it is a file or folder
var fileInfo = f as FileInfo;
if (fileInfo != null)
{
AddInt(fileInfo.Length.GetHashCode());
}
var dirInfo = f as DirectoryInfo;
if (dirInfo != null)
{
foreach (var d in dirInfo.GetFiles())
{
AddFile(d);
}
foreach (var s in dirInfo.GetDirectories())
{
AddFolder(s);
}
}
}
AddCaseInsensitiveString(f.FullName);
AddDateTime(f.CreationTimeUtc);
AddDateTime(f.LastWriteTimeUtc);
internal void AddFile(FileInfo f)
{
AddFileSystemItem(f);
}
//check if it is a file or folder
var fileInfo = f as FileInfo;
if (fileInfo != null)
{
AddInt(fileInfo.Length.GetHashCode());
}
internal void AddFolder(DirectoryInfo d)
{
AddFileSystemItem(d);
}
var dirInfo = f as DirectoryInfo;
if (dirInfo != null)
{
foreach (var d in dirInfo.GetFiles())
{
AddFile(d);
}
foreach (var s in dirInfo.GetDirectories())
{
AddFolder(s);
}
}
}
/// <summary>
/// Returns the hex code of the combined hash code
/// </summary>
/// <returns></returns>
internal string GetCombinedHashCode()
{
return _combinedHash.ToString("x", CultureInfo.InvariantCulture);
}
internal void AddFile(FileInfo f)
{
AddFileSystemItem(f);
}
}
internal void AddFolder(DirectoryInfo d)
{
AddFileSystemItem(d);
}
/// <summary>
/// Returns the hex code of the combined hash code
/// </summary>
/// <returns></returns>
internal string GetCombinedHashCode()
{
return _combinedHash.ToString("x", CultureInfo.InvariantCulture);
}
}
}
+104
View File
@@ -0,0 +1,104 @@
using System.Collections.Generic;
namespace Umbraco.Core
{
/// <summary>
/// Borrowed from http://stackoverflow.com/a/2575444/694494
/// </summary>
internal static class HashCodeHelper
{
public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
{
unchecked
{
return 31 * arg1.GetHashCode() + arg2.GetHashCode();
}
}
public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
{
unchecked
{
int hash = arg1.GetHashCode();
hash = 31 * hash + arg2.GetHashCode();
return 31 * hash + arg3.GetHashCode();
}
}
public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3,
T4 arg4)
{
unchecked
{
int hash = arg1.GetHashCode();
hash = 31 * hash + arg2.GetHashCode();
hash = 31 * hash + arg3.GetHashCode();
return 31 * hash + arg4.GetHashCode();
}
}
public static int GetHashCode<T>(T[] list)
{
unchecked
{
int hash = 0;
foreach (var item in list)
{
if (item == null) continue;
hash = 31 * hash + item.GetHashCode();
}
return hash;
}
}
public static int GetHashCode<T>(IEnumerable<T> list)
{
unchecked
{
int hash = 0;
foreach (var item in list)
{
if (item == null) continue;
hash = 31 * hash + item.GetHashCode();
}
return hash;
}
}
/// <summary>
/// Gets a hashcode for a collection for that the order of items
/// does not matter.
/// So {1, 2, 3} and {3, 2, 1} will get same hash code.
/// </summary>
public static int GetHashCodeForOrderNoMatterCollection<T>(
IEnumerable<T> list)
{
unchecked
{
int hash = 0;
int count = 0;
foreach (var item in list)
{
if (item == null) continue;
hash += item.GetHashCode();
count++;
}
return 31 * hash + count.GetHashCode();
}
}
/// <summary>
/// Alternative way to get a hashcode is to use a fluent
/// interface like this:<br />
/// return 0.CombineHashCode(field1).CombineHashCode(field2).
/// CombineHashCode(field3);
/// </summary>
public static int CombineHashCode<T>(this int hashCode, T arg)
{
unchecked
{
return 31 * hashCode + arg.GetHashCode();
}
}
}
}
@@ -42,6 +42,7 @@ namespace Umbraco.Core.Models
public Guid Key { get; set; }
public DateTime CreateDate { get; set; }
public DateTime UpdateDate { get; set; }
public DateTime? DeletedDate { get; set; }
/// <summary>
/// Special case, always return false, this will cause the repositories managing
@@ -60,5 +61,7 @@ namespace Umbraco.Core.Models
DeepCloneHelper.DeepCloneRefProperties(this, clone);
return clone;
}
}
}
@@ -101,6 +101,9 @@ namespace Umbraco.Core.Models.EntityBase
set { SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); }
}
[IgnoreDataMember]
public DateTime? DeletedDate { get; set; }
internal virtual void ResetIdentity()
{
_hasIdentity = false;
@@ -3,7 +3,7 @@
/// <summary>
/// Marker interface for aggregate roots
/// </summary>
public interface IAggregateRoot : IEntity
public interface IAggregateRoot : IDeletableEntity
{
}
@@ -0,0 +1,11 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.EntityBase
{
public interface IDeletableEntity : IEntity
{
[DataMember]
DateTime? DeletedDate { get; set; }
}
}
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.EntityBase
/// <summary>
/// Guid based Id
/// </summary>
/// <remarks>The key is currectly used to store the Unique Id from the
/// <remarks>The key is currectly used to store the Unique Id from the
/// umbracoNode table, which many of the entities are based on.</remarks>
[DataMember]
Guid Key { get; set; }
+1 -1
View File
@@ -11,7 +11,7 @@ namespace Umbraco.Core
{
private readonly bool _keepOldest;
public OrderedHashSet(bool keepOldest = true)
public OrderedHashSet(bool keepOldest = true)
{
_keepOldest = keepOldest;
}
@@ -70,6 +70,8 @@ namespace Umbraco.Core.Persistence.Repositories
{
//Remove 'published' xml from the cmsContentXml table for the unpublished content
Database.Delete<ContentXmlDto>("WHERE nodeId = @Id", new { Id = entity.Id });
entity.DeletedDate = DateTime.Now;
}
protected override void PersistNewItem(ContentXmlEntity<TContent> entity)
@@ -270,6 +270,8 @@ AND umbracoNode.id <> @id",
//Delete (base) node data
Database.Delete<NodeDto>("WHERE uniqueID = @Id", new { Id = entity.Key });
entity.DeletedDate = DateTime.Now;
}
#endregion
@@ -539,6 +541,8 @@ AND umbracoNode.id <> @id",
Database.Execute(
"DELETE FROM cmsDataTypePreValues WHERE id=@Id",
new { Id = entity.Id });
entity.DeletedDate = DateTime.Now;
}
protected override void PersistNewItem(PreValueEntity entity)
@@ -195,6 +195,8 @@ namespace Umbraco.Core.Persistence.Repositories
//Clear the cache entries that exist by uniqueid/item key
IsolatedCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.ItemKey));
IsolatedCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.Key));
entity.DeletedDate = DateTime.Now;
}
private void RecursiveDelete(Guid parentId)
@@ -167,6 +167,8 @@ namespace Umbraco.Core.Persistence.Repositories
// delete
Database.Delete(nodeDto);
entity.DeletedDate = DateTime.Now;
}
protected override void PersistNewItem(EntityContainer entity)
@@ -74,6 +74,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
Database.Execute(delete, new { Id = GetEntityId(entity) });
}
entity.DeletedDate = DateTime.Now;
}
}
}
@@ -311,6 +311,8 @@ namespace Umbraco.Core.Persistence.Repositories
var masterpageName = string.Concat(entity.Alias, ".master");
_masterpagesFileSystem.DeleteFile(masterpageName);
}
entity.DeletedDate = DateTime.Now;
}
#endregion
+2 -2
View File
@@ -166,8 +166,8 @@ namespace Umbraco.Core
return true;
}
return false;
}
}
/// <summary>
/// Determines whether [is of generic type] [the specified type].
/// </summary>
+45 -4
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
@@ -15,11 +16,51 @@ namespace Umbraco.Core
{
private static readonly ConcurrentDictionary<Tuple<Type, bool, bool, bool>, PropertyInfo[]> GetPropertiesCache
= new ConcurrentDictionary<Tuple<Type, bool, bool, bool>, PropertyInfo[]>();
private static readonly ConcurrentDictionary<Type, FieldInfo[]> GetFieldsCache
private static readonly ConcurrentDictionary<Type, FieldInfo[]> GetFieldsCache
= new ConcurrentDictionary<Type, FieldInfo[]>();
private static readonly Assembly[] EmptyAssemblies = new Assembly[0];
/// <summary>
/// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also deal with array types and return List{T} for those too.
/// If it cannot be done, null is returned.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
internal static IList CreateGenericEnumerableFromObject(object obj)
{
var type = obj.GetType();
if (type.IsGenericType)
{
var genericTypeDef = type.GetGenericTypeDefinition();
if (genericTypeDef == typeof(IEnumerable<>)
|| genericTypeDef == typeof(ICollection<>)
|| genericTypeDef == typeof(Collection<>)
|| genericTypeDef == typeof(IList<>)
|| genericTypeDef == typeof(List<>)
//this will occur when Linq is used and we get the odd WhereIterator or DistinctIterators since those are special iterator types
|| obj is IEnumerable)
{
//if it is a IEnumerable<>, IList<T> or ICollection<> we'll use a List<>
var genericType = typeof(List<>).MakeGenericType(type.GetGenericArguments());
//pass in obj to fill the list
return (IList)Activator.CreateInstance(genericType, obj);
}
}
if (type.IsArray)
{
//if its an array, we'll use a List<>
var genericType = typeof(List<>).MakeGenericType(type.GetElementType());
//pass in obj to fill the list
return (IList)Activator.CreateInstance(genericType, obj);
}
return null;
}
/// <summary>
/// Checks if the method is actually overriding a base method
/// </summary>
@@ -45,8 +86,8 @@ namespace Umbraco.Core
if (assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly())
return EmptyAssemblies;
// find all assembly references that are referencing the current type's assembly since we
// find all assembly references that are referencing the current type's assembly since we
// should only be scanning those assemblies because any other assembly will definitely not
// contain sub type's of the one we're currently looking for
var name = assembly.GetName().Name;
+3
View File
@@ -339,8 +339,11 @@
<Compile Include="Events\IDeletingMediaFilesEventArgs.cs" />
<Compile Include="Events\ScopeEventDispatcherBase.cs" />
<Compile Include="Events\ScopeLifespanMessagesFactory.cs" />
<Compile Include="Events\SupersedeEventAttribute.cs" />
<Compile Include="Exceptions\ConnectionException.cs" />
<Compile Include="HashCodeHelper.cs" />
<Compile Include="IHttpContextAccessor.cs" />
<Compile Include="Models\EntityBase\IDeletableEntity.cs" />
<Compile Include="Models\PublishedContent\PublishedContentTypeConverter.cs" />
<Compile Include="OrderedHashSet.cs" />
<Compile Include="Events\UninstallPackageEventArgs.cs" />
@@ -7,6 +7,7 @@ using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web.Cache;
using System.Linq;
namespace Umbraco.Tests.Cache
{
@@ -31,70 +32,70 @@ namespace Umbraco.Tests.Cache
new EventDefinition<ISectionService, EventArgs>(null, ServiceContext.SectionService, new EventArgs(), "Deleted"),
new EventDefinition<ISectionService, EventArgs>(null, ServiceContext.SectionService, new EventArgs(), "New"),
new EventDefinition<IUserService, SaveEventArgs<IUserType>>(null, ServiceContext.UserService, new SaveEventArgs<IUserType>((IUserType) null)),
new EventDefinition<IUserService, DeleteEventArgs<IUserType>>(null, ServiceContext.UserService, new DeleteEventArgs<IUserType>((IUserType) null)),
new EventDefinition<IUserService, SaveEventArgs<IUserType>>(null, ServiceContext.UserService, new SaveEventArgs<IUserType>(Enumerable.Empty<IUserType>())),
new EventDefinition<IUserService, DeleteEventArgs<IUserType>>(null, ServiceContext.UserService, new DeleteEventArgs<IUserType>(Enumerable.Empty<IUserType>())),
new EventDefinition<IUserService, SaveEventArgs<IUser>>(null, ServiceContext.UserService, new SaveEventArgs<IUser>((IUser) null)),
new EventDefinition<IUserService, DeleteEventArgs<IUser>>(null, ServiceContext.UserService, new DeleteEventArgs<IUser>((IUser) null)),
new EventDefinition<IUserService, DeleteEventArgs<IUser>>(null, ServiceContext.UserService, new DeleteEventArgs<IUser>((IUser) null)),
new EventDefinition<IUserService, SaveEventArgs<IUser>>(null, ServiceContext.UserService, new SaveEventArgs<IUser>(Enumerable.Empty<IUser>())),
new EventDefinition<IUserService, DeleteEventArgs<IUser>>(null, ServiceContext.UserService, new DeleteEventArgs<IUser>(Enumerable.Empty<IUser>())),
new EventDefinition<IUserService, DeleteEventArgs<IUser>>(null, ServiceContext.UserService, new DeleteEventArgs<IUser>(Enumerable.Empty<IUser>())),
new EventDefinition<ILocalizationService, SaveEventArgs<IDictionaryItem>>(null, ServiceContext.LocalizationService, new SaveEventArgs<IDictionaryItem>((IDictionaryItem) null)),
new EventDefinition<ILocalizationService, DeleteEventArgs<IDictionaryItem>>(null, ServiceContext.LocalizationService, new DeleteEventArgs<IDictionaryItem>((IDictionaryItem) null)),
new EventDefinition<ILocalizationService, SaveEventArgs<IDictionaryItem>>(null, ServiceContext.LocalizationService, new SaveEventArgs<IDictionaryItem>(Enumerable.Empty<IDictionaryItem>())),
new EventDefinition<ILocalizationService, DeleteEventArgs<IDictionaryItem>>(null, ServiceContext.LocalizationService, new DeleteEventArgs<IDictionaryItem>(Enumerable.Empty<IDictionaryItem>())),
new EventDefinition<IDataTypeService, SaveEventArgs<IDataTypeDefinition>>(null, ServiceContext.DataTypeService, new SaveEventArgs<IDataTypeDefinition>((IDataTypeDefinition) null)),
new EventDefinition<IDataTypeService, DeleteEventArgs<IDataTypeDefinition>>(null, ServiceContext.DataTypeService, new DeleteEventArgs<IDataTypeDefinition>((IDataTypeDefinition) null)),
new EventDefinition<IDataTypeService, SaveEventArgs<IDataTypeDefinition>>(null, ServiceContext.DataTypeService, new SaveEventArgs<IDataTypeDefinition>(Enumerable.Empty<IDataTypeDefinition>())),
new EventDefinition<IDataTypeService, DeleteEventArgs<IDataTypeDefinition>>(null, ServiceContext.DataTypeService, new DeleteEventArgs<IDataTypeDefinition>(Enumerable.Empty<IDataTypeDefinition>())),
new EventDefinition<IFileService, SaveEventArgs<Stylesheet>>(null, ServiceContext.FileService, new SaveEventArgs<Stylesheet>((Stylesheet) null)),
new EventDefinition<IFileService, DeleteEventArgs<Stylesheet>>(null, ServiceContext.FileService, new DeleteEventArgs<Stylesheet>((Stylesheet) null)),
new EventDefinition<IFileService, SaveEventArgs<Stylesheet>>(null, ServiceContext.FileService, new SaveEventArgs<Stylesheet>(Enumerable.Empty<Stylesheet>())),
new EventDefinition<IFileService, DeleteEventArgs<Stylesheet>>(null, ServiceContext.FileService, new DeleteEventArgs<Stylesheet>(Enumerable.Empty<Stylesheet>())),
new EventDefinition<IDomainService, SaveEventArgs<IDomain>>(null, ServiceContext.DomainService, new SaveEventArgs<IDomain>((IDomain) null)),
new EventDefinition<IDomainService, DeleteEventArgs<IDomain>>(null, ServiceContext.DomainService, new DeleteEventArgs<IDomain>((IDomain) null)),
new EventDefinition<IDomainService, SaveEventArgs<IDomain>>(null, ServiceContext.DomainService, new SaveEventArgs<IDomain>(Enumerable.Empty<IDomain>())),
new EventDefinition<IDomainService, DeleteEventArgs<IDomain>>(null, ServiceContext.DomainService, new DeleteEventArgs<IDomain>(Enumerable.Empty<IDomain>())),
new EventDefinition<ILocalizationService, SaveEventArgs<ILanguage>>(null, ServiceContext.LocalizationService, new SaveEventArgs<ILanguage>((ILanguage) null)),
new EventDefinition<ILocalizationService, DeleteEventArgs<ILanguage>>(null, ServiceContext.LocalizationService, new DeleteEventArgs<ILanguage>((ILanguage) null)),
new EventDefinition<ILocalizationService, SaveEventArgs<ILanguage>>(null, ServiceContext.LocalizationService, new SaveEventArgs<ILanguage>(Enumerable.Empty<ILanguage>())),
new EventDefinition<ILocalizationService, DeleteEventArgs<ILanguage>>(null, ServiceContext.LocalizationService, new DeleteEventArgs<ILanguage>(Enumerable.Empty<ILanguage>())),
new EventDefinition<IContentTypeService, SaveEventArgs<IContentType>>(null, ServiceContext.ContentTypeService, new SaveEventArgs<IContentType>((IContentType) null)),
new EventDefinition<IContentTypeService, DeleteEventArgs<IContentType>>(null, ServiceContext.ContentTypeService, new DeleteEventArgs<IContentType>((IContentType) null)),
new EventDefinition<IContentTypeService, SaveEventArgs<IMediaType>>(null, ServiceContext.ContentTypeService, new SaveEventArgs<IMediaType>((IMediaType) null)),
new EventDefinition<IContentTypeService, DeleteEventArgs<IMediaType>>(null, ServiceContext.ContentTypeService, new DeleteEventArgs<IMediaType>((IMediaType) null)),
new EventDefinition<IContentTypeService, SaveEventArgs<IContentType>>(null, ServiceContext.ContentTypeService, new SaveEventArgs<IContentType>(Enumerable.Empty<IContentType>())),
new EventDefinition<IContentTypeService, DeleteEventArgs<IContentType>>(null, ServiceContext.ContentTypeService, new DeleteEventArgs<IContentType>(Enumerable.Empty<IContentType>())),
new EventDefinition<IContentTypeService, SaveEventArgs<IMediaType>>(null, ServiceContext.ContentTypeService, new SaveEventArgs<IMediaType>(Enumerable.Empty<IMediaType>())),
new EventDefinition<IContentTypeService, DeleteEventArgs<IMediaType>>(null, ServiceContext.ContentTypeService, new DeleteEventArgs<IMediaType>(Enumerable.Empty<IMediaType>())),
new EventDefinition<IMemberTypeService, SaveEventArgs<IMemberType>>(null, ServiceContext.MemberTypeService, new SaveEventArgs<IMemberType>((IMemberType) null)),
new EventDefinition<IMemberTypeService, DeleteEventArgs<IMemberType>>(null, ServiceContext.MemberTypeService, new DeleteEventArgs<IMemberType>((IMemberType) null)),
new EventDefinition<IMemberTypeService, SaveEventArgs<IMemberType>>(null, ServiceContext.MemberTypeService, new SaveEventArgs<IMemberType>(Enumerable.Empty<IMemberType>())),
new EventDefinition<IMemberTypeService, DeleteEventArgs<IMemberType>>(null, ServiceContext.MemberTypeService, new DeleteEventArgs<IMemberType>(Enumerable.Empty<IMemberType>())),
new EventDefinition<IFileService, SaveEventArgs<ITemplate>>(null, ServiceContext.FileService, new SaveEventArgs<ITemplate>((ITemplate) null)),
new EventDefinition<IFileService, DeleteEventArgs<ITemplate>>(null, ServiceContext.FileService, new DeleteEventArgs<ITemplate>((ITemplate) null)),
new EventDefinition<IFileService, SaveEventArgs<ITemplate>>(null, ServiceContext.FileService, new SaveEventArgs<ITemplate>(Enumerable.Empty<ITemplate>())),
new EventDefinition<IFileService, DeleteEventArgs<ITemplate>>(null, ServiceContext.FileService, new DeleteEventArgs<ITemplate>(Enumerable.Empty<ITemplate>())),
new EventDefinition<IMacroService, SaveEventArgs<IMacro>>(null, ServiceContext.MacroService, new SaveEventArgs<IMacro>((IMacro) null)),
new EventDefinition<IMacroService, DeleteEventArgs<IMacro>>(null, ServiceContext.MacroService, new DeleteEventArgs<IMacro>((IMacro) null)),
new EventDefinition<IMacroService, SaveEventArgs<IMacro>>(null, ServiceContext.MacroService, new SaveEventArgs<IMacro>(Enumerable.Empty<IMacro>())),
new EventDefinition<IMacroService, DeleteEventArgs<IMacro>>(null, ServiceContext.MacroService, new DeleteEventArgs<IMacro>(Enumerable.Empty<IMacro>())),
new EventDefinition<IMemberService, SaveEventArgs<IMember>>(null, ServiceContext.MemberService, new SaveEventArgs<IMember>((IMember) null)),
new EventDefinition<IMemberService, DeleteEventArgs<IMember>>(null, ServiceContext.MemberService, new DeleteEventArgs<IMember>((IMember) null)),
new EventDefinition<IMemberService, SaveEventArgs<IMember>>(null, ServiceContext.MemberService, new SaveEventArgs<IMember>(Enumerable.Empty<IMember>())),
new EventDefinition<IMemberService, DeleteEventArgs<IMember>>(null, ServiceContext.MemberService, new DeleteEventArgs<IMember>(Enumerable.Empty<IMember>())),
new EventDefinition<IMemberGroupService, SaveEventArgs<IMemberGroup>>(null, ServiceContext.MemberGroupService, new SaveEventArgs<IMemberGroup>((IMemberGroup) null)),
new EventDefinition<IMemberGroupService, DeleteEventArgs<IMemberGroup>>(null, ServiceContext.MemberGroupService, new DeleteEventArgs<IMemberGroup>((IMemberGroup) null)),
new EventDefinition<IMemberGroupService, SaveEventArgs<IMemberGroup>>(null, ServiceContext.MemberGroupService, new SaveEventArgs<IMemberGroup>(Enumerable.Empty<IMemberGroup>())),
new EventDefinition<IMemberGroupService, DeleteEventArgs<IMemberGroup>>(null, ServiceContext.MemberGroupService, new DeleteEventArgs<IMemberGroup>(Enumerable.Empty<IMemberGroup>())),
new EventDefinition<IMediaService, SaveEventArgs<IMedia>>(null, ServiceContext.MediaService, new SaveEventArgs<IMedia>((IMedia) null)),
new EventDefinition<IMediaService, DeleteEventArgs<IMedia>>(null, ServiceContext.MediaService, new DeleteEventArgs<IMedia>((IMedia) null)),
new EventDefinition<IMediaService, SaveEventArgs<IMedia>>(null, ServiceContext.MediaService, new SaveEventArgs<IMedia>(Enumerable.Empty<IMedia>())),
new EventDefinition<IMediaService, DeleteEventArgs<IMedia>>(null, ServiceContext.MediaService, new DeleteEventArgs<IMedia>(Enumerable.Empty<IMedia>())),
new EventDefinition<IMediaService, MoveEventArgs<IMedia>>(null, ServiceContext.MediaService, new MoveEventArgs<IMedia>(new MoveEventInfo<IMedia>(null, "", -1)), "Moved"),
new EventDefinition<IMediaService, MoveEventArgs<IMedia>>(null, ServiceContext.MediaService, new MoveEventArgs<IMedia>(new MoveEventInfo<IMedia>(null, "", -1)), "Trashed"),
new EventDefinition<IMediaService, RecycleBinEventArgs>(null, ServiceContext.MediaService, new RecycleBinEventArgs(Guid.NewGuid(), new Dictionary<int, IEnumerable<Property>>(), true)),
new EventDefinition<IContentService, SaveEventArgs<IContent>>(null, ServiceContext.ContentService, new SaveEventArgs<IContent>((IContent) null)),
new EventDefinition<IContentService, DeleteEventArgs<IContent>>(null, ServiceContext.ContentService, new DeleteEventArgs<IContent>((IContent) null)),
new EventDefinition<IContentService, SaveEventArgs<IContent>>(null, ServiceContext.ContentService, new SaveEventArgs<IContent>(Enumerable.Empty<IContent>())),
new EventDefinition<IContentService, DeleteEventArgs<IContent>>(null, ServiceContext.ContentService, new DeleteEventArgs<IContent>(Enumerable.Empty<IContent>())),
new EventDefinition<IContentService, CopyEventArgs<IContent>>(null, ServiceContext.ContentService, new CopyEventArgs<IContent>(null, null, -1)),
new EventDefinition<IContentService, MoveEventArgs<IContent>>(null, ServiceContext.ContentService, new MoveEventArgs<IContent>(new MoveEventInfo<IContent>(null, "", -1)), "Trashed"),
new EventDefinition<IContentService, RecycleBinEventArgs>(null, ServiceContext.ContentService, new RecycleBinEventArgs(Guid.NewGuid(), new Dictionary<int, IEnumerable<Property>>(), true)),
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, ServiceContext.ContentService, new PublishEventArgs<IContent>((IContent) null), "Published"),
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, ServiceContext.ContentService, new PublishEventArgs<IContent>((IContent) null), "UnPublished"),
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, ServiceContext.ContentService, new PublishEventArgs<IContent>(Enumerable.Empty<IContent>()), "Published"),
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, ServiceContext.ContentService, new PublishEventArgs<IContent>(Enumerable.Empty<IContent>()), "UnPublished"),
new EventDefinition<IPublicAccessService, SaveEventArgs<PublicAccessEntry>>(null, ServiceContext.PublicAccessService, new SaveEventArgs<PublicAccessEntry>((PublicAccessEntry) null)),
new EventDefinition<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>>(null, ServiceContext.PublicAccessService, new DeleteEventArgs<PublicAccessEntry>((PublicAccessEntry) null)),
new EventDefinition<IPublicAccessService, SaveEventArgs<PublicAccessEntry>>(null, ServiceContext.PublicAccessService, new SaveEventArgs<PublicAccessEntry>(Enumerable.Empty<PublicAccessEntry>())),
new EventDefinition<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>>(null, ServiceContext.PublicAccessService, new DeleteEventArgs<PublicAccessEntry>(Enumerable.Empty<PublicAccessEntry>())),
new EventDefinition<IRelationService, SaveEventArgs<IRelationType>>(null, ServiceContext.RelationService, new SaveEventArgs<IRelationType>((IRelationType) null)),
new EventDefinition<IRelationService, DeleteEventArgs<IRelationType>>(null, ServiceContext.RelationService, new DeleteEventArgs<IRelationType>((IRelationType) null)),
new EventDefinition<IRelationService, SaveEventArgs<IRelationType>>(null, ServiceContext.RelationService, new SaveEventArgs<IRelationType>(Enumerable.Empty<IRelationType>())),
new EventDefinition<IRelationService, DeleteEventArgs<IRelationType>>(null, ServiceContext.RelationService, new DeleteEventArgs<IRelationType>(Enumerable.Empty<IRelationType>())),
new EventDefinition<IRelationService, SaveEventArgs<IRelationType>>(null, ServiceContext.RelationService, new SaveEventArgs<IRelationType>((IRelationType) null)),
new EventDefinition<IRelationService, DeleteEventArgs<IRelationType>>(null, ServiceContext.RelationService, new DeleteEventArgs<IRelationType>((IRelationType) null)),
new EventDefinition<IRelationService, SaveEventArgs<IRelationType>>(null, ServiceContext.RelationService, new SaveEventArgs<IRelationType>(Enumerable.Empty<IRelationType>())),
new EventDefinition<IRelationService, DeleteEventArgs<IRelationType>>(null, ServiceContext.RelationService, new DeleteEventArgs<IRelationType>(Enumerable.Empty<IRelationType>())),
};
foreach (var definition in definitions)
@@ -4,13 +4,16 @@ using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
namespace Umbraco.Tests.Scoping
{
[TestFixture]
public class ScopeEventDispatcherTests
public class ScopeEventDispatcherTests : BaseUmbracoConfigurationTest
{
[SetUp]
public void Setup()
@@ -79,7 +82,7 @@ namespace Umbraco.Tests.Scoping
var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray();
var knownNames = new[] { "DoThing1", "DoThing2", "DoThing3" };
var knownArgTypes = new[] { typeof (SaveEventArgs<string>), typeof (SaveEventArgs<int>), typeof (SaveEventArgs<decimal>) };
var knownArgTypes = new[] { typeof(SaveEventArgs<string>), typeof(SaveEventArgs<int>), typeof(SaveEventArgs<decimal>) };
for (var i = 0; i < events.Length; i++)
{
@@ -89,6 +92,166 @@ namespace Umbraco.Tests.Scoping
}
}
[Test]
public void SupersededEvents()
{
DoSaveForContent += OnDoThingFail;
DoDeleteForContent += OnDoThingFail;
DoForTestArgs += OnDoThingFail;
DoForTestArgs2 += OnDoThingFail;
var contentType = MockedContentTypes.CreateBasicContentType();
var content1 = MockedContent.CreateBasicContent(contentType);
content1.Id = 123;
var content2 = MockedContent.CreateBasicContent(contentType);
content2.Id = 456;
var content3 = MockedContent.CreateBasicContent(contentType);
content3.Id = 789;
var scopeProvider = new ScopeProvider(Mock.Of<IDatabaseFactory2>());
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
{
//content1 will be filtered from the args
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(new[]{ content1 , content3}));
scope.Events.Dispatch(DoDeleteForContent, this, new DeleteEventArgs<IContent>(content1));
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content2));
//this entire event will be filtered
scope.Events.Dispatch(DoForTestArgs, this, new TestEventArgs(content1));
scope.Events.Dispatch(DoForTestArgs2, this, new TestEventArgs2(content1));
// events have been queued
var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray();
Assert.AreEqual(4, events.Length);
Assert.AreEqual(typeof(SaveEventArgs<IContent>), events[0].Args.GetType());
Assert.AreEqual(1, ((SaveEventArgs<IContent>)events[0].Args).SavedEntities.Count());
Assert.AreEqual(content3.Id, ((SaveEventArgs<IContent>)events[0].Args).SavedEntities.First().Id);
Assert.AreEqual(typeof(DeleteEventArgs<IContent>), events[1].Args.GetType());
Assert.AreEqual(content1.Id, ((DeleteEventArgs<IContent>) events[1].Args).DeletedEntities.First().Id);
Assert.AreEqual(typeof(SaveEventArgs<IContent>), events[2].Args.GetType());
Assert.AreEqual(content2.Id, ((SaveEventArgs<IContent>)events[2].Args).SavedEntities.First().Id);
Assert.AreEqual(typeof(TestEventArgs2), events[3].Args.GetType());
}
}
/// <summary>
/// This will test that when we track events that before we Get the events we normalize all of the
/// event entities to be the latest one (most current) found amongst the event so that there is
/// no 'stale' entities in any of the args
/// </summary>
[Test]
public void LatestEntities()
{
DoSaveForContent += OnDoThingFail;
var now = DateTime.Now;
var contentType = MockedContentTypes.CreateBasicContentType();
var content1 = MockedContent.CreateBasicContent(contentType);
content1.Id = 123;
content1.UpdateDate = now.AddMinutes(1);
var content2 = MockedContent.CreateBasicContent(contentType);
content2.Id = 123;
content2.UpdateDate = now.AddMinutes(2);
var content3 = MockedContent.CreateBasicContent(contentType);
content3.Id = 123;
content3.UpdateDate = now.AddMinutes(3);
var scopeProvider = new ScopeProvider(Mock.Of<IDatabaseFactory2>());
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
{
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content1));
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content2));
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content3));
// events have been queued
var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray();
Assert.AreEqual(3, events.Length);
foreach (var t in events)
{
var args = (SaveEventArgs<IContent>)t.Args;
foreach (var entity in args.SavedEntities)
{
Assert.AreEqual(content3, entity);
Assert.IsTrue(object.ReferenceEquals(content3, entity));
}
}
}
}
[Test]
public void FirstIn()
{
DoSaveForContent += OnDoThingFail;
var now = DateTime.Now;
var contentType = MockedContentTypes.CreateBasicContentType();
var content1 = MockedContent.CreateBasicContent(contentType);
content1.Id = 123;
content1.UpdateDate = now.AddMinutes(1);
var content2 = MockedContent.CreateBasicContent(contentType);
content2.Id = 123;
content1.UpdateDate = now.AddMinutes(2);
var content3 = MockedContent.CreateBasicContent(contentType);
content3.Id = 123;
content1.UpdateDate = now.AddMinutes(3);
var scopeProvider = new ScopeProvider(Mock.Of<IDatabaseFactory2>());
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
{
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content1));
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content2));
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content3));
// events have been queued
var events = scope.Events.GetEvents(EventDefinitionFilter.FirstIn).ToArray();
Assert.AreEqual(1, events.Length);
Assert.AreEqual(content1, ((SaveEventArgs<IContent>) events[0].Args).SavedEntities.First());
Assert.IsTrue(object.ReferenceEquals(content1, ((SaveEventArgs<IContent>)events[0].Args).SavedEntities.First()));
Assert.AreEqual(content1.UpdateDate, ((SaveEventArgs<IContent>) events[0].Args).SavedEntities.First().UpdateDate);
}
}
[Test]
public void LastIn()
{
DoSaveForContent += OnDoThingFail;
var now = DateTime.Now;
var contentType = MockedContentTypes.CreateBasicContentType();
var content1 = MockedContent.CreateBasicContent(contentType);
content1.Id = 123;
content1.UpdateDate = now.AddMinutes(1);
var content2 = MockedContent.CreateBasicContent(contentType);
content2.Id = 123;
content2.UpdateDate = now.AddMinutes(2);
var content3 = MockedContent.CreateBasicContent(contentType);
content3.Id = 123;
content3.UpdateDate = now.AddMinutes(3);
var scopeProvider = new ScopeProvider(Mock.Of<IDatabaseFactory2>());
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
{
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content1));
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content2));
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content3));
// events have been queued
var events = scope.Events.GetEvents(EventDefinitionFilter.LastIn).ToArray();
Assert.AreEqual(1, events.Length);
Assert.AreEqual(content3, ((SaveEventArgs<IContent>)events[0].Args).SavedEntities.First());
Assert.IsTrue(object.ReferenceEquals(content3, ((SaveEventArgs<IContent>)events[0].Args).SavedEntities.First()));
Assert.AreEqual(content3.UpdateDate, ((SaveEventArgs<IContent>)events[0].Args).SavedEntities.First().UpdateDate);
}
}
[TestCase(true)]
[TestCase(false)]
public void EventsDispatching_Passive(bool complete)
@@ -177,12 +340,41 @@ namespace Umbraco.Tests.Scoping
Assert.Fail();
}
public static event EventHandler<SaveEventArgs<IContent>> DoSaveForContent;
public static event EventHandler<DeleteEventArgs<IContent>> DoDeleteForContent;
public static event EventHandler<TestEventArgs> DoForTestArgs;
public static event EventHandler<TestEventArgs2> DoForTestArgs2;
public static event EventHandler<SaveEventArgs<string>> DoThing1;
public static event EventHandler<SaveEventArgs<int>> DoThing2;
public static event TypedEventHandler<ScopeEventDispatcherTests, SaveEventArgs<decimal>> DoThing3;
public class TestEventArgs : CancellableObjectEventArgs
{
public TestEventArgs(object eventObject) : base(eventObject)
{
}
public object MyEventObject
{
get { return EventObject; }
}
}
[SupersedeEvent(typeof(TestEventArgs))]
public class TestEventArgs2 : CancellableObjectEventArgs
{
public TestEventArgs2(object eventObject) : base(eventObject)
{
}
public object MyEventObject
{
get { return EventObject; }
}
}
public class PassiveEventDispatcher : ScopeEventDispatcherBase
{
public PassiveEventDispatcher()
+3 -1
View File
@@ -179,7 +179,9 @@ namespace Umbraco.Tests.Scoping
scope.Complete();
}
Assert.AreEqual(complete ? 2 : 0, evented);
//The reason why there is only 1 event occuring is because we are publishing twice for the same event for the same
//object and the scope deduplicates the events (uses the latest)
Assert.AreEqual(complete ? 1 : 0, evented);
// this should never change
Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
@@ -28,6 +28,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(x => x.Snippet, exp => exp.Ignore());
config.CreateMap<CodeFileDisplay, IPartialView>()
.ForMember(x => x.DeletedDate, exp => exp.Ignore())
.ForMember(x => x.Id, exp => exp.Ignore())
.ForMember(x => x.Key, exp => exp.Ignore())
.ForMember(x => x.Path, exp => exp.Ignore())
@@ -40,6 +41,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(x => x.HasIdentity, exp => exp.Ignore());
config.CreateMap<CodeFileDisplay, Script>()
.ForMember(x => x.DeletedDate, exp => exp.Ignore())
.ForMember(x => x.Id, exp => exp.Ignore())
.ForMember(x => x.Key, exp => exp.Ignore())
.ForMember(x => x.Path, exp => exp.Ignore())
@@ -48,6 +48,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(type => type.Key, expression => expression.Ignore())
.ForMember(type => type.CreateDate, expression => expression.Ignore())
.ForMember(type => type.UpdateDate, expression => expression.Ignore())
.ForMember(type => type.DeletedDate, expression => expression.Ignore())
.ForMember(type => type.HasIdentity, expression => expression.Ignore());
config.CreateMap<DocumentTypeSave, IContentType>()
@@ -72,7 +73,7 @@ namespace Umbraco.Web.Models.Mapping
config.CreateMap<MediaTypeSave, IMediaType>()
//do the base mapping
.MapBaseContentTypeSaveToEntity<MediaTypeSave, PropertyTypeBasic, IMediaType>(applicationContext)
.ConstructUsing((source) => new MediaType(source.ParentId))
.ConstructUsing((source) => new MediaType(source.ParentId))
.AfterMap((source, dest) =>
{
ContentTypeModelMapperExtensions.AfterMapMediaTypeSaveToEntity(source, dest, applicationContext);
@@ -31,6 +31,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dest => dest.HasIdentity, map => map.Ignore())
.ForMember(dest => dest.CreateDate, map => map.Ignore())
.ForMember(dest => dest.UpdateDate, map => map.Ignore())
.ForMember(dest => dest.DeletedDate, map => map.Ignore())
.ForMember(dest => dest.PropertyTypes, map => map.Ignore());
}
@@ -175,6 +176,7 @@ namespace Umbraco.Web.Models.Mapping
//These get persisted as part of the saving procedure, nothing to do with the display model
.ForMember(dto => dto.CreateDate, expression => expression.Ignore())
.ForMember(dto => dto.UpdateDate, expression => expression.Ignore())
.ForMember(dto => dto.DeletedDate, expression => expression.Ignore())
.ForMember(dto => dto.AllowedAsRoot, expression => expression.MapFrom(display => display.AllowAsRoot))
.ForMember(dto => dto.CreatorId, expression => expression.Ignore())
@@ -108,6 +108,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(x => x.Level, expression => expression.Ignore())
.ForMember(x => x.SortOrder, expression => expression.Ignore())
.ForMember(x => x.CreateDate, expression => expression.Ignore())
.ForMember(x => x.DeletedDate, expression => expression.Ignore())
.ForMember(x => x.UpdateDate, expression => expression.Ignore());
//Converts a property editor to a new list of pre-value fields - used when creating a new data type or changing a data type with new pre-vals
@@ -55,8 +55,9 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(member => member.SortOrder, expression => expression.Ignore())
.ForMember(member => member.AdditionalData, expression => expression.Ignore())
.ForMember(member => member.FailedPasswordAttempts, expression => expression.Ignore())
//TODO: Support these eventually
.ForMember(member => member.PasswordQuestion, expression => expression.Ignore())
.ForMember(member => member.DeletedDate, expression => expression.Ignore())
//TODO: Support these eventually
.ForMember(member => member.PasswordQuestion, expression => expression.Ignore())
.ForMember(member => member.RawPasswordAnswerValue, expression => expression.Ignore());
//FROM IMember TO MediaItemDisplay
@@ -14,6 +14,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(x => x.Notifications, exp => exp.Ignore());
config.CreateMap<TemplateDisplay, Template>()
.ForMember(x => x.DeletedDate, exp => exp.Ignore())
.ForMember(x => x.Key, exp => exp.Ignore())
.ForMember(x => x.Path, exp => exp.Ignore())
.ForMember(x => x.CreateDate, exp => exp.Ignore())