diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
index 7815747f18..63562eb53e 100644
--- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
+++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
@@ -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
{
- ///
- /// Event args for a strongly typed object that can support cancellation
- ///
- ///
- [HostProtection(SecurityAction.LinkDemand, SharedState = true)]
- public class CancellableObjectEventArgs : CancellableEventArgs, IEquatable>
- {
- public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary additionalData)
+ ///
+ /// Used as a base class for the generic type CancellableObjectEventArgs{T} so that we can get direct 'object' access to the underlying EventObject
+ ///
+ [HostProtection(SecurityAction.LinkDemand, SharedState = true)]
+ public abstract class CancellableObjectEventArgs : CancellableEventArgs
+ {
+ protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages messages, IDictionary 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)
+ {
+ }
+
+ ///
+ /// Returns the object relating to the event
+ ///
+ ///
+ /// This is protected so that inheritors can expose it with their own name
+ ///
+ internal object EventObject { get; set; }
+
+ }
+
+ ///
+ /// Event args for a strongly typed object that can support cancellation
+ ///
+ ///
+ [HostProtection(SecurityAction.LinkDemand, SharedState = true)]
+ public class CancellableObjectEventArgs : CancellableObjectEventArgs, IEquatable>
+ {
+ public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary 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)
+ {
+ }
- ///
- /// Returns the object relating to the event
- ///
- ///
- /// This is protected so that inheritors can expose it with their own name
- ///
- protected T EventObject { get; set; }
+ ///
+ /// Returns the object relating to the event
+ ///
+ ///
+ /// This is protected so that inheritors can expose it with their own name
+ ///
+ protected new T EventObject
+ {
+ get { return (T) base.EventObject; }
+ set { base.EventObject = value; }
+ }
- public bool Equals(CancellableObjectEventArgs other)
- {
- if (ReferenceEquals(null, other)) return false;
- if (ReferenceEquals(this, other)) return true;
- return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject);
- }
+ public bool Equals(CancellableObjectEventArgs other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return base.Equals(other) && EqualityComparer.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) 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)obj);
+ }
- public override int GetHashCode()
- {
- unchecked
- {
- return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject);
- }
- }
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject);
+ }
+ }
- public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right)
- {
- return Equals(left, right);
- }
+ public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right)
+ {
+ return Equals(left, right);
+ }
- public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right)
- {
- return !Equals(left, right);
- }
- }
+ public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right)
+ {
+ return !Equals(left, right);
+ }
+ }
+
+ [HostProtection(SecurityAction.LinkDemand, SharedState = true)]
+ public class CancellableEnumerableObjectEventArgs : CancellableObjectEventArgs>, IEquatable>
+ {
+ public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData)
+ : base(eventObject, canCancel, messages, additionalData)
+ { }
+
+ public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages)
+ : base(eventObject, canCancel, eventMessages)
+ { }
+
+ public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, EventMessages eventMessages)
+ : base(eventObject, eventMessages)
+ { }
+
+ public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel)
+ : base(eventObject, canCancel)
+ { }
+
+ public CancellableEnumerableObjectEventArgs(IEnumerable eventObject)
+ : base(eventObject)
+ { }
+
+ public bool Equals(CancellableEnumerableObjectEventArgs 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)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCodeHelper.GetHashCode(EventObject);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs
index df13363b95..d0a4f024e1 100644
--- a/src/Umbraco.Core/Events/DeleteEventArgs.cs
+++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs
@@ -1,9 +1,14 @@
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Umbraco.Core.Events
{
- public class DeleteEventArgs : CancellableObjectEventArgs>, IEquatable>, IDeletingMediaFilesEventArgs
+ [SupersedeEvent(typeof(SaveEventArgs<>))]
+ [SupersedeEvent(typeof(PublishEventArgs<>))]
+ [SupersedeEvent(typeof(MoveEventArgs<>))]
+ [SupersedeEvent(typeof(CopyEventArgs<>))]
+ public class DeleteEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>, IDeletingMediaFilesEventArgs
{
///
/// 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)
diff --git a/src/Umbraco.Core/Events/ImportEventArgs.cs b/src/Umbraco.Core/Events/ImportEventArgs.cs
index dcecf5c36b..892149c0a2 100644
--- a/src/Umbraco.Core/Events/ImportEventArgs.cs
+++ b/src/Umbraco.Core/Events/ImportEventArgs.cs
@@ -4,7 +4,7 @@ using System.Xml.Linq;
namespace Umbraco.Core.Events
{
- public class ImportEventArgs : CancellableObjectEventArgs>, IEquatable>
+ public class ImportEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>
{
///
/// Constructor accepting an XElement with the xml being imported
diff --git a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs
index 231e58c07e..4477faea50 100644
--- a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs
+++ b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs
@@ -4,7 +4,7 @@ using Umbraco.Core.Packaging.Models;
namespace Umbraco.Core.Events
{
- internal class ImportPackageEventArgs : CancellableObjectEventArgs>, IEquatable>
+ internal class ImportPackageEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>
{
private readonly MetaData _packageMetaData;
@@ -32,7 +32,8 @@ namespace Umbraco.Core.Events
public bool Equals(ImportPackageEventArgs 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);
}
diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs
index 1aa7c2308c..10bf94146c 100644
--- a/src/Umbraco.Core/Events/PublishEventArgs.cs
+++ b/src/Umbraco.Core/Events/PublishEventArgs.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Umbraco.Core.Events
{
- public class PublishEventArgs : CancellableObjectEventArgs>, IEquatable>
+ public class PublishEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>
{
///
/// Constructor accepting multiple entities that are used in the publish operation
diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs
index e816a8f8bd..cd19038d8e 100644
--- a/src/Umbraco.Core/Events/SaveEventArgs.cs
+++ b/src/Umbraco.Core/Events/SaveEventArgs.cs
@@ -1,8 +1,9 @@
using System.Collections.Generic;
+using System.Linq;
namespace Umbraco.Core.Events
{
- public class SaveEventArgs : CancellableObjectEventArgs>
+ public class SaveEventArgs : CancellableEnumerableObjectEventArgs
{
///
/// Constructor accepting multiple entities that are used in the saving operation
@@ -116,7 +117,5 @@ namespace Umbraco.Core.Events
{
get { return EventObject; }
}
-
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs
index 4dcb15515a..c703a10cb4 100644
--- a/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs
+++ b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs
@@ -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
///
public abstract class ScopeEventDispatcherBase : IEventDispatcher
{
+ //events will be enlisted in the order they are raised
private List _events;
private readonly bool _raiseCancelable;
@@ -73,29 +76,263 @@ namespace Umbraco.Core.Events
{
if (_events == null)
return Enumerable.Empty();
-
+
switch (filter)
{
case EventDefinitionFilter.All:
- return _events;
+ return FilterSupersededAndUpdateToLatestEntity(_events);
case EventDefinitionFilter.FirstIn:
var l1 = new OrderedHashSet();
foreach (var e in _events)
{
l1.Add(e);
}
- return l1;
+ return FilterSupersededAndUpdateToLatestEntity(l1);
case EventDefinitionFilter.LastIn:
var l2 = new OrderedHashSet(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; }
+ }
+
+ ///
+ /// 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)
+ ///
+ ///
+ ///
+ private static IEnumerable FilterSupersededAndUpdateToLatestEntity(IReadOnlyList events)
+ {
+ //used to keep the 'latest' entity and associated event definition data
+ var allEntities = new List>();
+
+ //tracks all CancellableObjectEventArgs instances in the events which is the only type of args we can work with
+ var cancelableArgs = new List();
+
+ var result = new List();
+
+ //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(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();
+ 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> allEntities, IEnumerable cancelableArgs)
+ {
+ //Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
+
+ var latestEntities = new OrderedHashSet(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;
+ }
+ }
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static bool IsFiltered(
+ IEntity entity,
+ EventDefinitionTypeData eventDef,
+ List> 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)
{
diff --git a/src/Umbraco.Core/Events/SupersedeEventAttribute.cs b/src/Umbraco.Core/Events/SupersedeEventAttribute.cs
new file mode 100644
index 0000000000..c7a14ea158
--- /dev/null
+++ b/src/Umbraco.Core/Events/SupersedeEventAttribute.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Umbraco.Core.Events
+{
+ ///
+ /// 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.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ internal class SupersedeEventAttribute : Attribute
+ {
+ public Type SupersededEventArgsType { get; private set; }
+
+ public SupersedeEventAttribute(Type supersededEventArgsType)
+ {
+ SupersededEventArgsType = supersededEventArgsType;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs
index b97a3cfacf..d3a55d5256 100644
--- a/src/Umbraco.Core/HashCodeCombiner.cs
+++ b/src/Umbraco.Core/HashCodeCombiner.cs
@@ -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
{
- ///
- /// Used to create a hash code from multiple objects.
- ///
- ///
- /// .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.
- ///
- internal class HashCodeCombiner
- {
- private long _combinedHash = 5381L;
+ ///
+ /// Used to create a hash code from multiple objects.
+ ///
+ ///
+ /// .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.
+ ///
+ 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);
+ }
+ }
+ }
- ///
- /// Returns the hex code of the combined hash code
- ///
- ///
- internal string GetCombinedHashCode()
- {
- return _combinedHash.ToString("x", CultureInfo.InvariantCulture);
- }
+ internal void AddFile(FileInfo f)
+ {
+ AddFileSystemItem(f);
+ }
- }
+ internal void AddFolder(DirectoryInfo d)
+ {
+ AddFileSystemItem(d);
+ }
+
+ ///
+ /// Returns the hex code of the combined hash code
+ ///
+ ///
+ internal string GetCombinedHashCode()
+ {
+ return _combinedHash.ToString("x", CultureInfo.InvariantCulture);
+ }
+
+ }
}
diff --git a/src/Umbraco.Core/HashCodeHelper.cs b/src/Umbraco.Core/HashCodeHelper.cs
new file mode 100644
index 0000000000..f0f281056d
--- /dev/null
+++ b/src/Umbraco.Core/HashCodeHelper.cs
@@ -0,0 +1,104 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core
+{
+ ///
+ /// Borrowed from http://stackoverflow.com/a/2575444/694494
+ ///
+ internal static class HashCodeHelper
+ {
+ public static int GetHashCode(T1 arg1, T2 arg2)
+ {
+ unchecked
+ {
+ return 31 * arg1.GetHashCode() + arg2.GetHashCode();
+ }
+ }
+
+ public static int GetHashCode(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 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[] 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(IEnumerable list)
+ {
+ unchecked
+ {
+ int hash = 0;
+ foreach (var item in list)
+ {
+ if (item == null) continue;
+ hash = 31 * hash + item.GetHashCode();
+ }
+ return hash;
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ public static int GetHashCodeForOrderNoMatterCollection(
+ IEnumerable 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();
+ }
+ }
+
+ ///
+ /// Alternative way to get a hashcode is to use a fluent
+ /// interface like this:
+ /// return 0.CombineHashCode(field1).CombineHashCode(field2).
+ /// CombineHashCode(field3);
+ ///
+ public static int CombineHashCode(this int hashCode, T arg)
+ {
+ unchecked
+ {
+ return 31 * hashCode + arg.GetHashCode();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/ContentXmlEntity.cs b/src/Umbraco.Core/Models/ContentXmlEntity.cs
index 0450fdc72e..93185834a3 100644
--- a/src/Umbraco.Core/Models/ContentXmlEntity.cs
+++ b/src/Umbraco.Core/Models/ContentXmlEntity.cs
@@ -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; }
///
/// 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;
}
+
+
}
}
\ 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 d605759ed1..d4da2676c1 100644
--- a/src/Umbraco.Core/Models/EntityBase/Entity.cs
+++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs
@@ -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;
diff --git a/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs b/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs
index 4298dd9cf4..aacb5185e0 100644
--- a/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs
+++ b/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs
@@ -3,7 +3,7 @@
///
/// Marker interface for aggregate roots
///
- public interface IAggregateRoot : IEntity
+ public interface IAggregateRoot : IDeletableEntity
{
}
diff --git a/src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs b/src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs
new file mode 100644
index 0000000000..42f91b6a6c
--- /dev/null
+++ b/src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Umbraco.Core.Models.EntityBase
+{
+ public interface IDeletableEntity : IEntity
+ {
+ [DataMember]
+ DateTime? DeletedDate { get; set; }
+ }
+}
\ 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 81f5f632ef..059983bb38 100644
--- a/src/Umbraco.Core/Models/EntityBase/IEntity.cs
+++ b/src/Umbraco.Core/Models/EntityBase/IEntity.cs
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.EntityBase
///
/// Guid based Id
///
- /// The key is currectly used to store the Unique Id from the
+ /// The key is currectly used to store the Unique Id from the
/// umbracoNode table, which many of the entities are based on.
[DataMember]
Guid Key { get; set; }
diff --git a/src/Umbraco.Core/OrderedHashSet.cs b/src/Umbraco.Core/OrderedHashSet.cs
index 2fd545c915..801f1a9a41 100644
--- a/src/Umbraco.Core/OrderedHashSet.cs
+++ b/src/Umbraco.Core/OrderedHashSet.cs
@@ -11,7 +11,7 @@ namespace Umbraco.Core
{
private readonly bool _keepOldest;
- public OrderedHashSet(bool keepOldest = true)
+ public OrderedHashSet(bool keepOldest = true)
{
_keepOldest = keepOldest;
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs
index 464ae16961..ef3ce3eec2 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs
@@ -70,6 +70,8 @@ namespace Umbraco.Core.Persistence.Repositories
{
//Remove 'published' xml from the cmsContentXml table for the unpublished content
Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id });
+
+ entity.DeletedDate = DateTime.Now;
}
protected override void PersistNewItem(ContentXmlEntity entity)
diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
index 1ebfb4bc47..1690b36148 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
@@ -270,6 +270,8 @@ AND umbracoNode.id <> @id",
//Delete (base) node data
Database.Delete("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)
diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
index 41bdf020cd..968c2d9cb0 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
@@ -195,6 +195,8 @@ namespace Umbraco.Core.Persistence.Repositories
//Clear the cache entries that exist by uniqueid/item key
IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey));
IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.Key));
+
+ entity.DeletedDate = DateTime.Now;
}
private void RecursiveDelete(Guid parentId)
diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs
index b741238fb9..bc23d7201c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs
@@ -167,6 +167,8 @@ namespace Umbraco.Core.Persistence.Repositories
// delete
Database.Delete(nodeDto);
+
+ entity.DeletedDate = DateTime.Now;
}
protected override void PersistNewItem(EntityContainer entity)
diff --git a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs
index dc61da119e..afe1a78b68 100644
--- a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs
@@ -74,6 +74,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
Database.Execute(delete, new { Id = GetEntityId(entity) });
}
+ entity.DeletedDate = DateTime.Now;
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs
index 6bc801da4d..7f265aceb7 100644
--- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs
@@ -311,6 +311,8 @@ namespace Umbraco.Core.Persistence.Repositories
var masterpageName = string.Concat(entity.Alias, ".master");
_masterpagesFileSystem.DeleteFile(masterpageName);
}
+
+ entity.DeletedDate = DateTime.Now;
}
#endregion
diff --git a/src/Umbraco.Core/TypeExtensions.cs b/src/Umbraco.Core/TypeExtensions.cs
index 76dc79c219..84d2a2d47a 100644
--- a/src/Umbraco.Core/TypeExtensions.cs
+++ b/src/Umbraco.Core/TypeExtensions.cs
@@ -166,8 +166,8 @@ namespace Umbraco.Core
return true;
}
return false;
- }
-
+ }
+
///
/// Determines whether [is of generic type] [the specified type].
///
diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs
index 14c441dcd5..166b7e308f 100644
--- a/src/Umbraco.Core/TypeHelper.cs
+++ b/src/Umbraco.Core/TypeHelper.cs
@@ -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, PropertyInfo[]> GetPropertiesCache
= new ConcurrentDictionary, PropertyInfo[]>();
- private static readonly ConcurrentDictionary GetFieldsCache
+ private static readonly ConcurrentDictionary GetFieldsCache
= new ConcurrentDictionary();
private static readonly Assembly[] EmptyAssemblies = new Assembly[0];
-
+
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ 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 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;
+ }
+
///
/// Checks if the method is actually overriding a base method
///
@@ -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;
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 74cbe5d534..795ad24328 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -339,8 +339,11 @@
+
+
+
diff --git a/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs b/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs
index 3aa1db675f..a5036188fd 100644
--- a/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs
+++ b/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.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(null, ServiceContext.SectionService, new EventArgs(), "Deleted"),
new EventDefinition(null, ServiceContext.SectionService, new EventArgs(), "New"),
- new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs((IUserType) null)),
- new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs((IUserType) null)),
+ new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs((IUser) null)),
- new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs((IUser) null)),
- new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs((IUser) null)),
+ new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.LocalizationService, new SaveEventArgs((IDictionaryItem) null)),
- new EventDefinition>(null, ServiceContext.LocalizationService, new DeleteEventArgs((IDictionaryItem) null)),
+ new EventDefinition>(null, ServiceContext.LocalizationService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.LocalizationService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.DataTypeService, new SaveEventArgs((IDataTypeDefinition) null)),
- new EventDefinition>(null, ServiceContext.DataTypeService, new DeleteEventArgs((IDataTypeDefinition) null)),
+ new EventDefinition>(null, ServiceContext.DataTypeService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.DataTypeService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.FileService, new SaveEventArgs((Stylesheet) null)),
- new EventDefinition>(null, ServiceContext.FileService, new DeleteEventArgs((Stylesheet) null)),
+ new EventDefinition>(null, ServiceContext.FileService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.FileService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.DomainService, new SaveEventArgs((IDomain) null)),
- new EventDefinition>(null, ServiceContext.DomainService, new DeleteEventArgs((IDomain) null)),
+ new EventDefinition>(null, ServiceContext.DomainService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.DomainService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.LocalizationService, new SaveEventArgs((ILanguage) null)),
- new EventDefinition>(null, ServiceContext.LocalizationService, new DeleteEventArgs((ILanguage) null)),
+ new EventDefinition>(null, ServiceContext.LocalizationService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.LocalizationService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs((IContentType) null)),
- new EventDefinition>(null, ServiceContext.ContentTypeService, new DeleteEventArgs((IContentType) null)),
- new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs((IMediaType) null)),
- new EventDefinition>(null, ServiceContext.ContentTypeService, new DeleteEventArgs((IMediaType) null)),
+ new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.ContentTypeService, new DeleteEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.ContentTypeService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.MemberTypeService, new SaveEventArgs((IMemberType) null)),
- new EventDefinition>(null, ServiceContext.MemberTypeService, new DeleteEventArgs((IMemberType) null)),
+ new EventDefinition>(null, ServiceContext.MemberTypeService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.MemberTypeService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.FileService, new SaveEventArgs((ITemplate) null)),
- new EventDefinition>(null, ServiceContext.FileService, new DeleteEventArgs((ITemplate) null)),
+ new EventDefinition>(null, ServiceContext.FileService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.FileService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.MacroService, new SaveEventArgs((IMacro) null)),
- new EventDefinition>(null, ServiceContext.MacroService, new DeleteEventArgs((IMacro) null)),
+ new EventDefinition>(null, ServiceContext.MacroService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.MacroService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.MemberService, new SaveEventArgs((IMember) null)),
- new EventDefinition>(null, ServiceContext.MemberService, new DeleteEventArgs((IMember) null)),
+ new EventDefinition>(null, ServiceContext.MemberService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.MemberService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.MemberGroupService, new SaveEventArgs((IMemberGroup) null)),
- new EventDefinition>(null, ServiceContext.MemberGroupService, new DeleteEventArgs((IMemberGroup) null)),
+ new EventDefinition>(null, ServiceContext.MemberGroupService, new SaveEventArgs(Enumerable.Empty())),
+ new EventDefinition>(null, ServiceContext.MemberGroupService, new DeleteEventArgs(Enumerable.Empty())),
- new EventDefinition>(null, ServiceContext.MediaService, new SaveEventArgs((IMedia) null)),
- new EventDefinition>(null, ServiceContext.MediaService, new DeleteEventArgs