Added support for sync notification handlers in the event aggregator
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a notification async handler against the Umbraco service collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotification">The type of notification.</typeparam>
|
||||
/// <typeparam name="TNotificationAsyncHandler">The type of notification async handler.</typeparam>
|
||||
/// <param name="builder">The Umbraco builder.</param>
|
||||
/// <returns>The <see cref="IUmbracoBuilder"/>.</returns>
|
||||
public static IUmbracoBuilder AddNotificationAsyncHandler<TNotification, TNotificationAsyncHandler>(this IUmbracoBuilder builder)
|
||||
where TNotificationAsyncHandler : INotificationAsyncHandler<TNotification>
|
||||
where TNotification : INotification
|
||||
{
|
||||
// Register the handler as transient. This ensures that anything can be injected into it.
|
||||
var descriptor = new ServiceDescriptor(typeof(INotificationAsyncHandler<TNotification>), 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace Umbraco.Core.DependencyInjection
|
||||
|
||||
Services.AddSingleton<ManifestWatcher>();
|
||||
Services.AddSingleton<UmbracoRequestPaths>();
|
||||
this.AddNotificationHandler<UmbracoApplicationStarting, AppPluginsManifestWatcherNotificationHandler>();
|
||||
this.AddNotificationAsyncHandler<UmbracoApplicationStarting, AppPluginsManifestWatcherNotificationHandler>();
|
||||
|
||||
Services.AddUnique<InstallStatusTracker>();
|
||||
|
||||
|
||||
@@ -15,17 +15,30 @@ namespace Umbraco.Core.Events
|
||||
/// </content>
|
||||
public partial class EventAggregator : IEventAggregator
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, NotificationAsyncHandlerWrapper> s_notificationAsyncHandlers
|
||||
= new ConcurrentDictionary<Type, NotificationAsyncHandlerWrapper>();
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, NotificationHandlerWrapper> s_notificationHandlers
|
||||
= new ConcurrentDictionary<Type, NotificationHandlerWrapper>();
|
||||
|
||||
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<Action<INotification>> allHandlers,
|
||||
INotification notification)
|
||||
{
|
||||
foreach (Action<INotification> handler in allHandlers)
|
||||
{
|
||||
handler(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class NotificationHandlerWrapper
|
||||
{
|
||||
public abstract void Handle(
|
||||
INotification notification,
|
||||
ServiceFactory serviceFactory,
|
||||
Action<IEnumerable<Action<INotification>>, INotification> publish);
|
||||
}
|
||||
|
||||
internal abstract class NotificationAsyncHandlerWrapper
|
||||
{
|
||||
public abstract Task HandleAsync(
|
||||
INotification notification,
|
||||
@@ -49,7 +80,7 @@ namespace Umbraco.Core.Events
|
||||
Func<IEnumerable<Func<INotification, CancellationToken, Task>>, INotification, CancellationToken, Task> publish);
|
||||
}
|
||||
|
||||
internal class NotificationHandlerWrapperImpl<TNotification> : NotificationHandlerWrapper
|
||||
internal class NotificationAsyncHandlerWrapperImpl<TNotification> : NotificationAsyncHandlerWrapper
|
||||
where TNotification : INotification
|
||||
{
|
||||
public override Task HandleAsync(
|
||||
@@ -59,7 +90,7 @@ namespace Umbraco.Core.Events
|
||||
Func<IEnumerable<Func<INotification, CancellationToken, Task>>, INotification, CancellationToken, Task> publish)
|
||||
{
|
||||
IEnumerable<Func<INotification, CancellationToken, Task>> handlers = serviceFactory
|
||||
.GetInstances<INotificationHandler<TNotification>>()
|
||||
.GetInstances<INotificationAsyncHandler<TNotification>>()
|
||||
.Select(x => new Func<INotification, CancellationToken, Task>(
|
||||
(theNotification, theToken) =>
|
||||
x.HandleAsync((TNotification)theNotification, theToken)));
|
||||
@@ -67,4 +98,22 @@ namespace Umbraco.Core.Events
|
||||
return publish(handlers, notification, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class NotificationHandlerWrapperImpl<TNotification> : NotificationHandlerWrapper
|
||||
where TNotification : INotification
|
||||
{
|
||||
public override void Handle(
|
||||
INotification notification,
|
||||
ServiceFactory serviceFactory,
|
||||
Action<IEnumerable<Action<INotification>>, INotification> publish)
|
||||
{
|
||||
IEnumerable<Action<INotification>> handlers = serviceFactory
|
||||
.GetInstances<INotificationHandler<TNotification>>()
|
||||
.Select(x => new Action<INotification>(
|
||||
(theNotification) =>
|
||||
x.Handle((TNotification)theNotification)));
|
||||
|
||||
publish(handlers, notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace Umbraco.Core.Events
|
||||
throw new ArgumentNullException(nameof(notification));
|
||||
}
|
||||
|
||||
PublishNotification(notification);
|
||||
return PublishNotificationAsync(notification, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Events
|
||||
public interface IEventAggregator
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronously send a notification to multiple handlers
|
||||
/// Asynchronously send a notification to multiple handlers of both sync and async
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotification">The type of notification being handled.</typeparam>
|
||||
/// <param name="notification">The notification object.</param>
|
||||
|
||||
@@ -12,6 +12,20 @@ namespace Umbraco.Core.Events
|
||||
/// <typeparam name="TNotification">The type of notification being handled.</typeparam>
|
||||
public interface INotificationHandler<in TNotification>
|
||||
where TNotification : INotification
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles a notification
|
||||
/// </summary>
|
||||
/// <param name="notification">The notification</param>
|
||||
void Handle(TNotification notification);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a handler for a async notification.
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotification">The type of notification being handled.</typeparam>
|
||||
public interface INotificationAsyncHandler<in TNotification>
|
||||
where TNotification : INotification
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles a notification
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Runtime
|
||||
/// <summary>
|
||||
/// Starts monitoring AppPlugins directory during debug runs, to restart site when a plugin manifest changes.
|
||||
/// </summary>
|
||||
public sealed class AppPluginsManifestWatcherNotificationHandler : INotificationHandler<UmbracoApplicationStarting>
|
||||
public sealed class AppPluginsManifestWatcherNotificationHandler : INotificationAsyncHandler<UmbracoApplicationStarting>
|
||||
{
|
||||
private readonly ManifestWatcher _manifestWatcher;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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()
|
||||
|
||||
@@ -28,6 +28,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Events
|
||||
_builder = new UmbracoBuilder(register, Mock.Of<IConfiguration>(), TestHelper.GetMockedTypeLoader());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CanPublishAsyncEvents()
|
||||
{
|
||||
_builder.Services.AddScoped<Adder>();
|
||||
_builder.AddNotificationAsyncHandler<Notification, NotificationAsyncHandlerA>();
|
||||
_builder.AddNotificationAsyncHandler<Notification, NotificationAsyncHandlerB>();
|
||||
_builder.AddNotificationAsyncHandler<Notification, NotificationAsyncHandlerC>();
|
||||
ServiceProvider provider = _builder.Services.BuildServiceProvider();
|
||||
|
||||
var notification = new Notification();
|
||||
IEventAggregator aggregator = provider.GetService<IEventAggregator>();
|
||||
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<Notification>
|
||||
{
|
||||
public Task HandleAsync(Notification notification, CancellationToken cancellationToken)
|
||||
public void Handle(Notification notification)
|
||||
{
|
||||
notification.SubscriberCount += A;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class NotificationHandlerB : INotificationHandler<Notification>
|
||||
{
|
||||
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<Notification>
|
||||
{
|
||||
public Task HandleAsync(Notification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
notification.SubscriberCount += A;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class NotificationAsyncHandlerB : INotificationAsyncHandler<Notification>
|
||||
{
|
||||
public Task HandleAsync(Notification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
notification.SubscriberCount += B;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class NotificationAsyncHandlerC : INotificationAsyncHandler<Notification>
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user