From 838a8e12b62ee95f2f1caa503d282a8d9bce6047 Mon Sep 17 00:00:00 2001 From: Jimmy Bogard Date: Mon, 13 Feb 2023 14:41:25 -0600 Subject: [PATCH 1/3] Adding notification publisher strategies --- .../CustomMediator.cs | 8 +- .../Publisher.cs | 24 ++-- src/MediatR/INotificationPublisher.cs | 11 ++ src/MediatR/MediatR.csproj | 6 +- src/MediatR/Mediator.cs | 29 +++-- .../MediatrServiceConfiguration.cs | 69 ++++++++++ src/MediatR/NotificationHandlerExecutor.cs | 7 + .../ForeachAwaitPublisher.cs | 24 ++++ .../TaskWhenAllPublisher.cs | 28 ++++ src/MediatR/Registration/ServiceRegistrar.cs | 5 + .../Wrappers/NotificationHandlerWrapper.cs | 9 +- .../NotificationPublisherTests.cs | 120 ++++++++++++++++++ .../NotificationPublisherTests.cs | 6 + test/MediatR.Tests/PublishTests.cs | 49 ++++++- 14 files changed, 361 insertions(+), 34 deletions(-) create mode 100644 src/MediatR/INotificationPublisher.cs create mode 100644 src/MediatR/NotificationHandlerExecutor.cs create mode 100644 src/MediatR/NotificationPublishers/ForeachAwaitPublisher.cs create mode 100644 src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs create mode 100644 test/MediatR.Tests/MicrosoftExtensionsDI/NotificationPublisherTests.cs create mode 100644 test/MediatR.Tests/NotificationPublisherTests.cs diff --git a/samples/MediatR.Examples.PublishStrategies/CustomMediator.cs b/samples/MediatR.Examples.PublishStrategies/CustomMediator.cs index 262ae749..062dea19 100644 --- a/samples/MediatR.Examples.PublishStrategies/CustomMediator.cs +++ b/samples/MediatR.Examples.PublishStrategies/CustomMediator.cs @@ -7,11 +7,11 @@ namespace MediatR.Examples.PublishStrategies; public class CustomMediator : Mediator { - private readonly Func>, INotification, CancellationToken, Task> _publish; + private readonly Func, INotification, CancellationToken, Task> _publish; - public CustomMediator(IServiceProvider serviceFactory, Func>, INotification, CancellationToken, Task> publish) : base(serviceFactory) + public CustomMediator(IServiceProvider serviceFactory, Func, INotification, CancellationToken, Task> publish) : base(serviceFactory) => _publish = publish; - protected override Task PublishCore(IEnumerable> allHandlers, INotification notification, CancellationToken cancellationToken) - => _publish(allHandlers, notification, cancellationToken); + protected override Task PublishCore(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) + => _publish(handlerExecutors, notification, cancellationToken); } \ No newline at end of file diff --git a/samples/MediatR.Examples.PublishStrategies/Publisher.cs b/samples/MediatR.Examples.PublishStrategies/Publisher.cs index d05ddb3d..9df554c4 100644 --- a/samples/MediatR.Examples.PublishStrategies/Publisher.cs +++ b/samples/MediatR.Examples.PublishStrategies/Publisher.cs @@ -50,41 +50,41 @@ public Task Publish(TNotification notification, PublishStrategy s return mediator.Publish(notification, cancellationToken); } - private Task ParallelWhenAll(IEnumerable> handlers, INotification notification, CancellationToken cancellationToken) + private Task ParallelWhenAll(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { var tasks = new List(); foreach (var handler in handlers) { - tasks.Add(Task.Run(() => handler(notification, cancellationToken))); + tasks.Add(Task.Run(() => handler.HandlerCallback(notification, cancellationToken))); } return Task.WhenAll(tasks); } - private Task ParallelWhenAny(IEnumerable> handlers, INotification notification, CancellationToken cancellationToken) + private Task ParallelWhenAny(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { var tasks = new List(); foreach (var handler in handlers) { - tasks.Add(Task.Run(() => handler(notification, cancellationToken))); + tasks.Add(Task.Run(() => handler.HandlerCallback(notification, cancellationToken))); } return Task.WhenAny(tasks); } - private Task ParallelNoWait(IEnumerable> handlers, INotification notification, CancellationToken cancellationToken) + private Task ParallelNoWait(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { foreach (var handler in handlers) { - Task.Run(() => handler(notification, cancellationToken)); + Task.Run(() => handler.HandlerCallback(notification, cancellationToken)); } return Task.CompletedTask; } - private async Task AsyncContinueOnException(IEnumerable> handlers, INotification notification, CancellationToken cancellationToken) + private async Task AsyncContinueOnException(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { var tasks = new List(); var exceptions = new List(); @@ -93,7 +93,7 @@ private async Task AsyncContinueOnException(IEnumerable> handlers, INotification notification, CancellationToken cancellationToken) + private async Task SyncStopOnException(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { foreach (var handler in handlers) { - await handler(notification, cancellationToken).ConfigureAwait(false); + await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false); } } - private async Task SyncContinueOnException(IEnumerable> handlers, INotification notification, CancellationToken cancellationToken) + private async Task SyncContinueOnException(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { var exceptions = new List(); @@ -136,7 +136,7 @@ private async Task SyncContinueOnException(IEnumerable handlerExecutors, INotification notification, + CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/MediatR/MediatR.csproj b/src/MediatR/MediatR.csproj index 90447ebb..bb8ef6b1 100644 --- a/src/MediatR/MediatR.csproj +++ b/src/MediatR/MediatR.csproj @@ -27,11 +27,15 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + diff --git a/src/MediatR/Mediator.cs b/src/MediatR/Mediator.cs index 6745842f..58b3f808 100644 --- a/src/MediatR/Mediator.cs +++ b/src/MediatR/Mediator.cs @@ -1,3 +1,5 @@ +using MediatR.NotificationPublishers; + namespace MediatR; using System; @@ -14,6 +16,7 @@ namespace MediatR; public class Mediator : IMediator { private readonly IServiceProvider _serviceProvider; + private readonly INotificationPublisher _publisher; private static readonly ConcurrentDictionary _requestHandlers = new(); private static readonly ConcurrentDictionary _notificationHandlers = new(); private static readonly ConcurrentDictionary _streamRequestHandlers = new(); @@ -23,7 +26,18 @@ public class Mediator : IMediator /// /// Service provider. Can be a scoped or root provider public Mediator(IServiceProvider serviceProvider) - => _serviceProvider = serviceProvider; + : this(serviceProvider, new ForeachAwaitPublisher()) { } + + /// + /// Initializes a new instance of the class. + /// + /// Service provider. Can be a scoped or root provider + /// Notification publisher. Defaults to . + public Mediator(IServiceProvider serviceProvider, INotificationPublisher publisher) + { + _serviceProvider = serviceProvider; + _publisher = publisher; + } public Task Send(IRequest request, CancellationToken cancellationToken = default) { @@ -124,19 +138,14 @@ notification switch }; /// - /// Override in a derived class to control how the tasks are awaited. By default the implementation is a foreach and await of each handler + /// Override in a derived class to control how the tasks are awaited. By default the implementation calls the . /// - /// Enumerable of tasks representing invoking each notification handler + /// Enumerable of tasks representing invoking each notification handler /// The notification being published /// The cancellation token /// A task representing invoking all handlers - protected virtual async Task PublishCore(IEnumerable> allHandlers, INotification notification, CancellationToken cancellationToken) - { - foreach (var handler in allHandlers) - { - await handler(notification, cancellationToken).ConfigureAwait(false); - } - } + protected virtual Task PublishCore(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) + => _publisher.Publish(handlerExecutors, notification, cancellationToken); private Task PublishNotification(INotification notification, CancellationToken cancellationToken = default) { diff --git a/src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs b/src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs index 685cf8d3..8e43aa6e 100644 --- a/src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs +++ b/src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs @@ -2,27 +2,71 @@ using System.Collections.Generic; using System.Reflection; using MediatR; +using MediatR.NotificationPublishers; namespace Microsoft.Extensions.DependencyInjection; public class MediatRServiceConfiguration { + /// + /// Optional filter for types to register. Default value is a function returning true. + /// public Func TypeEvaluator { get; set; } = t => true; + + /// + /// Mediator implementation type to register. Default is + /// public Type MediatorImplementationType { get; set; } = typeof(Mediator); + + /// + /// Strategy for publishing notifications. Defaults to + /// + public INotificationPublisher NotificationPublisher { get; set; } = new ForeachAwaitPublisher(); + + /// + /// Type of notification publisher strategy to register. If set, overrides + /// + public Type? NotificationPublisherType { get; set; } + + /// + /// Service lifetime to register services under. Default value is + /// public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient; + + /// + /// Request exception action processor strategy. Default value is + /// public RequestExceptionActionProcessorStrategy RequestExceptionActionProcessorStrategy { get; set; } = RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions; internal List AssembliesToRegister { get; } = new(); + /// + /// List of behaviors to register in specific order + /// public List BehaviorsToRegister { get; } = new(); + /// + /// Register various handlers from assembly containing given type + /// + /// Type from assembly to scan + /// This public MediatRServiceConfiguration RegisterServicesFromAssemblyContaining() => RegisterServicesFromAssemblyContaining(typeof(T)); + /// + /// Register various handlers from assembly containing given type + /// + /// Type from assembly to scan + /// This public MediatRServiceConfiguration RegisterServicesFromAssemblyContaining(Type type) => RegisterServicesFromAssembly(type.Assembly); + /// + /// Register various handlers from assembly + /// + /// Assembly to scan + /// This public MediatRServiceConfiguration RegisterServicesFromAssembly(Assembly assembly) { AssembliesToRegister.Add(assembly); @@ -30,6 +74,11 @@ public MediatRServiceConfiguration RegisterServicesFromAssembly(Assembly assembl return this; } + /// + /// Register various handlers from assemblies + /// + /// Assemblies to scan + /// This public MediatRServiceConfiguration RegisterServicesFromAssemblies( params Assembly[] assemblies) { @@ -38,10 +87,24 @@ public MediatRServiceConfiguration RegisterServicesFromAssembly(Assembly assembl return this; } + /// + /// Register a closed behavior type + /// + /// Closed behavior interface type + /// Closed behavior implementation type + /// Optional service lifetime, defaults to . + /// This public MediatRServiceConfiguration AddBehavior( ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddBehavior(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); + /// + /// Register a closed behavior type + /// + /// Closed behavior interface type + /// Closed behavior implementation type + /// Optional service lifetime, defaults to . + /// This public MediatRServiceConfiguration AddBehavior( Type serviceType, Type implementationType, @@ -52,6 +115,12 @@ public MediatRServiceConfiguration RegisterServicesFromAssembly(Assembly assembl return this; } + /// + /// Registers an open behavior type against the open generic interface type + /// + /// An open generic behavior type + /// Optional service lifetime, defaults to . + /// This public MediatRServiceConfiguration AddOpenBehavior(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { var serviceType = typeof(IPipelineBehavior<,>); diff --git a/src/MediatR/NotificationHandlerExecutor.cs b/src/MediatR/NotificationHandlerExecutor.cs new file mode 100644 index 00000000..463c13ba --- /dev/null +++ b/src/MediatR/NotificationHandlerExecutor.cs @@ -0,0 +1,7 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediatR; + +public record NotificationHandlerExecutor(object HandlerInstance, Func HandlerCallback); \ No newline at end of file diff --git a/src/MediatR/NotificationPublishers/ForeachAwaitPublisher.cs b/src/MediatR/NotificationPublishers/ForeachAwaitPublisher.cs new file mode 100644 index 00000000..7403db49 --- /dev/null +++ b/src/MediatR/NotificationPublishers/ForeachAwaitPublisher.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediatR.NotificationPublishers; + +/// +/// Awaits each notification handler in a single foreach loop: +/// +/// foreach (var handler in handlers) { +/// await handler(notification, cancellationToken); +/// } +/// +/// +public class ForeachAwaitPublisher : INotificationPublisher +{ + public async Task Publish(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) + { + foreach (var handler in handlerExecutors) + { + await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs b/src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs new file mode 100644 index 00000000..262b26fa --- /dev/null +++ b/src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediatR.NotificationPublishers; + +/// +/// Uses Task.WhenAll with the list of Handler tasks: +/// +/// var tasks = handlers +/// .Select(handler => handler.Handle(notification, cancellationToken)) +/// .ToList(); +/// +/// return Task.WhenAll(tasks); +/// +/// +public class TaskWhenAllPublisher : INotificationPublisher +{ + public Task Publish(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) + { + var tasks = handlerExecutors + .Select(handler => handler.HandlerCallback(notification, cancellationToken)) + .ToList(); + + return Task.WhenAll(tasks); + } +} \ No newline at end of file diff --git a/src/MediatR/Registration/ServiceRegistrar.cs b/src/MediatR/Registration/ServiceRegistrar.cs index e56f7ee0..de3d920b 100644 --- a/src/MediatR/Registration/ServiceRegistrar.cs +++ b/src/MediatR/Registration/ServiceRegistrar.cs @@ -218,6 +218,11 @@ public static void AddRequiredServices(IServiceCollection services, MediatRServi services.TryAdd(new ServiceDescriptor(typeof(ISender), sp => sp.GetRequiredService(), serviceConfiguration.Lifetime)); services.TryAdd(new ServiceDescriptor(typeof(IPublisher), sp => sp.GetRequiredService(), serviceConfiguration.Lifetime)); + services.TryAdd(serviceConfiguration.NotificationPublisherType != null + ? new ServiceDescriptor(typeof(INotificationPublisher), serviceConfiguration.NotificationPublisherType, + serviceConfiguration.Lifetime) + : new ServiceDescriptor(typeof(INotificationPublisher), serviceConfiguration.NotificationPublisher)); + foreach (var serviceDescriptor in serviceConfiguration.BehaviorsToRegister) { services.Add(serviceDescriptor); diff --git a/src/MediatR/Wrappers/NotificationHandlerWrapper.cs b/src/MediatR/Wrappers/NotificationHandlerWrapper.cs index b778cf88..2d47d083 100644 --- a/src/MediatR/Wrappers/NotificationHandlerWrapper.cs +++ b/src/MediatR/Wrappers/NotificationHandlerWrapper.cs @@ -1,5 +1,3 @@ -using Microsoft.Extensions.DependencyInjection; - namespace MediatR.Wrappers; using System; @@ -7,11 +5,12 @@ namespace MediatR.Wrappers; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; public abstract class NotificationHandlerWrapper { public abstract Task Handle(INotification notification, IServiceProvider serviceFactory, - Func>, INotification, CancellationToken, Task> publish, + Func, INotification, CancellationToken, Task> publish, CancellationToken cancellationToken); } @@ -19,12 +18,12 @@ public class NotificationHandlerWrapperImpl : NotificationHandler where TNotification : INotification { public override Task Handle(INotification notification, IServiceProvider serviceFactory, - Func>, INotification, CancellationToken, Task> publish, + Func, INotification, CancellationToken, Task> publish, CancellationToken cancellationToken) { var handlers = serviceFactory .GetServices>() - .Select(static x => new Func((theNotification, theToken) => x.Handle((TNotification)theNotification, theToken))); + .Select(static x => new NotificationHandlerExecutor(x, (theNotification, theToken) => x.Handle((TNotification)theNotification, theToken))); return publish(handlers, notification, cancellationToken); } diff --git a/test/MediatR.Tests/MicrosoftExtensionsDI/NotificationPublisherTests.cs b/test/MediatR.Tests/MicrosoftExtensionsDI/NotificationPublisherTests.cs new file mode 100644 index 00000000..01fb3f3a --- /dev/null +++ b/test/MediatR.Tests/MicrosoftExtensionsDI/NotificationPublisherTests.cs @@ -0,0 +1,120 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR.NotificationPublishers; +using Shouldly; +using Xunit; + +namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; + +public class NotificationPublisherTests +{ + public class MockPublisher : INotificationPublisher + { + public int CallCount { get; set; } + + public async Task Publish(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) + { + foreach (var handlerExecutor in handlerExecutors) + { + await handlerExecutor.HandlerCallback(notification, cancellationToken); + CallCount++; + } + } + } + + [Fact] + public void ShouldResolveDefaultPublisher() + { + var services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(cfg => + { + cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests)); + }); + + var provider = services.BuildServiceProvider(); + var mediator = provider.GetService(); + + mediator.ShouldNotBeNull(); + + var publisher = provider.GetService(); + + publisher.ShouldNotBeNull(); + } + + [Fact] + public async Task ShouldSubstitutePublisherInstance() + { + var publisher = new MockPublisher(); + var services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(cfg => + { + cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests)); + cfg.NotificationPublisher = publisher; + }); + + var provider = services.BuildServiceProvider(); + var mediator = provider.GetService(); + + mediator.ShouldNotBeNull(); + + await mediator.Publish(new Pinged()); + + publisher.CallCount.ShouldBeGreaterThan(0); + } + + [Fact] + public async Task ShouldSubstitutePublisherServiceType() + { + var services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(cfg => + { + cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests)); + cfg.NotificationPublisherType = typeof(MockPublisher); + cfg.Lifetime = ServiceLifetime.Singleton; + }); + + var provider = services.BuildServiceProvider(); + var mediator = provider.GetService(); + var publisher = provider.GetService(); + + mediator.ShouldNotBeNull(); + publisher.ShouldNotBeNull(); + + await mediator.Publish(new Pinged()); + + var mock = publisher.ShouldBeOfType(); + + mock.CallCount.ShouldBeGreaterThan(0); + } + + [Fact] + public async Task ShouldSubstitutePublisherServiceTypeWithWhenAll() + { + var services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(cfg => + { + cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests)); + cfg.NotificationPublisherType = typeof(TaskWhenAllPublisher); + cfg.Lifetime = ServiceLifetime.Singleton; + }); + + var provider = services.BuildServiceProvider(); + var mediator = provider.GetService(); + var publisher = provider.GetService(); + + mediator.ShouldNotBeNull(); + publisher.ShouldNotBeNull(); + + await Should.NotThrowAsync(mediator.Publish(new Pinged())); + + publisher.ShouldBeOfType(); + } +} \ No newline at end of file diff --git a/test/MediatR.Tests/NotificationPublisherTests.cs b/test/MediatR.Tests/NotificationPublisherTests.cs new file mode 100644 index 00000000..052d5230 --- /dev/null +++ b/test/MediatR.Tests/NotificationPublisherTests.cs @@ -0,0 +1,6 @@ +namespace MediatR.Tests; + +public class NotificationPublisherTests +{ + +} \ No newline at end of file diff --git a/test/MediatR.Tests/PublishTests.cs b/test/MediatR.Tests/PublishTests.cs index d23ce788..ce33dc50 100644 --- a/test/MediatR.Tests/PublishTests.cs +++ b/test/MediatR.Tests/PublishTests.cs @@ -112,11 +112,25 @@ public SequentialMediator(IServiceProvider serviceProvider) { } - protected override async Task PublishCore(IEnumerable> allHandlers, INotification notification, CancellationToken cancellationToken) + protected override async Task PublishCore(IEnumerable allHandlers, INotification notification, CancellationToken cancellationToken) { foreach (var handler in allHandlers) { - await handler(notification, cancellationToken).ConfigureAwait(false); + await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false); + } + } + } + + public class SequentialPublisher : INotificationPublisher + { + public int CallCount { get; set; } + + public async Task Publish(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) + { + foreach (var handler in handlerExecutors) + { + await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false); + CallCount++; } } } @@ -149,6 +163,37 @@ public async Task Should_override_with_sequential_firing() result.ShouldContain("Ping Pung"); } + [Fact] + public async Task Should_override_with_sequential_firing_through_injection() + { + var builder = new StringBuilder(); + var writer = new StringWriter(builder); + var publisher = new SequentialPublisher(); + + var container = new Container(cfg => + { + cfg.Scan(scanner => + { + scanner.AssemblyContainingType(typeof(PublishTests)); + scanner.IncludeNamespaceContainingType(); + scanner.WithDefaultConventions(); + scanner.AddAllTypesOf(typeof(INotificationHandler<>)); + }); + cfg.For().Use(writer); + cfg.For().Use(publisher); + cfg.For().Use(); + }); + + var mediator = container.GetInstance(); + + await mediator.Publish(new Ping { Message = "Ping" }); + + var result = builder.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); + result.ShouldContain("Ping Pong"); + result.ShouldContain("Ping Pung"); + publisher.CallCount.ShouldBe(2); + } + [Fact] public async Task Should_resolve_handlers_given_interface() { From 40afa9fc6ec7ddcfc8fac2584861916fb571f817 Mon Sep 17 00:00:00 2001 From: Jimmy Bogard Date: Mon, 13 Feb 2023 15:08:36 -0600 Subject: [PATCH 2/3] Switching to ToArray from ToList --- src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs b/src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs index 262b26fa..4b948eb1 100644 --- a/src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs +++ b/src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs @@ -21,7 +21,7 @@ public Task Publish(IEnumerable handlerExecutors, I { var tasks = handlerExecutors .Select(handler => handler.HandlerCallback(notification, cancellationToken)) - .ToList(); + .ToArray(); return Task.WhenAll(tasks); } From 9da86cda1a44c76a7e54f4f4530b1d8d6b313708 Mon Sep 17 00:00:00 2001 From: Jimmy Bogard Date: Tue, 14 Feb 2023 09:14:57 -0600 Subject: [PATCH 3/3] Adding tests for publish strategies --- .../NotificationPublisherTests.cs | 80 ++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/test/MediatR.Tests/NotificationPublisherTests.cs b/test/MediatR.Tests/NotificationPublisherTests.cs index 052d5230..40999052 100644 --- a/test/MediatR.Tests/NotificationPublisherTests.cs +++ b/test/MediatR.Tests/NotificationPublisherTests.cs @@ -1,6 +1,82 @@ -namespace MediatR.Tests; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using MediatR.NotificationPublishers; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +namespace MediatR.Tests; public class NotificationPublisherTests { - + private readonly ITestOutputHelper _output; + + public NotificationPublisherTests(ITestOutputHelper output) => _output = output; + + public class Notification : INotification + { + } + + public class FirstHandler : INotificationHandler + { + public async Task Handle(Notification notification, CancellationToken cancellationToken) + => await Task.Delay(500, cancellationToken); + } + public class SecondHandler : INotificationHandler + { + public async Task Handle(Notification notification, CancellationToken cancellationToken) + => await Task.Delay(250, cancellationToken); + } + + [Fact] + public async Task Should_handle_sequentially_by_default() + { + var services = new ServiceCollection(); + services.AddMediatR(cfg => + { + cfg.RegisterServicesFromAssemblyContaining(); + }); + var serviceProvider = services.BuildServiceProvider(); + + var mediator = serviceProvider.GetRequiredService(); + + var timer = new Stopwatch(); + timer.Start(); + + await mediator.Publish(new Notification()); + + timer.Stop(); + + timer.ElapsedMilliseconds.ShouldBeGreaterThan(750); + + _output.WriteLine(timer.ElapsedMilliseconds.ToString()); + } + + + [Fact] + public async Task Should_handle_in_parallel_with_when_all() + { + var services = new ServiceCollection(); + services.AddMediatR(cfg => + { + cfg.RegisterServicesFromAssemblyContaining(); + cfg.NotificationPublisherType = typeof(TaskWhenAllPublisher); + }); + var serviceProvider = services.BuildServiceProvider(); + + var mediator = serviceProvider.GetRequiredService(); + + var timer = new Stopwatch(); + timer.Start(); + + await mediator.Publish(new Notification()); + + timer.Stop(); + + timer.ElapsedMilliseconds.ShouldBeLessThan(750); + + _output.WriteLine(timer.ElapsedMilliseconds.ToString()); + } } \ No newline at end of file