Skip to content

Commit 22b7d02

Browse files
authored
support non-generic notifications (#38)
1 parent f2b734e commit 22b7d02

3 files changed

Lines changed: 85 additions & 3 deletions

File tree

src/DispatchR/Constants.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace DispatchR;
2+
3+
internal class Constants
4+
{
5+
private const string DiagnosticPrefix = "DR";
6+
internal const string DiagnosticPerformanceIssue = $"{DiagnosticPrefix}1000";
7+
}

src/DispatchR/IMediator.cs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ IAsyncEnumerable<TResponse> CreateStream<TRequest, TResponse>(IStreamRequest<TRe
1717

1818
ValueTask Publish<TNotification>(TNotification request, CancellationToken cancellationToken)
1919
where TNotification : INotification;
20+
21+
/// <summary>
22+
/// This method is not recommended for performance-critical scenarios.
23+
/// Use it only if it is strictly necessary, as its performance is lower compared
24+
/// to similar methods in terms of both memory usage and CPU consumption.
25+
/// </summary>
26+
/// <param name="request">
27+
/// An object that implements INotification
28+
/// </param>
29+
/// <param name="cancellationToken"></param>
30+
/// <returns></returns>
31+
[Obsolete(message: "This method has performance issues. Use only if strictly necessary",
32+
error: false,
33+
DiagnosticId = Constants.DiagnosticPerformanceIssue)]
34+
ValueTask Publish(object request, CancellationToken cancellationToken);
2035
}
2136

2237
public sealed class Mediator(IServiceProvider serviceProvider) : IMediator
@@ -35,17 +50,18 @@ public TResponse Send<TRequest, TResponse>(IRequest<TRequest, TResponse> request
3550
}
3651
}
3752

38-
public IAsyncEnumerable<TResponse> CreateStream<TRequest, TResponse>(IStreamRequest<TRequest, TResponse> request,
53+
public IAsyncEnumerable<TResponse> CreateStream<TRequest, TResponse>(IStreamRequest<TRequest, TResponse> request,
3954
CancellationToken cancellationToken) where TRequest : class, IStreamRequest
4055
{
4156
return serviceProvider.GetRequiredService<IStreamRequestHandler<TRequest, TResponse>>()
4257
.Handle(Unsafe.As<TRequest>(request), cancellationToken);
4358
}
4459

45-
public async ValueTask Publish<TNotification>(TNotification request, CancellationToken cancellationToken) where TNotification : INotification
60+
public async ValueTask Publish<TNotification>(TNotification request, CancellationToken cancellationToken)
61+
where TNotification : INotification
4662
{
4763
var notificationsInDi = serviceProvider.GetRequiredService<IEnumerable<INotificationHandler<TNotification>>>();
48-
64+
4965
var notifications = Unsafe.As<INotificationHandler<TNotification>[]>(notificationsInDi);
5066
foreach (var notification in notifications)
5167
{
@@ -56,4 +72,26 @@ public async ValueTask Publish<TNotification>(TNotification request, Cancellatio
5672
}
5773
}
5874
}
75+
76+
public async ValueTask Publish(object request, CancellationToken cancellationToken)
77+
{
78+
ArgumentNullException.ThrowIfNull(request);
79+
80+
var requestType = request.GetType();
81+
var handlerType = typeof(INotificationHandler<>).MakeGenericType(requestType);
82+
83+
var notificationsInDi = serviceProvider.GetServices(handlerType);
84+
85+
foreach (var handler in notificationsInDi)
86+
{
87+
var handleMethod = handlerType.GetMethod(nameof(INotificationHandler<INotification>.Handle));
88+
ArgumentNullException.ThrowIfNull(handleMethod);
89+
90+
var valueTask = (ValueTask?)handleMethod.Invoke(handler, [request, cancellationToken]);
91+
ArgumentNullException.ThrowIfNull(valueTask);
92+
93+
if (!valueTask.Value.IsCompletedSuccessfully)
94+
await valueTask.Value;
95+
}
96+
}
5997
}

tests/DispatchR.IntegrationTest/NotificationTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,41 @@ public async Task Publish_CallsAllHandlers_WhenMultipleHandlersAreRegistered()
4444
spyPipelineTwoMock.Verify(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()), Times.Exactly(1));
4545
spyPipelineThreeMock.Verify(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()), Times.Exactly(1));
4646
}
47+
48+
[Fact]
49+
public async Task PublishObject_CallsAllHandlers_WhenMultipleHandlersAreRegistered()
50+
{
51+
// Arrange
52+
var services = new ServiceCollection();
53+
services.AddDispatchR(cfg =>
54+
{
55+
cfg.Assemblies.Add(typeof(Fixture).Assembly);
56+
cfg.RegisterPipelines = false;
57+
cfg.RegisterNotifications = true;
58+
});
59+
60+
var spyPipelineOneMock = new Mock<INotificationHandler<MultiHandlersNotification>>();
61+
var spyPipelineTwoMock = new Mock<INotificationHandler<MultiHandlersNotification>>();
62+
var spyPipelineThreeMock = new Mock<INotificationHandler<MultiHandlersNotification>>();
63+
64+
spyPipelineOneMock.Setup(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()));
65+
spyPipelineTwoMock.Setup(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()));
66+
spyPipelineThreeMock.Setup(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()));
67+
68+
services.AddScoped<INotificationHandler<MultiHandlersNotification>>(sp => spyPipelineOneMock.Object);
69+
services.AddScoped<INotificationHandler<MultiHandlersNotification>>(sp => spyPipelineTwoMock.Object);
70+
services.AddScoped<INotificationHandler<MultiHandlersNotification>>(sp => spyPipelineThreeMock.Object);
71+
72+
var serviceProvider = services.BuildServiceProvider();
73+
var mediator = serviceProvider.GetRequiredService<IMediator>();
74+
75+
// Act
76+
object notificationObject = new MultiHandlersNotification(Guid.Empty);
77+
await mediator.Publish(notificationObject, CancellationToken.None);
78+
79+
// Assert
80+
spyPipelineOneMock.Verify(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()), Times.Exactly(1));
81+
spyPipelineTwoMock.Verify(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()), Times.Exactly(1));
82+
spyPipelineThreeMock.Verify(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()), Times.Exactly(1));
83+
}
4784
}

0 commit comments

Comments
 (0)