A lightweight, high-performance mediator pattern implementation for .NET 9+. Supports commands, queries, and domain events with built-in event deferral, validation, pipeline behaviors, and optional transaction scope support. Zero external dependencies.
$ dotnet add package AsyncMediatorA lightweight, high-performance mediator for .NET 9/10. Zero dependencies. Minimal allocations.
Install-Package AsyncMediator
services.AddScoped<IMediator>(sp => new Mediator(
type => sp.GetServices(type),
type => sp.GetRequiredService(type)));
services.AddTransient<ICommandHandler<CreateOrderCommand>, CreateOrderHandler>();
services.AddTransient<IEventHandler<OrderCreatedEvent>, SendConfirmationEmailHandler>();
services.AddTransient<IQuery<OrderSearchCriteria, List<Order>>, OrderQuery>();
public record CreateOrderCommand(Guid CustomerId, List<OrderItem> Items) : ICommand;
public class CreateOrderHandler(IMediator mediator, IOrderRepository repo)
: CommandHandler<CreateOrderCommand>(mediator)
{
protected override Task Validate(ValidationContext ctx, CancellationToken cancellationToken)
{
if (Command.Items.Count == 0)
ctx.AddError(nameof(Command.Items), "Order must have items");
return Task.CompletedTask;
}
protected override async Task<ICommandWorkflowResult> DoHandle(ValidationContext ctx, CancellationToken cancellationToken)
{
var order = await repo.Create(Command.CustomerId, Command.Items, cancellationToken);
Mediator.DeferEvent(new OrderCreatedEvent(order.Id));
return CommandWorkflowResult.Ok();
}
}
var result = await mediator.Send(new CreateOrderCommand(customerId, items));
if (!result.Success)
return BadRequest(result.ValidationResults);
// With cancellation support (e.g., from HttpContext.RequestAborted)
var result = await mediator.Send(command, cancellationToken);
// With criteria
public record OrderSearchCriteria(Guid? CustomerId, DateOnly? Since);
public class OrderQuery(IOrderRepository repo) : IQuery<OrderSearchCriteria, List<Order>>
{
public Task<List<Order>> Query(OrderSearchCriteria c, CancellationToken cancellationToken = default) =>
repo.Search(c.CustomerId, c.Since, cancellationToken);
}
var orders = await mediator.Query<OrderSearchCriteria, List<Order>>(criteria);
// Without criteria
public class CountryLookup(ICountryRepository repo) : ILookupQuery<List<Country>>
{
public Task<List<Country>> Query(CancellationToken cancellationToken = default) =>
repo.GetAll(cancellationToken);
}
var countries = await mediator.LoadList<List<Country>>();
Events are deferred during command execution and published after DoHandle completes.
public record OrderCreatedEvent(Guid OrderId) : IDomainEvent;
public class SendConfirmationEmailHandler(IEmailService email) : IEventHandler<OrderCreatedEvent>
{
public async Task Handle(OrderCreatedEvent e, CancellationToken cancellationToken = default) =>
await email.SendOrderConfirmation(e.OrderId, cancellationToken);
}Multiple handlers per event are supported. They execute in DI registration order.
TransactionScope is opt-in for performance. Override when ACID guarantees are needed:
public class TransferFundsHandler(IMediator mediator) : CommandHandler<TransferFundsCommand>(mediator)
{
protected override bool UseTransactionScope => true; // Enables TransactionScope
protected override async Task<ICommandWorkflowResult> DoHandle(ValidationContext ctx, CancellationToken cancellationToken)
{
await DebitAccount(Command.FromAccount, Command.Amount, cancellationToken);
await CreditAccount(Command.ToAccount, Command.Amount, cancellationToken);
Mediator.DeferEvent(new FundsTransferredEvent(Command.FromAccount, Command.ToAccount));
return CommandWorkflowResult.Ok();
}
}| Operation | Latency | Memory |
|---|---|---|
| Command (success) | ~170 ns | ~200 B |
| Command (10 concurrent) | ~1.7 μs | ~2 KB |
| Query | ~150 ns | ~200 B |
87% faster and 83% less memory than v2.x.
All async operations support cancellation tokens for graceful shutdown and timeout handling:
// In ASP.NET Core controllers
public async Task<IActionResult> CreateOrder(CreateOrderCommand command, CancellationToken cancellationToken)
{
var result = await _mediator.Send(command, cancellationToken);
return result.Success ? Ok() : BadRequest(result.ValidationResults);
}
// With timeout
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var result = await mediator.Send(command, cts.Token);
// Execute deferred events with cancellation
await mediator.ExecuteDeferredEvents(cancellationToken);CancellationToken parameter added to all async interfaces (see Migration Guide)TransactionScope now opt-in (override UseTransactionScope => true)HandlerOrderAttribute (use DI registration order)ICommandWorkflowResult.ValidationResults is now List<T>