Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/DispatchR/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace DispatchR;

internal class Constants
{
private const string DiagnosticPrefix = "DR";
internal const string DiagnosticPerformanceIssue = $"{DiagnosticPrefix}1000";
}
44 changes: 41 additions & 3 deletions src/DispatchR/IMediator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ IAsyncEnumerable<TResponse> CreateStream<TRequest, TResponse>(IStreamRequest<TRe

ValueTask Publish<TNotification>(TNotification request, CancellationToken cancellationToken)
where TNotification : INotification;

/// <summary>
/// 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.
/// </summary>
/// <param name="request">
/// An object that implements INotification
/// </param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[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
Expand All @@ -35,17 +50,18 @@ public TResponse Send<TRequest, TResponse>(IRequest<TRequest, TResponse> request
}
}

public IAsyncEnumerable<TResponse> CreateStream<TRequest, TResponse>(IStreamRequest<TRequest, TResponse> request,
public IAsyncEnumerable<TResponse> CreateStream<TRequest, TResponse>(IStreamRequest<TRequest, TResponse> request,
CancellationToken cancellationToken) where TRequest : class, IStreamRequest
{
return serviceProvider.GetRequiredService<IStreamRequestHandler<TRequest, TResponse>>()
.Handle(Unsafe.As<TRequest>(request), cancellationToken);
}

public async ValueTask Publish<TNotification>(TNotification request, CancellationToken cancellationToken) where TNotification : INotification
public async ValueTask Publish<TNotification>(TNotification request, CancellationToken cancellationToken)
where TNotification : INotification
{
var notificationsInDi = serviceProvider.GetRequiredService<IEnumerable<INotificationHandler<TNotification>>>();

var notifications = Unsafe.As<INotificationHandler<TNotification>[]>(notificationsInDi);
foreach (var notification in notifications)
{
Expand All @@ -56,4 +72,26 @@ public async ValueTask Publish<TNotification>(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<INotification>.Handle));
ArgumentNullException.ThrowIfNull(handleMethod);

var valueTask = (ValueTask?)handleMethod.Invoke(handler, [request, cancellationToken]);
ArgumentNullException.ThrowIfNull(valueTask);

if (!valueTask.Value.IsCompletedSuccessfully)
await valueTask.Value;
}
}
}
37 changes: 37 additions & 0 deletions tests/DispatchR.IntegrationTest/NotificationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,41 @@
spyPipelineTwoMock.Verify(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()), Times.Exactly(1));
spyPipelineThreeMock.Verify(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()), 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<INotificationHandler<MultiHandlersNotification>>();
var spyPipelineTwoMock = new Mock<INotificationHandler<MultiHandlersNotification>>();
var spyPipelineThreeMock = new Mock<INotificationHandler<MultiHandlersNotification>>();

spyPipelineOneMock.Setup(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()));
spyPipelineTwoMock.Setup(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()));
spyPipelineThreeMock.Setup(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()));

services.AddScoped<INotificationHandler<MultiHandlersNotification>>(sp => spyPipelineOneMock.Object);
services.AddScoped<INotificationHandler<MultiHandlersNotification>>(sp => spyPipelineTwoMock.Object);
services.AddScoped<INotificationHandler<MultiHandlersNotification>>(sp => spyPipelineThreeMock.Object);

var serviceProvider = services.BuildServiceProvider();
var mediator = serviceProvider.GetRequiredService<IMediator>();

// Act
object notificationObject = new MultiHandlersNotification(Guid.Empty);
await mediator.Publish(notificationObject, CancellationToken.None);

Check warning on line 77 in tests/DispatchR.IntegrationTest/NotificationTests.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

'IMediator.Publish(object, CancellationToken)' is obsolete: 'This method has performance issues. Use only if strictly necessary'

// Assert
spyPipelineOneMock.Verify(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()), Times.Exactly(1));
spyPipelineTwoMock.Verify(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()), Times.Exactly(1));
spyPipelineThreeMock.Verify(p => p.Handle(It.IsAny<MultiHandlersNotification>(), It.IsAny<CancellationToken>()), Times.Exactly(1));
}
}
Loading