Remove IEventDispatcher, it's no longer used.
This commit is contained in:
@@ -1,98 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Cms.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Dispatches events from within a scope.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The name of the event is auto-magically discovered by matching the sender type, args type, and
|
||||
/// eventHandler type. If the match is not unique, then the name parameter must be used to specify the
|
||||
/// name in an explicit way.</para>
|
||||
/// <para>What happens when an event is dispatched depends on the scope settings. It can be anything from
|
||||
/// "trigger immediately" to "just ignore". Refer to the scope documentation for more details.</para>
|
||||
/// </remarks>
|
||||
public interface IEventDispatcher
|
||||
{
|
||||
// not sure about the Dispatch & DispatchCancelable signatures at all for now
|
||||
// nor about the event name thing, etc - but let's keep it like this
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches a cancelable event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <returns>A value indicating whether the cancelable event was cancelled.</returns>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string name = null);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches a cancelable event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <returns>A value indicating whether the cancelable event was cancelled.</returns>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
bool DispatchCancelable<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string name = null)
|
||||
where TArgs : CancellableEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches a cancelable event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <returns>A value indicating whether the cancelable event was cancelled.</returns>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
bool DispatchCancelable<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string name = null)
|
||||
where TArgs : CancellableEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches an event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string name = null);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches an event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
void Dispatch<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string name = null);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches an event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
void Dispatch<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string name = null);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the dispatcher that the scope is exiting.
|
||||
/// </summary>
|
||||
/// <param name="completed">A value indicating whether the scope completed.</param>
|
||||
void ScopeExit(bool completed);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collected events.
|
||||
/// </summary>
|
||||
/// <returns>The collected events.</returns>
|
||||
IEnumerable<IEventDefinition> GetEvents(EventDefinitionFilter filter);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Cms.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// An IEventDispatcher that immediately raise all events.
|
||||
/// </summary>
|
||||
/// <remarks>This means that events will be raised during the scope transaction,
|
||||
/// whatever happens, and the transaction could roll back in the end.</remarks>
|
||||
internal class PassThroughEventDispatcher : IEventDispatcher
|
||||
{
|
||||
public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public bool DispatchCancelable<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
|
||||
where TArgs : CancellableEventArgs
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public bool DispatchCancelable<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
|
||||
where TArgs : CancellableEventArgs
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null)
|
||||
{
|
||||
eventHandler?.Invoke(sender, args);
|
||||
}
|
||||
|
||||
public void Dispatch<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
|
||||
{
|
||||
eventHandler?.Invoke(sender, args);
|
||||
}
|
||||
|
||||
public void Dispatch<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
|
||||
{
|
||||
eventHandler?.Invoke(sender, args);
|
||||
}
|
||||
|
||||
public IEnumerable<IEventDefinition> GetEvents(EventDefinitionFilter filter)
|
||||
{
|
||||
return Enumerable.Empty<IEventDefinition>();
|
||||
}
|
||||
|
||||
public void ScopeExit(bool completed)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Umbraco.Cms.Core.IO;
|
||||
|
||||
namespace Umbraco.Cms.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// An IEventDispatcher that queues events, and raise them when the scope
|
||||
/// exits and has been completed.
|
||||
/// </summary>
|
||||
public class QueuingEventDispatcher : QueuingEventDispatcherBase
|
||||
{
|
||||
private readonly MediaFileManager _mediaFileManager;
|
||||
public QueuingEventDispatcher(MediaFileManager mediaFileManager)
|
||||
: base(true)
|
||||
{
|
||||
_mediaFileManager = mediaFileManager;
|
||||
}
|
||||
|
||||
protected override void ScopeExitCompleted()
|
||||
{
|
||||
// processing only the last instance of each event...
|
||||
// this is probably far from perfect, because if eg a content is saved in a list
|
||||
// and then as a single content, the two events will probably not be de-duplicated,
|
||||
// but it's better than nothing
|
||||
|
||||
foreach (var e in GetEvents(EventDefinitionFilter.LastIn))
|
||||
{
|
||||
e.RaiseEvent();
|
||||
|
||||
// separating concerns means that this should probably not be here,
|
||||
// but then where should it be (without making things too complicated)?
|
||||
var delete = e.Args as IDeletingMediaFilesEventArgs;
|
||||
if (delete != null && delete.MediaFilesToDelete.Count > 0)
|
||||
_mediaFileManager.DeleteMediaFiles(delete.MediaFilesToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.Collections;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// An IEventDispatcher that queues events.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Can raise, or ignore, cancelable events, depending on option.</para>
|
||||
/// <para>Implementations must override ScopeExitCompleted to define what
|
||||
/// to do with the events when the scope exits and has been completed.</para>
|
||||
/// <para>If the scope exits without being completed, events are ignored.</para>
|
||||
/// </remarks>
|
||||
public abstract class QueuingEventDispatcherBase : IEventDispatcher
|
||||
{
|
||||
//events will be enlisted in the order they are raised
|
||||
private List<IEventDefinition> _events;
|
||||
private readonly bool _raiseCancelable;
|
||||
|
||||
protected QueuingEventDispatcherBase(bool raiseCancelable)
|
||||
{
|
||||
_raiseCancelable = raiseCancelable;
|
||||
}
|
||||
|
||||
private List<IEventDefinition> Events => _events ?? (_events = new List<IEventDefinition>());
|
||||
|
||||
public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
if (_raiseCancelable == false) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public bool DispatchCancelable<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
|
||||
where TArgs : CancellableEventArgs
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
if (_raiseCancelable == false) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public bool DispatchCancelable<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
|
||||
where TArgs : CancellableEventArgs
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
if (_raiseCancelable == false) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return;
|
||||
Events.Add(new EventDefinition(eventHandler, sender, args, eventName));
|
||||
}
|
||||
|
||||
public void Dispatch<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return;
|
||||
Events.Add(new EventDefinition<TArgs>(eventHandler, sender, args, eventName));
|
||||
}
|
||||
|
||||
public void Dispatch<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return;
|
||||
Events.Add(new EventDefinition<TSender, TArgs>(eventHandler, sender, args, eventName));
|
||||
}
|
||||
|
||||
public IEnumerable<IEventDefinition> GetEvents(EventDefinitionFilter filter)
|
||||
{
|
||||
if (_events == null)
|
||||
return Enumerable.Empty<IEventDefinition>();
|
||||
|
||||
IReadOnlyList<IEventDefinition> events;
|
||||
switch (filter)
|
||||
{
|
||||
case EventDefinitionFilter.All:
|
||||
events = _events;
|
||||
break;
|
||||
case EventDefinitionFilter.FirstIn:
|
||||
var l1 = new OrderedHashSet<IEventDefinition>();
|
||||
foreach (var e in _events)
|
||||
l1.Add(e);
|
||||
events = l1;
|
||||
break;
|
||||
case EventDefinitionFilter.LastIn:
|
||||
var l2 = new OrderedHashSet<IEventDefinition>(keepOldest: false);
|
||||
foreach (var e in _events)
|
||||
l2.Add(e);
|
||||
events = l2;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("filter", filter, null);
|
||||
}
|
||||
|
||||
return FilterSupersededAndUpdateToLatestEntity(events);
|
||||
}
|
||||
|
||||
private class EventDefinitionInfos
|
||||
{
|
||||
public IEventDefinition EventDefinition { get; set; }
|
||||
public Type[] SupersedeTypes { get; set; }
|
||||
}
|
||||
|
||||
// this is way too convoluted, the supersede attribute is used only on DeleteEventargs to specify
|
||||
// that it supersedes save, publish, move and copy - BUT - publish event args is also used for
|
||||
// unpublishing and should NOT be superseded - so really it should not be managed at event args
|
||||
// level but at event level
|
||||
//
|
||||
// what we want is:
|
||||
// if an entity is deleted, then all Saved, Moved, Copied, Published events prior to this should
|
||||
// not trigger for the entity - and even though, does it make any sense? making a copy of an entity
|
||||
// should ... trigger?
|
||||
//
|
||||
// not going to refactor it all - we probably want to *always* trigger event but tell people that
|
||||
// due to scopes, they should not expected eg a saved entity to still be around - however, now,
|
||||
// going to write a ugly condition to deal with U4-10764
|
||||
|
||||
// iterates 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)
|
||||
internal static IEnumerable<IEventDefinition> FilterSupersededAndUpdateToLatestEntity(IReadOnlyList<IEventDefinition> events)
|
||||
{
|
||||
// keeps the 'latest' entity and associated event data
|
||||
var entities = new List<Tuple<IEntity, EventDefinitionInfos>>();
|
||||
|
||||
// collects the event definitions
|
||||
// collects the arguments in result, that require their entities to be updated
|
||||
var result = new List<IEventDefinition>();
|
||||
var resultArgs = new List<CancellableObjectEventArgs>();
|
||||
|
||||
// eagerly fetch superseded arg types for each arg type
|
||||
var argTypeSuperceeding = events.Select(x => x.Args.GetType())
|
||||
.Distinct()
|
||||
.ToDictionary(x => x, x => x.GetCustomAttributes<SupersedeEventAttribute>(false).Select(y => y.SupersededEventArgsType).ToArray());
|
||||
|
||||
// iterate over all events and filter
|
||||
//
|
||||
// process the list in reverse, because events are added in the order they are raised and we want to keep
|
||||
// the latest (most recent) entities and filter out what is not relevant anymore (too old), eg if an entity
|
||||
// is Deleted after being Saved, we want to filter out the Saved event
|
||||
for (var index = events.Count - 1; index >= 0; index--)
|
||||
{
|
||||
var def = events[index];
|
||||
|
||||
var infos = new EventDefinitionInfos
|
||||
{
|
||||
EventDefinition = def,
|
||||
SupersedeTypes = argTypeSuperceeding[def.Args.GetType()]
|
||||
};
|
||||
|
||||
var args = def.Args as CancellableObjectEventArgs;
|
||||
if (args == null)
|
||||
{
|
||||
// not a cancellable event arg, include event definition in result
|
||||
result.Add(def);
|
||||
}
|
||||
else
|
||||
{
|
||||
// event object can either be a single object or an enumerable of objects
|
||||
// try to get as an enumerable, get null if it's not
|
||||
var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
if (eventObjects == null)
|
||||
{
|
||||
// single object, cast as an IEntity
|
||||
// if cannot cast, cannot filter, nothing - just include event definition in result
|
||||
var eventEntity = args.EventObject as IEntity;
|
||||
if (eventEntity == null)
|
||||
{
|
||||
result.Add(def);
|
||||
continue;
|
||||
}
|
||||
|
||||
// look for this entity in superseding event args
|
||||
// found = must be removed (ie not added), else track
|
||||
if (IsSuperceeded(eventEntity, infos, entities) == false)
|
||||
{
|
||||
// track
|
||||
entities.Add(Tuple.Create(eventEntity, infos));
|
||||
|
||||
// track result arguments
|
||||
// include event definition in result
|
||||
resultArgs.Add(args);
|
||||
result.Add(def);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// enumerable of objects
|
||||
var toRemove = new List<IEntity>();
|
||||
foreach (var eventObject in eventObjects)
|
||||
{
|
||||
// extract the event object, cast as an IEntity
|
||||
// if cannot cast, cannot filter, nothing to do - just leave it in the list & continue
|
||||
var eventEntity = eventObject as IEntity;
|
||||
if (eventEntity == null)
|
||||
continue;
|
||||
|
||||
// look for this entity in superseding event args
|
||||
// found = must be removed, else track
|
||||
if (IsSuperceeded(eventEntity, infos, entities))
|
||||
toRemove.Add(eventEntity);
|
||||
else
|
||||
entities.Add(Tuple.Create(eventEntity, infos));
|
||||
}
|
||||
|
||||
// remove superseded entities
|
||||
foreach (var entity in toRemove)
|
||||
eventObjects.Remove(entity);
|
||||
|
||||
// if there are still entities in the list, keep the event definition
|
||||
if (eventObjects.Count > 0)
|
||||
{
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
// re-assign if changed
|
||||
args.EventObject = eventObjects;
|
||||
}
|
||||
|
||||
// track result arguments
|
||||
// include event definition in result
|
||||
resultArgs.Add(args);
|
||||
result.Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// go over all args in result, and update them with the latest instanceof each entity
|
||||
UpdateToLatestEntities(entities, resultArgs);
|
||||
|
||||
// reverse, since we processed the list in reverse
|
||||
result.Reverse();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// edits event args to use the latest instance of each entity
|
||||
private static void UpdateToLatestEntities(IEnumerable<Tuple<IEntity, EventDefinitionInfos>> entities, IEnumerable<CancellableObjectEventArgs> args)
|
||||
{
|
||||
// get the latest entities
|
||||
// ordered hash set + keepOldest will keep the latest inserted entity (in case of duplicates)
|
||||
var latestEntities = new OrderedHashSet<IEntity>(keepOldest: true);
|
||||
foreach (var entity in entities.OrderByDescending(entity => entity.Item1.UpdateDate))
|
||||
latestEntities.Add(entity.Item1);
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
// event object can either be a single object or an enumerable of objects
|
||||
// try to get as an enumerable, get null if it's not
|
||||
var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(arg.EventObject);
|
||||
if (eventObjects == null)
|
||||
{
|
||||
// single object
|
||||
// look for a more recent entity for that object, and replace if any
|
||||
// works by "equalling" entities ie the more recent one "equals" this one (though different object)
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, arg.EventObject));
|
||||
if (foundEntity != null)
|
||||
arg.EventObject = foundEntity;
|
||||
}
|
||||
else
|
||||
{
|
||||
// enumerable of objects
|
||||
// same as above but for each object
|
||||
var updated = false;
|
||||
for (var i = 0; i < eventObjects.Count; i++)
|
||||
{
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, eventObjects[i]));
|
||||
if (foundEntity == null) continue;
|
||||
eventObjects[i] = foundEntity;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (updated)
|
||||
arg.EventObject = eventObjects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// determines if a given entity, appearing in a given event definition, should be filtered out,
|
||||
// considering the entities that have already been visited - an entity is filtered out if it
|
||||
// appears in another even definition, which supersedes this event definition.
|
||||
private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, List<Tuple<IEntity, EventDefinitionInfos>> entities)
|
||||
{
|
||||
//var argType = meta.EventArgsType;
|
||||
var argType = infos.EventDefinition.Args.GetType();
|
||||
|
||||
// look for other instances of the same entity, coming from an event args that supersedes other event args,
|
||||
// ie is marked with the attribute, and is not this event args (cannot supersede itself)
|
||||
var superceeding = entities
|
||||
.Where(x => x.Item2.SupersedeTypes.Length > 0 // has the attribute
|
||||
&& x.Item2.EventDefinition.Args.GetType() != argType // is not the same
|
||||
&& Equals(x.Item1, entity)) // same entity
|
||||
.ToArray();
|
||||
|
||||
// first time we see this entity = not filtered
|
||||
if (superceeding.Length == 0)
|
||||
return false;
|
||||
|
||||
// delete event args does NOT supersedes 'unpublished' event
|
||||
if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "Unpublished")
|
||||
return false;
|
||||
|
||||
// found occurrences, need to determine if this event args is superseded
|
||||
if (argType.IsGenericType)
|
||||
{
|
||||
// generic, must compare type arguments
|
||||
var supercededBy = superceeding.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeTypes.Any(y =>
|
||||
// superseding a generic type which has the same generic type definition
|
||||
// (but ... no matter the generic type parameters? could be different?)
|
||||
y.IsGenericTypeDefinition && y == argType.GetGenericTypeDefinition()
|
||||
// or superceeding a non-generic type which is ... (but... how is this ever possible? argType *is* generic?
|
||||
|| y.IsGenericTypeDefinition == false && y == argType));
|
||||
return supercededBy != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// non-generic, can compare types 1:1
|
||||
var supercededBy = superceeding.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeTypes.Any(y => y == argType));
|
||||
return supercededBy != null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ScopeExit(bool completed)
|
||||
{
|
||||
if (_events == null) return;
|
||||
if (completed)
|
||||
ScopeExitCompleted();
|
||||
_events.Clear();
|
||||
}
|
||||
|
||||
protected abstract void ScopeExitCompleted();
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
/// </summary>
|
||||
EventMessages Messages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scope event dispatcher.
|
||||
/// </summary>
|
||||
IEventDispatcher Events { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scope notification publisher
|
||||
/// </summary>
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
/// </summary>
|
||||
/// <param name="isolationLevel">The transaction isolation level.</param>
|
||||
/// <param name="repositoryCacheMode">The repositories cache mode.</param>
|
||||
/// <param name="eventDispatcher">An optional events dispatcher.</param>
|
||||
/// <param name="scopedNotificationPublisher">An optional notification publisher.</param>
|
||||
/// <param name="scopeFileSystems">A value indicating whether to scope the filesystems.</param>
|
||||
/// <param name="callContext">A value indicating whether this scope should always be registered in the call context.</param>
|
||||
@@ -35,7 +34,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
IScope CreateScope(
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
IScopedNotificationPublisher scopedNotificationPublisher = null,
|
||||
bool? scopeFileSystems = null,
|
||||
bool callContext = false,
|
||||
@@ -47,7 +45,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
/// <returns>A detached scope.</returns>
|
||||
/// <param name="isolationLevel">The transaction isolation level.</param>
|
||||
/// <param name="repositoryCacheMode">The repositories cache mode.</param>
|
||||
/// <param name="eventDispatcher">An optional events dispatcher.</param>
|
||||
/// <param name="scopedNotificationPublisher">An option notification publisher.</param>
|
||||
/// <param name="scopeFileSystems">A value indicating whether to scope the filesystems.</param>
|
||||
/// <remarks>
|
||||
@@ -58,7 +55,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
IScope CreateDetachedScope(
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
IScopedNotificationPublisher scopedNotificationPublisher = null,
|
||||
bool? scopeFileSystems = null);
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
private IUmbracoDatabase _database;
|
||||
|
||||
private bool _disposed;
|
||||
private IEventDispatcher _eventDispatcher;
|
||||
private ICompletable _fscope;
|
||||
|
||||
private IsolatedCaches _isolatedCaches;
|
||||
@@ -68,7 +67,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
bool detachable,
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
IScopedNotificationPublisher notificationPublisher = null,
|
||||
bool? scopeFileSystems = null,
|
||||
bool callContext = false,
|
||||
@@ -83,7 +81,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
|
||||
_isolationLevel = isolationLevel;
|
||||
_repositoryCacheMode = repositoryCacheMode;
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_notificationPublisher = notificationPublisher;
|
||||
_scopeFileSystem = scopeFileSystems;
|
||||
_callContext = callContext;
|
||||
@@ -141,12 +138,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
nameof(repositoryCacheMode));
|
||||
}
|
||||
|
||||
// cannot specify a dispatcher!
|
||||
if (_eventDispatcher != null)
|
||||
{
|
||||
throw new ArgumentException("Value cannot be specified on nested scope.", nameof(eventDispatcher));
|
||||
}
|
||||
|
||||
// Only the outermost scope can specify the notification publisher
|
||||
if (_notificationPublisher != null)
|
||||
{
|
||||
@@ -187,13 +178,12 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
IScopeContext scopeContext,
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
IScopedNotificationPublisher scopedNotificationPublisher = null,
|
||||
bool? scopeFileSystems = null,
|
||||
bool callContext = false,
|
||||
bool autoComplete = false)
|
||||
: this(scopeProvider, coreDebugSettings, mediaFileManager, eventAggregator, logger, fileSystems, null,
|
||||
scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher,
|
||||
scopeContext, detachable, isolationLevel, repositoryCacheMode,
|
||||
scopedNotificationPublisher, scopeFileSystems, callContext, autoComplete)
|
||||
{
|
||||
}
|
||||
@@ -209,13 +199,12 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
Scope parent,
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
IScopedNotificationPublisher notificationPublisher = null,
|
||||
bool? scopeFileSystems = null,
|
||||
bool callContext = false,
|
||||
bool autoComplete = false)
|
||||
: this(scopeProvider, coreDebugSettings, mediaFileManager, eventAggregator, logger, fileSystems, parent,
|
||||
null, false, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher,
|
||||
null, false, isolationLevel, repositoryCacheMode, notificationPublisher,
|
||||
scopeFileSystems, callContext, autoComplete)
|
||||
{
|
||||
}
|
||||
@@ -455,21 +444,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEventDispatcher Events
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
if (ParentScope != null)
|
||||
{
|
||||
return ParentScope.Events;
|
||||
}
|
||||
|
||||
return _eventDispatcher ??= new QueuingEventDispatcher(_mediaFileManager);
|
||||
}
|
||||
}
|
||||
|
||||
public IScopedNotificationPublisher Notifications
|
||||
{
|
||||
get
|
||||
@@ -857,7 +831,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
// deal with events
|
||||
if (onException == false)
|
||||
{
|
||||
_eventDispatcher?.ScopeExit(completed);
|
||||
_notificationPublisher?.ScopeExit(completed);
|
||||
}
|
||||
}, () =>
|
||||
|
||||
@@ -381,10 +381,9 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
public IScope CreateDetachedScope(
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
IScopedNotificationPublisher scopedNotificationPublisher = null,
|
||||
bool? scopeFileSystems = null)
|
||||
=> new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopedNotificationPublisher, scopeFileSystems);
|
||||
=> new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, scopedNotificationPublisher, scopeFileSystems);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AttachScope(IScope other, bool callContext = false)
|
||||
@@ -453,7 +452,6 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
public IScope CreateScope(
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
IScopedNotificationPublisher notificationPublisher = null,
|
||||
bool? scopeFileSystems = null,
|
||||
bool callContext = false,
|
||||
@@ -464,7 +462,7 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
{
|
||||
IScopeContext ambientContext = AmbientContext;
|
||||
ScopeContext newContext = ambientContext == null ? new ScopeContext() : null;
|
||||
var scope = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete);
|
||||
var scope = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, notificationPublisher, scopeFileSystems, callContext, autoComplete);
|
||||
// assign only if scope creation did not throw!
|
||||
PushAmbientScope(scope);
|
||||
if (newContext != null)
|
||||
@@ -474,7 +472,7 @@ namespace Umbraco.Cms.Core.Scoping
|
||||
return scope;
|
||||
}
|
||||
|
||||
var nested = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete);
|
||||
var nested = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, notificationPublisher, scopeFileSystems, callContext, autoComplete);
|
||||
PushAmbientScope(nested);
|
||||
return nested;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine
|
||||
scopeProviderMock.Setup(x => x.CreateScope(
|
||||
It.IsAny<IsolationLevel>(),
|
||||
It.IsAny<RepositoryCacheMode>(),
|
||||
It.IsAny<IEventDispatcher>(),
|
||||
It.IsAny<IScopedNotificationPublisher>(),
|
||||
It.IsAny<bool?>(),
|
||||
It.IsAny<bool>(),
|
||||
|
||||
@@ -318,20 +318,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping
|
||||
public static string GetCacheIdKey<T>(object id) => $"{GetCacheTypeKey<T>()}{id}";
|
||||
|
||||
public static string GetCacheTypeKey<T>() => $"uRepo_{typeof(T).Name}_";
|
||||
|
||||
public class PassiveEventDispatcher : QueuingEventDispatcherBase
|
||||
{
|
||||
public PassiveEventDispatcher()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ScopeExitCompleted()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class LocalServerMessenger : ServerMessengerBase
|
||||
{
|
||||
public LocalServerMessenger()
|
||||
|
||||
@@ -230,7 +230,6 @@ namespace Umbraco.Cms.Tests.UnitTests.TestHelpers
|
||||
.Setup(x => x.CreateScope(
|
||||
It.IsAny<IsolationLevel>(),
|
||||
It.IsAny<RepositoryCacheMode>(),
|
||||
It.IsAny<IEventDispatcher>(),
|
||||
It.IsAny<IScopedNotificationPublisher>(),
|
||||
It.IsAny<bool?>(),
|
||||
It.IsAny<bool>(),
|
||||
|
||||
@@ -1,452 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Scoping
|
||||
{
|
||||
[TestFixture]
|
||||
public class ScopeEventDispatcherTests
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
// remove all handlers first
|
||||
DoThing1 = null;
|
||||
DoThing2 = null;
|
||||
DoThing3 = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[TestCase(false, true, true)]
|
||||
[TestCase(false, true, false)]
|
||||
[TestCase(false, false, true)]
|
||||
[TestCase(false, false, false)]
|
||||
[TestCase(true, true, true)]
|
||||
[TestCase(true, true, false)]
|
||||
[TestCase(true, false, true)]
|
||||
[TestCase(true, false, false)]
|
||||
public void EventsHandling(bool passive, bool cancel, bool complete)
|
||||
{
|
||||
var counter1 = 0;
|
||||
var counter2 = 0;
|
||||
|
||||
DoThing1 += (sender, args) => { counter1++; if (cancel) args.Cancel = true; };
|
||||
DoThing2 += (sender, args) => { counter2++; };
|
||||
|
||||
var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance);
|
||||
using (var scope = scopeProvider.CreateScope(eventDispatcher: passive ? new PassiveEventDispatcher() : null))
|
||||
{
|
||||
var cancelled = scope.Events.DispatchCancelable(DoThing1, this, new SaveEventArgs<string>("test"));
|
||||
if (cancelled == false)
|
||||
scope.Events.Dispatch(DoThing2, this, new SaveEventArgs<int>(0));
|
||||
if (complete)
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
var expected1 = passive ? 0 : 1;
|
||||
Assert.AreEqual(expected1, counter1);
|
||||
|
||||
int expected2;
|
||||
if (passive)
|
||||
expected2 = 0;
|
||||
else
|
||||
expected2 = cancel ? 0 : (complete ? 1 : 0);
|
||||
|
||||
Assert.AreEqual(expected2, counter2);
|
||||
}
|
||||
|
||||
private ScopeProvider GetScopeProvider(NullLoggerFactory instance)
|
||||
{
|
||||
var fileSystems = new FileSystems(
|
||||
instance,
|
||||
Mock.Of<IIOHelper>(),
|
||||
Options.Create(new GlobalSettings()),
|
||||
Mock.Of<IHostingEnvironment>());
|
||||
|
||||
var mediaFileManager = new MediaFileManager(
|
||||
Mock.Of<IFileSystem>(),
|
||||
Mock.Of<IMediaPathScheme>(),
|
||||
instance.CreateLogger<MediaFileManager>(),
|
||||
Mock.Of<IShortStringHelper>(),
|
||||
Mock.Of<IServiceProvider>(),
|
||||
Options.Create(new ContentSettings()));
|
||||
|
||||
return new ScopeProvider(
|
||||
Mock.Of<IUmbracoDatabaseFactory>(),
|
||||
fileSystems,
|
||||
new TestOptionsMonitor<CoreDebugSettings>(new CoreDebugSettings()),
|
||||
mediaFileManager,
|
||||
Mock.Of<ILogger<ScopeProvider>>(),
|
||||
instance,
|
||||
Mock.Of<IRequestCache>(),
|
||||
Mock.Of<IEventAggregator>()
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void QueueEvents()
|
||||
{
|
||||
DoThing1 += OnDoThingFail;
|
||||
DoThing2 += OnDoThingFail;
|
||||
DoThing3 += OnDoThingFail;
|
||||
|
||||
var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance);
|
||||
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
|
||||
{
|
||||
scope.Events.Dispatch(DoThing1, this, new SaveEventArgs<string>("test"));
|
||||
scope.Events.Dispatch(DoThing2, this, new SaveEventArgs<int>(0));
|
||||
scope.Events.Dispatch(DoThing3, this, new SaveEventArgs<decimal>(0));
|
||||
|
||||
// events have been queued
|
||||
Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count());
|
||||
|
||||
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>) };
|
||||
|
||||
for (var i = 0; i < events.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(knownNames[i], events[i].EventName);
|
||||
Assert.AreEqual(knownArgTypes[i], events[i].Args.GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SupersededEvents()
|
||||
{
|
||||
DoSaveForContent += OnDoThingFail;
|
||||
DoDeleteForContent += OnDoThingFail;
|
||||
DoForTestArgs += OnDoThingFail;
|
||||
DoForTestArgs2 += OnDoThingFail;
|
||||
|
||||
var contentType = ContentTypeBuilder.CreateBasicContentType();
|
||||
|
||||
var content1 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content1.Id = 123;
|
||||
|
||||
var content2 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content2.Id = 456;
|
||||
|
||||
var content3 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content3.Id = 789;
|
||||
|
||||
var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance);
|
||||
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), "DoDeleteForContent");
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SupersededEvents2()
|
||||
{
|
||||
Test_Unpublished += OnDoThingFail;
|
||||
Test_Deleted += OnDoThingFail;
|
||||
|
||||
var contentService = Mock.Of<IContentService>();
|
||||
var content = Mock.Of<IContent>();
|
||||
|
||||
var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance);
|
||||
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
|
||||
{
|
||||
scope.Events.Dispatch(Test_Unpublished, contentService, new PublishEventArgs<IContent>(new[] { content }), "Unpublished");
|
||||
scope.Events.Dispatch(Test_Deleted, contentService, new DeleteEventArgs<IContent>(new[] { content }), "Deleted");
|
||||
|
||||
// see U4-10764
|
||||
var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray();
|
||||
Assert.AreEqual(2, events.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 = ContentTypeBuilder.CreateBasicContentType();
|
||||
var content1 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content1.Id = 123;
|
||||
content1.UpdateDate = now.AddMinutes(1);
|
||||
var content2 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content2.Id = 123;
|
||||
content2.UpdateDate = now.AddMinutes(2);
|
||||
var content3 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content3.Id = 123;
|
||||
content3.UpdateDate = now.AddMinutes(3);
|
||||
|
||||
var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance);
|
||||
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 = ContentTypeBuilder.CreateBasicContentType();
|
||||
var content1 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content1.Id = 123;
|
||||
content1.UpdateDate = now.AddMinutes(1);
|
||||
var content2 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content2.Id = 123;
|
||||
content1.UpdateDate = now.AddMinutes(2);
|
||||
var content3 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content3.Id = 123;
|
||||
content1.UpdateDate = now.AddMinutes(3);
|
||||
|
||||
var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance);
|
||||
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 = ContentTypeBuilder.CreateBasicContentType();
|
||||
var content1 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content1.Id = 123;
|
||||
content1.UpdateDate = now.AddMinutes(1);
|
||||
var content2 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content2.Id = 123;
|
||||
content2.UpdateDate = now.AddMinutes(2);
|
||||
var content3 = ContentBuilder.CreateBasicContent(contentType);
|
||||
content3.Id = 123;
|
||||
content3.UpdateDate = now.AddMinutes(3);
|
||||
|
||||
var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance);
|
||||
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)
|
||||
{
|
||||
DoThing1 += OnDoThingFail;
|
||||
DoThing2 += OnDoThingFail;
|
||||
DoThing3 += OnDoThingFail;
|
||||
|
||||
var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance);
|
||||
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
|
||||
{
|
||||
scope.Events.Dispatch(DoThing1, this, new SaveEventArgs<string>("test"));
|
||||
scope.Events.Dispatch(DoThing2, this, new SaveEventArgs<int>(0));
|
||||
scope.Events.Dispatch(DoThing3, this, new SaveEventArgs<decimal>(0));
|
||||
|
||||
// events have been queued
|
||||
Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count());
|
||||
|
||||
if (complete)
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
// no event has been raised (else OnDoThingFail would have failed)
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void EventsDispatching_Scope(bool complete)
|
||||
{
|
||||
var counter = 0;
|
||||
IScope ambientScope = null;
|
||||
IScopeContext ambientContext = null;
|
||||
Guid value = Guid.Empty;
|
||||
|
||||
var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance) as ScopeProvider;
|
||||
|
||||
DoThing1 += (sender, args) => { counter++; };
|
||||
DoThing2 += (sender, args) => { counter++; };
|
||||
DoThing3 += (sender, args) =>
|
||||
{
|
||||
ambientScope = scopeProvider.AmbientScope;
|
||||
ambientContext = scopeProvider.AmbientContext;
|
||||
value = scopeProvider.Context.Enlist("value", Guid.NewGuid, (c, o) => { });
|
||||
counter++;
|
||||
};
|
||||
|
||||
Guid guid;
|
||||
using (var scope = scopeProvider.CreateScope())
|
||||
{
|
||||
Assert.IsNotNull(scopeProvider.AmbientContext);
|
||||
guid = scopeProvider.Context.Enlist("value", Guid.NewGuid, (c, o) => { });
|
||||
|
||||
scope.Events.Dispatch(DoThing1, this, new SaveEventArgs<string>("test"));
|
||||
scope.Events.Dispatch(DoThing2, this, new SaveEventArgs<int>(0));
|
||||
scope.Events.Dispatch(DoThing3, this, new SaveEventArgs<decimal>(0));
|
||||
|
||||
// events have been queued
|
||||
Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count());
|
||||
Assert.AreEqual(0, counter);
|
||||
|
||||
if (complete)
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
if (complete)
|
||||
{
|
||||
// events have been raised
|
||||
Assert.AreEqual(3, counter);
|
||||
Assert.IsNull(ambientScope); // scope was gone
|
||||
Assert.IsNotNull(ambientContext); // but not context
|
||||
Assert.AreEqual(guid, value); // so we got the same value!
|
||||
}
|
||||
else
|
||||
{
|
||||
// else, no event has been raised
|
||||
Assert.AreEqual(0, counter);
|
||||
}
|
||||
|
||||
// everything's gone
|
||||
Assert.IsNull(scopeProvider.AmbientScope);
|
||||
Assert.IsNull(scopeProvider.AmbientContext);
|
||||
}
|
||||
|
||||
private static void OnDoThingFail(object sender, EventArgs eventArgs)
|
||||
{
|
||||
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 static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Test_Unpublished;
|
||||
public static event TypedEventHandler<IContentService, DeleteEventArgs<IContent>> Test_Deleted;
|
||||
|
||||
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 : QueuingEventDispatcherBase
|
||||
{
|
||||
public PassiveEventDispatcher()
|
||||
: base(false)
|
||||
{ }
|
||||
|
||||
protected override void ScopeExitCompleted()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.HostedServices
|
||||
var mockScope = new Mock<IScope>();
|
||||
var mockScopeProvider = new Mock<IScopeProvider>();
|
||||
mockScopeProvider
|
||||
.Setup(x => x.CreateScope(It.IsAny<IsolationLevel>(), It.IsAny<RepositoryCacheMode>(), It.IsAny<IEventDispatcher>(), It.IsAny<IScopedNotificationPublisher>(), It.IsAny<bool?>(), It.IsAny<bool>(), It.IsAny<bool>()))
|
||||
.Setup(x => x.CreateScope(It.IsAny<IsolationLevel>(), It.IsAny<RepositoryCacheMode>(), It.IsAny<IScopedNotificationPublisher>(), It.IsAny<bool?>(), It.IsAny<bool>(), It.IsAny<bool>()))
|
||||
.Returns(mockScope.Object);
|
||||
var mockLogger = new Mock<ILogger<LogScrubber>>();
|
||||
var mockProfilingLogger = new Mock<IProfilingLogger>();
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Mapping
|
||||
scopeMock.Setup(x => x.CreateScope(
|
||||
It.IsAny<IsolationLevel>(),
|
||||
It.IsAny<RepositoryCacheMode>(),
|
||||
It.IsAny<IEventDispatcher>(),
|
||||
It.IsAny<IScopedNotificationPublisher>(),
|
||||
It.IsAny<bool?>(),
|
||||
It.IsAny<bool>(),
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
|
||||
public IScope CreateScope(
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
IScopedNotificationPublisher notificationPublisher = null,
|
||||
bool? scopeFileSystems = null,
|
||||
bool callContext = false,
|
||||
@@ -37,7 +36,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Migrations
|
||||
public IScope CreateDetachedScope(
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
IScopedNotificationPublisher notificationPublisher = null,
|
||||
bool? scopeFileSystems = null) => throw new NotImplementedException();
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security
|
||||
var mockScope = new Mock<IScope>();
|
||||
var mockScopeProvider = new Mock<IScopeProvider>();
|
||||
mockScopeProvider
|
||||
.Setup(x => x.CreateScope(It.IsAny<IsolationLevel>(), It.IsAny<RepositoryCacheMode>(), It.IsAny<IEventDispatcher>(), It.IsAny<IScopedNotificationPublisher>(), It.IsAny<bool?>(), It.IsAny<bool>(), It.IsAny<bool>()))
|
||||
.Setup(x => x.CreateScope(It.IsAny<IsolationLevel>(), It.IsAny<RepositoryCacheMode>(), It.IsAny<IScopedNotificationPublisher>(), It.IsAny<bool?>(), It.IsAny<bool>(), It.IsAny<bool>()))
|
||||
.Returns(mockScope.Object);
|
||||
|
||||
return new MemberUserStore(
|
||||
|
||||
@@ -523,7 +523,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
||||
var scopeProvider = Mock.Of<IScopeProvider>(x => x.CreateScope(
|
||||
It.IsAny<IsolationLevel>(),
|
||||
It.IsAny<RepositoryCacheMode>(),
|
||||
It.IsAny<IEventDispatcher>(),
|
||||
It.IsAny<IScopedNotificationPublisher>(),
|
||||
It.IsAny<bool?>(),
|
||||
It.IsAny<bool>(),
|
||||
|
||||
Reference in New Issue
Block a user