Remove IEventDispatcher, it's no longer used.

This commit is contained in:
Paul Johnson
2022-01-11 16:59:31 +00:00
parent 8d1fa717ba
commit ae2ff856fb
17 changed files with 8 additions and 1062 deletions

View File

@@ -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);
}
}

View File

@@ -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)
{ }
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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);
}
}, () =>

View File

@@ -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;
}

View File

@@ -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>(),

View File

@@ -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()

View File

@@ -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>(),

View File

@@ -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
}
}
}
}

View File

@@ -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>();

View File

@@ -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>(),

View File

@@ -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();

View File

@@ -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(

View File

@@ -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>(),