From a42495baf939b3eddea3624d265ad45b5b1c596b Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 21 Sep 2025 01:15:40 +0330 Subject: [PATCH] support non-generic notifications --- src/DispatchR/Constants.cs | 7 +++ src/DispatchR/IMediator.cs | 44 +++++++++++++++++-- .../NotificationTests.cs | 37 ++++++++++++++++ 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/DispatchR/Constants.cs diff --git a/src/DispatchR/Constants.cs b/src/DispatchR/Constants.cs new file mode 100644 index 0000000..f5134a6 --- /dev/null +++ b/src/DispatchR/Constants.cs @@ -0,0 +1,7 @@ +namespace DispatchR; + +internal class Constants +{ + private const string DiagnosticPrefix = "DR"; + internal const string DiagnosticPerformanceIssue = $"{DiagnosticPrefix}1000"; +} \ No newline at end of file diff --git a/src/DispatchR/IMediator.cs b/src/DispatchR/IMediator.cs index fd5df0c..c5f8a65 100644 --- a/src/DispatchR/IMediator.cs +++ b/src/DispatchR/IMediator.cs @@ -17,6 +17,21 @@ IAsyncEnumerable CreateStream(IStreamRequest(TNotification request, CancellationToken cancellationToken) where TNotification : INotification; + + /// + /// This method is not recommended for performance-critical scenarios. + /// Use it only if it is strictly necessary, as its performance is lower compared + /// to similar methods in terms of both memory usage and CPU consumption. + /// + /// + /// An object that implements INotification + /// + /// + /// + [Obsolete(message: "This method has performance issues. Use only if strictly necessary", + error: false, + DiagnosticId = Constants.DiagnosticPerformanceIssue)] + ValueTask Publish(object request, CancellationToken cancellationToken); } public sealed class Mediator(IServiceProvider serviceProvider) : IMediator @@ -35,17 +50,18 @@ public TResponse Send(IRequest request } } - public IAsyncEnumerable CreateStream(IStreamRequest request, + public IAsyncEnumerable CreateStream(IStreamRequest request, CancellationToken cancellationToken) where TRequest : class, IStreamRequest { return serviceProvider.GetRequiredService>() .Handle(Unsafe.As(request), cancellationToken); } - public async ValueTask Publish(TNotification request, CancellationToken cancellationToken) where TNotification : INotification + public async ValueTask Publish(TNotification request, CancellationToken cancellationToken) + where TNotification : INotification { var notificationsInDi = serviceProvider.GetRequiredService>>(); - + var notifications = Unsafe.As[]>(notificationsInDi); foreach (var notification in notifications) { @@ -56,4 +72,26 @@ public async ValueTask Publish(TNotification request, Cancellatio } } } + + public async ValueTask Publish(object request, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(request); + + var requestType = request.GetType(); + var handlerType = typeof(INotificationHandler<>).MakeGenericType(requestType); + + var notificationsInDi = serviceProvider.GetServices(handlerType); + + foreach (var handler in notificationsInDi) + { + var handleMethod = handlerType.GetMethod(nameof(INotificationHandler.Handle)); + ArgumentNullException.ThrowIfNull(handleMethod); + + var valueTask = (ValueTask?)handleMethod.Invoke(handler, [request, cancellationToken]); + ArgumentNullException.ThrowIfNull(valueTask); + + if (!valueTask.Value.IsCompletedSuccessfully) + await valueTask.Value; + } + } } \ No newline at end of file diff --git a/tests/DispatchR.IntegrationTest/NotificationTests.cs b/tests/DispatchR.IntegrationTest/NotificationTests.cs index 9fec49e..43cc932 100644 --- a/tests/DispatchR.IntegrationTest/NotificationTests.cs +++ b/tests/DispatchR.IntegrationTest/NotificationTests.cs @@ -44,4 +44,41 @@ public async Task Publish_CallsAllHandlers_WhenMultipleHandlersAreRegistered() spyPipelineTwoMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); spyPipelineThreeMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); } + + [Fact] + public async Task PublishObject_CallsAllHandlers_WhenMultipleHandlersAreRegistered() + { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = false; + cfg.RegisterNotifications = true; + }); + + var spyPipelineOneMock = new Mock>(); + var spyPipelineTwoMock = new Mock>(); + var spyPipelineThreeMock = new Mock>(); + + spyPipelineOneMock.Setup(p => p.Handle(It.IsAny(), It.IsAny())); + spyPipelineTwoMock.Setup(p => p.Handle(It.IsAny(), It.IsAny())); + spyPipelineThreeMock.Setup(p => p.Handle(It.IsAny(), It.IsAny())); + + services.AddScoped>(sp => spyPipelineOneMock.Object); + services.AddScoped>(sp => spyPipelineTwoMock.Object); + services.AddScoped>(sp => spyPipelineThreeMock.Object); + + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + object notificationObject = new MultiHandlersNotification(Guid.Empty); + await mediator.Publish(notificationObject, CancellationToken.None); + + // Assert + spyPipelineOneMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); + spyPipelineTwoMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); + spyPipelineThreeMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); + } } \ No newline at end of file