DispatchR.Mediator.Abstractions
2.0.0
dotnet add package DispatchR.Mediator.Abstractions --version 2.0.0
NuGet\Install-Package DispatchR.Mediator.Abstractions -Version 2.0.0
<PackageReference Include="DispatchR.Mediator.Abstractions" Version="2.0.0" />
<PackageVersion Include="DispatchR.Mediator.Abstractions" Version="2.0.0" />
<PackageReference Include="DispatchR.Mediator.Abstractions" />
paket add DispatchR.Mediator.Abstractions --version 2.0.0
#r "nuget: DispatchR.Mediator.Abstractions, 2.0.0"
#:package DispatchR.Mediator.Abstractions@2.0.0
#addin nuget:?package=DispatchR.Mediator.Abstractions&version=2.0.0
#tool nuget:?package=DispatchR.Mediator.Abstractions&version=2.0.0
<img src="./icon-mini.png" width="25">ispatchR ๐
A High-Performance Mediator Implementation for .NET :trollface:
** Minimal memory footprint. Blazing-fast execution. **
If you're curious to see the power of this library, check out the benchmark comparing MediatR vs Mediator Source Generator vs DispatchR.
โก Key Features
- Built entirely on top of Dependency Injection
- Zero runtime reflection after registration
- Choose your handler return type:
Task
,ValueTask
, orSynchronous Method
- Allocates nothing on the heap โ ideal for high-throughput scenarios
- Outperforms existing solutions in most real-world benchmarks
- Seamlessly compatible with MediatR โ migrate with minimal effort
- Include or exclude a set of handlers from an assembly โ ideal for use with Aspire
- Currently supports
- Simple Request:
IRequest<TRquest, TResponse>
IRequestHandler<TRequest, TResponse>
IPipelineBehavior<TRequest, TResponse>
- Stream Request:
IStreamRequest<TRquest, TResponse>
IStreamRequestHandler<TRequest, TResponse>
IStreamPipelineBehavior<TRequest, TResponse>
- Notifications:
INotification
INotificationHandler<TRequestEvent>
- Simple Request:
๐ก Tip: If you're looking for a mediator with the raw performance of hand-written code, DispatchR is built for you.
โจ How to install?
dotnet add package DispatchR.Mediator
You can also separately add only the abstractions, which include the interfaces, in another layer:
dotnet add package DispatchR.Mediator.Abstractions
Syntax Comparison: DispatchR vs MediatR
In the following, you will see the key differences and implementation details between MediatR and DispatchR.
Request Definition
MediatR
public sealed class PingMediatR : IRequest<int> { }
DispatchR
- Sending
TRequest
toIRequest
- Precise selection of output for both
async
andsync
handlers- Ability to choose between
Task
andValueTask
- Ability to choose between
public sealed class PingDispatchR : IRequest<PingDispatchR, ValueTask<int>> { }
Handler Definition
MediatR
public sealed class PingHandlerMediatR : IRequestHandler<PingMediatR, int>
{
public Task<int> Handle(PingMediatR request, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
}
DispatchR (Don't change)
public sealed class PingHandlerDispatchR : IRequestHandler<PingDispatchR, ValueTask<int>>
{
public ValueTask<int> Handle(PingDispatchR request, CancellationToken cancellationToken)
{
return ValueTask.FromResult(0);
}
}
Pipeline Behavior
MediatR
public sealed class LoggingBehaviorMediat : IPipelineBehavior<PingMediatR, int>
{
public Task<int> Handle(PingMediatR request, RequestHandlerDelegate<int> next, CancellationToken cancellationToken)
{
return next(cancellationToken);
}
}
DispatchR
- Use Chain of Responsibility pattern
public sealed class LoggingBehaviorDispatchR : IPipelineBehavior<PingDispatchR, ValueTask<int>>
{
public required IRequestHandler<PingDispatchR, ValueTask<int>> NextPipeline { get; set; }
public ValueTask<int> Handle(PingDispatchR request, CancellationToken cancellationToken)
{
return NextPipeline.Handle(request, cancellationToken);
}
}
Generic pipeline behavior DispatchR
- For every kind of return type โ
Task
,ValueTask
, or synchronous methods โ you need to write a generic pipeline behavior. However, you don't need a separate pipeline for each request. As shown in the code below, this is a GenericPipeline for requests that return aValueTask
.
public class GenericPipelineBehavior<TRequest, TResponse>() : IPipelineBehavior<TRequest, ValueTask<TResponse>>
where TRequest : class, IRequest<TRequest, ValueTask<TResponse>>
{
public required IRequestHandler<TRequest, ValueTask<TResponse>> NextPipeline { get; set; }
public ValueTask<TResponse> Handle(TRequest request, CancellationToken cancellationToken)
{
// You can add custom logic here, like logging or validation
// This pipeline behavior can be used for any request type
return NextPipeline.Handle(request, cancellationToken);
}
}
Summary
- DispatchR lets the request itself define the return type.
- No runtime reflection in DispatchR โ it's optimized for performance.
- No static behavior chains โ pipelines are chained via DI and handler wiring.
- Supports
void
,Task
, orValueTask
as return types.
Ideal for high-performance .NET applications.
Stream Request Definition
MediatR Stream
public sealed class CounterStreamRequestMediatR : IStreamRequest<int> { }
DispatchR
- Sending
TRequest
toIStreamRequest
public sealed class CounterStreamRequestDispatchR : IStreamRequest<PingDispatchR, ValueTask<int>> { }
Stream Handler Definition
Stream Handler MediatR
public sealed class CounterStreamHandlerMediatR : IStreamRequestHandler<CounterStreamRequestMediatR, int>
{
public async IAsyncEnumerable<int> Handle(CounterStreamRequestMediatR request, CancellationToken cancellationToken)
{
yield return 1;
}
}
Stream Handler DispatchR (Don't change)
public sealed class CounterStreamHandlerDispatchR : IStreamRequestHandler<CounterStreamHandlerDispatchR, int>
{
public async IAsyncEnumerable<int> Handle(CounterStreamHandlerDispatchR request, CancellationToken cancellationToken)
{
yield return 1;
}
}
Stream Pipeline Behavior
Stream Pipeline MediatR
public sealed class CounterPipelineStreamHandler : IStreamPipelineBehavior<CounterStreamRequestMediatR, string>
{
public async IAsyncEnumerable<string> Handle(CounterStreamRequestMediatR request, StreamHandlerDelegate<string> next, [EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var response in next().WithCancellation(cancellationToken).ConfigureAwait(false))
{
yield return response;
}
}
}
Stream Pipeline DispatchR
- Use Chain of Responsibility pattern
public sealed class CounterPipelineStreamHandler : IStreamPipelineBehavior<CounterStreamRequestDispatchR, string>
{
public required IStreamRequestHandler<CounterStreamRequestDispatchR, string> NextPipeline { get; set; }
public async IAsyncEnumerable<string> Handle(CounterStreamRequestDispatchR request, [EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false))
{
yield return response;
}
}
}
Generic stream pipeline behavior DispatchR
public class GenericStreamPipelineBehavior<TRequest, TResponse>() : IStreamPipelineBehavior<TRequest, TResponse>
where TRequest : class, IStreamRequest<TRequest, TResponse>
{
public IStreamRequestHandler<TRequest, TResponse> NextPipeline { get; set; }
public async IAsyncEnumerable<TResponse> Handle(TRequest request, CancellationToken cancellationToken)
{
await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false))
{
yield return response;
}
}
}
Notification
Notification MediatR
public sealed record Event(Guid Id) : INotification;
public sealed class EventHandler(ILogger<Event> logger) : INotificationHandler<Event>
{
public Task Handle(Event notification, CancellationToken cancellationToken)
{
logger.LogInformation("Received notification");
return Task.CompletedTask;
}
}
Stream Pipeline DispatchR
- Use ValueTask
public sealed record Event(Guid Id) : INotification;
public sealed class EventHandler(ILogger<Event> logger) : INotificationHandler<Event>
{
public ValueTask Handle(Event notification, CancellationToken cancellationToken)
{
logger.LogInformation("Received notification");
return ValueTask.CompletedTask;
}
}
โก How DispatchR Achieves High Performance
DispatchR is designed with one goal in mind: maximize performance with minimal memory usage. Here's how it accomplishes that:
What Happens Inside the Send
Method?
public TResponse Send<TRequest, TResponse>(IRequest<TRequest, TResponse> request,
CancellationToken cancellationToken) where TRequest : class, IRequest
{
return serviceProvider
.GetRequiredService<IRequestHandler<TRequest, TResponse>>()
.Handle(Unsafe.As<TRequest>(request), cancellationToken);
}
What Happens Inside the CreateStream
Method?
public IAsyncEnumerable<TResponse> CreateStream<TRequest, TResponse>(IStreamRequest<TRequest, TResponse> request,
CancellationToken cancellationToken) where TRequest : class, IStreamRequest
{
return serviceProvider.GetRequiredService<IStreamRequestHandler<TRequest, TResponse>>()
.Handle(Unsafe.As<TRequest>(request), cancellationToken);
}
Only the handler is resolved and directly invoked!
What Happens Inside the Publish
Method?
public async ValueTask Publish<TNotification>(TNotification request, CancellationToken cancellationToken) where TNotification : INotification
{
var notificationsInDi = serviceProvider.GetRequiredService<IEnumerable<INotificationHandler<TNotification>>>();
var notifications = Unsafe.As<INotificationHandler<TNotification>[]>(notificationsInDi);
foreach (var notification in notifications)
{
var valueTask = notification.Handle(request, cancellationToken);
if (valueTask.IsCompletedSuccessfully is false) // <-- Handle sync notifications
{
await valueTask;
}
}
}
But the real magic happens behind the scenes when DI resolves the handler dependency:
๐ก Tips:
We cache the handler using DI, so in scoped scenarios, the object is constructed only once and reused afterward.
In terms of Dependency Injection (DI), everything in Requests is an IRequestHandler, it's just the keys that differ. 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.
services.AddScoped(handlerInterface, sp =>
{
var pipelinesWithHandler = Unsafe
.As<IRequestHandler[]>(sp.GetKeyedServices<IRequestHandler>(key));
IRequestHandler lastPipeline = pipelinesWithHandler[0];
for (int i = 1; i < pipelinesWithHandler.Length; i++)
{
var pipeline = pipelinesWithHandler[i];
pipeline.SetNext(lastPipeline);
lastPipeline = pipeline;
}
return lastPipeline;
});
This elegant design chains pipeline behaviors at resolution time โ no static lists, no reflection, no magic.
๐ชด How to use?
It's simple! Just use the following code:
builder.Services.AddDispatchR(typeof(MyCommand).Assembly, withPipelines: true, withNotifications: true);
This code will automatically register all pipelines by default. If you need to register them in a specific order, you can pass the order via ConfigurationOptions, as shown in the example below. <br> Additionally, you can include or exclude specific handlers from an assembly โ which is especially useful when working with Aspire. You can also check the Samples section to see the Aspire-specific example.
builder.Services.AddDispatchR(options =>
{
options.Assemblies.Add(typeof(DispatchRSample.Ping).Assembly);
options.RegisterPipelines = true;
options.RegisterNotifications = true;
options.PipelineOrder =
[
typeof(DispatchRSample.FirstPipelineBehavior),
typeof(DispatchRSample.SecondPipelineBehavior),
typeof(DispatchRSample.GenericPipelineBehavior<,>)
];
options.IncludeHandlers = null;
options.ExcludeHandlers = null;
});
If you need additional customization, you can either add them manually or write your own reflection logic:
builder.Services.AddDispatchR(typeof(MyCommand).Assembly, withPipelines: false, withNotifications: false);
builder.Services.AddScoped<IPipelineBehavior<MyCommand, int>, PipelineBehavior>();
builder.Services.AddScoped<IPipelineBehavior<MyCommand, int>, ValidationBehavior>();
builder.Services.AddScoped<IStreamPipelineBehavior<MyStreamCommand, int>, ValidationBehavior>();
builder.Services.AddScoped<INotificationHandler<Event>, EventHandler>();
๐ก Key Notes:
- Automatic pipeline and notification registration is enabled by default
- Manual registration allows for custom pipeline or notification ordering
- You can implement custom reflection if needed
๐งช Bechmark Result:
This benchmark was conducted using MediatR version 12.5.0 and the stable release of Mediator Source Generator, version 2.1.7. Version 3 of Mediator Source Generator was excluded due to significantly lower performance.
Send Request
1. MediatR vs Mediator Source Generator vs DispatchR With Pipeline
2. MediatR vs Mediator Source Generator vs DispatchR Without Pipeline
Stream Request
1. MediatR vs Mediator Source Generator vs DispatchR With Pipeline
2. MediatR vs Mediator Source Generator vs DispatchR Without Pipeline
Notification
1. MediatR vs Mediator Source Generator vs DispatchR
โจ Contribute & Help Grow This Package! โจ
We welcome contributions to make this package even better! โค๏ธ
- Found a bug? โ Open an issue
- Have an idea? โ Suggest a feature
- Want to code? โ Submit a PR
Star History
<a href="https://www.star-history.com/#hasanxdev/DispatchR&Timeline"> <img src="https://api.star-history.com/svg?repos=hasanxdev/DispatchR&type=Timeline" width="400"/> </a>
Let's build something amazing together! ๐
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on DispatchR.Mediator.Abstractions:
Package | Downloads |
---|---|
DispatchR.Mediator
A fast, zero-alloc Mediator pattern alternative to MediatR in .NET โ minimal, blazing fast, and DI-friendly (Dependency Injection). |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
2.0.0 | 668 | 8/24/2025 |