HamedStack.MiniMediator is a lightweight, zero-dependency, performance-friendly mediator library built for .NET. It provides an abstraction for sending commands/queries and publishing events in a clean, decoupled way — inspired by MediatR but simplified for minimal overhead, making it easy to replace MediatR if you're looking for something more lightweight and flexible.
- ✅ Request/Response (Command-Query) pattern
- ✅ Notification pattern (Pub/Sub to multiple handlers)
- ✅ Pipeline behaviors (Middleware support)
- ✅ DI-ready with
IServiceCollection
extension - ✅ Runtime dynamic dispatch support
- ✅ No external package dependencies
- ✅ Similar API to MediatR, making it easy to replace
public class Ping : IRequest<string>
{
public string Message { get; set; }
}
public class PingHandler : IRequestHandler<Ping, string>
{
public Task<string> Handle(Ping request, CancellationToken cancellationToken)
{
return Task.FromResult($"Pong: {request.Message}");
}
}
public class UserCreated : INotification
{
public string UserName { get; set; }
}
public class SendWelcomeEmail : INotificationHandler<UserCreated>
{
public Task Handle(UserCreated notification, CancellationToken cancellationToken)
{
Console.WriteLine($"Welcome email sent to {notification.UserName}");
return Task.CompletedTask;
}
}
You can register the MiniMediator services into your DI container by calling AddMiniMediator
.
builder.Services.AddMiniMediator(typeof(Program).Assembly);
This will scan the given assembly for any request handlers and notification handlers.
If you don’t provide any assemblies, MiniMediator will automatically scan all non-system assemblies in the current AppDomain for handler implementations.
builder.Services.AddMiniMediator();
This is useful when you want to automatically discover and register all handlers in the app, eliminating the need to manually specify each assembly.
public class SomeService
{
private readonly IMediator _mediator;
public SomeService(IMediator mediator)
{
_mediator = mediator;
}
public async Task DoSomething()
{
// Sending request and getting response
var result = await _mediator.Send(new Ping { Message = "Hello" });
Console.WriteLine(result); // Output: Pong: Hello
// Publishing a notification
await _mediator.Publish(new UserCreated { UserName = "Hamed" });
}
}
Create a behavior to log request processing times:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var name = typeof(TRequest).Name;
Console.WriteLine($"[START] Handling {name}");
var response = await next();
Console.WriteLine($"[END] Finished {name}");
return response;
}
}
Add them to your DI container:
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
Pipeline behaviors can be stacked, and they execute in the order they are registered (outermost to innermost).
You can send dynamically typed requests or notifications:
object dynamicRequest = new Ping { Message = "Dynamic Hello" };
var result = await _mediator.Send(dynamicRequest); // returns object
object dynamicNotification = new UserCreated { UserName = "DynamicUser" };
await _mediator.Publish(dynamicNotification);
Interface | Description |
---|---|
IRequest |
Marker for requests with no return value |
IRequest<TResponse> |
Marker for requests with a return value |
INotification |
Marker for notifications (pub-sub) |
IRequestHandler<TRequest> |
Handler for void requests |
IRequestHandler<TRequest, TResponse> |
Handler for response-returning requests |
INotificationHandler<TNotification> |
Handler for notifications |
IPipelineBehavior<TRequest, TResponse> |
Middleware behavior in the pipeline |
IMediator |
Mediator interface to send/publish |