├── Icon.png ├── src └── MDiator │ ├── IMDiatorEvent.cs │ ├── IMDiatorRequest.cs │ ├── IMDiatorEventHandler.cs │ ├── IMDiatorHandler.cs │ ├── IMediator.cs │ ├── Mediator.cs │ ├── MDiator.csproj │ ├── Extensions │ ├── ServiceCollectionExtensions.cs │ └── ExpressionExentions.cs │ ├── HandlerInvokerCache.cs │ └── EventInvokerCache.cs ├── .gitignore ├── test ├── MDiator.Benchmarks │ ├── Program.cs │ ├── Events │ │ ├── LongEvent.cs │ │ ├── ShortEvent.cs │ │ └── Handlers │ │ │ ├── MDiatorShortEventHandler.cs │ │ │ ├── MDiatorLongEventHandler.cs │ │ │ ├── MediatRShortEventHandler.cs │ │ │ └── MediatRLongEventHandler.cs │ ├── Requests │ │ ├── LongRequest.cs │ │ ├── ShortRequest.cs │ │ └── Handlers │ │ │ ├── MDiatorShortRequestHandler.cs │ │ │ ├── MediatRShortRequestHandler.cs │ │ │ ├── MDiatorLongRequestHandler.cs │ │ │ └── MediatRLongRequestHandler.cs │ ├── MDiator.Benchmarks.csproj │ ├── MDiatorBenchmarks.cs │ ├── MediatRBenchmarks.cs │ ├── BaseBenchmarks.cs │ └── ComparisonBenchmarks.cs └── MDiator.Tests │ ├── Requests │ └── MyRequest.cs │ ├── Events │ └── MyEvent.cs │ ├── Handlers │ └── MyHandler.cs │ ├── RequestTests.cs │ ├── MDiator.Tests.csproj │ └── EventTests.cs ├── samples └── MDiator.SampleApp │ ├── Events │ └── OrderCreatedEvent.cs │ ├── Requests │ └── CreateUserCommand.cs │ ├── Handlers │ ├── NotifyTeamHandler.cs │ └── CreateUserHandler.cs │ ├── MDiator.SampleApp.csproj │ └── Program.cs ├── LICENSE ├── README.md └── MDiator.sln /Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MostafaDesoky95/MDiator/HEAD/Icon.png -------------------------------------------------------------------------------- /src/MDiator/IMDiatorEvent.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace MDiator 3 | { 4 | public interface IMDiatorEvent { } 5 | } 6 | -------------------------------------------------------------------------------- /src/MDiator/IMDiatorRequest.cs: -------------------------------------------------------------------------------- 1 | namespace MDiator 2 | { 3 | public interface IMDiatorRequest { } 4 | 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | *.user 4 | *.suo 5 | .vscode/ 6 | .env 7 | *.nupkg 8 | .vs/ 9 | MDiator.csproj.Backup.tmp 10 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using MDiator.Benchmarks; 3 | 4 | BenchmarkRunner.Run(); 5 | -------------------------------------------------------------------------------- /samples/MDiator.SampleApp/Events/OrderCreatedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace MDiator.SampleApp.Events 2 | { 3 | public class OrderCreatedEvent : IMDiatorEvent 4 | { 5 | public int OrderId { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Events/LongEvent.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MDiator.Benchmarks.Events; 4 | 5 | public class LongEvent : IMDiatorEvent, INotification 6 | { 7 | public string Message => "Long Event"; 8 | } 9 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Events/ShortEvent.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MDiator.Benchmarks.Events; 4 | 5 | public class ShortEvent : IMDiatorEvent, INotification 6 | { 7 | public string Message => "Short Event"; 8 | } 9 | -------------------------------------------------------------------------------- /samples/MDiator.SampleApp/Requests/CreateUserCommand.cs: -------------------------------------------------------------------------------- 1 | namespace MDiator.SampleApp.Requests 2 | { 3 | public class CreateUserCommand : IMDiatorRequest 4 | { 5 | public string UserName { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Requests/LongRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MDiator.Benchmarks.Requests; 4 | 5 | public class LongRequest : IRequest, IMDiatorRequest 6 | { 7 | public string Payload => "Long Ping"; 8 | } 9 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Requests/ShortRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MDiator.Benchmarks.Requests; 4 | 5 | public class ShortRequest : IRequest, IMDiatorRequest 6 | { 7 | public string Payload => "Short Ping"; 8 | } 9 | -------------------------------------------------------------------------------- /test/MDiator.Tests/Requests/MyRequest.cs: -------------------------------------------------------------------------------- 1 | using MDiator; 2 | 3 | 4 | namespace MDiator.Tests.Requests 5 | { 6 | public class MyRequest : IMDiatorRequest 7 | { 8 | public string Message { get; set; } = "Test"; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/MDiator/IMDiatorEventHandler.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace MDiator 3 | { 4 | public interface IMDiatorEventHandler 5 | where TEvent : IMDiatorEvent 6 | { 7 | Task Handle(TEvent @event, CancellationToken cancellationToken); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Events/Handlers/MDiatorShortEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MDiator.Benchmarks.Events.Handlers; 2 | 3 | public class MDiatorShortEventHandler : IMDiatorEventHandler 4 | { 5 | public async Task Handle(ShortEvent notification, CancellationToken cancellationToken) => await Task.CompletedTask; 6 | } 7 | -------------------------------------------------------------------------------- /test/MDiator.Tests/Events/MyEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MDiator.Tests.Events 8 | { 9 | public class MyEvent : IMDiatorEvent 10 | { 11 | public int Id { get; set; } = 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Events/Handlers/MDiatorLongEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MDiator.Benchmarks.Events.Handlers; 2 | 3 | public class MDiatorLongEventHandler : IMDiatorEventHandler 4 | { 5 | public async Task Handle(LongEvent notification, CancellationToken cancellationToken) => await Task.Delay(1000 * 15, cancellationToken); 6 | } 7 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Requests/Handlers/MDiatorShortRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MDiator.Benchmarks.Requests.Handlers; 2 | 3 | public class MDiatorShortRequestHandler : IMDiatorHandler 4 | { 5 | public Task Handle(ShortRequest request, CancellationToken cancellationToken) => Task.FromResult("Short Pong"); 6 | } 7 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Events/Handlers/MediatRShortEventHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MDiator.Benchmarks.Events.Handlers; 4 | 5 | public class MediatRShortEventHandler : INotificationHandler 6 | { 7 | public async Task Handle(ShortEvent notification, CancellationToken cancellationToken) => await Task.CompletedTask; 8 | } 9 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Requests/Handlers/MediatRShortRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MDiator.Benchmarks.Requests.Handlers; 4 | 5 | public class MediatRShortRequestHandler : IRequestHandler 6 | { 7 | public Task Handle(ShortRequest request, CancellationToken cancellationToken) => Task.FromResult("Short Pong"); 8 | } -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Events/Handlers/MediatRLongEventHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MDiator.Benchmarks.Events.Handlers; 4 | 5 | public class MediatRLongEventHandler : INotificationHandler 6 | { 7 | public async Task Handle(LongEvent notification, CancellationToken cancellationToken) 8 | => await Task.Delay(1000 * 15, cancellationToken); 9 | } 10 | -------------------------------------------------------------------------------- /src/MDiator/IMDiatorHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MDiator 8 | { 9 | public interface IMDiatorHandler 10 | where TRequest : IMDiatorRequest 11 | { 12 | Task Handle(TRequest request, CancellationToken cancellationToken); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Requests/Handlers/MDiatorLongRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MDiator.Benchmarks.Requests.Handlers; 2 | 3 | public class MDiatorLongRequestHandler : IMDiatorHandler 4 | { 5 | public async Task Handle(LongRequest request, CancellationToken cancellationToken) 6 | { 7 | await Task.Delay(1000 * 15); 8 | 9 | return await Task.FromResult("Long Pong"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/Requests/Handlers/MediatRLongRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MDiator.Benchmarks.Requests.Handlers; 4 | 5 | public class MediatRLongRequestHandler : IRequestHandler 6 | { 7 | public async Task Handle(LongRequest request, CancellationToken cancellationToken) 8 | { 9 | await Task.Delay(1000 * 15, cancellationToken); 10 | 11 | return await Task.FromResult("Long Pong"); 12 | } 13 | } -------------------------------------------------------------------------------- /samples/MDiator.SampleApp/Handlers/NotifyTeamHandler.cs: -------------------------------------------------------------------------------- 1 | using MDiator.SampleApp.Events; 2 | 3 | namespace MDiator.SampleApp.Handlers 4 | { 5 | public class NotifyTeamHandler : IMDiatorEventHandler 6 | { 7 | public Task Handle(OrderCreatedEvent e, CancellationToken cancellationToken) 8 | { 9 | Console.WriteLine($"🔔 Team notified about Order #{e.OrderId}"); 10 | return Task.CompletedTask; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/MDiator/IMediator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MDiator 8 | { 9 | public interface IMediator 10 | { 11 | Task Send(IMDiatorRequest request, CancellationToken cancellationToken = default); 12 | Task Publish(TEvent @event, CancellationToken cancellationToken = default) where TEvent : IMDiatorEvent; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/MDiator.SampleApp/MDiator.SampleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/MDiator.SampleApp/Handlers/CreateUserHandler.cs: -------------------------------------------------------------------------------- 1 | using MDiator.SampleApp.Requests; 2 | 3 | namespace MDiator.SampleApp.Handlers 4 | { 5 | public class CreateUserHandler : IMDiatorHandler 6 | { 7 | public async Task Handle(CreateUserCommand request, CancellationToken cancellationToken) 8 | { 9 | await Task.Delay(1000, cancellationToken); // Simulate some async work 10 | 11 | return await Task.FromResult($"✅ User '{request.UserName}' created."); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/MDiator.Tests/Handlers/MyHandler.cs: -------------------------------------------------------------------------------- 1 | using MDiator.Tests.Requests; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MDiator.Tests.Handlers 9 | { 10 | public class MyHandler : IMDiatorHandler 11 | { 12 | public async Task Handle(MyRequest request, CancellationToken cancellationToken) 13 | { 14 | await Task.Delay(10000, cancellationToken); // Simulate some async work 15 | return await Task.FromResult($"Handled: {request.Message}"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/MDiator.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | disable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/MDiatorBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using MDiator.Benchmarks.Events; 2 | using MDiator.Benchmarks.Requests; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace MDiator.Benchmarks; 6 | 7 | public class MDiatorBenchmarks : BaseBenchmarks 8 | { 9 | private readonly IMediator _mediator; 10 | 11 | public MDiatorBenchmarks() 12 | => _mediator = ServiceProvider.GetRequiredService(); 13 | 14 | public override async Task HandleEventAsync() => await _mediator.Publish(new ShortEvent()); 15 | 16 | public override async Task HandleLongEventAsync() => await _mediator.Publish(new LongEvent()); 17 | 18 | public override async Task HandleRequestAsync() => await _mediator.Send(new ShortRequest()); 19 | 20 | public override async Task HandleLongRequestAsync() => await _mediator.Send(new LongRequest()); 21 | } 22 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/MediatRBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using MDiator.Benchmarks.Events; 2 | using MDiator.Benchmarks.Requests; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace MDiator.Benchmarks; 6 | 7 | public class MediatRBenchmarks : BaseBenchmarks 8 | { 9 | private readonly MediatR.IMediator _mediator; 10 | 11 | public MediatRBenchmarks() 12 | => _mediator = ServiceProvider.GetRequiredService(); 13 | 14 | public override async Task HandleEventAsync() => await _mediator.Publish(new ShortEvent()); 15 | 16 | public override async Task HandleLongEventAsync() => await _mediator.Publish(new LongEvent()); 17 | 18 | public override async Task HandleRequestAsync() => await _mediator.Send(new ShortRequest()); 19 | 20 | public override async Task HandleLongRequestAsync() => await _mediator.Send(new LongRequest()); 21 | } 22 | -------------------------------------------------------------------------------- /src/MDiator/Mediator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace MDiator 4 | { 5 | public class Mediator : IMediator 6 | { 7 | private readonly IServiceProvider _provider; 8 | 9 | public Mediator(IServiceProvider provider) 10 | { 11 | _provider = provider; 12 | } 13 | 14 | public async Task Send(IMDiatorRequest request, CancellationToken cancellationToken = default) 15 | { 16 | var invoker = HandlerInvokerCache.Get(request.GetType()); 17 | return await invoker(_provider, request, cancellationToken); 18 | } 19 | 20 | public Task Publish(TEvent @event, CancellationToken cancellationToken = default) where TEvent : IMDiatorEvent 21 | { 22 | return EventInvokerCache.Invoke(_provider, @event, cancellationToken); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /test/MDiator.Tests/RequestTests.cs: -------------------------------------------------------------------------------- 1 | using MDiator.Tests.Handlers; 2 | using MDiator.Tests.Requests; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace MDiator.Tests 6 | { 7 | public class RequestTests 8 | { 9 | [Fact] 10 | public async Task Send_Should_Invoke_Handler_And_Return_Result() 11 | { 12 | var services = new ServiceCollection(); 13 | services.AddMDiator(typeof(MyRequest).Assembly); 14 | services.AddScoped, MyHandler>(); 15 | 16 | var provider = services.BuildServiceProvider(); 17 | var mediator = provider.GetRequiredService(); 18 | var cancellationToken = new CancellationTokenSource().Token; 19 | var result = await mediator.Send(new MyRequest(), cancellationToken); 20 | 21 | Assert.Equal("Handled: Test", result); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/BaseBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace MDiator.Benchmarks; 4 | 5 | public abstract class BaseBenchmarks 6 | { 7 | private static readonly ServiceProvider _serviceProvider; 8 | 9 | static BaseBenchmarks() 10 | { 11 | var services = new ServiceCollection(); 12 | var assembly = typeof(BaseBenchmarks).Assembly; 13 | services 14 | .AddMediatR(cfg => cfg.RegisterServicesFromAssembly(assembly)) 15 | .AddMDiator(assembly); 16 | 17 | _serviceProvider = services.BuildServiceProvider(); 18 | } 19 | 20 | protected ServiceProvider ServiceProvider => _serviceProvider; 21 | 22 | public abstract Task HandleEventAsync(); 23 | 24 | public abstract Task HandleLongEventAsync(); 25 | 26 | public abstract Task HandleRequestAsync(); 27 | 28 | public abstract Task HandleLongRequestAsync(); 29 | } 30 | -------------------------------------------------------------------------------- /test/MDiator.Tests/MDiator.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /samples/MDiator.SampleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using MDiator.SampleApp.Events; 2 | using MDiator.SampleApp.Requests; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace MDiator.SampleApp 6 | { 7 | public class Program 8 | { 9 | static async Task Main(string[] args) 10 | { 11 | // ✅ Setup DI 12 | var services = new ServiceCollection(); 13 | services.AddMDiator(typeof(Program).Assembly); 14 | var provider = services.BuildServiceProvider(); 15 | 16 | var mediator = provider.GetRequiredService(); 17 | 18 | // ✅ Send Command 19 | var cts = new CancellationTokenSource(); 20 | cts.CancelAfter(2000); 21 | var result = await mediator.Send(new CreateUserCommand { UserName = "Mostafa" }, cts.Token); 22 | Console.WriteLine(result); 23 | 24 | // ✅ Publish Event 25 | await mediator.Publish(new OrderCreatedEvent { OrderId = 123 }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 MostafaDesoky95 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/MDiator/MDiator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | true 6 | Release 7 | 0.1.1 8 | Mostafa Desoky 9 | OpenSource 10 | Lightweight mediator for .NET - IMDiatorRequest, Events, CQRS. 11 | mediator cqrs dotnet pipeline events 12 | 13 | 14 | 15 | net9.0 16 | enable 17 | enable 18 | LICENSE 19 | https://github.com/MostafaDesoky95/MDiator/ 20 | icon.png 21 | README.md 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/MDiator.Tests/EventTests.cs: -------------------------------------------------------------------------------- 1 | using MDiator.Tests.Events; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Moq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MDiator.Tests 11 | { 12 | public class EventTests 13 | { 14 | [Fact] 15 | public async Task Publish_Should_Invoke_All_EventHandlers() 16 | { 17 | var mock1 = new Mock>(); 18 | var mock2 = new Mock>(); 19 | 20 | var cancellationToken = new CancellationTokenSource(); 21 | 22 | var services = new ServiceCollection(); 23 | services.AddMDiator(typeof(MyEvent).Assembly); 24 | services.AddSingleton(mock1.Object); 25 | services.AddSingleton(mock2.Object); 26 | 27 | var provider = services.BuildServiceProvider(); 28 | var mediator = provider.GetRequiredService(); 29 | 30 | await mediator.Publish(new MyEvent()); 31 | 32 | mock1.Verify(m => m.Handle(It.IsAny(), cancellationToken.Token), Times.Once); 33 | mock2.Verify(m => m.Handle(It.IsAny(), cancellationToken.Token), Times.Once); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/MDiator/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using MDiator; 2 | using System.Reflection; 3 | 4 | namespace Microsoft.Extensions.DependencyInjection 5 | { 6 | public static class ServiceCollectionExtensions 7 | { 8 | public static IServiceCollection AddMDiator(this IServiceCollection services, params Assembly[] assemblies) 9 | { 10 | // Register the core mediator 11 | services.AddScoped(); 12 | 13 | var handlerInterfaceType = typeof(IMDiatorHandler<,>); 14 | var eventHandlerInterfaceType = typeof(IMDiatorEventHandler<>); 15 | 16 | foreach (var assembly in assemblies) 17 | { 18 | var types = assembly.GetTypes(); 19 | 20 | foreach (var type in types.Where(t => t.IsClass && !t.IsAbstract)) 21 | { 22 | var interfaces = type.GetInterfaces(); 23 | 24 | foreach (var iface in interfaces) 25 | { 26 | if (!iface.IsGenericType) continue; 27 | 28 | var definition = iface.GetGenericTypeDefinition(); 29 | 30 | if (definition == handlerInterfaceType || definition == eventHandlerInterfaceType) 31 | { 32 | services.AddScoped(iface, type); 33 | } 34 | } 35 | } 36 | } 37 | 38 | return services; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /test/MDiator.Benchmarks/ComparisonBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Configs; 3 | 4 | namespace MDiator.Benchmarks; 5 | 6 | [MemoryDiagnoser] 7 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 8 | [ShortRunJob] 9 | public class ComparisonBenchmarks 10 | { 11 | private readonly MDiatorBenchmarks _mDiatorBenchmarks = new(); 12 | private readonly MediatRBenchmarks _mediatRBenchmarks = new(); 13 | 14 | [Benchmark(Baseline = true), BenchmarkCategory("HandleEvent")] 15 | public async Task MDiator_HandleEventAsync() => await _mDiatorBenchmarks.HandleEventAsync(); 16 | 17 | [Benchmark, BenchmarkCategory("HandleEvent")] 18 | public async Task MediatR_HandleEventAsync() => await _mediatRBenchmarks.HandleEventAsync(); 19 | 20 | [Benchmark(Baseline = true), BenchmarkCategory("HandleLongEvent")] 21 | public async Task MDiator_HandleEventLongAsync() => await _mDiatorBenchmarks.HandleLongEventAsync(); 22 | 23 | [Benchmark, BenchmarkCategory("HandleLongEvent")] 24 | public async Task MediatR_HandleEventLongAsync() => await _mediatRBenchmarks.HandleLongEventAsync(); 25 | 26 | [Benchmark(Baseline = true), BenchmarkCategory("HandleRequest")] 27 | public async Task MDiator_HandleRequestAsync() => await _mDiatorBenchmarks.HandleRequestAsync(); 28 | 29 | [Benchmark, BenchmarkCategory("HandleRequest")] 30 | public async Task MediatR_HandleRequestAsync() => await _mediatRBenchmarks.HandleRequestAsync(); 31 | 32 | [Benchmark(Baseline = true), BenchmarkCategory("HandleLongRequest")] 33 | public async Task MDiator_HandleLongRequestAsync() => await _mDiatorBenchmarks.HandleLongRequestAsync(); 34 | 35 | [Benchmark, BenchmarkCategory("HandleLongRequest")] 36 | public async Task MediatR_HandleLongRequestAsync() => await _mediatRBenchmarks.HandleLongRequestAsync(); 37 | } 38 | -------------------------------------------------------------------------------- /src/MDiator/Extensions/ExpressionExentions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace System.Linq.Expressions 4 | { 5 | internal static class ExpressionExentions 6 | { 7 | public static Expression ForEach(this Expression collectionExpression, ParameterExpression loopParameterExpression, Expression loopBodyExpression) 8 | { 9 | ArgumentNullException.ThrowIfNull(collectionExpression); 10 | ArgumentNullException.ThrowIfNull(loopParameterExpression); 11 | ArgumentNullException.ThrowIfNull(loopBodyExpression); 12 | 13 | var enumerableType = typeof(IEnumerable<>).MakeGenericType(loopParameterExpression.Type); 14 | var enumeratorType = typeof(IEnumerator<>).MakeGenericType(loopParameterExpression.Type); 15 | var enumeratorVariableExpression = Expression.Variable(enumeratorType, "enumerator"); 16 | 17 | var getEnumeratorCall = Expression.Call(collectionExpression, enumerableType.GetMethod(nameof(IEnumerable.GetEnumerator))!); 18 | var moveNextCall = Expression.Call(enumeratorVariableExpression, typeof(IEnumerator).GetMethod(nameof(IEnumerator.MoveNext))!); 19 | var breakLabel = Expression.Label("LoopBreak"); 20 | 21 | var block = Expression.Block( 22 | [enumeratorVariableExpression], 23 | Expression.Assign(enumeratorVariableExpression, getEnumeratorCall), 24 | Expression.TryFinally( 25 | Expression.Loop( 26 | Expression.IfThenElse( 27 | Expression.IsFalse(moveNextCall), 28 | Expression.Break(breakLabel), 29 | Expression.Block( 30 | [loopParameterExpression], 31 | Expression.Assign(loopParameterExpression, Expression.Property(enumeratorVariableExpression, nameof(IEnumerator.Current))), 32 | loopBodyExpression 33 | ) 34 | ), 35 | breakLabel 36 | ), 37 | Expression.Call(enumeratorVariableExpression, typeof(IDisposable).GetMethod(nameof(IDisposable.Dispose))!) 38 | ) 39 | ); 40 | 41 | return block; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/MDiator/HandlerInvokerCache.cs: -------------------------------------------------------------------------------- 1 | using MDiator; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System.Collections.Concurrent; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | namespace MDiator 8 | { 9 | public static class HandlerInvokerCache 10 | { 11 | private static readonly ConcurrentDictionary, CancellationToken, Task>> _cache = new(); 12 | 13 | public static Func, CancellationToken, Task> Get(Type requestType) 14 | { 15 | return _cache.GetOrAdd(requestType, BuildInvoker); 16 | } 17 | 18 | private static Func, CancellationToken, Task> BuildInvoker(Type requestType) 19 | { 20 | var handlerInterface = typeof(IMDiatorHandler<,>).MakeGenericType(requestType, typeof(TResponse)); 21 | var handleMethod = handlerInterface.GetMethod("Handle"); 22 | 23 | var spParam = Expression.Parameter(typeof(IServiceProvider), "sp"); 24 | var requestParam = Expression.Parameter(typeof(IMDiatorRequest), "request"); 25 | var cancellationTokenParam = Expression.Parameter(typeof(CancellationToken), "cancellationToken"); 26 | 27 | // sp.GetRequiredService>() 28 | var getHandlerCall = Expression.Call( 29 | typeof(ServiceProviderServiceExtensions), 30 | nameof(ServiceProviderServiceExtensions.GetRequiredService), 31 | new[] { handlerInterface }, 32 | spParam 33 | ); 34 | 35 | var call = Expression.Call( 36 | Expression.Convert(getHandlerCall, handlerInterface), 37 | handleMethod!, 38 | Expression.Convert(requestParam, requestType), 39 | cancellationTokenParam 40 | ); 41 | 42 | var lambda = Expression.Lambda, CancellationToken, Task>>( 43 | call, 44 | spParam, 45 | requestParam, 46 | cancellationTokenParam 47 | ); 48 | 49 | return lambda.Compile(); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MDiator 🧩 2 | 3 | **MDiator** is a lightweight, extensible in-process messaging library for .NET. 4 | It provides clean abstractions for sending requests and publishing events using a mediator pattern — without the bloat. 5 | 6 | Inspired by MediatR, built for performance and clarity. 7 | 8 | --- 9 | 10 | ## 🚀 Features 11 | 12 | - `Send`: Handle a single request with a typed response 13 | - `Publish`: Broadcast events to multiple handlers 14 | - Auto-registration of handlers via assembly scanning 15 | - No reflection at runtime – uses compiled delegates 16 | - Minimal dependencies 17 | - CancelationToken support 18 | 19 | --- 20 | 21 | ## 📦 Installation 22 | 23 | Add it to your project (once published to NuGet): 24 | 25 | ```bash 26 | dotnet add package MDiator 27 | ``` 28 | 29 | Register in Program.cs 30 | ``` 31 | services.AddMDiator(typeof(Program).Assembly); 32 | ``` 33 | 34 | 35 | 🧪 Example Usage 36 | Define a Command 37 | 38 | ``` 39 | public class CreateUserCommand : IMDiatorRequest 40 | { 41 | public string UserName { get; set; } 42 | } 43 | 44 | public class CreateUserHandler : IMDiatorHandler 45 | { 46 | public Task Handle(CreateUserCommand command) 47 | => Task.FromResult($"Created user: {command.UserName}"); 48 | } 49 | ``` 50 | ``` 51 | var result = await mediator.Send(new CreateUserCommand { UserName = "Mostafa" }); 52 | ``` 53 | 54 | 📣 Event Example 55 | ``` 56 | public class OrderCreatedEvent : IMDiatorEvent 57 | { 58 | public int OrderId { get; set; } 59 | } 60 | 61 | public class NotifyTeamHandler : IMDiatorEventHandler 62 | { 63 | public Task Handle(OrderCreatedEvent e) 64 | { 65 | Console.WriteLine($"Notify team about order {e.OrderId}"); 66 | return Task.CompletedTask; 67 | } 68 | } 69 | ``` 70 | ``` 71 | await mediator.Publish(new OrderCreatedEvent { OrderId = 123 }); 72 | ``` 73 | 74 | ## 🚀 Performance Benchmark 75 | 76 | MDiator is designed to be lean and fast — with no runtime reflection and minimal allocations. 77 | Here’s a comparison with MediatR using [BenchmarkDotNet](https://benchmarkdotnet.org/): 78 | 79 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | 80 | |-------------------------------- |----------:|----------:|---------:|------:|--------:|-------:|----------:|------------:| 81 | | **MDiator_HandleEventAsync** | 105.51 ns | 13.308 ns | 0.729 ns | 1.00 | 0.01 | 0.0067 | 56 B | 1.00 | 82 | | MediatR_HandleEventAsync | 141.35 ns | 28.874 ns | 1.583 ns | 1.34 | 0.02 | 0.0372 | 312 B | 5.57 | 83 | | | | | | | | | | | 84 | | **MDiator_HandleRequestAsync** | 84.64 ns | 7.637 ns | 0.419 ns | 1.00 | 0.01 | 0.0200 | 168 B | 1.00 | 85 | | MediatR_HandleRequestAsync | 106.22 ns | 31.459 ns | 1.724 ns | 1.25 | 0.02 | 0.0343 | 288 B | 1.71 | 86 | 87 | > Event Handling ➔ 25% faster ⚡ and 82% less memory 📉 88 | 89 | > Request Handling ➔ 20% faster ⚡ and 42% less memory 📉 90 | 91 | 92 | 93 | Tested with: 94 | - .NET 9 95 | - Simple `Send()` request + one handler 96 | - Scoped resolution via Microsoft.Extensions.DependencyInjection -------------------------------------------------------------------------------- /MDiator.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.12.35527.113 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{080CEFCD-B292-46D3-ACD9-ADF23D6E4126}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{67B2F4DD-8749-4825-86A6-46B3A3546750}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{1BAAC11A-4A8D-4283-A75E-78F35BD2A573}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MDiator", "src\MDiator\MDiator.csproj", "{4BD087A6-3C5A-4BF0-AF07-AFC6891A5B31}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MDiator.SampleApp", "samples\MDiator.SampleApp\MDiator.SampleApp.csproj", "{C7078975-1B4D-47E6-8574-F767C9C9C158}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MDiator.Tests", "test\MDiator.Tests\MDiator.Tests.csproj", "{5589552D-2DC7-49D8-8103-B6403DD5AF04}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MDiator.Benchmarks", "test\MDiator.Benchmarks\MDiator.Benchmarks.csproj", "{28893588-63B5-49AA-9DE1-47752300AC43}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {4BD087A6-3C5A-4BF0-AF07-AFC6891A5B31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {4BD087A6-3C5A-4BF0-AF07-AFC6891A5B31}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {4BD087A6-3C5A-4BF0-AF07-AFC6891A5B31}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {4BD087A6-3C5A-4BF0-AF07-AFC6891A5B31}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {C7078975-1B4D-47E6-8574-F767C9C9C158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {C7078975-1B4D-47E6-8574-F767C9C9C158}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {C7078975-1B4D-47E6-8574-F767C9C9C158}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {C7078975-1B4D-47E6-8574-F767C9C9C158}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {5589552D-2DC7-49D8-8103-B6403DD5AF04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {5589552D-2DC7-49D8-8103-B6403DD5AF04}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {5589552D-2DC7-49D8-8103-B6403DD5AF04}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {5589552D-2DC7-49D8-8103-B6403DD5AF04}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {28893588-63B5-49AA-9DE1-47752300AC43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {28893588-63B5-49AA-9DE1-47752300AC43}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {28893588-63B5-49AA-9DE1-47752300AC43}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {28893588-63B5-49AA-9DE1-47752300AC43}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(NestedProjects) = preSolution 47 | {4BD087A6-3C5A-4BF0-AF07-AFC6891A5B31} = {080CEFCD-B292-46D3-ACD9-ADF23D6E4126} 48 | {C7078975-1B4D-47E6-8574-F767C9C9C158} = {1BAAC11A-4A8D-4283-A75E-78F35BD2A573} 49 | {5589552D-2DC7-49D8-8103-B6403DD5AF04} = {67B2F4DD-8749-4825-86A6-46B3A3546750} 50 | {28893588-63B5-49AA-9DE1-47752300AC43} = {67B2F4DD-8749-4825-86A6-46B3A3546750} 51 | EndGlobalSection 52 | EndGlobal 53 | -------------------------------------------------------------------------------- /src/MDiator/EventInvokerCache.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System.Collections.Concurrent; 3 | using System.Linq.Expressions; 4 | 5 | namespace MDiator 6 | { 7 | public static class EventInvokerCache 8 | { 9 | private static readonly ConcurrentDictionary> _cache = new(); 10 | 11 | public static Task Invoke(IServiceProvider provider, object @event, CancellationToken cancellationToken) 12 | { 13 | var eventType = @event.GetType(); 14 | var invoker = _cache.GetOrAdd(eventType, BuildInvoker); 15 | return invoker(provider, @event, cancellationToken); 16 | } 17 | 18 | private static Func BuildInvoker(Type eventType) 19 | { 20 | var spParam = Expression.Parameter(typeof(IServiceProvider), "sp"); 21 | var eventParam = Expression.Parameter(typeof(object), "event"); 22 | var cancellationTokenParam = Expression.Parameter(typeof(CancellationToken), "cancellationToken"); 23 | 24 | var handlerInterfaceType = typeof(IMDiatorEventHandler<>).MakeGenericType(eventType); 25 | var handleMethod = handlerInterfaceType.GetMethod("Handle")!; 26 | 27 | var handlersEnumerable = Expression.Call( 28 | typeof(ServiceProviderServiceExtensions), 29 | nameof(ServiceProviderServiceExtensions.GetServices), 30 | new[] { handlerInterfaceType }, 31 | spParam 32 | ); 33 | 34 | var handlersVar = Expression.Variable(typeof(IEnumerable<>).MakeGenericType(handlerInterfaceType), "handlers"); 35 | var handlersArrayVar = Expression.Variable(handlerInterfaceType.MakeArrayType(), "handlersArray"); 36 | var resultTaskVar = Expression.Variable(typeof(Task), "resultTask"); 37 | var tasksVar = Expression.Variable(typeof(List), "tasks"); 38 | var handlerVar = Expression.Variable(handlerInterfaceType, "handler"); 39 | 40 | var assignHandlers = Expression.Assign(handlersVar, handlersEnumerable); 41 | 42 | var assignHandlersArray = Expression.Assign( 43 | handlersArrayVar, 44 | Expression.Call(typeof(Enumerable), nameof(Enumerable.ToArray), new[] { handlerInterfaceType }, handlersVar) 45 | ); 46 | 47 | var handlersLength = Expression.PropertyOrField(handlersArrayVar, "Length"); 48 | var zero = Expression.Constant(0); 49 | var one = Expression.Constant(1); 50 | 51 | var callSingleHandle = Expression.Call( 52 | Expression.ArrayIndex(handlersArrayVar, zero), 53 | handleMethod, 54 | Expression.Convert(eventParam, eventType), 55 | cancellationTokenParam 56 | ); 57 | 58 | var callHandle = Expression.Call( 59 | handlerVar, 60 | handleMethod, 61 | Expression.Convert(eventParam, eventType), 62 | cancellationTokenParam 63 | ); 64 | 65 | var addTask = Expression.Call( 66 | tasksVar, 67 | typeof(List).GetMethod(nameof(List.Add))!, 68 | callHandle 69 | ); 70 | 71 | var foreachHandlers = handlersArrayVar.ForEach(handlerVar, addTask); 72 | 73 | var whenAllCall = Expression.Call( 74 | typeof(Task), 75 | nameof(Task.WhenAll), 76 | Type.EmptyTypes, 77 | tasksVar 78 | ); 79 | 80 | var assignCompletedTask = Expression.Assign(resultTaskVar, Expression.Constant(Task.CompletedTask, typeof(Task))); 81 | var assignSingleHandle = Expression.Assign(resultTaskVar, callSingleHandle); 82 | var assignWhenAll = Expression.Assign(resultTaskVar, whenAllCall); 83 | 84 | var ifZeroHandlers = Expression.IfThen( 85 | Expression.Equal(handlersLength, zero), 86 | assignCompletedTask 87 | ); 88 | 89 | var ifOneHandler = Expression.IfThen( 90 | Expression.Equal(handlersLength, one), 91 | assignSingleHandle 92 | ); 93 | 94 | var elseManyHandlers = Expression.Block( 95 | Expression.Assign(tasksVar, Expression.New(typeof(List))), 96 | foreachHandlers, 97 | assignWhenAll 98 | ); 99 | 100 | var block = Expression.Block( 101 | new[] { handlersVar, handlersArrayVar, tasksVar, resultTaskVar }, 102 | assignHandlers, 103 | assignHandlersArray, 104 | ifZeroHandlers, 105 | Expression.IfThenElse( 106 | Expression.Equal(handlersLength, one), 107 | ifOneHandler, 108 | elseManyHandlers 109 | ), 110 | resultTaskVar // <- Finally return the result 111 | ); 112 | 113 | return Expression.Lambda>( 114 | block, 115 | spParam, 116 | eventParam, 117 | cancellationTokenParam 118 | ).Compile(); 119 | } 120 | 121 | } 122 | } 123 | --------------------------------------------------------------------------------