diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs
index c24936b4fb..2b4f05a4f0 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs
@@ -2,7 +2,6 @@
// See LICENSE for more details.
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
using Umbraco.Core.Events;
namespace Umbraco.Core.DependencyInjection
@@ -35,5 +34,29 @@ namespace Umbraco.Core.DependencyInjection
return builder;
}
+
+ ///
+ /// Registers a notification async handler against the Umbraco service collection.
+ ///
+ /// The type of notification.
+ /// The type of notification async handler.
+ /// The Umbraco builder.
+ /// The .
+ public static IUmbracoBuilder AddNotificationAsyncHandler(this IUmbracoBuilder builder)
+ where TNotificationAsyncHandler : INotificationAsyncHandler
+ where TNotification : INotification
+ {
+ // Register the handler as transient. This ensures that anything can be injected into it.
+ var descriptor = new ServiceDescriptor(typeof(INotificationAsyncHandler), typeof(TNotificationAsyncHandler), ServiceLifetime.Transient);
+
+ // TODO: Waiting on feedback here https://github.com/umbraco/Umbraco-CMS/pull/9556/files#r548365396 about whether
+ // we perform this duplicate check or not.
+ if (!builder.Services.Contains(descriptor))
+ {
+ builder.Services.Add(descriptor);
+ }
+
+ return builder;
+ }
}
}
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
index 54e5982a58..b69a2c1677 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
@@ -146,7 +146,7 @@ namespace Umbraco.Core.DependencyInjection
Services.AddSingleton();
Services.AddSingleton();
- this.AddNotificationHandler();
+ this.AddNotificationAsyncHandler();
Services.AddUnique();
diff --git a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs
index 00982b52d7..faadc2fc59 100644
--- a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs
+++ b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs
@@ -15,17 +15,30 @@ namespace Umbraco.Core.Events
///
public partial class EventAggregator : IEventAggregator
{
+ private static readonly ConcurrentDictionary s_notificationAsyncHandlers
+ = new ConcurrentDictionary();
+
private static readonly ConcurrentDictionary s_notificationHandlers
= new ConcurrentDictionary();
private Task PublishNotificationAsync(INotification notification, CancellationToken cancellationToken = default)
{
Type notificationType = notification.GetType();
- NotificationHandlerWrapper handler = s_notificationHandlers.GetOrAdd(
+ NotificationAsyncHandlerWrapper asyncHandler = s_notificationAsyncHandlers.GetOrAdd(
+ notificationType,
+ t => (NotificationAsyncHandlerWrapper)Activator.CreateInstance(typeof(NotificationAsyncHandlerWrapperImpl<>).MakeGenericType(notificationType)));
+
+ return asyncHandler.HandleAsync(notification, cancellationToken, _serviceFactory, PublishCoreAsync);
+ }
+
+ private void PublishNotification(INotification notification)
+ {
+ Type notificationType = notification.GetType();
+ NotificationHandlerWrapper asyncHandler = s_notificationHandlers.GetOrAdd(
notificationType,
t => (NotificationHandlerWrapper)Activator.CreateInstance(typeof(NotificationHandlerWrapperImpl<>).MakeGenericType(notificationType)));
- return handler.HandleAsync(notification, cancellationToken, _serviceFactory, PublishCoreAsync);
+ asyncHandler.Handle(notification, _serviceFactory, PublishCore);
}
private async Task PublishCoreAsync(
@@ -38,9 +51,27 @@ namespace Umbraco.Core.Events
await handler(notification, cancellationToken).ConfigureAwait(false);
}
}
+
+ private void PublishCore(
+ IEnumerable> allHandlers,
+ INotification notification)
+ {
+ foreach (Action handler in allHandlers)
+ {
+ handler(notification);
+ }
+ }
}
internal abstract class NotificationHandlerWrapper
+ {
+ public abstract void Handle(
+ INotification notification,
+ ServiceFactory serviceFactory,
+ Action>, INotification> publish);
+ }
+
+ internal abstract class NotificationAsyncHandlerWrapper
{
public abstract Task HandleAsync(
INotification notification,
@@ -49,7 +80,7 @@ namespace Umbraco.Core.Events
Func>, INotification, CancellationToken, Task> publish);
}
- internal class NotificationHandlerWrapperImpl : NotificationHandlerWrapper
+ internal class NotificationAsyncHandlerWrapperImpl : NotificationAsyncHandlerWrapper
where TNotification : INotification
{
public override Task HandleAsync(
@@ -59,7 +90,7 @@ namespace Umbraco.Core.Events
Func>, INotification, CancellationToken, Task> publish)
{
IEnumerable> handlers = serviceFactory
- .GetInstances>()
+ .GetInstances>()
.Select(x => new Func(
(theNotification, theToken) =>
x.HandleAsync((TNotification)theNotification, theToken)));
@@ -67,4 +98,22 @@ namespace Umbraco.Core.Events
return publish(handlers, notification, cancellationToken);
}
}
+
+ internal class NotificationHandlerWrapperImpl : NotificationHandlerWrapper
+ where TNotification : INotification
+ {
+ public override void Handle(
+ INotification notification,
+ ServiceFactory serviceFactory,
+ Action>, INotification> publish)
+ {
+ IEnumerable> handlers = serviceFactory
+ .GetInstances>()
+ .Select(x => new Action(
+ (theNotification) =>
+ x.Handle((TNotification)theNotification)));
+
+ publish(handlers, notification);
+ }
+ }
}
diff --git a/src/Umbraco.Core/Events/EventAggregator.cs b/src/Umbraco.Core/Events/EventAggregator.cs
index 8d58175da4..7bc63df228 100644
--- a/src/Umbraco.Core/Events/EventAggregator.cs
+++ b/src/Umbraco.Core/Events/EventAggregator.cs
@@ -38,6 +38,7 @@ namespace Umbraco.Core.Events
throw new ArgumentNullException(nameof(notification));
}
+ PublishNotification(notification);
return PublishNotificationAsync(notification, cancellationToken);
}
}
diff --git a/src/Umbraco.Core/Events/IEventAggregator.cs b/src/Umbraco.Core/Events/IEventAggregator.cs
index d78aed36b7..dc4205b0ec 100644
--- a/src/Umbraco.Core/Events/IEventAggregator.cs
+++ b/src/Umbraco.Core/Events/IEventAggregator.cs
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Events
public interface IEventAggregator
{
///
- /// Asynchronously send a notification to multiple handlers
+ /// Asynchronously send a notification to multiple handlers of both sync and async
///
/// The type of notification being handled.
/// The notification object.
diff --git a/src/Umbraco.Core/Events/INotificationHandler.cs b/src/Umbraco.Core/Events/INotificationHandler.cs
index dc5d83e0f7..4cfe52e005 100644
--- a/src/Umbraco.Core/Events/INotificationHandler.cs
+++ b/src/Umbraco.Core/Events/INotificationHandler.cs
@@ -12,6 +12,20 @@ namespace Umbraco.Core.Events
/// The type of notification being handled.
public interface INotificationHandler
where TNotification : INotification
+ {
+ ///
+ /// Handles a notification
+ ///
+ /// The notification
+ void Handle(TNotification notification);
+ }
+
+ ///
+ /// Defines a handler for a async notification.
+ ///
+ /// The type of notification being handled.
+ public interface INotificationAsyncHandler
+ where TNotification : INotification
{
///
/// Handles a notification
diff --git a/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs b/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs
index 5f219ac51f..2b1fa3d0cc 100644
--- a/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs
+++ b/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Runtime
///
/// Starts monitoring AppPlugins directory during debug runs, to restart site when a plugin manifest changes.
///
- public sealed class AppPluginsManifestWatcherNotificationHandler : INotificationHandler
+ public sealed class AppPluginsManifestWatcherNotificationHandler : INotificationAsyncHandler
{
private readonly ManifestWatcher _manifestWatcher;
private readonly IHostingEnvironment _hostingEnvironment;
diff --git a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs
index 92ad5808b3..535a466e25 100644
--- a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs
+++ b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs
@@ -1,5 +1,3 @@
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Events;
@@ -21,7 +19,7 @@ namespace Umbraco.Core.Runtime
_globalSettings = globalSettings.Value;
}
- public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
+ public void Handle(UmbracoApplicationStarting notification)
{
// ensure we have some essential directories
// every other component can then initialize safely
@@ -31,7 +29,6 @@ namespace Umbraco.Core.Runtime
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews));
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials));
- return Task.CompletedTask;
}
}
}
diff --git a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs
index 5b4dd4f0ed..1ad2b44740 100644
--- a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs
+++ b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs
@@ -1,5 +1,3 @@
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Events;
using Umbraco.Core.Persistence;
@@ -39,7 +37,7 @@ namespace Umbraco.Infrastructure.Cache
}
///
- public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
+ public void Handle(UmbracoApplicationStarting notification)
{
// The scheduled tasks - TouchServerTask and InstructionProcessTask - run as .NET Core hosted services.
// The former (as well as other hosted services that run outside of an HTTP request context) depends on the application URL
@@ -50,8 +48,6 @@ namespace Umbraco.Infrastructure.Cache
_requestAccessor.EndRequest += EndRequest;
Startup();
-
- return Task.CompletedTask;
}
private void Startup()
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs
index cbf3d02542..57a3fb0bd7 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs
@@ -28,6 +28,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Events
_builder = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader());
}
+ [Test]
+ public async Task CanPublishAsyncEvents()
+ {
+ _builder.Services.AddScoped();
+ _builder.AddNotificationAsyncHandler();
+ _builder.AddNotificationAsyncHandler();
+ _builder.AddNotificationAsyncHandler();
+ ServiceProvider provider = _builder.Services.BuildServiceProvider();
+
+ var notification = new Notification();
+ IEventAggregator aggregator = provider.GetService();
+ await aggregator.PublishAsync(notification);
+
+ Assert.AreEqual(A + B + C, notification.SubscriberCount);
+ }
+
[Test]
public async Task CanPublishEvents()
{
@@ -51,19 +67,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Events
public class NotificationHandlerA : INotificationHandler
{
- public Task HandleAsync(Notification notification, CancellationToken cancellationToken)
+ public void Handle(Notification notification)
{
notification.SubscriberCount += A;
- return Task.CompletedTask;
}
}
public class NotificationHandlerB : INotificationHandler
{
- public Task HandleAsync(Notification notification, CancellationToken cancellationToken)
+ public void Handle(Notification notification)
{
notification.SubscriberCount += B;
- return Task.CompletedTask;
}
}
@@ -73,13 +87,42 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Events
public NotificationHandlerC(Adder adder) => _adder = adder;
+ public void Handle(Notification notification)
+ {
+ notification.SubscriberCount = _adder.Add(notification.SubscriberCount, C);
+ }
+ }
+
+ public class NotificationAsyncHandlerA : INotificationAsyncHandler
+ {
+ public Task HandleAsync(Notification notification, CancellationToken cancellationToken)
+ {
+ notification.SubscriberCount += A;
+ return Task.CompletedTask;
+ }
+ }
+
+ public class NotificationAsyncHandlerB : INotificationAsyncHandler
+ {
+ public Task HandleAsync(Notification notification, CancellationToken cancellationToken)
+ {
+ notification.SubscriberCount += B;
+ return Task.CompletedTask;
+ }
+ }
+
+ public class NotificationAsyncHandlerC : INotificationAsyncHandler
+ {
+ private readonly Adder _adder;
+
+ public NotificationAsyncHandlerC(Adder adder) => _adder = adder;
+
public Task HandleAsync(Notification notification, CancellationToken cancellationToken)
{
notification.SubscriberCount = _adder.Add(notification.SubscriberCount, C);
return Task.CompletedTask;
}
}
-
public class Adder
{
public int Add(int a, int b) => a + b;
diff --git a/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs b/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs
index fede88e14f..e939355c42 100644
--- a/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs
+++ b/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs
@@ -1,8 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
@@ -46,7 +44,7 @@ namespace Umbraco.Web.Common.Profiler
}
///
- public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
+ public void Handle(UmbracoApplicationStarting notification)
{
if (_profile)
{
@@ -57,8 +55,6 @@ namespace Umbraco.Web.Common.Profiler
// Stop the profiling of the booting process
_profiler.StopBoot();
}
-
- return Task.CompletedTask;
}
}
}