├── .github └── workflows │ └── build-release.yml ├── .gitignore ├── DispatchR.sln ├── DispatchR.sln.DotSettings.user ├── LICENSE ├── README.md ├── benchmark └── results │ ├── notification-stable.png │ ├── stream-with-pipeline-stable.png │ ├── stream-without-pipeline-stable.png │ ├── with-pipeline-stable.png │ └── without-pipeline-stable.png └── src ├── Benchmark ├── Benchmark.csproj ├── Notification │ ├── MultiHandlers │ │ ├── MultiHandler0.cs │ │ ├── MultiHandler1.cs │ │ ├── MultiHandler2.cs │ │ └── MultiHandlersNotification.cs │ ├── MultiHandlersAsync │ │ ├── MultiHandlerAsync0.cs │ │ ├── MultiHandlerAsync1.cs │ │ ├── MultiHandlerAsync2.cs │ │ └── MultiHandlersAsyncNotification.cs │ ├── NotificationBenchmarks.cs │ └── SingleHandler │ │ ├── SingleHandler.cs │ │ └── SingleHandlerNotification.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── SendRequest │ ├── DispatchRCommands.cs │ ├── MediatRCommands.cs │ ├── MediatRVsDispatchRBenchmark.cs │ ├── MediatRVsDispatchRWithPipelineBenchmark.cs │ └── MediatSGCommands.cs ├── StreamRequest │ ├── StreamDispatchRCommands.cs │ ├── StreamMediatRCommands.cs │ ├── StreamMediatRVsDispatchRBenchmark.cs │ ├── StreamMediatRVsDispatchRWithPipelineBenchmark.cs │ └── StreamMediatSGCommands.cs ├── appsettings.Development.json └── appsettings.json ├── DispatchR ├── DispatchR.csproj ├── DispatchRServiceCollection.cs └── Requests │ ├── IMediator.cs │ ├── Notification │ ├── INotification.cs │ └── INotificationHandler.cs │ ├── Send │ ├── IPipelineBehavior.cs │ ├── IRequest.cs │ └── IRequestHandler.cs │ └── Stream │ ├── IStreamPipelineBehavior.cs │ ├── IStreamRequest.cs │ └── IStreamRequestHandler.cs └── Sample ├── BigFile.txt ├── DispatchR ├── Notification │ ├── MultiHandlersNotification.cs │ ├── NotificationOneHandler.cs │ ├── NotificationThreeHandler.cs │ └── NotificationTwoHandler.cs ├── SendRequest │ ├── FirstPipelineBehavior.cs │ ├── Ping.cs │ ├── PingHandler.cs │ └── SecondPipelineBehavior.cs └── StreamRequest │ ├── CounterPipelineStreamHandler.cs │ ├── CounterStreamHandler.cs │ └── CounterStreamRequest.cs ├── MediatR ├── Notification │ ├── MultiHandlersNotification.cs │ ├── NotificationOneHandler.cs │ ├── NotificationThreeHandler.cs │ └── NotificationTwoHandler.cs ├── SendRequest │ ├── FirstPipelineBehavior.cs │ ├── Ping.cs │ ├── PingHandler.cs │ └── SecondPipelineBehavior.cs └── StreamRequest │ ├── CounterPipelineStreamHandler.cs │ ├── CounterStreamHandler.cs │ └── CounterStreamRequest.cs ├── Program.cs ├── Properties └── launchSettings.json ├── Sample.csproj ├── Sample.http ├── appsettings.Development.json └── appsettings.json /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest] 13 | fail-fast: false 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Setup dotnet 22 | uses: actions/setup-dotnet@v4 23 | with: 24 | dotnet-version: '9.x' 25 | 26 | - name: Restore 27 | run: dotnet restore src/DispatchR/DispatchR.csproj 28 | 29 | - name: Build 30 | run: dotnet build src/DispatchR/DispatchR.csproj --configuration Release --no-restore 31 | 32 | - name: Extract version from tag 33 | id: get_version 34 | run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" 35 | 36 | - name: Pack project 37 | run: dotnet pack src/DispatchR/DispatchR.csproj --configuration Release --no-build -o ./nupkgs /p:PackageVersion=${{ steps.get_version.outputs.version }} 38 | 39 | - name: Push to NuGet 40 | run: dotnet nuget push "./nupkgs/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .idea/ 4 | /packages/ 5 | riderModule.iml 6 | /_ReSharper.Caches/ 7 | BenchmarkDotNet.Artifacts/ -------------------------------------------------------------------------------- /DispatchR.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7F7601D5-C62E-4EA3-8B71-E946A62B4529}" 4 | EndProject 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{89F559F6-C217-4D24-9A2F-DF25AE215A7C}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DispatchR", "src\DispatchR\DispatchR.csproj", "{9A10D4A1-2E7F-4DE2-AC96-49E2914800D7}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "src\Sample\Sample.csproj", "{3EB8B2BF-4228-408D-93D8-5280AAB438A0}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "src\Benchmark\Benchmark.csproj", "{92588047-1FE1-429A-8679-7B4FB39381F5}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{31F27C13-121E-4784-B06B-0FEF19817791}" 14 | ProjectSection(SolutionItems) = preProject 15 | README.md = README.md 16 | .gitignore = .gitignore 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{0C407C1E-9E95-40A5-ABCA-52AACCC49C96}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{97F0685F-BF3D-4F1F-B195-E33DD15C071E}" 22 | ProjectSection(SolutionItems) = preProject 23 | .github\workflows\build-release.yml = .github\workflows\build-release.yml 24 | EndProjectSection 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {9A10D4A1-2E7F-4DE2-AC96-49E2914800D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {9A10D4A1-2E7F-4DE2-AC96-49E2914800D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {9A10D4A1-2E7F-4DE2-AC96-49E2914800D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {9A10D4A1-2E7F-4DE2-AC96-49E2914800D7}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {3EB8B2BF-4228-408D-93D8-5280AAB438A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {3EB8B2BF-4228-408D-93D8-5280AAB438A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {3EB8B2BF-4228-408D-93D8-5280AAB438A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {3EB8B2BF-4228-408D-93D8-5280AAB438A0}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {92588047-1FE1-429A-8679-7B4FB39381F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {92588047-1FE1-429A-8679-7B4FB39381F5}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {92588047-1FE1-429A-8679-7B4FB39381F5}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {92588047-1FE1-429A-8679-7B4FB39381F5}.Release|Any CPU.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(NestedProjects) = preSolution 46 | {9A10D4A1-2E7F-4DE2-AC96-49E2914800D7} = {89F559F6-C217-4D24-9A2F-DF25AE215A7C} 47 | {3EB8B2BF-4228-408D-93D8-5280AAB438A0} = {89F559F6-C217-4D24-9A2F-DF25AE215A7C} 48 | {92588047-1FE1-429A-8679-7B4FB39381F5} = {89F559F6-C217-4D24-9A2F-DF25AE215A7C} 49 | {0C407C1E-9E95-40A5-ABCA-52AACCC49C96} = {31F27C13-121E-4784-B06B-0FEF19817791} 50 | {97F0685F-BF3D-4F1F-B195-E33DD15C071E} = {0C407C1E-9E95-40A5-ABCA-52AACCC49C96} 51 | EndGlobalSection 52 | EndGlobal 53 | -------------------------------------------------------------------------------- /DispatchR.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | ForceIncluded 3 | ForceIncluded 4 | ForceIncluded 5 | ForceIncluded 6 | ForceIncluded 7 | ForceIncluded 8 | ForceIncluded 9 | ForceIncluded 10 | ForceIncluded 11 | ForceIncluded 12 | ForceIncluded 13 | ForceIncluded 14 | ForceIncluded 15 | ForceIncluded 16 | ForceIncluded 17 | ForceIncluded 18 | ForceIncluded 19 | ForceIncluded 20 | ForceIncluded 21 | ForceIncluded 22 | ForceIncluded 23 | ForceIncluded 24 | ForceIncluded 25 | ForceIncluded 26 | ForceIncluded 27 | ForceIncluded 28 | ForceIncluded 29 | ForceIncluded 30 | ForceIncluded 31 | ForceIncluded 32 | ForceIncluded 33 | ForceIncluded 34 | ForceIncluded 35 | ForceIncluded 36 | ForceIncluded 37 | ForceIncluded 38 | ForceIncluded 39 | ForceIncluded 40 | ForceIncluded 41 | ForceIncluded 42 | ForceIncluded 43 | ForceIncluded 44 | ForceIncluded 45 | ForceIncluded 46 | ForceIncluded 47 | ForceIncluded 48 | ForceIncluded 49 | ForceIncluded 50 | ForceIncluded 51 | ForceIncluded 52 | ForceIncluded 53 | ForceIncluded 54 | ForceIncluded 55 | ForceIncluded 56 | ForceIncluded 57 | ForceIncluded 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Hasan Arab Borzo 4 | 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DispatchR 🚀 2 | 3 | ![CI](https://github.com/hasanxdev/DispatchR/workflows/Release/badge.svg) 4 | [![NuGet](https://img.shields.io/nuget/dt/DispatchR.Mediator.svg)](https://www.nuget.org/packages/DispatchR.Mediator) 5 | [![NuGet](https://img.shields.io/nuget/vpre/DispatchR.Mediator.svg)](https://www.nuget.org/packages/DispatchR.Mediator) 6 | 7 | ### A High-Performance Mediator Implementation for .NET :trollface: 8 | ** *Minimal memory footprint. Blazing-fast execution.* ** 9 | 10 | > [!NOTE] 11 | > If you're curious to see the power of this library, [check out the benchmark](https://github.com/hasanxdev/DispatchR?tab=readme-ov-file#-bechmark-result) comparing MediatR vs Mediator Source Generator vs DispatchR. 12 | 13 | ## ⚡ Key Features 14 | - Built entirely on top of Dependency Injection 15 | - Zero runtime reflection after registration 16 | - Choose your handler return type: `Task`, `ValueTask`, or `Synchronous Method` 17 | - Allocates nothing on the heap — ideal for high-throughput scenarios 18 | - Outperforms existing solutions in most real-world benchmarks 19 | - Seamlessly compatible with MediatR — migrate with minimal effort 20 | - Currently supports 21 | 1. Simple Request: 22 | 1. `IRequest` 23 | 2. `IRequestHandler` 24 | 3. `IPipelineBehavior` 25 | 2. Stream Request: 26 | 1. `IStreamRequest` 27 | 2. `IStreamRequestHandler` 28 | 3. `IStreamPipelineBehavior` 29 | 3. Notifications: 30 | 1. `INotification` 31 | 2. `INotificationHandler` 32 | > :bulb: **Tip:** *If you're looking for a mediator with the raw performance of hand-written code, DispatchR is built for you.* 33 | 34 | # Syntax Comparison: DispatchR vs MediatR 35 | 36 | ###### In the following, you will see the key differences and implementation details between MediatR and DispatchR. 37 | 38 | ## Request Definition 39 | 40 | ### MediatR 41 | ```csharp 42 | public sealed class PingMediatR : IRequest { } 43 | ``` 44 | 45 | ### DispatchR 46 | 1. Sending `TRequest` to `IRequest` 47 | 2. Precise selection of output for both `async` and `sync` handlers 48 | 1. Ability to choose between `Task` and `ValueTask` 49 | 50 | ```csharp 51 | public sealed class PingDispatchR : IRequest> { } 52 | ``` 53 | 54 | ## Handler Definition 55 | 56 | ### MediatR 57 | ```csharp 58 | public sealed class PingHandlerMediatR : IRequestHandler 59 | { 60 | public Task Handle(PingMediatR request, CancellationToken cancellationToken) 61 | { 62 | return Task.FromResult(0); 63 | } 64 | } 65 | ``` 66 | 67 | ### DispatchR (Don't change) 68 | 69 | ```csharp 70 | public sealed class PingHandlerDispatchR : IRequestHandler> 71 | { 72 | public ValueTask Handle(PingDispatchR request, CancellationToken cancellationToken) 73 | { 74 | return ValueTask.FromResult(0); 75 | } 76 | } 77 | ``` 78 | 79 | ## Pipeline Behavior 80 | 81 | ### MediatR 82 | ```csharp 83 | public sealed class LoggingBehaviorMediat : IPipelineBehavior 84 | { 85 | public Task Handle(PingMediatR request, RequestHandlerDelegate next, CancellationToken cancellationToken) 86 | { 87 | return next(cancellationToken); 88 | } 89 | } 90 | ``` 91 | 92 | ### DispatchR 93 | 1. Use ___Chain of Responsibility___ pattern 94 | 95 | ```csharp 96 | public sealed class LoggingBehaviorDispatchR : IPipelineBehavior> 97 | { 98 | public required IRequestHandler> NextPipeline { get; set; } 99 | 100 | public ValueTask Handle(PingDispatchR request, CancellationToken cancellationToken) 101 | { 102 | return NextPipeline.Handle(request, cancellationToken); 103 | } 104 | } 105 | ``` 106 | 107 | ## Summary 108 | 109 | - **DispatchR** lets the request itself define the return type. 110 | - **No runtime reflection** in DispatchR — it's optimized for performance. 111 | - **No static behavior chains** — pipelines are chained via DI and handler wiring. 112 | - **Supports `void`, `Task`, or `ValueTask`** as return types. 113 | 114 | Ideal for high-performance .NET applications. 115 | 116 | ## Stream Request Definition 117 | 118 | ### MediatR Stream 119 | ```csharp 120 | public sealed class CounterStreamRequestMediatR : IStreamRequest { } 121 | ``` 122 | 123 | ### DispatchR 124 | 1. Sending `TRequest` to `IStreamRequest` 125 | 126 | ```csharp 127 | public sealed class CounterStreamRequestDispatchR : IStreamRequest> { } 128 | ``` 129 | 130 | ## Stream Handler Definition 131 | 132 | ### Stream Handler MediatR 133 | ```csharp 134 | public sealed class CounterStreamHandlerMediatR : IStreamRequestHandler 135 | { 136 | public async IAsyncEnumerable Handle(CounterStreamRequestMediatR request, CancellationToken cancellationToken) 137 | { 138 | yield return 1; 139 | } 140 | } 141 | ``` 142 | 143 | ### Stream Handler DispatchR (Don't change) 144 | 145 | ```csharp 146 | public sealed class CounterStreamHandlerDispatchR : IStreamRequestHandler 147 | { 148 | public async IAsyncEnumerable Handle(CounterStreamHandlerDispatchR request, CancellationToken cancellationToken) 149 | { 150 | yield return 1; 151 | } 152 | } 153 | ``` 154 | 155 | ## Stream Pipeline Behavior 156 | 157 | ### Stream Pipeline MediatR 158 | ```csharp 159 | public sealed class CounterPipelineStreamHandler : IStreamPipelineBehavior 160 | { 161 | public async IAsyncEnumerable Handle(CounterStreamRequestMediatR request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) 162 | { 163 | await foreach (var response in next().WithCancellation(cancellationToken).ConfigureAwait(false)) 164 | { 165 | yield return response; 166 | } 167 | } 168 | } 169 | ``` 170 | 171 | ### Stream Pipeline DispatchR 172 | 1. Use ___Chain of Responsibility___ pattern 173 | 174 | ```csharp 175 | public sealed class CounterPipelineStreamHandler : IStreamPipelineBehavior 176 | { 177 | public required IStreamRequestHandler NextPipeline { get; set; } 178 | 179 | public async IAsyncEnumerable Handle(CounterStreamRequestDispatchR request, [EnumeratorCancellation] CancellationToken cancellationToken) 180 | { 181 | await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false)) 182 | { 183 | yield return response; 184 | } 185 | } 186 | } 187 | ``` 188 | 189 | ------------ 190 | ## Notification 191 | 192 | ### Notification MediatR 193 | ```csharp 194 | public sealed record Event(Guid Id) : INotification; 195 | 196 | public sealed class EventHandler(ILogger logger) : INotificationHandler 197 | { 198 | public Task Handle(Event notification, CancellationToken cancellationToken) 199 | { 200 | logger.LogInformation("Received notification"); 201 | return Task.CompletedTask; 202 | } 203 | } 204 | ``` 205 | 206 | ### Stream Pipeline DispatchR 207 | 1. Use ___ValueTask___ 208 | 209 | ```csharp 210 | public sealed record Event(Guid Id) : INotification; 211 | 212 | public sealed class EventHandler(ILogger logger) : INotificationHandler 213 | { 214 | public ValueTask Handle(Event notification, CancellationToken cancellationToken) 215 | { 216 | logger.LogInformation("Received notification"); 217 | return ValueTask.CompletedTask; 218 | } 219 | } 220 | ``` 221 | 222 | # ⚡ How DispatchR Achieves High Performance 223 | 224 | ###### DispatchR is designed with one goal in mind: **maximize performance with minimal memory usage**. Here's how it accomplishes that: 225 | 226 | ## What Happens Inside the `Send` Method? 227 | 228 | ```csharp 229 | public TResponse Send(IRequest request, 230 | CancellationToken cancellationToken) where TRequest : class, IRequest, new() 231 | { 232 | return serviceProvider 233 | .GetRequiredService>() 234 | .Handle(Unsafe.As(request), cancellationToken); 235 | } 236 | ``` 237 | 238 | ## What Happens Inside the `CreateStream` Method? 239 | 240 | ```csharp 241 | public IAsyncEnumerable CreateStream(IStreamRequest request, 242 | CancellationToken cancellationToken) where TRequest : class, IStreamRequest, new() 243 | { 244 | return serviceProvider.GetRequiredService>() 245 | .Handle(Unsafe.As(request), cancellationToken); 246 | } 247 | ``` 248 | **Only the handler is resolved and directly invoked!** 249 | 250 | ## What Happens Inside the `Publish` Method? 251 | 252 | ```csharp 253 | public async ValueTask Publish(TNotification request, CancellationToken cancellationToken) where TNotification : INotification 254 | { 255 | var notificationsInDi = serviceProvider.GetRequiredService>>(); 256 | 257 | var notifications = Unsafe.As[]>(notificationsInDi); 258 | foreach (var notification in notifications) 259 | { 260 | var valueTask = notification.Handle(request, cancellationToken); 261 | if (valueTask.IsCompletedSuccessfully is false) // <-- Handle sync notifications 262 | { 263 | await valueTask; 264 | } 265 | } 266 | } 267 | ``` 268 | 269 | But the real magic happens behind the scenes when DI resolves the handler dependency: 270 | > 💡 __Tips:__ 271 | > 1. *We cache the handler using DI, so in scoped scenarios, the object is constructed only once and reused afterward.* 272 | > 273 | > 2. *In terms of Dependency Injection (DI), everything in Requests is an IRequestHandler, it's just the keys that differ. 274 | When you request a specific key, a set of 1+N objects is returned: the first one is the actual handler, and the rest are the pipeline behaviors.* 275 | 276 | ```csharp 277 | services.AddScoped(handlerInterface, sp => 278 | { 279 | var pipelinesWithHandler = Unsafe 280 | .As(sp.GetKeyedServices(key)); 281 | 282 | IRequestHandler lastPipeline = pipelinesWithHandler[0]; 283 | for (int i = 1; i < pipelinesWithHandler.Length; i++) 284 | { 285 | var pipeline = pipelinesWithHandler[i]; 286 | pipeline.SetNext(lastPipeline); 287 | lastPipeline = pipeline; 288 | } 289 | 290 | return lastPipeline; 291 | }); 292 | ``` 293 | 294 | This elegant design chains pipeline behaviors at resolution time — no static lists, no reflection, no magic. 295 | 296 | ## 🪴 How to use? 297 | It's simple! Just use the following code: 298 | ```csharp 299 | builder.Services.AddDispatchR(typeof(MyCommand).Assembly, withPipelines: true, withNotifications: true); 300 | ``` 301 | This code will automatically register all pipelines by default. If you need to register them in a specific order, you can either add them manually or write your own reflection logic: 302 | ```csharp 303 | builder.Services.AddDispatchR(typeof(MyCommand).Assembly, withPipelines: false, withNotifications: false); 304 | builder.Services.AddScoped, PipelineBehavior>(); 305 | builder.Services.AddScoped, ValidationBehavior>(); 306 | builder.Services.AddScoped, ValidationBehavior>(); 307 | builder.Services.AddScoped, EventHandler>(); 308 | ``` 309 | ### 💡 Key Notes: 310 | 1. Automatic pipeline and notification registration is enabled by default 311 | 2. Manual registration allows for custom pipeline or notification ordering 312 | 3. You can implement custom reflection if needed 313 | 314 | ## ✨ How to install? 315 | ``` 316 | dotnet add package DispatchR.Mediator --version 1.2.0 317 | ``` 318 | 319 | # 🧪 Bechmark Result: 320 | > [!IMPORTANT] 321 | > This benchmark was conducted using MediatR version 12.5.0 and the stable release of Mediator Source Generator, version 2.1.7. 322 | Version 3 of Mediator Source Generator was excluded due to significantly lower performance. 323 | 324 | ### Send Request 325 | #### 1. MediatR vs Mediator Source Generator vs DispatchR With Pipeline 326 | ![Benchmark Result](./benchmark/results/with-pipeline-stable.png) 327 | #### 2. MediatR vs Mediator Source Generator vs DispatchR Without Pipeline 328 | ![Benchmark Result](./benchmark/results/without-pipeline-stable.png) 329 | 330 | ### Stream Request 331 | #### 1. MediatR vs Mediator Source Generator vs DispatchR With Pipeline 332 | ![Benchmark Result](./benchmark/results/stream-with-pipeline-stable.png) 333 | #### 2. MediatR vs Mediator Source Generator vs DispatchR Without Pipeline 334 | ![Benchmark Result](./benchmark/results/stream-without-pipeline-stable.png) 335 | 336 | ### Notification 337 | #### 1. MediatR vs Mediator Source Generator vs DispatchR 338 | ![Benchmark Result](./benchmark/results/notification-stable.png) 339 | 340 | ## ✨ Contribute & Help Grow This Package! ✨ 341 | We welcome contributions to make this package even better! ❤️ 342 | - Found a bug? → Open an issue 343 | - Have an idea? → Suggest a feature 344 | - Want to code? → Submit a PR 345 | 346 | Let's build something amazing together! 🚀 -------------------------------------------------------------------------------- /benchmark/results/notification-stable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanxdev/DispatchR/6bd968e73a4172c4da02be300587ff26a7e607f3/benchmark/results/notification-stable.png -------------------------------------------------------------------------------- /benchmark/results/stream-with-pipeline-stable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanxdev/DispatchR/6bd968e73a4172c4da02be300587ff26a7e607f3/benchmark/results/stream-with-pipeline-stable.png -------------------------------------------------------------------------------- /benchmark/results/stream-without-pipeline-stable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanxdev/DispatchR/6bd968e73a4172c4da02be300587ff26a7e607f3/benchmark/results/stream-without-pipeline-stable.png -------------------------------------------------------------------------------- /benchmark/results/with-pipeline-stable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanxdev/DispatchR/6bd968e73a4172c4da02be300587ff26a7e607f3/benchmark/results/with-pipeline-stable.png -------------------------------------------------------------------------------- /benchmark/results/without-pipeline-stable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanxdev/DispatchR/6bd968e73a4172c4da02be300587ff26a7e607f3/benchmark/results/without-pipeline-stable.png -------------------------------------------------------------------------------- /src/Benchmark/Benchmark.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Benchmark/Notification/MultiHandlers/MultiHandler0.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.Notification.MultiHandlers; 4 | 5 | public sealed class MultiHandler0 6 | : INotificationHandler, 7 | MediatR.INotificationHandler, 8 | DispatchR.Requests.Notification.INotificationHandler 9 | { 10 | public ValueTask Handle(MultiHandlersNotification notification, CancellationToken cancellationToken) => default; 11 | 12 | Task MediatR.INotificationHandler.Handle( 13 | MultiHandlersNotification notification, 14 | CancellationToken cancellationToken 15 | ) => Task.CompletedTask; 16 | } -------------------------------------------------------------------------------- /src/Benchmark/Notification/MultiHandlers/MultiHandler1.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.Notification.MultiHandlers; 4 | 5 | public sealed class MultiHandler1 6 | : INotificationHandler, 7 | MediatR.INotificationHandler, 8 | DispatchR.Requests.Notification.INotificationHandler 9 | { 10 | public ValueTask Handle(MultiHandlersNotification notification, CancellationToken cancellationToken) => default; 11 | 12 | Task MediatR.INotificationHandler.Handle( 13 | MultiHandlersNotification notification, 14 | CancellationToken cancellationToken 15 | ) => Task.CompletedTask; 16 | } -------------------------------------------------------------------------------- /src/Benchmark/Notification/MultiHandlers/MultiHandler2.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.Notification.MultiHandlers; 4 | 5 | public sealed class MultiHandler2 6 | : INotificationHandler, 7 | MediatR.INotificationHandler, 8 | DispatchR.Requests.Notification.INotificationHandler 9 | { 10 | public ValueTask Handle(MultiHandlersNotification notification, CancellationToken cancellationToken) => default; 11 | 12 | Task MediatR.INotificationHandler.Handle( 13 | MultiHandlersNotification notification, 14 | CancellationToken cancellationToken 15 | ) => Task.CompletedTask; 16 | } -------------------------------------------------------------------------------- /src/Benchmark/Notification/MultiHandlers/MultiHandlersNotification.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.Notification.MultiHandlers; 4 | 5 | public sealed record MultiHandlersNotification(Guid Id) : INotification, MediatR.INotification, 6 | DispatchR.Requests.Notification.INotification; -------------------------------------------------------------------------------- /src/Benchmark/Notification/MultiHandlersAsync/MultiHandlerAsync0.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.Notification.MultiHandlersAsync; 4 | 5 | public sealed class MultiHandlerAsync0 6 | : INotificationHandler, 7 | MediatR.INotificationHandler, 8 | DispatchR.Requests.Notification.INotificationHandler 9 | { 10 | public async ValueTask Handle(MultiHandlersAsyncNotification notification, CancellationToken cancellationToken) => 11 | await Task.Yield(); 12 | 13 | async Task MediatR.INotificationHandler.Handle( 14 | MultiHandlersAsyncNotification notification, 15 | CancellationToken cancellationToken 16 | ) => await Task.Yield(); 17 | } -------------------------------------------------------------------------------- /src/Benchmark/Notification/MultiHandlersAsync/MultiHandlerAsync1.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.Notification.MultiHandlersAsync; 4 | 5 | public sealed class MultiHandlerAsync1 6 | : INotificationHandler, 7 | MediatR.INotificationHandler, 8 | DispatchR.Requests.Notification.INotificationHandler 9 | { 10 | public async ValueTask Handle(MultiHandlersAsyncNotification notification, CancellationToken cancellationToken) => 11 | await Task.Yield(); 12 | 13 | async Task MediatR.INotificationHandler.Handle( 14 | MultiHandlersAsyncNotification notification, 15 | CancellationToken cancellationToken 16 | ) => await Task.Yield(); 17 | } -------------------------------------------------------------------------------- /src/Benchmark/Notification/MultiHandlersAsync/MultiHandlerAsync2.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.Notification.MultiHandlersAsync; 4 | 5 | public sealed class MultiHandlerAsync2 6 | : INotificationHandler, 7 | MediatR.INotificationHandler, 8 | DispatchR.Requests.Notification.INotificationHandler 9 | { 10 | public async ValueTask Handle(MultiHandlersAsyncNotification notification, CancellationToken cancellationToken) => 11 | await Task.Yield(); 12 | 13 | async Task MediatR.INotificationHandler.Handle( 14 | MultiHandlersAsyncNotification notification, 15 | CancellationToken cancellationToken 16 | ) => await Task.Yield(); 17 | } -------------------------------------------------------------------------------- /src/Benchmark/Notification/MultiHandlersAsync/MultiHandlersAsyncNotification.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.Notification.MultiHandlersAsync; 4 | 5 | public sealed record MultiHandlersAsyncNotification(Guid Id) : INotification, MediatR.INotification, 6 | DispatchR.Requests.Notification.INotification; -------------------------------------------------------------------------------- /src/Benchmark/Notification/NotificationBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using Benchmark.Notification.MultiHandlers; 2 | using Benchmark.Notification.MultiHandlersAsync; 3 | using Benchmark.Notification.SingleHandler; 4 | using BenchmarkDotNet.Attributes; 5 | using BenchmarkDotNet.Order; 6 | using DispatchR; 7 | 8 | namespace Benchmark.Notification; 9 | 10 | [MemoryDiagnoser] 11 | [Orderer(SummaryOrderPolicy.FastestToSlowest)] 12 | public class NotificationBenchmarks 13 | { 14 | private IServiceProvider _serviceProvider; 15 | private IServiceScope _serviceScope; 16 | private Mediator.IMediator _mediator; 17 | private DispatchR.Requests.IMediator _dispatchR; 18 | private Mediator.Mediator _concreteMediator; 19 | private MediatR.IMediator _mediatr; 20 | private SingleHandler.SingleHandler _singleHandler; 21 | private SingleHandlerNotification _singleHandlerNotification; 22 | private MultiHandler0 _multiHandler0; 23 | private MultiHandler1 _multiHandler1; 24 | private MultiHandler2 _multiHandler2; 25 | private MultiHandlersNotification _multiHandlersNotification; 26 | private MultiHandlerAsync0 _multiHandlerAsync0; 27 | private MultiHandlerAsync1 _multiHandlerAsync1; 28 | private MultiHandlerAsync2 _multiHandlerAsync2; 29 | private MultiHandlersAsyncNotification _multiHandlersAsyncNotification; 30 | 31 | public enum ScenarioType 32 | { 33 | SingleHandlerSync, 34 | MultiHandlersSync, 35 | MultiHandlersAsync, 36 | } 37 | 38 | [ParamsAllValues] 39 | public ScenarioType Scenario { get; set; } 40 | 41 | [GlobalSetup] 42 | public void Setup() 43 | { 44 | var services = new ServiceCollection(); 45 | services.AddMediator(opts => 46 | { 47 | opts.ServiceLifetime = ServiceLifetime.Scoped; 48 | }); 49 | services.AddDispatchR(typeof(SingleHandler.SingleHandler).Assembly); 50 | services.AddMediatR(opts => 51 | { 52 | opts.Lifetime = ServiceLifetime.Scoped; 53 | opts.RegisterServicesFromAssembly(typeof(SingleHandler.SingleHandler).Assembly); 54 | }); 55 | 56 | _serviceProvider = services.BuildServiceProvider(); 57 | _serviceScope = _serviceProvider.CreateScope(); 58 | _serviceProvider = _serviceScope.ServiceProvider; 59 | 60 | _mediator = _serviceProvider.GetRequiredService(); 61 | _dispatchR = _serviceProvider.GetRequiredService(); 62 | _concreteMediator = _serviceProvider.GetRequiredService(); 63 | _mediatr = _serviceProvider.GetRequiredService(); 64 | 65 | _singleHandler = _serviceProvider.GetRequiredService(); 66 | _singleHandlerNotification = new(Guid.NewGuid()); 67 | 68 | _multiHandler0 = _serviceProvider.GetRequiredService(); 69 | _multiHandler1 = _serviceProvider.GetRequiredService(); 70 | _multiHandler2 = _serviceProvider.GetRequiredService(); 71 | _multiHandlersNotification = new(Guid.NewGuid()); 72 | 73 | _multiHandlerAsync0 = _serviceProvider.GetRequiredService(); 74 | _multiHandlerAsync1 = _serviceProvider.GetRequiredService(); 75 | _multiHandlerAsync2 = _serviceProvider.GetRequiredService(); 76 | _multiHandlersAsyncNotification = new(Guid.NewGuid()); 77 | } 78 | 79 | [GlobalCleanup] 80 | public void Cleanup() 81 | { 82 | if (_serviceScope is not null) 83 | _serviceScope.Dispose(); 84 | else 85 | (_serviceProvider as IDisposable)?.Dispose(); 86 | } 87 | 88 | [Benchmark] 89 | public Task Publish_Notification_MediatR() 90 | { 91 | return Scenario switch 92 | { 93 | ScenarioType.SingleHandlerSync => _mediatr.Publish(_singleHandlerNotification), 94 | ScenarioType.MultiHandlersSync => _mediatr.Publish(_multiHandlersNotification), 95 | ScenarioType.MultiHandlersAsync => _mediatr.Publish(_multiHandlersAsyncNotification), 96 | }; 97 | } 98 | 99 | [Benchmark] 100 | public ValueTask Publish_Notification_DispatchR() 101 | { 102 | return Scenario switch 103 | { 104 | ScenarioType.SingleHandlerSync => _dispatchR.Publish(_singleHandlerNotification, CancellationToken.None), 105 | ScenarioType.MultiHandlersSync => _dispatchR.Publish(_multiHandlersNotification, CancellationToken.None), 106 | ScenarioType.MultiHandlersAsync => _dispatchR.Publish(_multiHandlersAsyncNotification, CancellationToken.None), 107 | }; 108 | } 109 | 110 | [Benchmark] 111 | public ValueTask Publish_Notification_IMediator() 112 | { 113 | return Scenario switch 114 | { 115 | ScenarioType.SingleHandlerSync => _mediator.Publish(_singleHandlerNotification), 116 | ScenarioType.MultiHandlersSync => _mediator.Publish(_multiHandlersNotification), 117 | ScenarioType.MultiHandlersAsync => _mediator.Publish(_multiHandlersAsyncNotification), 118 | }; 119 | } 120 | 121 | [Benchmark] 122 | public ValueTask Publish_Notification_Mediator() 123 | { 124 | return Scenario switch 125 | { 126 | ScenarioType.SingleHandlerSync => _concreteMediator.Publish(_singleHandlerNotification), 127 | ScenarioType.MultiHandlersSync => _concreteMediator.Publish(_multiHandlersNotification), 128 | ScenarioType.MultiHandlersAsync => _concreteMediator.Publish(_multiHandlersAsyncNotification), 129 | }; 130 | } 131 | 132 | [Benchmark(Baseline = true)] 133 | public ValueTask Publish_Notification_Baseline() 134 | { 135 | switch (Scenario) 136 | { 137 | case ScenarioType.SingleHandlerSync: 138 | return _singleHandler.Handle(_singleHandlerNotification, default); 139 | case ScenarioType.MultiHandlersSync: 140 | _multiHandler0.Handle(_multiHandlersNotification, default).GetAwaiter().GetResult(); 141 | _multiHandler1.Handle(_multiHandlersNotification, default).GetAwaiter().GetResult(); 142 | _multiHandler2.Handle(_multiHandlersNotification, default).GetAwaiter().GetResult(); 143 | return default; 144 | case ScenarioType.MultiHandlersAsync: 145 | return AwaitMultipleHandlersAsync(); 146 | } 147 | 148 | return default; 149 | 150 | async ValueTask AwaitMultipleHandlersAsync() 151 | { 152 | await _multiHandlerAsync0.Handle(_multiHandlersAsyncNotification, default); 153 | await _multiHandlerAsync1.Handle(_multiHandlersAsyncNotification, default); 154 | await _multiHandlerAsync2.Handle(_multiHandlersAsyncNotification, default); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Benchmark/Notification/SingleHandler/SingleHandler.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.Notification.SingleHandler; 4 | 5 | public sealed class SingleHandler 6 | : INotificationHandler, 7 | MediatR.INotificationHandler, 8 | DispatchR.Requests.Notification.INotificationHandler 9 | { 10 | public ValueTask Handle(SingleHandlerNotification notification, CancellationToken cancellationToken) => default; 11 | 12 | Task MediatR.INotificationHandler.Handle( 13 | SingleHandlerNotification notification, 14 | CancellationToken cancellationToken 15 | ) => Task.CompletedTask; 16 | } -------------------------------------------------------------------------------- /src/Benchmark/Notification/SingleHandler/SingleHandlerNotification.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.Notification.SingleHandler; 4 | 5 | public sealed record SingleHandlerNotification(Guid Id) : INotification, MediatR.INotification, 6 | DispatchR.Requests.Notification.INotification; -------------------------------------------------------------------------------- /src/Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using Benchmark; 3 | using Benchmark.Notification; 4 | using Benchmark.SendRequest; 5 | using Benchmark.StreamRequest; 6 | using BenchmarkDotNet.Attributes; 7 | using BenchmarkDotNet.Columns; 8 | using BenchmarkDotNet.Configs; 9 | using BenchmarkDotNet.Reports; 10 | using BenchmarkDotNet.Running; 11 | 12 | BenchmarkSwitcher.FromTypes([ 13 | typeof(MediatRVsDispatchBenchmark), 14 | typeof(MediatRVsDispatchWithPipelineRBenchmark), 15 | typeof(StreamMediatRVsDispatchBenchmark), 16 | typeof(StreamMediatRVsDispatchWithPipelineRBenchmark), 17 | typeof(NotificationBenchmarks), 18 | ]).Run(args, ManualConfig.Create(DefaultConfig.Instance) 19 | .AddColumn(new OperationsColumn()) 20 | ); 21 | 22 | public class OperationsColumn : IColumn 23 | { 24 | public string Id => nameof(OperationsColumn); 25 | public string ColumnName => "OpsCount"; 26 | public bool AlwaysShow => true; 27 | public ColumnCategory Category => ColumnCategory.Custom; 28 | public int PriorityInCategory => -10; 29 | public bool IsNumeric => true; 30 | public UnitType UnitType => UnitType.Dimensionless; 31 | public string Legend => "Number of operations per invoke"; 32 | 33 | public string GetValue(Summary summary, BenchmarkCase benchmarkCase) 34 | { 35 | return benchmarkCase.Descriptor.WorkloadMethod 36 | .GetCustomAttributes(typeof(BenchmarkAttribute), false) 37 | .Cast() 38 | .FirstOrDefault()?.OperationsPerInvoke.ToString() ?? "1"; 39 | } 40 | 41 | public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) 42 | => GetValue(summary, benchmarkCase); 43 | 44 | public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) 45 | { 46 | return true; 47 | } 48 | 49 | public bool IsAvailable(Summary summary) => true; 50 | public bool IsDefault(Summary summary) => false; 51 | } -------------------------------------------------------------------------------- /src/Benchmark/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": false, 7 | "launchBrowser": false, 8 | "applicationUrl": "http://localhost:5126", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | }, 13 | "https": { 14 | "commandName": "Project", 15 | "dotnetRunMessages": false, 16 | "launchBrowser": false, 17 | "applicationUrl": "https://localhost:7100;http://localhost:5126", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Benchmark/SendRequest/DispatchRCommands.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests; 2 | using DispatchR.Requests.Send; 3 | 4 | namespace Benchmark.SendRequest; 5 | 6 | public sealed record PingDispatchR : IRequest> { } 7 | 8 | public sealed record PingDispatchRWithOutHandler : IRequest> { } 9 | 10 | public sealed class PingHandlerDispatchR : IRequestHandler> 11 | { 12 | public ValueTask Handle(PingDispatchR request, CancellationToken cancellationToken) 13 | { 14 | return ValueTask.FromResult(0); 15 | } 16 | } 17 | 18 | public sealed class LoggingBehaviorDispatchR : IPipelineBehavior> 19 | { 20 | public required IRequestHandler> NextPipeline { get; set; } 21 | 22 | public ValueTask Handle(PingDispatchR request, CancellationToken cancellationToken) 23 | { 24 | return NextPipeline.Handle(request, cancellationToken); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Benchmark/SendRequest/MediatRCommands.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Benchmark.SendRequest; 4 | 5 | public sealed class PingMediatR : IRequest { } 6 | public sealed class PingMediatRWithOutHandler : IRequest { } 7 | 8 | public sealed class PingHandlerMediatR : IRequestHandler 9 | { 10 | public Task Handle(PingMediatR request, CancellationToken cancellationToken) 11 | { 12 | return Task.FromResult(0); 13 | } 14 | } 15 | 16 | public sealed class LoggingBehaviorMediat : IPipelineBehavior 17 | { 18 | public Task Handle(PingMediatR request, RequestHandlerDelegate next, CancellationToken cancellationToken) 19 | { 20 | return next(cancellationToken); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Benchmark/SendRequest/MediatRVsDispatchRBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Order; 3 | using DispatchR; 4 | using IMediator = MediatR.IMediator; 5 | 6 | namespace Benchmark.SendRequest; 7 | 8 | [MemoryDiagnoser] 9 | [Orderer(SummaryOrderPolicy.FastestToSlowest)] 10 | public class MediatRVsDispatchBenchmark 11 | { 12 | private const int TotalSendRequests = 5_000; 13 | private IServiceScope _serviceScopeForMediatRWithoutPipeline; 14 | private IServiceScope _serviceScopeForMediatSgWithoutPipeline; 15 | private IServiceScope _serviceScopeForDispatchRWithoutPipeline; 16 | private DispatchR.Requests.IMediator _dispatchRWithoutPipeline; 17 | private IMediator _mediatRWithoutPipeline; 18 | private Mediator.IMediator _mediatSgWithoutPipeline; 19 | private static readonly PingDispatchR StaticDispatchR = new(); 20 | private static readonly PingMediatR StaticPingMediatR = new(); 21 | private static readonly PingMediatSG StaticPingMediatSg = new(); 22 | private static readonly PingDispatchRWithOutHandler StaticDispatchRRequestWithOutHandler = new(); 23 | private static readonly PingMediatRWithOutHandler StaticPingMediatRWithOutHandler = new(); 24 | private static readonly PingMediatSGWithOutHandler StaticPingMediatSgWithOutHandler = new(); 25 | private static List ScopesForMediatRWithoutPipeline { get; set; } = new(TotalSendRequests); 26 | private static List ScopesForMediatSgWithoutPipeline { get; set; } = new(TotalSendRequests); 27 | private static List ScopesForDispatchRWithoutPipeline { get; set; } = new(TotalSendRequests); 28 | 29 | [GlobalSetup] 30 | public void Setup() 31 | { 32 | var withoutPipelineServices = new ServiceCollection(); 33 | withoutPipelineServices.AddMediatR(cfg => 34 | { 35 | cfg.Lifetime = ServiceLifetime.Scoped; 36 | cfg.RegisterServicesFromAssemblies(typeof(PingHandlerMediatR).Assembly); 37 | }); 38 | withoutPipelineServices.AddMediator(opts => 39 | { 40 | opts.ServiceLifetime = ServiceLifetime.Scoped; 41 | }); 42 | withoutPipelineServices.AddDispatchR(typeof(PingDispatchR).Assembly, withPipelines: false); 43 | var buildServicesWithoutPipeline = withoutPipelineServices.BuildServiceProvider(); 44 | _dispatchRWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 45 | _mediatRWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 46 | _mediatSgWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 47 | _serviceScopeForMediatRWithoutPipeline = buildServicesWithoutPipeline.CreateScope(); 48 | _serviceScopeForMediatSgWithoutPipeline = buildServicesWithoutPipeline.CreateScope(); 49 | _serviceScopeForDispatchRWithoutPipeline = buildServicesWithoutPipeline.CreateScope(); 50 | ScopesForMediatRWithoutPipeline.Clear(); 51 | ScopesForMediatSgWithoutPipeline.Clear(); 52 | ScopesForDispatchRWithoutPipeline.Clear(); 53 | Parallel.For(0, TotalSendRequests, i => 54 | { 55 | ScopesForMediatRWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 56 | ScopesForDispatchRWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 57 | ScopesForMediatSgWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 58 | }); 59 | } 60 | 61 | [GlobalCleanup] 62 | public void Cleanup() 63 | { 64 | _serviceScopeForMediatRWithoutPipeline.Dispose(); 65 | _serviceScopeForMediatSgWithoutPipeline.Dispose(); 66 | _serviceScopeForDispatchRWithoutPipeline.Dispose(); 67 | _dispatchRWithoutPipeline = null!; 68 | _mediatRWithoutPipeline = null!; 69 | _mediatSgWithoutPipeline = null!; 70 | Parallel.ForEach(ScopesForMediatRWithoutPipeline, scope => scope.Dispose()); 71 | Parallel.ForEach(ScopesForMediatSgWithoutPipeline, scope => scope.Dispose()); 72 | Parallel.ForEach(ScopesForDispatchRWithoutPipeline, scope => scope.Dispose()); 73 | } 74 | 75 | #region SendRequest_With_ExistRequest_ExistMediator_WithOutHandler 76 | 77 | [Benchmark(Baseline = true)] 78 | public Task MediatR___SendRequest_With_ExistRequest_ExistMediator_WithOut_Handler() 79 | { 80 | try 81 | { 82 | return _mediatRWithoutPipeline.Send(StaticPingMediatRWithOutHandler, CancellationToken.None); 83 | } 84 | catch 85 | { 86 | return Task.FromResult(0); 87 | } 88 | } 89 | 90 | [Benchmark] 91 | public ValueTask MediatSG__SendRequest_With_ExistRequest_ExistMediator_WithOut_Handler() 92 | { 93 | try 94 | { 95 | return _mediatSgWithoutPipeline.Send(StaticPingMediatSgWithOutHandler, CancellationToken.None); 96 | } 97 | catch 98 | { 99 | return ValueTask.FromResult(0); 100 | } 101 | } 102 | 103 | [Benchmark] 104 | public ValueTask DispatchR_SendRequest_With_ExistRequest_ExistMediator_WithOut_Handler() 105 | { 106 | try 107 | { 108 | return _dispatchRWithoutPipeline.Send(StaticDispatchRRequestWithOutHandler, CancellationToken.None); 109 | } 110 | catch 111 | { 112 | return ValueTask.FromResult(0); 113 | } 114 | } 115 | 116 | #endregion 117 | 118 | #region SendRequest_With_ExistRequest_ExistMediator 119 | 120 | [Benchmark] 121 | public Task MediatR___SendRequest_With_ExistRequest_ExistMediator() 122 | { 123 | return _mediatRWithoutPipeline.Send(StaticPingMediatR, CancellationToken.None); 124 | } 125 | 126 | [Benchmark] 127 | public ValueTask MediatSG__SendRequest_With_ExistRequest_ExistMediator() 128 | { 129 | return _mediatSgWithoutPipeline.Send(StaticPingMediatSg, CancellationToken.None); 130 | } 131 | 132 | [Benchmark] 133 | public ValueTask DispatchR_SendRequest_With_ExistRequest_ExistMediator() 134 | { 135 | return _dispatchRWithoutPipeline.Send(StaticDispatchR, CancellationToken.None); 136 | } 137 | 138 | #endregion 139 | 140 | #region SendRequest_With_ExistRequest_GetMediator 141 | 142 | [Benchmark] 143 | public async Task MediatR___SendRequest_With_ExistRequest_GetMediator() 144 | { 145 | var result = await _serviceScopeForMediatRWithoutPipeline 146 | .ServiceProvider 147 | .GetRequiredService() 148 | .Send(StaticPingMediatR, CancellationToken.None); 149 | 150 | return result; 151 | } 152 | 153 | [Benchmark] 154 | public async ValueTask MediatSG__SendRequest_With_ExistRequest_GetMediator() 155 | { 156 | var result = await _serviceScopeForMediatSgWithoutPipeline 157 | .ServiceProvider 158 | .GetRequiredService() 159 | .Send(StaticPingMediatSg, CancellationToken.None); 160 | 161 | return result; 162 | } 163 | 164 | [Benchmark] 165 | public ValueTask DispatchR_SendRequest_With_ExistRequest_GetMediator() 166 | { 167 | return _serviceScopeForDispatchRWithoutPipeline 168 | .ServiceProvider 169 | .GetRequiredService() 170 | .Send(StaticDispatchR, CancellationToken.None); 171 | } 172 | 173 | #endregion 174 | 175 | #region SendRequest_With_ExistRequest_ExistMediator_Parallel 176 | 177 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 178 | public async Task MediatR___SendRequest_With_ExistRequest_ExistMediator_Parallel() 179 | { 180 | var result = 0; 181 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) => 182 | { 183 | result = await _mediatRWithoutPipeline.Send(StaticPingMediatR, CancellationToken.None); 184 | }); 185 | 186 | return result; 187 | } 188 | 189 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 190 | public async Task MediatSG__SendRequest_With_ExistRequest_ExistMediator_Parallel() 191 | { 192 | var result = 0; 193 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) => 194 | { 195 | result = await _mediatSgWithoutPipeline.Send(StaticPingMediatSg, CancellationToken.None); 196 | }); 197 | 198 | return result; 199 | } 200 | 201 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 202 | public async Task DispatchR_SendRequest_With_ExistRequest_ExistMediator_Parallel() 203 | { 204 | var result = 0; 205 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) => 206 | { 207 | result = await _dispatchRWithoutPipeline.Send(StaticDispatchR, CancellationToken.None); 208 | }); 209 | 210 | return result; 211 | } 212 | 213 | #endregion 214 | 215 | #region SendRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel 216 | 217 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 218 | public async Task MediatR___SendRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel() 219 | { 220 | var result = 0; 221 | await Parallel.ForEachAsync(ScopesForMediatRWithoutPipeline, async (scope, ct) => 222 | { 223 | result = await scope.ServiceProvider.GetRequiredService() 224 | .Send(StaticPingMediatR, CancellationToken.None); 225 | }); 226 | 227 | return result; 228 | } 229 | 230 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 231 | public async Task MediatSG__SendRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel() 232 | { 233 | var result = 0; 234 | await Parallel.ForEachAsync(ScopesForMediatSgWithoutPipeline, async (scope, ct) => 235 | { 236 | result = await scope.ServiceProvider.GetRequiredService() 237 | .Send(StaticPingMediatSg, CancellationToken.None); 238 | }); 239 | 240 | return result; 241 | } 242 | 243 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 244 | public async Task DispatchR_SendRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel() 245 | { 246 | var result = 0; 247 | await Parallel.ForEachAsync(ScopesForDispatchRWithoutPipeline, async (scope, ct) => 248 | { 249 | result = await scope.ServiceProvider.GetRequiredService() 250 | .Send(StaticDispatchR, CancellationToken.None); 251 | }); 252 | 253 | return result; 254 | } 255 | 256 | #endregion 257 | } -------------------------------------------------------------------------------- /src/Benchmark/SendRequest/MediatRVsDispatchRWithPipelineBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Order; 3 | using DispatchR; 4 | using Mediator; 5 | using IMediator = MediatR.IMediator; 6 | 7 | namespace Benchmark.SendRequest; 8 | 9 | [MemoryDiagnoser] 10 | [Orderer(SummaryOrderPolicy.FastestToSlowest)] 11 | public class MediatRVsDispatchWithPipelineRBenchmark 12 | { 13 | private const int TotalSendRequests = 5_000; 14 | private IServiceScope _serviceScopeForMediatRWithPipeline; 15 | private IServiceScope _serviceScopeForMediatSgWithPipeline; 16 | private IServiceScope _serviceScopeForDispatchRWithPipeline; 17 | private DispatchR.Requests.IMediator _dispatchRWithPipeline; 18 | private IMediator _mediatRWithPipeline; 19 | private Mediator.IMediator _mediatSgWithPipeline; 20 | private static readonly PingDispatchR StaticDispatchR = new(); 21 | private static readonly PingMediatR StaticPingMediatR = new(); 22 | private static readonly PingMediatSG StaticPingMediatSg = new(); 23 | private static readonly PingDispatchRWithOutHandler StaticDispatchRRequestWithOutHandler = new(); 24 | private static readonly PingMediatRWithOutHandler StaticPingMediatRWithOutHandler = new(); 25 | private static readonly PingMediatSGWithOutHandler StaticPingMediatSgWithOutHandler = new(); 26 | private static List ScopesForMediatRWithPipeline { get; set; } = new(TotalSendRequests); 27 | private static List ScopesForMediatSgWithPipeline { get; set; } = new(TotalSendRequests); 28 | private static List ScopesForDispatchRWithPipeline { get; set; } = new(TotalSendRequests); 29 | 30 | [GlobalSetup] 31 | public void Setup() 32 | { 33 | var withPipelineServices = new ServiceCollection(); 34 | 35 | withPipelineServices.AddMediatR(cfg => 36 | { 37 | cfg.Lifetime = ServiceLifetime.Scoped; 38 | cfg.RegisterServicesFromAssemblies(typeof(PingHandlerMediatR).Assembly); 39 | }); 40 | withPipelineServices.AddScoped, LoggingBehaviorMediat>(); 41 | 42 | withPipelineServices.AddMediator((MediatorOptions options) => 43 | { 44 | options.ServiceLifetime = ServiceLifetime.Scoped; 45 | // options.PipelineBehaviors = [typeof(LoggingBehaviorMediatSG)]; 46 | }); 47 | withPipelineServices.AddScoped, LoggingBehaviorMediatSG>(); 48 | 49 | withPipelineServices.AddDispatchR(typeof(PingDispatchR).Assembly); 50 | var buildServicesWithoutPipeline = withPipelineServices.BuildServiceProvider(); 51 | _dispatchRWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 52 | _mediatRWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 53 | _mediatSgWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 54 | _serviceScopeForMediatRWithPipeline = buildServicesWithoutPipeline.CreateScope(); 55 | _serviceScopeForMediatSgWithPipeline = buildServicesWithoutPipeline.CreateScope(); 56 | _serviceScopeForDispatchRWithPipeline = buildServicesWithoutPipeline.CreateScope(); 57 | ScopesForMediatRWithPipeline.Clear(); 58 | ScopesForMediatSgWithPipeline.Clear(); 59 | ScopesForDispatchRWithPipeline.Clear(); 60 | Parallel.For(0, TotalSendRequests, i => 61 | { 62 | ScopesForMediatRWithPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 63 | ScopesForDispatchRWithPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 64 | ScopesForMediatSgWithPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 65 | }); 66 | } 67 | 68 | [GlobalCleanup] 69 | public void Cleanup() 70 | { 71 | _serviceScopeForMediatRWithPipeline.Dispose(); 72 | _serviceScopeForMediatSgWithPipeline.Dispose(); 73 | _serviceScopeForDispatchRWithPipeline.Dispose(); 74 | _dispatchRWithPipeline = null!; 75 | _mediatRWithPipeline = null!; 76 | _mediatSgWithPipeline = null!; 77 | Parallel.ForEach(ScopesForMediatRWithPipeline, scope => scope.Dispose()); 78 | Parallel.ForEach(ScopesForMediatSgWithPipeline, scope => scope.Dispose()); 79 | Parallel.ForEach(ScopesForDispatchRWithPipeline, scope => scope.Dispose()); 80 | } 81 | 82 | #region SendRequest_With_Pipeline_ExistRequest_ExistMediator_WithOutHandler 83 | 84 | [Benchmark(Baseline = true)] 85 | public Task MediatR___SendRequest_ExistRequest_ExistMediator_WithOut_Handler() 86 | { 87 | try 88 | { 89 | return _mediatRWithPipeline.Send(StaticPingMediatRWithOutHandler, CancellationToken.None); 90 | } 91 | catch 92 | { 93 | return Task.FromResult(0); 94 | } 95 | } 96 | 97 | [Benchmark] 98 | public ValueTask MediatSG__SendRequest_ExistRequest_ExistMediator_WithOut_Handler() 99 | { 100 | try 101 | { 102 | return _mediatSgWithPipeline.Send(StaticPingMediatSgWithOutHandler, CancellationToken.None); 103 | } 104 | catch 105 | { 106 | return ValueTask.FromResult(0); 107 | } 108 | } 109 | 110 | [Benchmark] 111 | public ValueTask DispatchR_SendRequest_ExistRequest_ExistMediator_WithOut_Handler() 112 | { 113 | try 114 | { 115 | return _dispatchRWithPipeline.Send(StaticDispatchRRequestWithOutHandler, CancellationToken.None); 116 | } 117 | catch 118 | { 119 | return ValueTask.FromResult(0); 120 | } 121 | } 122 | 123 | #endregion 124 | 125 | #region SendRequest_ExistRequest_ExistMediator 126 | 127 | [Benchmark] 128 | public Task MediatR___SendRequest_ExistRequest_ExistMediator() 129 | { 130 | return _mediatRWithPipeline.Send(StaticPingMediatR, CancellationToken.None); 131 | } 132 | 133 | [Benchmark] 134 | public ValueTask MediatSG__SendRequest_ExistRequest_ExistMediator() 135 | { 136 | return _mediatSgWithPipeline.Send(StaticPingMediatSg, CancellationToken.None); 137 | } 138 | 139 | [Benchmark] 140 | public ValueTask DispatchR_SendRequest_ExistRequest_ExistMediator() 141 | { 142 | return _dispatchRWithPipeline.Send(StaticDispatchR, CancellationToken.None); 143 | } 144 | 145 | #endregion 146 | 147 | #region SendRequest_ExistRequest_GetMediator 148 | 149 | [Benchmark] 150 | public Task MediatR___SendRequest_ExistRequest_GetMediator() 151 | { 152 | return _serviceScopeForMediatRWithPipeline 153 | .ServiceProvider 154 | .GetRequiredService() 155 | .Send(StaticPingMediatR, CancellationToken.None); 156 | } 157 | 158 | [Benchmark] 159 | public ValueTask MediatSG__SendRequest_ExistRequest_GetMediator() 160 | { 161 | return _serviceScopeForMediatSgWithPipeline 162 | .ServiceProvider 163 | .GetRequiredService() 164 | .Send(StaticPingMediatSg, CancellationToken.None); 165 | } 166 | 167 | [Benchmark] 168 | public ValueTask DispatchR_SendRequest_ExistRequest_GetMediator() 169 | { 170 | return _serviceScopeForDispatchRWithPipeline 171 | .ServiceProvider 172 | .GetRequiredService() 173 | .Send(StaticDispatchR, CancellationToken.None); 174 | } 175 | 176 | #endregion 177 | 178 | #region SendRequest_ExistRequest_ExistMediator_Parallel 179 | 180 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 181 | public async Task MediatR___SendRequest_ExistRequest_ExistMediator_Parallel() 182 | { 183 | var result = 0; 184 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) => 185 | { 186 | result = await _mediatRWithPipeline.Send(StaticPingMediatR, CancellationToken.None); 187 | }); 188 | 189 | return result; 190 | } 191 | 192 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 193 | public async Task MediatSG__SendRequest_ExistRequest_ExistMediator_Parallel() 194 | { 195 | var result = 0; 196 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) => 197 | { 198 | result = await _mediatSgWithPipeline.Send(StaticPingMediatSg, CancellationToken.None); 199 | }); 200 | 201 | return result; 202 | } 203 | 204 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 205 | public async Task DispatchR_SendRequest_ExistRequest_ExistMediator_Parallel() 206 | { 207 | var result = 0; 208 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) => 209 | { 210 | result = await _dispatchRWithPipeline.Send(StaticDispatchR, CancellationToken.None); 211 | }); 212 | 213 | return result; 214 | } 215 | 216 | #endregion 217 | 218 | #region SendRequest_ExistRequest_GetMediator_ExistScopes_Parallel 219 | 220 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 221 | public async Task MediatR___SendRequest_ExistRequest_GetMediator_ExistScopes_Parallel() 222 | { 223 | var result = 0; 224 | await Parallel.ForEachAsync(ScopesForMediatRWithPipeline, async (scope, ct) => 225 | { 226 | result = await scope.ServiceProvider.GetRequiredService() 227 | .Send(StaticPingMediatR, CancellationToken.None); 228 | }); 229 | 230 | return result; 231 | } 232 | 233 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 234 | public async Task MediatSG__SendRequest_ExistRequest_GetMediator_ExistScopes_Parallel() 235 | { 236 | var result = 0; 237 | await Parallel.ForEachAsync(ScopesForMediatSgWithPipeline, async (scope, ct) => 238 | { 239 | result = await scope.ServiceProvider.GetRequiredService() 240 | .Send(StaticPingMediatSg, CancellationToken.None); 241 | }); 242 | 243 | return result; 244 | } 245 | 246 | [Benchmark(OperationsPerInvoke = TotalSendRequests)] 247 | public async Task DispatchR_SendRequest_ExistRequest_GetMediator_ExistScopes_Parallel() 248 | { 249 | var result = 0; 250 | await Parallel.ForEachAsync(ScopesForDispatchRWithPipeline, async (scope, ct) => 251 | { 252 | result = await scope.ServiceProvider.GetRequiredService() 253 | .Send(StaticDispatchR, CancellationToken.None); 254 | }); 255 | 256 | return result; 257 | } 258 | 259 | #endregion 260 | } -------------------------------------------------------------------------------- /src/Benchmark/SendRequest/MediatSGCommands.cs: -------------------------------------------------------------------------------- 1 | using Mediator; 2 | 3 | namespace Benchmark.SendRequest; 4 | 5 | public sealed class PingMediatSG : IRequest { } 6 | public sealed class PingMediatSGWithOutHandler : IRequest { } 7 | 8 | public sealed class PingHandlerMediatSG : IRequestHandler 9 | { 10 | public ValueTask Handle(PingMediatSG request, CancellationToken cancellationToken) 11 | { 12 | return ValueTask.FromResult(0); 13 | } 14 | } 15 | 16 | public sealed class LoggingBehaviorMediatSG : IPipelineBehavior 17 | { 18 | // version 2.x 19 | public ValueTask Handle(PingMediatSG message, CancellationToken cancellationToken, MessageHandlerDelegate next) 20 | { 21 | return next(message, cancellationToken); 22 | } 23 | 24 | // version 3.x 25 | public ValueTask Handle(PingMediatSG message, MessageHandlerDelegate next, CancellationToken cancellationToken) 26 | { 27 | return next(message, cancellationToken); 28 | } 29 | } -------------------------------------------------------------------------------- /src/Benchmark/StreamRequest/StreamDispatchRCommands.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 2 | using System.Runtime.CompilerServices; 3 | using DispatchR.Requests; 4 | using DispatchR.Requests.Stream; 5 | 6 | namespace Benchmark.StreamRequest; 7 | 8 | public sealed record PingStreamDispatchR : IStreamRequest { } 9 | 10 | public sealed record PingStreamDispatchRWithOutHandler : IStreamRequest { } 11 | 12 | public sealed class PingHandlerDispatchR : IStreamRequestHandler 13 | { 14 | public async IAsyncEnumerable Handle(PingStreamDispatchR request, [EnumeratorCancellation] CancellationToken cancellationToken) 15 | { 16 | yield return 0; 17 | } 18 | } 19 | 20 | public sealed class LoggingBehaviorDispatchR : IStreamPipelineBehavior 21 | { 22 | public required IStreamRequestHandler NextPipeline { get; set; } 23 | 24 | public async IAsyncEnumerable Handle(PingStreamDispatchR request, [EnumeratorCancellation] CancellationToken cancellationToken) 25 | { 26 | await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false)) 27 | { 28 | yield return response; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Benchmark/StreamRequest/StreamMediatRCommands.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 2 | using System.Runtime.CompilerServices; 3 | using MediatR; 4 | namespace Benchmark.StreamRequest; 5 | 6 | public sealed class PingStreamMediatR : IStreamRequest { } 7 | public sealed class PingStreamMediatRWithOutHandler : IStreamRequest { } 8 | 9 | public sealed class PingHandlerMediatR : IStreamRequestHandler 10 | { 11 | public async IAsyncEnumerable Handle(PingStreamMediatR request, [EnumeratorCancellation] CancellationToken cancellationToken) 12 | { 13 | yield return 0; 14 | } 15 | } 16 | 17 | public sealed class LoggingBehaviorMediat : IStreamPipelineBehavior 18 | { 19 | public async IAsyncEnumerable Handle(PingStreamMediatR request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) 20 | { 21 | await foreach (var response in next().WithCancellation(cancellationToken).ConfigureAwait(false)) 22 | { 23 | yield return response; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Benchmark/StreamRequest/StreamMediatRVsDispatchRBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Order; 3 | using DispatchR; 4 | using IMediator = MediatR.IMediator; 5 | 6 | namespace Benchmark.StreamRequest; 7 | 8 | [MemoryDiagnoser] 9 | [Orderer(SummaryOrderPolicy.FastestToSlowest)] 10 | public class StreamMediatRVsDispatchBenchmark 11 | { 12 | private const int TotalStreamRequests = 5_000; 13 | private IServiceScope _serviceScopeForMediatRWithoutPipeline; 14 | private IServiceScope _serviceScopeForMediatSgWithoutPipeline; 15 | private IServiceScope _serviceScopeForDispatchRWithoutPipeline; 16 | private DispatchR.Requests.IMediator _dispatchRWithoutPipeline; 17 | private IMediator _mediatRWithoutPipeline; 18 | private Mediator.IMediator _mediatSgWithoutPipeline; 19 | private static readonly PingStreamDispatchR StaticStreamDispatchR = new(); 20 | private static readonly PingStreamMediatR StaticPingStreamMediatR = new(); 21 | private static readonly PingStreamMediatSg StaticPingStreamMediatSg = new(); 22 | private static readonly PingStreamDispatchRWithOutHandler StaticStreamDispatchRRequestWithOutHandler = new(); 23 | private static readonly PingStreamMediatRWithOutHandler StaticPingStreamMediatRWithOutHandler = new(); 24 | private static readonly PingStreamMediatSgWithOutHandler StaticPingStreamMediatSgWithOutHandler = new(); 25 | private static List ScopesForMediatRWithoutPipeline { get; set; } = new(TotalStreamRequests); 26 | private static List ScopesForMediatSgWithoutPipeline { get; set; } = new(TotalStreamRequests); 27 | private static List ScopesForDispatchRWithoutPipeline { get; set; } = new(TotalStreamRequests); 28 | 29 | [GlobalSetup] 30 | public void Setup() 31 | { 32 | var withoutPipelineServices = new ServiceCollection(); 33 | withoutPipelineServices.AddMediatR(cfg => 34 | { 35 | cfg.Lifetime = ServiceLifetime.Scoped; 36 | cfg.RegisterServicesFromAssemblies(typeof(PingHandlerMediatR).Assembly); 37 | }); 38 | withoutPipelineServices.AddMediator(opts => 39 | { 40 | opts.ServiceLifetime = ServiceLifetime.Scoped; 41 | }); 42 | withoutPipelineServices.AddDispatchR(typeof(PingStreamDispatchR).Assembly, withPipelines: false); 43 | var buildServicesWithoutPipeline = withoutPipelineServices.BuildServiceProvider(); 44 | _dispatchRWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 45 | _mediatRWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 46 | _mediatSgWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 47 | _serviceScopeForMediatRWithoutPipeline = buildServicesWithoutPipeline.CreateScope(); 48 | _serviceScopeForMediatSgWithoutPipeline = buildServicesWithoutPipeline.CreateScope(); 49 | _serviceScopeForDispatchRWithoutPipeline = buildServicesWithoutPipeline.CreateScope(); 50 | ScopesForMediatRWithoutPipeline.Clear(); 51 | ScopesForMediatSgWithoutPipeline.Clear(); 52 | ScopesForDispatchRWithoutPipeline.Clear(); 53 | Parallel.For(0, TotalStreamRequests, i => 54 | { 55 | ScopesForMediatRWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 56 | ScopesForDispatchRWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 57 | ScopesForMediatSgWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 58 | }); 59 | } 60 | 61 | [GlobalCleanup] 62 | public void Cleanup() 63 | { 64 | _serviceScopeForMediatRWithoutPipeline.Dispose(); 65 | _serviceScopeForMediatSgWithoutPipeline.Dispose(); 66 | _serviceScopeForDispatchRWithoutPipeline.Dispose(); 67 | _dispatchRWithoutPipeline = null!; 68 | _mediatRWithoutPipeline = null!; 69 | _mediatSgWithoutPipeline = null!; 70 | Parallel.ForEach(ScopesForMediatRWithoutPipeline, scope => scope.Dispose()); 71 | Parallel.ForEach(ScopesForMediatSgWithoutPipeline, scope => scope.Dispose()); 72 | Parallel.ForEach(ScopesForDispatchRWithoutPipeline, scope => scope.Dispose()); 73 | } 74 | 75 | #region StreamRequest_With_ExistRequest_ExistMediator_WithOutHandler 76 | 77 | [Benchmark(Baseline = true)] 78 | public async Task MediatR___StreamRequest_With_ExistRequest_ExistMediator_WithOut_Handler() 79 | { 80 | try 81 | { 82 | var last = 0; 83 | await foreach (var response in _mediatRWithoutPipeline.CreateStream(StaticPingStreamMediatRWithOutHandler, CancellationToken.None).ConfigureAwait(false)) 84 | { 85 | last = response; 86 | } 87 | 88 | return last; 89 | } 90 | catch 91 | { 92 | return 0; 93 | } 94 | } 95 | 96 | [Benchmark] 97 | public async Task MediatSG__StreamRequest_With_ExistRequest_ExistMediator_WithOut_Handler() 98 | { 99 | try 100 | { 101 | var last = 0; 102 | await foreach (var response in _mediatSgWithoutPipeline.CreateStream(StaticPingStreamMediatSgWithOutHandler, CancellationToken.None).ConfigureAwait(false)) 103 | { 104 | last = response; 105 | } 106 | 107 | return last; 108 | } 109 | catch 110 | { 111 | return 0; 112 | } 113 | } 114 | 115 | [Benchmark] 116 | public async Task DispatchR_StreamRequest_With_ExistRequest_ExistMediator_WithOut_Handler() 117 | { 118 | try 119 | { 120 | var last = 0; 121 | await foreach (var response in _dispatchRWithoutPipeline.CreateStream(StaticStreamDispatchRRequestWithOutHandler, CancellationToken.None).ConfigureAwait(false)) 122 | { 123 | last = response; 124 | } 125 | 126 | return last; 127 | } 128 | catch 129 | { 130 | return 0; 131 | } 132 | } 133 | 134 | #endregion 135 | 136 | #region StreamRequest_With_ExistRequest_ExistMediator 137 | 138 | [Benchmark] 139 | public async Task MediatR___StreamRequest_With_ExistRequest_ExistMediator() 140 | { 141 | var last = 0; 142 | await foreach (var response in _mediatRWithoutPipeline.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false)) 143 | { 144 | last = response; 145 | } 146 | return last; 147 | } 148 | 149 | [Benchmark] 150 | public async Task MediatSG__StreamRequest_With_ExistRequest_ExistMediator() 151 | { 152 | var last = 0; 153 | await foreach (var response in _mediatSgWithoutPipeline.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false)) 154 | { 155 | last = response; 156 | } 157 | return last; 158 | } 159 | 160 | [Benchmark] 161 | public async Task DispatchR_StreamRequest_With_ExistRequest_ExistMediator() 162 | { 163 | var last = 0; 164 | await foreach (var response in _dispatchRWithoutPipeline.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false)) 165 | { 166 | last = response; 167 | } 168 | return last; 169 | } 170 | 171 | #endregion 172 | 173 | #region StreamRequest_With_ExistRequest_GetMediator 174 | 175 | [Benchmark] 176 | public async Task MediatR___StreamRequest_With_ExistRequest_GetMediator() 177 | { 178 | var mediator = _serviceScopeForMediatRWithoutPipeline 179 | .ServiceProvider 180 | .GetRequiredService();; 181 | 182 | var last = 0; 183 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false)) 184 | { 185 | last = response; 186 | } 187 | return last; 188 | } 189 | 190 | [Benchmark] 191 | public async Task MediatSG__StreamRequest_With_ExistRequest_GetMediator() 192 | { 193 | var mediator = _serviceScopeForMediatSgWithoutPipeline 194 | .ServiceProvider 195 | .GetRequiredService(); 196 | 197 | var last = 0; 198 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false)) 199 | { 200 | last = response; 201 | } 202 | return last; 203 | } 204 | 205 | [Benchmark] 206 | public async Task DispatchR_StreamRequest_With_ExistRequest_GetMediator() 207 | { 208 | var mediator = _serviceScopeForDispatchRWithoutPipeline 209 | .ServiceProvider 210 | .GetRequiredService(); 211 | 212 | var last = 0; 213 | await foreach (var response in mediator.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false)) 214 | { 215 | last = response; 216 | } 217 | return last; 218 | } 219 | 220 | #endregion 221 | 222 | #region StreamRequest_With_ExistRequest_ExistMediator_Parallel 223 | 224 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 225 | public async Task MediatR___StreamRequest_With_ExistRequest_ExistMediator_Parallel() 226 | { 227 | var result = 0; 228 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) => 229 | { 230 | await foreach (var response in _mediatRWithoutPipeline.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false)) 231 | { 232 | result = response; 233 | } 234 | }); 235 | 236 | return result; 237 | } 238 | 239 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 240 | public async Task MediatSG__StreamRequest_With_ExistRequest_ExistMediator_Parallel() 241 | { 242 | var result = 0; 243 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) => 244 | { 245 | await foreach (var response in _mediatSgWithoutPipeline.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false)) 246 | { 247 | result = response; 248 | } 249 | }); 250 | 251 | return result; 252 | } 253 | 254 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 255 | public async Task DispatchR_StreamRequest_With_ExistRequest_ExistMediator_Parallel() 256 | { 257 | var result = 0; 258 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) => 259 | { 260 | await foreach (var response in _dispatchRWithoutPipeline.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false)) 261 | { 262 | result = response; 263 | } 264 | }); 265 | 266 | return result; 267 | } 268 | 269 | #endregion 270 | 271 | #region StreamRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel 272 | 273 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 274 | public async Task MediatR___StreamRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel() 275 | { 276 | var result = 0; 277 | await Parallel.ForEachAsync(ScopesForMediatRWithoutPipeline, async (scope, ct) => 278 | { 279 | var mediator = scope.ServiceProvider.GetRequiredService(); 280 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false)) 281 | { 282 | result = response; 283 | } 284 | }); 285 | 286 | return result; 287 | } 288 | 289 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 290 | public async Task MediatSG__StreamRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel() 291 | { 292 | var result = 0; 293 | await Parallel.ForEachAsync(ScopesForMediatSgWithoutPipeline, async (scope, ct) => 294 | { 295 | var mediator = scope.ServiceProvider.GetRequiredService(); 296 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false)) 297 | { 298 | result = response; 299 | } 300 | }); 301 | 302 | return result; 303 | } 304 | 305 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 306 | public async Task DispatchR_StreamRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel() 307 | { 308 | var result = 0; 309 | await Parallel.ForEachAsync(ScopesForDispatchRWithoutPipeline, async (scope, ct) => 310 | { 311 | var mediator = scope.ServiceProvider.GetRequiredService(); 312 | await foreach (var response in mediator.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false)) 313 | { 314 | result = response; 315 | } 316 | }); 317 | 318 | return result; 319 | } 320 | 321 | #endregion 322 | } -------------------------------------------------------------------------------- /src/Benchmark/StreamRequest/StreamMediatRVsDispatchRWithPipelineBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Order; 3 | using DispatchR; 4 | using Mediator; 5 | using IMediator = MediatR.IMediator; 6 | 7 | namespace Benchmark.StreamRequest; 8 | 9 | [MemoryDiagnoser] 10 | [Orderer(SummaryOrderPolicy.FastestToSlowest)] 11 | public class StreamMediatRVsDispatchWithPipelineRBenchmark 12 | { 13 | private const int TotalStreamRequests = 5_000; 14 | private IServiceScope _serviceScopeForMediatRWithPipeline; 15 | private IServiceScope _serviceScopeForMediatSgWithPipeline; 16 | private IServiceScope _serviceScopeForDispatchRWithPipeline; 17 | private DispatchR.Requests.IMediator _dispatchRWithPipeline; 18 | private IMediator _mediatRWithPipeline; 19 | private Mediator.IMediator _mediatSgWithPipeline; 20 | private static readonly PingStreamDispatchR StaticStreamDispatchR = new(); 21 | private static readonly PingStreamMediatR StaticPingStreamMediatR = new(); 22 | private static readonly PingStreamMediatSg StaticPingStreamMediatSg = new(); 23 | private static readonly PingStreamDispatchRWithOutHandler StaticStreamDispatchRRequestWithOutHandler = new(); 24 | private static readonly PingStreamMediatRWithOutHandler StaticPingStreamMediatRWithOutHandler = new(); 25 | private static readonly PingStreamMediatSgWithOutHandler StaticPingStreamMediatSgWithOutHandler = new(); 26 | private static List ScopesForMediatRWithPipeline { get; set; } = new(TotalStreamRequests); 27 | private static List ScopesForMediatSgWithPipeline { get; set; } = new(TotalStreamRequests); 28 | private static List ScopesForDispatchRWithPipeline { get; set; } = new(TotalStreamRequests); 29 | 30 | [GlobalSetup] 31 | public void Setup() 32 | { 33 | var withPipelineServices = new ServiceCollection(); 34 | 35 | withPipelineServices.AddMediatR(cfg => 36 | { 37 | cfg.Lifetime = ServiceLifetime.Scoped; 38 | cfg.RegisterServicesFromAssemblies(typeof(PingHandlerMediatR).Assembly); 39 | }); 40 | withPipelineServices.AddScoped, LoggingBehaviorMediat>(); 41 | 42 | withPipelineServices.AddMediator((MediatorOptions options) => 43 | { 44 | options.ServiceLifetime = ServiceLifetime.Scoped; 45 | // options.PipelineBehaviors = [typeof(LoggingBehaviorMediatSG)]; 46 | }); 47 | withPipelineServices.AddScoped, LoggingBehaviorMediatSg>(); 48 | 49 | withPipelineServices.AddDispatchR(typeof(PingStreamDispatchR).Assembly); 50 | var buildServicesWithoutPipeline = withPipelineServices.BuildServiceProvider(); 51 | _dispatchRWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 52 | _mediatRWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 53 | _mediatSgWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService(); 54 | _serviceScopeForMediatRWithPipeline = buildServicesWithoutPipeline.CreateScope(); 55 | _serviceScopeForMediatSgWithPipeline = buildServicesWithoutPipeline.CreateScope(); 56 | _serviceScopeForDispatchRWithPipeline = buildServicesWithoutPipeline.CreateScope(); 57 | ScopesForMediatRWithPipeline.Clear(); 58 | ScopesForMediatSgWithPipeline.Clear(); 59 | ScopesForDispatchRWithPipeline.Clear(); 60 | Parallel.For(0, TotalStreamRequests, i => 61 | { 62 | ScopesForMediatRWithPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 63 | ScopesForDispatchRWithPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 64 | ScopesForMediatSgWithPipeline.Add(buildServicesWithoutPipeline.CreateScope()); 65 | }); 66 | } 67 | 68 | [GlobalCleanup] 69 | public void Cleanup() 70 | { 71 | _serviceScopeForMediatRWithPipeline.Dispose(); 72 | _serviceScopeForMediatSgWithPipeline.Dispose(); 73 | _serviceScopeForDispatchRWithPipeline.Dispose(); 74 | _dispatchRWithPipeline = null!; 75 | _mediatRWithPipeline = null!; 76 | _mediatSgWithPipeline = null!; 77 | Parallel.ForEach(ScopesForMediatRWithPipeline, scope => scope.Dispose()); 78 | Parallel.ForEach(ScopesForMediatSgWithPipeline, scope => scope.Dispose()); 79 | Parallel.ForEach(ScopesForDispatchRWithPipeline, scope => scope.Dispose()); 80 | } 81 | 82 | #region StreamRequest_With_Pipeline_ExistRequest_ExistMediator_WithOutHandler 83 | 84 | [Benchmark(Baseline = true)] 85 | public async Task MediatR___StreamRequest_ExistRequest_ExistMediator_WithOut_Handler() 86 | { 87 | try 88 | { 89 | var result = 0; 90 | await foreach (var response in _mediatRWithPipeline.CreateStream(StaticPingStreamMediatRWithOutHandler, CancellationToken.None).ConfigureAwait(false)) 91 | { 92 | result = response; 93 | } 94 | 95 | return result; 96 | } 97 | catch 98 | { 99 | return 0; 100 | } 101 | } 102 | 103 | [Benchmark] 104 | public async Task MediatSG__StreamRequest_ExistRequest_ExistMediator_WithOut_Handler() 105 | { 106 | try 107 | { 108 | var result = 0; 109 | await foreach (var response in _mediatSgWithPipeline.CreateStream(StaticPingStreamMediatSgWithOutHandler, CancellationToken.None).ConfigureAwait(false)) 110 | { 111 | result = response; 112 | } 113 | 114 | return result; 115 | } 116 | catch 117 | { 118 | return 0; 119 | } 120 | } 121 | 122 | [Benchmark] 123 | public async Task DispatchR_StreamRequest_ExistRequest_ExistMediator_WithOut_Handler() 124 | { 125 | try 126 | { 127 | var result = 0; 128 | await foreach (var response in _dispatchRWithPipeline.CreateStream(StaticStreamDispatchRRequestWithOutHandler, CancellationToken.None).ConfigureAwait(false)) 129 | { 130 | result = response; 131 | } 132 | 133 | return result; 134 | } 135 | catch 136 | { 137 | return 0; 138 | } 139 | } 140 | 141 | #endregion 142 | 143 | #region StreamRequest_ExistRequest_ExistMediator 144 | 145 | [Benchmark] 146 | public async Task MediatR___StreamRequest_ExistRequest_ExistMediator() 147 | { 148 | var result = 0; 149 | await foreach (var response in _mediatRWithPipeline.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false)) 150 | { 151 | result = response; 152 | } 153 | 154 | return result; 155 | } 156 | 157 | [Benchmark] 158 | public async Task MediatSG__StreamRequest_ExistRequest_ExistMediator() 159 | { 160 | var result = 0; 161 | await foreach (var response in _mediatSgWithPipeline.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false)) 162 | { 163 | result = response; 164 | } 165 | 166 | return result; 167 | } 168 | 169 | [Benchmark] 170 | public async Task DispatchR_StreamRequest_ExistRequest_ExistMediator() 171 | { 172 | var result = 0; 173 | await foreach (var response in _dispatchRWithPipeline.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false)) 174 | { 175 | result = response; 176 | } 177 | 178 | return result; 179 | } 180 | 181 | #endregion 182 | 183 | #region StreamRequest_ExistRequest_GetMediator 184 | 185 | [Benchmark] 186 | public async Task MediatR___StreamRequest_ExistRequest_GetMediator() 187 | { 188 | var mediator = _serviceScopeForMediatRWithPipeline 189 | .ServiceProvider 190 | .GetRequiredService(); 191 | 192 | var result = 0; 193 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false)) 194 | { 195 | result = response; 196 | } 197 | 198 | return result; 199 | } 200 | 201 | [Benchmark] 202 | public async Task MediatSG__StreamRequest_ExistRequest_GetMediator() 203 | { 204 | var mediator = _serviceScopeForMediatSgWithPipeline 205 | .ServiceProvider 206 | .GetRequiredService(); 207 | 208 | var result = 0; 209 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false)) 210 | { 211 | result = response; 212 | } 213 | 214 | return result; 215 | } 216 | 217 | [Benchmark] 218 | public async Task DispatchR_StreamRequest_ExistRequest_GetMediator() 219 | { 220 | var mediator = _serviceScopeForDispatchRWithPipeline 221 | .ServiceProvider 222 | .GetRequiredService(); 223 | 224 | var result = 0; 225 | await foreach (var response in mediator.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false)) 226 | { 227 | result = response; 228 | } 229 | 230 | return result; 231 | } 232 | 233 | #endregion 234 | 235 | #region StreamRequest_ExistRequest_ExistMediator_Parallel 236 | 237 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 238 | public async Task MediatR___StreamRequest_ExistRequest_ExistMediator_Parallel() 239 | { 240 | var result = 0; 241 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) => 242 | { 243 | await foreach (var response in _mediatRWithPipeline.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false)) 244 | { 245 | result = response; 246 | } 247 | }); 248 | 249 | return result; 250 | } 251 | 252 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 253 | public async Task MediatSG__StreamRequest_ExistRequest_ExistMediator_Parallel() 254 | { 255 | var result = 0; 256 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) => 257 | { 258 | await foreach (var response in _mediatSgWithPipeline.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false)) 259 | { 260 | result = response; 261 | } 262 | }); 263 | 264 | return result; 265 | } 266 | 267 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 268 | public async Task DispatchR_StreamRequest_ExistRequest_ExistMediator_Parallel() 269 | { 270 | var result = 0; 271 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) => 272 | { 273 | await foreach (var response in _dispatchRWithPipeline.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false)) 274 | { 275 | result = response; 276 | } 277 | }); 278 | 279 | return result; 280 | } 281 | 282 | #endregion 283 | 284 | #region StreamRequest_ExistRequest_GetMediator_ExistScopes_Parallel 285 | 286 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 287 | public async Task MediatR___StreamRequest_ExistRequest_GetMediator_ExistScopes_Parallel() 288 | { 289 | var result = 0; 290 | await Parallel.ForEachAsync(ScopesForMediatRWithPipeline, async (scope, ct) => 291 | { 292 | var mediator = scope.ServiceProvider.GetRequiredService(); 293 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false)) 294 | { 295 | result = response; 296 | } 297 | }); 298 | 299 | return result; 300 | } 301 | 302 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 303 | public async Task MediatSG__StreamRequest_ExistRequest_GetMediator_ExistScopes_Parallel() 304 | { 305 | var result = 0; 306 | await Parallel.ForEachAsync(ScopesForMediatSgWithPipeline, async (scope, ct) => 307 | { 308 | var mediator = scope.ServiceProvider.GetRequiredService(); 309 | 310 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false)) 311 | { 312 | result = response; 313 | } 314 | }); 315 | 316 | return result; 317 | } 318 | 319 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)] 320 | public async Task DispatchR_StreamRequest_ExistRequest_GetMediator_ExistScopes_Parallel() 321 | { 322 | var result = 0; 323 | await Parallel.ForEachAsync(ScopesForDispatchRWithPipeline, async (scope, ct) => 324 | { 325 | var mediator = scope.ServiceProvider.GetRequiredService(); 326 | await foreach (var response in mediator.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false)) 327 | { 328 | result = response; 329 | } 330 | }); 331 | 332 | return result; 333 | } 334 | 335 | #endregion 336 | } -------------------------------------------------------------------------------- /src/Benchmark/StreamRequest/StreamMediatSGCommands.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 2 | using System.Runtime.CompilerServices; 3 | using Mediator; 4 | 5 | namespace Benchmark.StreamRequest; 6 | 7 | public sealed class PingStreamMediatSg : IStreamRequest { } 8 | public sealed class PingStreamMediatSgWithOutHandler : IStreamRequest { } 9 | 10 | public sealed class PingHandlerMediatSg : IStreamRequestHandler 11 | { 12 | public async IAsyncEnumerable Handle(PingStreamMediatSg request, [EnumeratorCancellation] CancellationToken cancellationToken) 13 | { 14 | yield return 0; 15 | } 16 | } 17 | 18 | public sealed class LoggingBehaviorMediatSg : IStreamPipelineBehavior 19 | { 20 | // version 2.x 21 | public async IAsyncEnumerable Handle(PingStreamMediatSg message, [EnumeratorCancellation] CancellationToken cancellationToken, StreamHandlerDelegate next) 22 | { 23 | await foreach (var response in next(message, cancellationToken).ConfigureAwait(false)) 24 | { 25 | yield return response; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Benchmark/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Benchmark/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/DispatchR/DispatchR.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | README.md 8 | DispatchR.Mediator 9 | hasanxdev 10 | 11 | Fast, zero-alloc alternative to MediatR for .NET – minimal, blazing fast, and DI-friendly. 12 | 13 | DispatchR;Mediator;MediatR 14 | https://github.com/hasanxdev/DispatchR 15 | https://github.com/hasanxdev/DispatchR 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/DispatchR/DispatchRServiceCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using DispatchR.Requests; 4 | using DispatchR.Requests.Notification; 5 | using DispatchR.Requests.Send; 6 | using DispatchR.Requests.Stream; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace DispatchR; 10 | 11 | public static class DispatchRServiceCollection 12 | { 13 | public static void AddDispatchR(this IServiceCollection services, Assembly assembly, bool withPipelines = true, bool withNotifications = true) 14 | { 15 | services.AddScoped(); 16 | var requestHandlerType = typeof(IRequestHandler<,>); 17 | var pipelineBehaviorType = typeof(IPipelineBehavior<,>); 18 | var streamRequestHandlerType = typeof(IStreamRequestHandler<,>); 19 | var streamPipelineBehaviorType = typeof(IStreamPipelineBehavior<,>); 20 | var syncNotificationHandlerType = typeof(INotificationHandler<>); 21 | 22 | var allTypes = assembly.GetTypes() 23 | .Where(p => 24 | { 25 | var interfaces = p.GetInterfaces(); 26 | return interfaces.Length >= 1 && 27 | interfaces 28 | .Where(i => i.IsGenericType) 29 | .Select(i => i.GetGenericTypeDefinition()) 30 | .Any(i => new[] 31 | { 32 | requestHandlerType, 33 | pipelineBehaviorType, 34 | streamRequestHandlerType, 35 | streamPipelineBehaviorType, 36 | syncNotificationHandlerType 37 | }.Contains(i)); 38 | }).ToList(); 39 | 40 | if (withNotifications) 41 | { 42 | RegisterNotification(services, allTypes, syncNotificationHandlerType); 43 | } 44 | 45 | RegisterHandlers(services, allTypes, requestHandlerType, pipelineBehaviorType, 46 | streamRequestHandlerType, streamPipelineBehaviorType, withPipelines); 47 | } 48 | 49 | private static void RegisterHandlers(IServiceCollection services, List allTypes, 50 | Type requestHandlerType, Type pipelineBehaviorType, Type streamRequestHandlerType, 51 | Type streamPipelineBehaviorType, bool withPipelines) 52 | { 53 | var allHandlers = allTypes 54 | .Where(p => 55 | { 56 | var @interface = p.GetInterfaces().First(i => i.IsGenericType); 57 | return new[] { requestHandlerType, streamRequestHandlerType } 58 | .Contains(@interface.GetGenericTypeDefinition()); 59 | }).ToList(); 60 | 61 | var allPipelines = allTypes 62 | .Where(p => 63 | { 64 | var @interface = p.GetInterfaces().First(i => i.IsGenericType); 65 | return new[] { pipelineBehaviorType, streamPipelineBehaviorType } 66 | .Contains(@interface.GetGenericTypeDefinition()); 67 | }).ToList(); 68 | 69 | foreach (var handler in allHandlers) 70 | { 71 | object key = handler.GUID; 72 | var handlerType = requestHandlerType; 73 | var behaviorType = pipelineBehaviorType; 74 | 75 | var isStream = handler.GetInterfaces() 76 | .Any(p => p.IsGenericType && p.GetGenericTypeDefinition() == streamRequestHandlerType); 77 | if (isStream) 78 | { 79 | handlerType = streamRequestHandlerType; 80 | behaviorType = streamPipelineBehaviorType; 81 | } 82 | 83 | services.AddKeyedScoped(typeof(IRequestHandler), key, handler); 84 | 85 | var handlerInterface = handler.GetInterfaces() 86 | .First(p => p.IsGenericType && p.GetGenericTypeDefinition() == handlerType); 87 | 88 | // find pipelines 89 | if (withPipelines) 90 | { 91 | var pipelines = allPipelines 92 | .Where(p => 93 | { 94 | var interfaces = p.GetInterfaces(); 95 | return interfaces 96 | .FirstOrDefault(inter => 97 | inter.IsGenericType && 98 | inter.GetGenericTypeDefinition() == behaviorType) 99 | ?.GetInterfaces().First().GetGenericTypeDefinition() == 100 | handlerInterface.GetGenericTypeDefinition(); 101 | }).ToList(); 102 | 103 | foreach (var pipeline in pipelines) 104 | { 105 | services.AddKeyedScoped(typeof(IRequestHandler), key, pipeline); 106 | } 107 | } 108 | 109 | services.AddScoped(handlerInterface, sp => 110 | { 111 | var pipelinesWithHandler = Unsafe 112 | .As(sp.GetKeyedServices(key)); 113 | 114 | IRequestHandler lastPipeline = pipelinesWithHandler[0]; 115 | for (int i = 1; i < pipelinesWithHandler.Length; i++) 116 | { 117 | var pipeline = pipelinesWithHandler[i]; 118 | pipeline.SetNext(lastPipeline); 119 | lastPipeline = pipeline; 120 | } 121 | 122 | return lastPipeline; 123 | }); 124 | } 125 | } 126 | 127 | private static void RegisterNotification(IServiceCollection services, List allTypes, Type syncNotificationHandlerType) 128 | { 129 | var allNotifications = allTypes 130 | .Where(p => 131 | { 132 | return p.GetInterfaces() 133 | .Where(i => i.IsGenericType) 134 | .Select(i => i.GetGenericTypeDefinition()) 135 | .Any(i => new[] 136 | { 137 | syncNotificationHandlerType 138 | }.Contains(i)); 139 | }) 140 | .GroupBy(p => 141 | { 142 | var @interface = p.GetInterfaces() 143 | .Where(i => i.IsGenericType) 144 | .First(i => new[] 145 | { 146 | syncNotificationHandlerType 147 | }.Contains(i.GetGenericTypeDefinition())); 148 | return @interface.GenericTypeArguments.First(); 149 | }) 150 | .ToList(); 151 | 152 | foreach (var notification in allNotifications) 153 | { 154 | foreach (var types in notification.ToList()) 155 | { 156 | services.AddScoped(typeof(INotificationHandler<>).MakeGenericType(notification.Key), types); 157 | } 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /src/DispatchR/Requests/IMediator.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using DispatchR.Requests.Notification; 3 | using DispatchR.Requests.Send; 4 | using DispatchR.Requests.Stream; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace DispatchR.Requests; 8 | 9 | public interface IMediator 10 | { 11 | TResponse Send(IRequest request, 12 | CancellationToken cancellationToken) where TRequest : class, IRequest, new(); 13 | 14 | IAsyncEnumerable CreateStream(IStreamRequest request, 15 | CancellationToken cancellationToken) where TRequest : class, IStreamRequest, new(); 16 | 17 | ValueTask Publish(TNotification request, CancellationToken cancellationToken) 18 | where TNotification : INotification; 19 | } 20 | 21 | public sealed class Mediator(IServiceProvider serviceProvider) : IMediator 22 | { 23 | public TResponse Send(IRequest request, 24 | CancellationToken cancellationToken) where TRequest : class, IRequest, new() 25 | { 26 | return serviceProvider.GetRequiredService>() 27 | .Handle(Unsafe.As(request), cancellationToken); 28 | } 29 | 30 | public IAsyncEnumerable CreateStream(IStreamRequest request, 31 | CancellationToken cancellationToken) where TRequest : class, IStreamRequest, new() 32 | { 33 | return serviceProvider.GetRequiredService>() 34 | .Handle(Unsafe.As(request), cancellationToken); 35 | } 36 | 37 | public async ValueTask Publish(TNotification request, CancellationToken cancellationToken) where TNotification : INotification 38 | { 39 | var notificationsInDi = serviceProvider.GetRequiredService>>(); 40 | 41 | var notifications = Unsafe.As[]>(notificationsInDi); 42 | foreach (var notification in notifications) 43 | { 44 | var valueTask = notification.Handle(request, cancellationToken); 45 | if (valueTask.IsCompletedSuccessfully is false) 46 | { 47 | await valueTask; 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/DispatchR/Requests/Notification/INotification.cs: -------------------------------------------------------------------------------- 1 | namespace DispatchR.Requests.Notification; 2 | 3 | public interface INotification; -------------------------------------------------------------------------------- /src/DispatchR/Requests/Notification/INotificationHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using DispatchR.Requests.Send; 3 | 4 | namespace DispatchR.Requests.Notification; 5 | 6 | public interface INotificationHandler : IRequestHandler where TRequestEvent : INotification 7 | { 8 | ValueTask Handle(TRequestEvent request, CancellationToken cancellationToken); 9 | } -------------------------------------------------------------------------------- /src/DispatchR/Requests/Send/IPipelineBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace DispatchR.Requests.Send; 4 | 5 | public interface IPipelineBehavior : IRequestHandler 6 | where TRequest : class, IRequest, new() 7 | { 8 | public IRequestHandler NextPipeline { get; set; } 9 | 10 | void IRequestHandler.SetNext(object handler) 11 | { 12 | NextPipeline = Unsafe.As>(handler); 13 | } 14 | } -------------------------------------------------------------------------------- /src/DispatchR/Requests/Send/IRequest.cs: -------------------------------------------------------------------------------- 1 | namespace DispatchR.Requests.Send; 2 | 3 | public interface IRequest; 4 | 5 | public interface IRequest : IRequest where TRequest : class, new(); -------------------------------------------------------------------------------- /src/DispatchR/Requests/Send/IRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace DispatchR.Requests.Send; 2 | 3 | public interface IRequestHandler 4 | { 5 | internal void SetNext(object handler) 6 | { 7 | } 8 | } 9 | public interface IRequestHandler : IRequestHandler where TRequest : class, IRequest, new() 10 | { 11 | TResponse Handle(TRequest request, CancellationToken cancellationToken); 12 | } -------------------------------------------------------------------------------- /src/DispatchR/Requests/Stream/IStreamPipelineBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using DispatchR.Requests.Send; 3 | 4 | namespace DispatchR.Requests.Stream; 5 | 6 | public interface IStreamPipelineBehavior : IStreamRequestHandler 7 | where TRequest : class, IStreamRequest, new() 8 | { 9 | public IStreamRequestHandler NextPipeline { get; set; } 10 | 11 | void IRequestHandler.SetNext(object handler) 12 | { 13 | NextPipeline = Unsafe.As>(handler); 14 | } 15 | } -------------------------------------------------------------------------------- /src/DispatchR/Requests/Stream/IStreamRequest.cs: -------------------------------------------------------------------------------- 1 | namespace DispatchR.Requests.Stream; 2 | 3 | public interface IStreamRequest; 4 | 5 | public interface IStreamRequest : IStreamRequest where TRequest : class, new(); -------------------------------------------------------------------------------- /src/DispatchR/Requests/Stream/IStreamRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests.Send; 2 | 3 | namespace DispatchR.Requests.Stream; 4 | 5 | public interface IStreamRequestHandler : IRequestHandler where TRequest : class, IStreamRequest, new() 6 | { 7 | IAsyncEnumerable Handle(TRequest request, CancellationToken cancellationToken); 8 | } -------------------------------------------------------------------------------- /src/Sample/DispatchR/Notification/MultiHandlersNotification.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests.Notification; 2 | 3 | namespace Sample.DispatchR.Notification; 4 | 5 | public sealed record MultiHandlersNotification(Guid Id) : INotification; -------------------------------------------------------------------------------- /src/Sample/DispatchR/Notification/NotificationOneHandler.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests.Notification; 2 | using Sample.MediatR.Notification; 3 | 4 | namespace Sample.DispatchR.Notification; 5 | 6 | public sealed class NotificationOneHandler(ILogger logger) : INotificationHandler 7 | { 8 | public ValueTask Handle(MultiHandlersNotification request, CancellationToken cancellationToken) 9 | { 10 | logger.LogInformation("Received notification one"); 11 | return ValueTask.CompletedTask; 12 | } 13 | } -------------------------------------------------------------------------------- /src/Sample/DispatchR/Notification/NotificationThreeHandler.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests.Notification; 2 | using Sample.MediatR.Notification; 3 | 4 | namespace Sample.DispatchR.Notification; 5 | 6 | public sealed class NotificationThreeHandler(ILogger logger) : INotificationHandler 7 | { 8 | public ValueTask Handle(MultiHandlersNotification request, CancellationToken cancellationToken) 9 | { 10 | logger.LogInformation("Received notification three"); 11 | return ValueTask.CompletedTask; 12 | } 13 | } -------------------------------------------------------------------------------- /src/Sample/DispatchR/Notification/NotificationTwoHandler.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests.Notification; 2 | using Sample.MediatR.Notification; 3 | 4 | namespace Sample.DispatchR.Notification; 5 | 6 | public sealed class NotificationTwoHandler(ILogger logger) : INotificationHandler 7 | { 8 | public ValueTask Handle(MultiHandlersNotification request, CancellationToken cancellationToken) 9 | { 10 | logger.LogInformation("Received notification two"); 11 | return ValueTask.CompletedTask; 12 | } 13 | } -------------------------------------------------------------------------------- /src/Sample/DispatchR/SendRequest/FirstPipelineBehavior.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests; 2 | using DispatchR.Requests.Send; 3 | 4 | namespace Sample.DispatchR.SendRequest; 5 | 6 | public class FirstPipelineBehavior(ILogger logger) : IPipelineBehavior> 7 | { 8 | public required IRequestHandler> NextPipeline { get; set; } 9 | public ValueTask Handle(Ping request, CancellationToken cancellationToken) 10 | { 11 | logger.LogInformation("First Request Pipeline"); 12 | return NextPipeline.Handle(request, cancellationToken); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Sample/DispatchR/SendRequest/Ping.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests; 2 | using DispatchR.Requests.Send; 3 | 4 | namespace Sample.DispatchR.SendRequest 5 | { 6 | public class Ping : IRequest> 7 | { 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /src/Sample/DispatchR/SendRequest/PingHandler.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests; 2 | using DispatchR.Requests.Send; 3 | 4 | namespace Sample.DispatchR.SendRequest 5 | { 6 | public class PingHandler(ILogger logger) : IRequestHandler> 7 | { 8 | public ValueTask Handle(Ping request, CancellationToken cancellationToken) 9 | { 10 | logger.LogInformation("Received in request handler"); 11 | return ValueTask.FromResult(1); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Sample/DispatchR/SendRequest/SecondPipelineBehavior.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests; 2 | using DispatchR.Requests.Send; 3 | 4 | namespace Sample.DispatchR.SendRequest 5 | { 6 | public class SecondPipelineBehavior(ILogger logger) : IPipelineBehavior> 7 | { 8 | public required IRequestHandler> NextPipeline { get; set; } 9 | 10 | public ValueTask Handle(Ping request, CancellationToken cancellationToken) 11 | { 12 | logger.LogInformation("Second Request Pipeline"); 13 | return NextPipeline.Handle(request, cancellationToken); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Sample/DispatchR/StreamRequest/CounterPipelineStreamHandler.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Runtime.CompilerServices; 3 | using DispatchR.Requests.Stream; 4 | 5 | namespace Sample.DispatchR.StreamRequest; 6 | 7 | public class CounterPipelineStreamHandler : IStreamPipelineBehavior 8 | { 9 | public required IStreamRequestHandler NextPipeline { get; set; } 10 | 11 | public async IAsyncEnumerable Handle(CounterStreamRequest request, [EnumeratorCancellation] CancellationToken cancellationToken) 12 | { 13 | await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false)) 14 | { 15 | yield return response; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Sample/DispatchR/StreamRequest/CounterStreamHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using DispatchR.Requests.Stream; 3 | 4 | namespace Sample.DispatchR.StreamRequest; 5 | 6 | public class CounterStreamHandler(IWebHostEnvironment webHostEnvironment) : IStreamRequestHandler 7 | { 8 | public async IAsyncEnumerable Handle(CounterStreamRequest request, [EnumeratorCancellation] CancellationToken cancellationToken) 9 | { 10 | await using var allLines = File 11 | .ReadLinesAsync(webHostEnvironment.ContentRootPath + "/BigFile.txt", cancellationToken) 12 | .GetAsyncEnumerator(cancellationToken); 13 | 14 | while (cancellationToken.IsCancellationRequested is false) 15 | { 16 | await allLines.MoveNextAsync(); 17 | yield return allLines.Current; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Sample/DispatchR/StreamRequest/CounterStreamRequest.cs: -------------------------------------------------------------------------------- 1 | using DispatchR.Requests.Stream; 2 | 3 | namespace Sample.DispatchR.StreamRequest; 4 | 5 | public class CounterStreamRequest : IStreamRequest { } -------------------------------------------------------------------------------- /src/Sample/MediatR/Notification/MultiHandlersNotification.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Sample.MediatR.Notification; 4 | 5 | public sealed record MultiHandlersNotification(Guid Id) : INotification; -------------------------------------------------------------------------------- /src/Sample/MediatR/Notification/NotificationOneHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Sample.MediatR.Notification; 4 | 5 | public sealed class NotificationOneHandler(ILogger logger) : INotificationHandler 6 | { 7 | public Task Handle(MultiHandlersNotification notification, 8 | CancellationToken cancellationToken) 9 | { 10 | logger.LogInformation("Received notification one"); 11 | return Task.CompletedTask; 12 | } 13 | } -------------------------------------------------------------------------------- /src/Sample/MediatR/Notification/NotificationThreeHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Sample.MediatR.Notification; 4 | 5 | public sealed class NotificationThreeHandler(ILogger logger) : INotificationHandler 6 | { 7 | public Task Handle(MultiHandlersNotification notification, 8 | CancellationToken cancellationToken) 9 | { 10 | logger.LogInformation("Received notification three"); 11 | return Task.CompletedTask; 12 | } 13 | } -------------------------------------------------------------------------------- /src/Sample/MediatR/Notification/NotificationTwoHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Sample.MediatR.Notification; 4 | 5 | public sealed class NotificationTwoHandler(ILogger logger) : INotificationHandler 6 | { 7 | public Task Handle(MultiHandlersNotification notification, 8 | CancellationToken cancellationToken) 9 | { 10 | logger.LogInformation("Received notification two"); 11 | return Task.CompletedTask; 12 | } 13 | } -------------------------------------------------------------------------------- /src/Sample/MediatR/SendRequest/FirstPipelineBehavior.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Sample.MediatR.SendRequest; 4 | 5 | public class FirstPipelineBehavior(ILogger logger) : IPipelineBehavior 6 | { 7 | public async Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) 8 | { 9 | logger.LogInformation("First Request Pipeline"); 10 | return await next(cancellationToken); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Sample/MediatR/SendRequest/Ping.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Sample.MediatR.SendRequest; 4 | 5 | public class Ping : IRequest 6 | { 7 | } -------------------------------------------------------------------------------- /src/Sample/MediatR/SendRequest/PingHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Sample.MediatR.SendRequest; 4 | 5 | public class PingHandler(ILogger logger) : IRequestHandler 6 | { 7 | public Task Handle(Ping request, CancellationToken cancellationToken) 8 | { 9 | logger.LogInformation("Received in request handler"); 10 | return Task.FromResult(1); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Sample/MediatR/SendRequest/SecondPipelineBehavior.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Sample.MediatR.SendRequest; 4 | 5 | public class SecondPipelineBehavior(ILogger logger) : IPipelineBehavior 6 | { 7 | public async Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) 8 | { 9 | logger.LogInformation("Second Request Pipeline"); 10 | return await next(cancellationToken); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Sample/MediatR/StreamRequest/CounterPipelineStreamHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using MediatR; 3 | 4 | namespace Sample.MediatR.StreamRequest; 5 | 6 | public class CounterPipelineStreamHandler : IStreamPipelineBehavior 7 | { 8 | public async IAsyncEnumerable Handle(CounterStreamRequest request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) 9 | { 10 | await foreach (var response in next().WithCancellation(cancellationToken).ConfigureAwait(false)) 11 | { 12 | yield return response; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Sample/MediatR/StreamRequest/CounterStreamHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using MediatR; 3 | 4 | namespace Sample.MediatR.StreamRequest; 5 | 6 | public class CounterStreamHandler(IWebHostEnvironment webHostEnvironment) : IStreamRequestHandler 7 | { 8 | public async IAsyncEnumerable Handle(CounterStreamRequest request, [EnumeratorCancellation] CancellationToken cancellationToken) 9 | { 10 | await using var allLines = File 11 | .ReadLinesAsync(webHostEnvironment.ContentRootPath + "/BigFile.txt", cancellationToken) 12 | .GetAsyncEnumerator(cancellationToken); 13 | 14 | while (cancellationToken.IsCancellationRequested is false) 15 | { 16 | await allLines.MoveNextAsync(); 17 | yield return allLines.Current; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Sample/MediatR/StreamRequest/CounterStreamRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Sample.MediatR.StreamRequest; 4 | 5 | public class CounterStreamRequest : IStreamRequest { } -------------------------------------------------------------------------------- /src/Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using DispatchR; 3 | using DispatchR.Requests; 4 | using Sample; 5 | using Sample.MediatR.Notification; 6 | using Scalar.AspNetCore; 7 | using DispatchRSample = Sample.DispatchR.SendRequest; 8 | using DispatchRStreamSample = Sample.DispatchR.StreamRequest; 9 | using DispatchRNotificationSample = Sample.DispatchR.Notification; 10 | using MediatRSample = Sample.MediatR.SendRequest; 11 | using MediatRStreamSample = Sample.MediatR.StreamRequest; 12 | using MediatRNotificationSample = Sample.MediatR.Notification; 13 | 14 | var builder = WebApplication.CreateBuilder(args); 15 | 16 | // Add services to the container. 17 | // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi 18 | builder.Services.AddOpenApi(); 19 | 20 | builder.Services.AddMediatR(cfg => 21 | { 22 | cfg.Lifetime = ServiceLifetime.Scoped; 23 | cfg.RegisterServicesFromAssemblies(typeof(MediatRSample.Ping).Assembly); 24 | }); 25 | builder.Services.AddTransient, MediatRSample.FirstPipelineBehavior>(); 26 | builder.Services.AddTransient, MediatRSample.SecondPipelineBehavior>(); 27 | builder.Services.AddTransient, MediatRStreamSample.CounterPipelineStreamHandler>(); 28 | 29 | builder.Services.AddDispatchR(typeof(DispatchRSample.Ping).Assembly); 30 | 31 | var app = builder.Build(); 32 | 33 | if (app.Environment.IsDevelopment()) 34 | { 35 | app.MapOpenApi(); 36 | app.MapScalarApiReference(); 37 | } 38 | 39 | app.UseHttpsRedirection(); 40 | 41 | var summaries = new[] 42 | { 43 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 44 | }; 45 | 46 | app.MapGet("/weatherforecast", () => 47 | { 48 | var forecast = Enumerable.Range(1, 5).Select(index => 49 | new WeatherForecast 50 | ( 51 | DateOnly.FromDateTime(DateTime.Now.AddDays(index)), 52 | Random.Shared.Next(-20, 55), 53 | summaries[Random.Shared.Next(summaries.Length)] 54 | )) 55 | .ToArray(); 56 | return forecast; 57 | }) 58 | .WithName("GetWeatherForecast"); 59 | 60 | app.MapGet("/Send/MediatR", (MediatR.IMediator mediatR, CancellationToken cancellationToken) 61 | => mediatR.Send(new MediatRSample.Ping(), cancellationToken)) 62 | .WithName("SendInMediatRWithPipeline"); 63 | 64 | app.MapGet("/Send/DispatchR", (DispatchR.Requests.IMediator dispatchR, CancellationToken cancellationToken) 65 | => dispatchR.Send(new DispatchRSample.Ping(), cancellationToken)) 66 | .WithName("SendInDispatchRWithPipeline"); 67 | 68 | app.MapGet("/Stream/MediatR", async (MediatR.IMediator mediatR, ILogger logger) => 69 | { 70 | CancellationTokenSource cts = new(); 71 | int count = 0; 72 | await foreach (var item in mediatR.CreateStream(new MediatRStreamSample.CounterStreamRequest(), cts.Token)) 73 | { 74 | count++; 75 | if (item.Contains("CodeWithHSN")) 76 | { 77 | cts.Cancel(); 78 | } 79 | 80 | logger.LogInformation($"stream count in MediatR: {count}"); 81 | } 82 | 83 | return "It works"; 84 | }); 85 | 86 | app.MapGet("/Stream/DispatchR", async (DispatchR.Requests.IMediator dispatchR, ILogger logger) => 87 | { 88 | CancellationTokenSource cts = new(); 89 | int count = 0; 90 | await foreach (var item in dispatchR.CreateStream(new DispatchRStreamSample.CounterStreamRequest(), cts.Token)) 91 | { 92 | count++; 93 | if (item.Contains("CodeWithHSN")) 94 | { 95 | cts.Cancel(); 96 | } 97 | 98 | logger.LogInformation($"stream count in DispatchR: {count}"); 99 | } 100 | 101 | return "It works"; 102 | }); 103 | 104 | app.MapGet("/Notification/MediatR", async (MediatR.IMediator mediator, ILogger logger) => 105 | { 106 | await mediator.Publish(new MediatRNotificationSample.MultiHandlersNotification(Guid.Empty)); 107 | return "It works"; 108 | }); 109 | 110 | app.MapGet("/Notification/DispatchR", async (DispatchR.Requests.IMediator mediator, ILogger logger) => 111 | { 112 | await mediator.Publish(new DispatchRNotificationSample.MultiHandlersNotification(Guid.Empty), CancellationToken.None); 113 | return "It works"; 114 | }); 115 | 116 | app.Run(); 117 | 118 | record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) 119 | { 120 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 121 | } -------------------------------------------------------------------------------- /src/Sample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": false, 8 | "applicationUrl": "http://localhost:5070", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | }, 13 | "https": { 14 | "commandName": "Project", 15 | "dotnetRunMessages": true, 16 | "launchBrowser": false, 17 | "applicationUrl": "https://localhost:7160;http://localhost:5070", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Sample/Sample.http: -------------------------------------------------------------------------------- 1 | @Sample_HostAddress = http://localhost:5070 2 | 3 | GET {{Sample_HostAddress}}/weatherforecast/ 4 | Accept: application/json 5 | 6 | ### 7 | -------------------------------------------------------------------------------- /src/Sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | --------------------------------------------------------------------------------