From d16dc618a6b29349fabdb1737fa8341ae19f3e5d Mon Sep 17 00:00:00 2001 From: mkyrylo Date: Tue, 24 Mar 2026 17:32:55 +0200 Subject: [PATCH] add full support --- .../IMediator.cs | 10 ++-- .../INotificationHandler.cs | 5 +- .../IPipelineBehavior.cs | 16 ++--- .../IRequest.cs | 5 +- .../IRequestHandler.cs | 14 ++--- .../RequestHandlerDelegate.cs | 5 ++ .../DependencyInjection.cs | 31 ++++++---- src/Bss.Platform.Mediation/Mediator.cs | 60 +++++++------------ .../Wrappers/NotificationHandlerWrapper.cs | 6 ++ .../NotificationHandlerWrapperImpl.cs | 19 ++++++ .../Wrappers/RequestHandlerWrapper.cs | 6 ++ .../Wrappers/RequestHandlerWrapperImpl.cs | 25 ++++++++ .../Wrappers/VoidRequestHandlerWrapper.cs | 6 ++ .../Wrappers/VoidRequestHandlerWrapperImpl.cs | 25 ++++++++ .../Platform/Mediation/NotificationTests.cs | 2 +- .../Platform/Mediation/RequestTests.cs | 12 ++-- src/__SolutionItems/CommonAssemblyInfo.cs | 6 +- 17 files changed, 163 insertions(+), 90 deletions(-) create mode 100644 src/Bss.Platform.Mediation.Abstractions/RequestHandlerDelegate.cs create mode 100644 src/Bss.Platform.Mediation/Wrappers/NotificationHandlerWrapper.cs create mode 100644 src/Bss.Platform.Mediation/Wrappers/NotificationHandlerWrapperImpl.cs create mode 100644 src/Bss.Platform.Mediation/Wrappers/RequestHandlerWrapper.cs create mode 100644 src/Bss.Platform.Mediation/Wrappers/RequestHandlerWrapperImpl.cs create mode 100644 src/Bss.Platform.Mediation/Wrappers/VoidRequestHandlerWrapper.cs create mode 100644 src/Bss.Platform.Mediation/Wrappers/VoidRequestHandlerWrapperImpl.cs diff --git a/src/Bss.Platform.Mediation.Abstractions/IMediator.cs b/src/Bss.Platform.Mediation.Abstractions/IMediator.cs index f5b58f2..05d7419 100644 --- a/src/Bss.Platform.Mediation.Abstractions/IMediator.cs +++ b/src/Bss.Platform.Mediation.Abstractions/IMediator.cs @@ -1,13 +1,11 @@ -namespace Bss.Platform.Mediation.Abstractions; +namespace Bss.Platform.Mediation.Abstractions; public interface IMediator { - Task Send(TRequest request, CancellationToken cancellationToken = default) - where TRequest : IRequest; + public Task Send(IRequest request, CancellationToken cancellationToken); - Task Send(TRequest request, CancellationToken cancellationToken = default) - where TRequest : IRequest; + public Task Send(IRequest request, CancellationToken cancellationToken); - Task Publish(TNotification notification, CancellationToken cancellationToken = default) + public Task Publish(TNotification notification, CancellationToken cancellationToken) where TNotification : INotification; } diff --git a/src/Bss.Platform.Mediation.Abstractions/INotificationHandler.cs b/src/Bss.Platform.Mediation.Abstractions/INotificationHandler.cs index 295f21c..e46b347 100644 --- a/src/Bss.Platform.Mediation.Abstractions/INotificationHandler.cs +++ b/src/Bss.Platform.Mediation.Abstractions/INotificationHandler.cs @@ -1,6 +1,7 @@ namespace Bss.Platform.Mediation.Abstractions; -public interface INotificationHandler where TNotification : INotification +public interface INotificationHandler + where TNotification : INotification { - Task Handle(TNotification notification, CancellationToken cancellationToken); + public Task Handle(TNotification notification, CancellationToken cancellationToken); } diff --git a/src/Bss.Platform.Mediation.Abstractions/IPipelineBehavior.cs b/src/Bss.Platform.Mediation.Abstractions/IPipelineBehavior.cs index f8b3510..96c6f5f 100644 --- a/src/Bss.Platform.Mediation.Abstractions/IPipelineBehavior.cs +++ b/src/Bss.Platform.Mediation.Abstractions/IPipelineBehavior.cs @@ -1,17 +1,11 @@ -namespace Bss.Platform.Mediation.Abstractions; +namespace Bss.Platform.Mediation.Abstractions; -public interface IPipelineBehavior +public interface IPipelineBehavior { - Task Handle( - TRequest request, - CancellationToken ct, - Func> next); + public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken); } -public interface IPipelineBehavior +public interface IPipelineBehavior { - Task Handle( - TRequest request, - CancellationToken ct, - Func next); + public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken); } diff --git a/src/Bss.Platform.Mediation.Abstractions/IRequest.cs b/src/Bss.Platform.Mediation.Abstractions/IRequest.cs index e79a62d..43c43dd 100644 --- a/src/Bss.Platform.Mediation.Abstractions/IRequest.cs +++ b/src/Bss.Platform.Mediation.Abstractions/IRequest.cs @@ -1,4 +1,5 @@ -namespace Bss.Platform.Mediation.Abstractions; +namespace Bss.Platform.Mediation.Abstractions; -public interface IRequest; public interface IRequest; + +public interface IRequest; diff --git a/src/Bss.Platform.Mediation.Abstractions/IRequestHandler.cs b/src/Bss.Platform.Mediation.Abstractions/IRequestHandler.cs index 5a46a00..57440d5 100644 --- a/src/Bss.Platform.Mediation.Abstractions/IRequestHandler.cs +++ b/src/Bss.Platform.Mediation.Abstractions/IRequestHandler.cs @@ -1,13 +1,13 @@ -namespace Bss.Platform.Mediation.Abstractions; +namespace Bss.Platform.Mediation.Abstractions; -public interface IRequestHandler - where TRequest : IRequest +public interface IRequestHandler + where TRequest : IRequest { - Task Handle(TRequest request, CancellationToken cancellationToken); + public Task Handle(TRequest request, CancellationToken cancellationToken); } -public interface IRequestHandler - where TRequest : IRequest +public interface IRequestHandler + where TRequest : IRequest { - Task Handle(TRequest request, CancellationToken cancellationToken); + public Task Handle(TRequest request, CancellationToken cancellationToken); } diff --git a/src/Bss.Platform.Mediation.Abstractions/RequestHandlerDelegate.cs b/src/Bss.Platform.Mediation.Abstractions/RequestHandlerDelegate.cs new file mode 100644 index 0000000..7b2ec34 --- /dev/null +++ b/src/Bss.Platform.Mediation.Abstractions/RequestHandlerDelegate.cs @@ -0,0 +1,5 @@ +namespace Bss.Platform.Mediation.Abstractions; + +public delegate Task RequestHandlerDelegate(); + +public delegate Task RequestHandlerDelegate(); diff --git a/src/Bss.Platform.Mediation/DependencyInjection.cs b/src/Bss.Platform.Mediation/DependencyInjection.cs index 730baec..c6cddd4 100644 --- a/src/Bss.Platform.Mediation/DependencyInjection.cs +++ b/src/Bss.Platform.Mediation/DependencyInjection.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Bss.Platform.Mediation.Abstractions; @@ -8,20 +8,27 @@ namespace Bss.Platform.Mediation; public static class DependencyInjection { - public static IServiceCollection AddMediation( - this IServiceCollection services, - params Assembly[] assemblies) + public static IServiceCollection AddMediation(this IServiceCollection services, params Assembly[] assemblies) { services.AddScoped(); - services.Scan(s => s - .FromAssemblies(assemblies) - .AddClasses(c => c.AssignableTo(typeof(IRequestHandler<,>))) - .AsImplementedInterfaces() - .WithScopedLifetime() - .AddClasses(c => c.AssignableTo(typeof(IPipelineBehavior<,>))) - .AsImplementedInterfaces() - .WithScopedLifetime()); + services.Scan(scan => scan + .FromAssemblies(assemblies) + .AddClasses(classes => classes.AssignableTo(typeof(IRequestHandler<,>))) + .AsImplementedInterfaces() + .WithScopedLifetime() + .AddClasses(classes => classes.AssignableTo(typeof(IRequestHandler<>))) + .AsImplementedInterfaces() + .WithScopedLifetime() + .AddClasses(classes => classes.AssignableTo(typeof(INotificationHandler<>))) + .AsImplementedInterfaces() + .WithScopedLifetime() + .AddClasses(classes => classes.AssignableTo(typeof(IPipelineBehavior<,>))) + .AsImplementedInterfaces() + .WithScopedLifetime() + .AddClasses(classes => classes.AssignableTo(typeof(IPipelineBehavior<>))) + .AsImplementedInterfaces() + .WithScopedLifetime()); return services; } diff --git a/src/Bss.Platform.Mediation/Mediator.cs b/src/Bss.Platform.Mediation/Mediator.cs index f10d2c7..8ee4d6d 100644 --- a/src/Bss.Platform.Mediation/Mediator.cs +++ b/src/Bss.Platform.Mediation/Mediator.cs @@ -1,58 +1,38 @@ -using Microsoft.Extensions.DependencyInjection; - using Bss.Platform.Mediation.Abstractions; +using Bss.Platform.Mediation.Wrappers; namespace Bss.Platform.Mediation; -public record Mediator(IServiceProvider ServiceProvider) : IMediator +public sealed class Mediator(IServiceProvider serviceProvider) : IMediator { - public Task Send(TRequest request, CancellationToken cancellationToken = default) - where TRequest : IRequest + public Task Send(IRequest request, CancellationToken cancellationToken) { - var handler = this.ServiceProvider.GetRequiredService>(); - var behaviors = this.GetBehaviors>(); - - Func> next = - (r, ct) => handler.Handle(r, ct); - foreach (var behavior in behaviors) - { - var prev = next; - next = (r, ct) => behavior.Handle(r, ct, prev); - } + var requestType = request.GetType(); + var wrapperType = typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse)); + var wrapper = (RequestHandlerWrapper)(Activator.CreateInstance(wrapperType) + ?? throw new InvalidOperationException($"Could not create wrapper for {requestType}")); - return next(request, cancellationToken); + return wrapper.Handle(request, serviceProvider, cancellationToken); } - public Task Send(TRequest request, CancellationToken cancellationToken = default) - where TRequest : IRequest + public Task Send(IRequest request, CancellationToken cancellationToken) { - var handler = this.ServiceProvider.GetRequiredService>(); - var behaviors = this.GetBehaviors>(); + var requestType = request.GetType(); + var wrapperType = typeof(VoidRequestHandlerWrapperImpl<>).MakeGenericType(requestType); + var wrapper = (VoidRequestHandlerWrapper)(Activator.CreateInstance(wrapperType) + ?? throw new InvalidOperationException($"Could not create wrapper for {requestType}")); - Func next = (r, ct) => handler.Handle(r, ct); - foreach (var behavior in behaviors) - { - var prev = next; - next = (r, ct) => behavior.Handle(r, ct, prev); - } - - return next(request, cancellationToken); + return wrapper.Handle(request, serviceProvider, cancellationToken); } - public async Task Publish(TNotification notification, CancellationToken cancellationToken = default) + public Task Publish(TNotification notification, CancellationToken cancellationToken) where TNotification : INotification { - var handlers = this.ServiceProvider.GetServices>(); + var notificationType = notification.GetType(); + var wrapperType = typeof(NotificationHandlerWrapperImpl<>).MakeGenericType(notificationType); + var wrapper = (NotificationHandlerWrapper)(Activator.CreateInstance(wrapperType) + ?? throw new InvalidOperationException($"Could not create notification wrapper for {notificationType}")); - foreach (var handler in handlers) - { - await handler.Handle(notification, cancellationToken); - } + return wrapper.Handle(notification, serviceProvider, cancellationToken); } - - private TInterface[] GetBehaviors() => - this.ServiceProvider - .GetServices() - .Reverse() - .ToArray(); } diff --git a/src/Bss.Platform.Mediation/Wrappers/NotificationHandlerWrapper.cs b/src/Bss.Platform.Mediation/Wrappers/NotificationHandlerWrapper.cs new file mode 100644 index 0000000..ae1563d --- /dev/null +++ b/src/Bss.Platform.Mediation/Wrappers/NotificationHandlerWrapper.cs @@ -0,0 +1,6 @@ +namespace Bss.Platform.Mediation.Wrappers; + +internal abstract class NotificationHandlerWrapper +{ + public abstract Task Handle(object notification, IServiceProvider serviceProvider, CancellationToken cancellationToken); +} diff --git a/src/Bss.Platform.Mediation/Wrappers/NotificationHandlerWrapperImpl.cs b/src/Bss.Platform.Mediation/Wrappers/NotificationHandlerWrapperImpl.cs new file mode 100644 index 0000000..39f7e4d --- /dev/null +++ b/src/Bss.Platform.Mediation/Wrappers/NotificationHandlerWrapperImpl.cs @@ -0,0 +1,19 @@ +using Bss.Platform.Mediation.Abstractions; + +using Microsoft.Extensions.DependencyInjection; + +namespace Bss.Platform.Mediation.Wrappers; + +internal class NotificationHandlerWrapperImpl : NotificationHandlerWrapper + where TNotification : INotification +{ + public override async Task Handle(object notification, IServiceProvider serviceProvider, CancellationToken cancellationToken) + { + var handlers = serviceProvider.GetServices>(); + + foreach (var handler in handlers) + { + await handler.Handle((TNotification)notification, cancellationToken); + } + } +} diff --git a/src/Bss.Platform.Mediation/Wrappers/RequestHandlerWrapper.cs b/src/Bss.Platform.Mediation/Wrappers/RequestHandlerWrapper.cs new file mode 100644 index 0000000..2812c6a --- /dev/null +++ b/src/Bss.Platform.Mediation/Wrappers/RequestHandlerWrapper.cs @@ -0,0 +1,6 @@ +namespace Bss.Platform.Mediation.Wrappers; + +internal abstract class RequestHandlerWrapper +{ + public abstract Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken); +} diff --git a/src/Bss.Platform.Mediation/Wrappers/RequestHandlerWrapperImpl.cs b/src/Bss.Platform.Mediation/Wrappers/RequestHandlerWrapperImpl.cs new file mode 100644 index 0000000..a66999b --- /dev/null +++ b/src/Bss.Platform.Mediation/Wrappers/RequestHandlerWrapperImpl.cs @@ -0,0 +1,25 @@ +using Bss.Platform.Mediation.Abstractions; + +using Microsoft.Extensions.DependencyInjection; + +namespace Bss.Platform.Mediation.Wrappers; + +internal class RequestHandlerWrapperImpl : RequestHandlerWrapper + where TRequest : IRequest +{ + public override Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken) + { + var handler = serviceProvider.GetRequiredService>(); + var behaviors = serviceProvider.GetServices>(); + + RequestHandlerDelegate handlerDelegate = () => handler.Handle((TRequest)request, cancellationToken); + + foreach (var behavior in behaviors.Reverse()) + { + var next = handlerDelegate; + handlerDelegate = () => behavior.Handle((TRequest)request, next, cancellationToken); + } + + return handlerDelegate(); + } +} diff --git a/src/Bss.Platform.Mediation/Wrappers/VoidRequestHandlerWrapper.cs b/src/Bss.Platform.Mediation/Wrappers/VoidRequestHandlerWrapper.cs new file mode 100644 index 0000000..3ba7f13 --- /dev/null +++ b/src/Bss.Platform.Mediation/Wrappers/VoidRequestHandlerWrapper.cs @@ -0,0 +1,6 @@ +namespace Bss.Platform.Mediation.Wrappers; + +internal abstract class VoidRequestHandlerWrapper +{ + public abstract Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken); +} diff --git a/src/Bss.Platform.Mediation/Wrappers/VoidRequestHandlerWrapperImpl.cs b/src/Bss.Platform.Mediation/Wrappers/VoidRequestHandlerWrapperImpl.cs new file mode 100644 index 0000000..7b3a857 --- /dev/null +++ b/src/Bss.Platform.Mediation/Wrappers/VoidRequestHandlerWrapperImpl.cs @@ -0,0 +1,25 @@ +using Bss.Platform.Mediation.Abstractions; + +using Microsoft.Extensions.DependencyInjection; + +namespace Bss.Platform.Mediation.Wrappers; + +internal class VoidRequestHandlerWrapperImpl : VoidRequestHandlerWrapper + where TRequest : IRequest +{ + public override Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken) + { + var handler = serviceProvider.GetRequiredService>(); + var behaviors = serviceProvider.GetServices>(); + + RequestHandlerDelegate handlerDelegate = () => handler.Handle((TRequest)request, cancellationToken); + + foreach (var behavior in behaviors.Reverse()) + { + var next = handlerDelegate; + handlerDelegate = () => behavior.Handle((TRequest)request, next, cancellationToken); + } + + return handlerDelegate(); + } +} diff --git a/src/Tests.Unit/Platform/Mediation/NotificationTests.cs b/src/Tests.Unit/Platform/Mediation/NotificationTests.cs index f054e79..9c66b60 100644 --- a/src/Tests.Unit/Platform/Mediation/NotificationTests.cs +++ b/src/Tests.Unit/Platform/Mediation/NotificationTests.cs @@ -52,7 +52,7 @@ public async Task Publish_Notification_ExecutesAllHandlers() var mediator = provider.GetRequiredService(); // Act - await mediator.Publish(new AlertNotification()); + await mediator.Publish(new AlertNotification(), CancellationToken.None); // Assert this.executionLog.Should().Contain("Handler1"); diff --git a/src/Tests.Unit/Platform/Mediation/RequestTests.cs b/src/Tests.Unit/Platform/Mediation/RequestTests.cs index 5597df0..1f17e19 100644 --- a/src/Tests.Unit/Platform/Mediation/RequestTests.cs +++ b/src/Tests.Unit/Platform/Mediation/RequestTests.cs @@ -34,10 +34,10 @@ public Task Handle(PingRequest request, CancellationToken cancellationTo public class LoggingBehavior(List log) : IPipelineBehavior { - public async Task Handle(TRequest request, CancellationToken ct, Func> next) + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { log.Add("Behavior Pre"); - var result = await next(request, ct); + var result = await next(); log.Add("Behavior Post"); return result; } @@ -56,10 +56,10 @@ public Task Handle(VoidRequest request, CancellationToken cancellationToken) public class LoggingVoidBehavior(List log) : IPipelineBehavior { - public async Task Handle(TRequest request, CancellationToken ct, Func next) + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { log.Add("VoidBehavior Pre"); - await next(request, ct); + await next(); log.Add("VoidBehavior Post"); } } @@ -75,7 +75,7 @@ public async Task Send_RequestWithResult_ExecutesBehaviorsAndHandler() var mediator = provider.GetRequiredService(); // Act - var result = await mediator.Send(new PingRequest("World")); + var result = await mediator.Send(new PingRequest("World"), CancellationToken.None); // Assert result.Should().Be("Hello World"); @@ -98,7 +98,7 @@ public async Task Send_VoidRequest_ExecutesBehaviorsAndHandler() var mediator = provider.GetRequiredService(); // Act - await mediator.Send(new VoidRequest()); + await mediator.Send(new VoidRequest(), CancellationToken.None); // Assert this.executionLog.Should() diff --git a/src/__SolutionItems/CommonAssemblyInfo.cs b/src/__SolutionItems/CommonAssemblyInfo.cs index 13f41e8..2166d3b 100644 --- a/src/__SolutionItems/CommonAssemblyInfo.cs +++ b/src/__SolutionItems/CommonAssemblyInfo.cs @@ -4,9 +4,9 @@ [assembly: AssemblyCompany("Luxoft")] [assembly: AssemblyCopyright("Copyright © Luxoft 2026")] -[assembly: AssemblyVersion("1.6.4.0")] -[assembly: AssemblyFileVersion("1.6.4.0")] -[assembly: AssemblyInformationalVersion("1.6.4.0")] +[assembly: AssemblyVersion("1.6.5.0")] +[assembly: AssemblyFileVersion("1.6.5.0")] +[assembly: AssemblyInformationalVersion("1.6.5.0")] #if DEBUG [assembly: AssemblyConfiguration("Debug")]