Fast mediator for handling requests, commands, notifications, and streams with ValueTask and ordered pipelines
$ dotnet add package MitMediatorIRequest<TResponse> and IRequest (void-style)IRequestHandler<,> and IRequestHandler<>ValueTask (for modern, efficient handlers) and Task (for compatibility with MediatR-style handlers)IPipelineBehavior<TRequest, TResponse>. A pipeline behavior can be defined for all or specific request types.INotificationHandler with serial and parallel publishingAddMitMediator() or assembly scanningIStreamRequestHandle and IStreamPipelineBehaviordotnet add package MitMediator -v 10.0.0
This example shows a basic setup of MitMediator that demonstrates:
using Microsoft.Extensions.DependencyInjection;
using MitMediator;
var services = new ServiceCollection();
services
.AddMitMediator(typeof(PingRequestHandler).Assembly)
.AddTransient(typeof(IPipelineBehavior<,>), typeof(HeightBehavior<,>))
.AddTransient(typeof(IPipelineBehavior<,>), typeof(LowBehavior<,>));
var provider = services.BuildServiceProvider();
var mediator = provider.GetRequiredService<IMediator>();
// HeightBehavior: Handling PingRequest
// LowBehavior: Handling PingRequest
// PingRequestHandler: Pong
// NotificationHandler: Notification!
// LowBehavior: Handled PingRequest
// HeightBehavior: Handled PingRequest
string result = await mediator.SendAsync<PingRequest, string>(new PingRequest(), CancellationToken.None);
Console.WriteLine(result); //Pong result
public class PingRequest : IRequest<string> { }
public class PingRequestHandler : IRequestHandler<PingRequest, string>
{
private readonly IMediator _mediator;
public PingRequestHandler(IMediator mediator)
{
_mediator = mediator;
}
public ValueTask<string> HandleAsync(PingRequest request, CancellationToken cancellationToken)
{
Console.WriteLine("PingRequestHandler: Pong");
_mediator.PublishAsync(new Notification(), cancellationToken);
return ValueTask.FromResult("Pong result");
}
}
public class LowBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
public async ValueTask<TResponse> HandleAsync(TRequest request, IRequestHandlerNext<TRequest, TResponse> next, CancellationToken cancellationToken)
{
Console.WriteLine($"LowBehavior: Handling {typeof(TRequest).Name}");
var result = await next.InvokeAsync(request, cancellationToken);
Console.WriteLine($"LowBehavior: Handled {typeof(TRequest).Name}");
return result;
}
}
public class HeightBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
public async ValueTask<TResponse> HandleAsync(TRequest request, IRequestHandlerNext<TRequest, TResponse> next, CancellationToken cancellationToken)
{
Console.WriteLine($"HeightBehavior: Handling {typeof(TRequest).Name}");
var result = await next.InvokeAsync(request, cancellationToken);
Console.WriteLine($"HeightBehavior: Handled {typeof(TRequest).Name}");
return result;
}
}
public class Notification : INotification{}
public class NotificationHandler : INotificationHandler<Notification>
{
public ValueTask HandleAsync(Notification notification, CancellationToken cancellationToken)
{
Console.WriteLine($"NotificationHandler: Notification!");
return ValueTask.CompletedTask;
}
}
To use
Taskinstead ofValueTaskfor handlers, reference the MitMediator.Tasks namespace
You can reuse your existing handlers with minimal modifications — just update the namespaces and registration calls
MitMediator package dotnet add package MitMediator -v 10.0.0MediatR with MitMediatorMediatR with MitMediator (andMitMediator.Tasks for Task result).AddMediatR(...) with .AddMitMediator()INotificationHandler, use ValueTask instead of TaskIPipelineBehavior, use ValueTask instead of Task and IRequestHandlerNext<TRequest, TResponse> instead of RequestHandlerDelegate<TResponse>. Use next.InvokeAsync(request, cancellationToken) for next pipeTask<Unit> instead of Task (return Unit.Value)mediator.Send(request, ct) to mediator.SendAsync<TRequset, TResponse>(request, ct) (or mediator.Send<TRequset, TResponse>(request, ct) for Task result)Use
SendAsync<TRequset, TResponse>(request, ct)for best performance orSend(request, ct)for backward compatibility with MediatR-style semantics
MitMediator is designed to feel familiar for those coming from MediatR. Core concepts like IRequest, IRequestHandle, and pipeline behaviors are preserved — but with a cleaner interface and support for ValueTask out of the box.
| Mediator | Method | Mean (ns) | Allocated (B) |
|---|---|---|---|
| MediatR | Send (return result) | 91.64 | 272 |
| MitMediator | SendAsync (return result) | 40.60 | 0 |
| MediatR | Send (return result, use behaviors) | 191.82 | 800 |
| MitMediator | SendAsync (return result, use behaviors) | 40.49 | 0 |
| MediatR | Send (Return void) | 77.73 | 128 |
| MitMediator | SendAsync (Return void) | 36.73 | 0 |
| MediatR | Publish | 138.79 | 592 |
| MitMediator | PublishAsync | 51.74 | 32 |
| MediatR | CreateStream (return stream, use behavior) | 809.3 | 1168 |
| MitMediator | CreateStream (return stream, use behavior) | 187.0 | 112 |
| Feature | MitMediator | MediatR |
|---|---|---|
| Return types | ValueTask (default, allocation-friendly) | Task (standard async support) |
| Send methods | Strongly typed requests (SendAsync<TRequest, TResponse>) | Loosely typed requests (Send(request)) |
| DI Registration | AddMitMediator() with optional assembly scanning | AddMediatR() with assemblies explicitly specified |
| Extensibility | Designed for lightweight extension and customization | More opinionated; extensibility requires deeper integration |
| Notification publishing | Serial and parallel | Only serial out of the box |
| Performance Focus | Async-first, zero-allocation for ValueTask | Flexible but not optimized for ValueTask |
| License & Availability | MIT | Reciprocal Public License 1.5 (RPL1.5) and commercial license |
MIT