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
10 changes: 4 additions & 6 deletions src/Bss.Platform.Mediation.Abstractions/IMediator.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
namespace Bss.Platform.Mediation.Abstractions;
namespace Bss.Platform.Mediation.Abstractions;

public interface IMediator
{
Task<TResult> Send<TRequest, TResult>(TRequest request, CancellationToken cancellationToken = default)
where TRequest : IRequest<TResult>;
public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken);

Task Send<TRequest>(TRequest request, CancellationToken cancellationToken = default)
where TRequest : IRequest;
public Task Send(IRequest request, CancellationToken cancellationToken);

Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
public Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken)
where TNotification : INotification;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Bss.Platform.Mediation.Abstractions;

public interface INotificationHandler<in TNotification> where TNotification : INotification
public interface INotificationHandler<in TNotification>
where TNotification : INotification
{
Task Handle(TNotification notification, CancellationToken cancellationToken);
public Task Handle(TNotification notification, CancellationToken cancellationToken);
}
16 changes: 5 additions & 11 deletions src/Bss.Platform.Mediation.Abstractions/IPipelineBehavior.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
namespace Bss.Platform.Mediation.Abstractions;
namespace Bss.Platform.Mediation.Abstractions;

public interface IPipelineBehavior<TRequest, TResult>
public interface IPipelineBehavior<in TRequest, TResult>
{
Task<TResult> Handle(
TRequest request,
CancellationToken ct,
Func<TRequest, CancellationToken, Task<TResult>> next);
public Task<TResult> Handle(TRequest request, RequestHandlerDelegate<TResult> next, CancellationToken cancellationToken);
}

public interface IPipelineBehavior<TRequest>
public interface IPipelineBehavior<in TRequest>
{
Task Handle(
TRequest request,
CancellationToken ct,
Func<TRequest, CancellationToken, Task> next);
public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken);
}
5 changes: 3 additions & 2 deletions src/Bss.Platform.Mediation.Abstractions/IRequest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Bss.Platform.Mediation.Abstractions;
namespace Bss.Platform.Mediation.Abstractions;

public interface IRequest<out TResult>;
public interface IRequest;

public interface IRequest<TResponse>;
14 changes: 7 additions & 7 deletions src/Bss.Platform.Mediation.Abstractions/IRequestHandler.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
namespace Bss.Platform.Mediation.Abstractions;
namespace Bss.Platform.Mediation.Abstractions;

public interface IRequestHandler<in TRequest, TResult>
where TRequest : IRequest<TResult>
public interface IRequestHandler<in TRequest>
where TRequest : IRequest
{
Task<TResult> Handle(TRequest request, CancellationToken cancellationToken);
public Task Handle(TRequest request, CancellationToken cancellationToken);
}

public interface IRequestHandler<in TRequest>
where TRequest : IRequest
public interface IRequestHandler<in TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
Task Handle(TRequest request, CancellationToken cancellationToken);
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Bss.Platform.Mediation.Abstractions;

public delegate Task RequestHandlerDelegate();

public delegate Task<TResult> RequestHandlerDelegate<TResult>();
31 changes: 19 additions & 12 deletions src/Bss.Platform.Mediation/DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Reflection;
using System.Reflection;

using Bss.Platform.Mediation.Abstractions;

Expand All @@ -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<IMediator, Mediator>();

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;
}
Expand Down
60 changes: 20 additions & 40 deletions src/Bss.Platform.Mediation/Mediator.cs
Original file line number Diff line number Diff line change
@@ -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<TResult> Send<TRequest, TResult>(TRequest request, CancellationToken cancellationToken = default)
where TRequest : IRequest<TResult>
public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken)
{
var handler = this.ServiceProvider.GetRequiredService<IRequestHandler<TRequest, TResult>>();
var behaviors = this.GetBehaviors<IPipelineBehavior<TRequest, TResult>>();

Func<TRequest, CancellationToken, Task<TResult>> 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<TResponse>)(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>(TRequest request, CancellationToken cancellationToken = default)
where TRequest : IRequest
public Task Send(IRequest request, CancellationToken cancellationToken)
{
var handler = this.ServiceProvider.GetRequiredService<IRequestHandler<TRequest>>();
var behaviors = this.GetBehaviors<IPipelineBehavior<TRequest>>();
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<TRequest, CancellationToken, Task> 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>(TNotification notification, CancellationToken cancellationToken = default)
public Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken)
where TNotification : INotification
{
var handlers = this.ServiceProvider.GetServices<INotificationHandler<TNotification>>();
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<TInterface>() =>
this.ServiceProvider
.GetServices<TInterface>()
.Reverse()
.ToArray();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Bss.Platform.Mediation.Wrappers;

internal abstract class NotificationHandlerWrapper
{
public abstract Task Handle(object notification, IServiceProvider serviceProvider, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Bss.Platform.Mediation.Abstractions;

using Microsoft.Extensions.DependencyInjection;

namespace Bss.Platform.Mediation.Wrappers;

internal class NotificationHandlerWrapperImpl<TNotification> : NotificationHandlerWrapper
where TNotification : INotification
{
public override async Task Handle(object notification, IServiceProvider serviceProvider, CancellationToken cancellationToken)
{
var handlers = serviceProvider.GetServices<INotificationHandler<TNotification>>();

foreach (var handler in handlers)
{
await handler.Handle((TNotification)notification, cancellationToken);
}
}
}
6 changes: 6 additions & 0 deletions src/Bss.Platform.Mediation/Wrappers/RequestHandlerWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Bss.Platform.Mediation.Wrappers;

internal abstract class RequestHandlerWrapper<TResponse>
{
public abstract Task<TResponse> Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken);
}
25 changes: 25 additions & 0 deletions src/Bss.Platform.Mediation/Wrappers/RequestHandlerWrapperImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Bss.Platform.Mediation.Abstractions;

using Microsoft.Extensions.DependencyInjection;

namespace Bss.Platform.Mediation.Wrappers;

internal class RequestHandlerWrapperImpl<TRequest, TResponse> : RequestHandlerWrapper<TResponse>
where TRequest : IRequest<TResponse>
{
public override Task<TResponse> Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken)
{
var handler = serviceProvider.GetRequiredService<IRequestHandler<TRequest, TResponse>>();
var behaviors = serviceProvider.GetServices<IPipelineBehavior<TRequest, TResponse>>();

RequestHandlerDelegate<TResponse> handlerDelegate = () => handler.Handle((TRequest)request, cancellationToken);

foreach (var behavior in behaviors.Reverse())
{
var next = handlerDelegate;
handlerDelegate = () => behavior.Handle((TRequest)request, next, cancellationToken);
}

return handlerDelegate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Bss.Platform.Mediation.Wrappers;

internal abstract class VoidRequestHandlerWrapper
{
public abstract Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Bss.Platform.Mediation.Abstractions;

using Microsoft.Extensions.DependencyInjection;

namespace Bss.Platform.Mediation.Wrappers;

internal class VoidRequestHandlerWrapperImpl<TRequest> : VoidRequestHandlerWrapper
where TRequest : IRequest
{
public override Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken)
{
var handler = serviceProvider.GetRequiredService<IRequestHandler<TRequest>>();
var behaviors = serviceProvider.GetServices<IPipelineBehavior<TRequest>>();

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();
}
}
2 changes: 1 addition & 1 deletion src/Tests.Unit/Platform/Mediation/NotificationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task Publish_Notification_ExecutesAllHandlers()
var mediator = provider.GetRequiredService<IMediator>();

// Act
await mediator.Publish(new AlertNotification());
await mediator.Publish(new AlertNotification(), CancellationToken.None);

// Assert
this.executionLog.Should().Contain("Handler1");
Expand Down
12 changes: 6 additions & 6 deletions src/Tests.Unit/Platform/Mediation/RequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public Task<string> Handle(PingRequest request, CancellationToken cancellationTo

public class LoggingBehavior<TRequest, TResult>(List<string> log) : IPipelineBehavior<TRequest, TResult>
{
public async Task<TResult> Handle(TRequest request, CancellationToken ct, Func<TRequest, CancellationToken, Task<TResult>> next)
public async Task<TResult> Handle(TRequest request, RequestHandlerDelegate<TResult> next, CancellationToken cancellationToken)
{
log.Add("Behavior Pre");
var result = await next(request, ct);
var result = await next();
log.Add("Behavior Post");
return result;
}
Expand All @@ -56,10 +56,10 @@ public Task Handle(VoidRequest request, CancellationToken cancellationToken)

public class LoggingVoidBehavior<TRequest>(List<string> log) : IPipelineBehavior<TRequest>
{
public async Task Handle(TRequest request, CancellationToken ct, Func<TRequest, CancellationToken, Task> 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");
}
}
Expand All @@ -75,7 +75,7 @@ public async Task Send_RequestWithResult_ExecutesBehaviorsAndHandler()
var mediator = provider.GetRequiredService<IMediator>();

// Act
var result = await mediator.Send<PingRequest, string>(new PingRequest("World"));
var result = await mediator.Send(new PingRequest("World"), CancellationToken.None);

// Assert
result.Should().Be("Hello World");
Expand All @@ -98,7 +98,7 @@ public async Task Send_VoidRequest_ExecutesBehaviorsAndHandler()
var mediator = provider.GetRequiredService<IMediator>();

// Act
await mediator.Send(new VoidRequest());
await mediator.Send(new VoidRequest(), CancellationToken.None);

// Assert
this.executionLog.Should()
Expand Down
6 changes: 3 additions & 3 deletions src/__SolutionItems/CommonAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
Loading