AsyncMediator 3.1.2
dotnet add package AsyncMediator --version 3.1.2
NuGet\Install-Package AsyncMediator -Version 3.1.2
<PackageReference Include="AsyncMediator" Version="3.1.2" />
<PackageVersion Include="AsyncMediator" Version="3.1.2" />
<PackageReference Include="AsyncMediator" />
paket add AsyncMediator --version 3.1.2
#r "nuget: AsyncMediator, 3.1.2"
#:package AsyncMediator@3.1.2
#addin nuget:?package=AsyncMediator&version=3.1.2
#tool nuget:?package=AsyncMediator&version=3.1.2
AsyncMediator
A lightweight, high-performance mediator for .NET 9/10 with compile-time handler discovery.
Highlights
- Zero-config setup - Source generator discovers handlers at compile time
- Zero runtime dependencies - Core library has no external packages
- Safe event deferral - Events only fire after successful command execution
- Pipeline behaviors - Add logging, validation, caching without touching handlers
- Built-in validation -
CommandHandler<T>base class withValidate()+DoHandle()flow - High performance - ~163ns command dispatch, minimal allocations
Installation
dotnet add package AsyncMediator
dotnet add package AsyncMediator.SourceGenerator
Recommended: Always install both packages. The source generator eliminates manual handler registration and catches missing handlers at compile time.
Quick Start
1. Register in Program.cs
builder.Services.AddAsyncMediator();
That's it. All handlers are discovered automatically.
2. Create a Command
public record CreateOrderCommand(Guid CustomerId, List<OrderItem> Items) : ICommand;
3. Create a Handler
public class CreateOrderHandler(IMediator mediator, IOrderRepository repo)
: CommandHandler<CreateOrderCommand>(mediator)
{
protected override Task Validate(ValidationContext ctx, CancellationToken ct)
{
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 ct)
{
var order = await repo.Create(Command.CustomerId, Command.Items, ct);
Mediator.DeferEvent(new OrderCreatedEvent(order.Id));
return CommandWorkflowResult.Ok();
}
}
4. Send Commands
var result = await mediator.Send(new CreateOrderCommand(customerId, items), ct);
if (result.Success)
// Order created, events fired
Core Concepts
Commands
Commands change state. Handlers return ICommandWorkflowResult with validation support.
public record CreateOrderCommand(Guid CustomerId) : ICommand;
Queries
Queries read data without side effects.
// With criteria
public class OrderQuery(IOrderRepository repo) : IQuery<OrderSearchCriteria, List<Order>>
{
public Task<List<Order>> Query(OrderSearchCriteria c, CancellationToken ct) =>
repo.Search(c.CustomerId, ct);
}
var orders = await mediator.Query<OrderSearchCriteria, List<Order>>(criteria, ct);
// Without criteria
public class AllOrdersQuery(IOrderRepository repo) : ILookupQuery<List<Order>>
{
public Task<List<Order>> Query(CancellationToken ct) => repo.GetAll(ct);
}
var orders = await mediator.LoadList<List<Order>>(ct);
Events
Events fire after successful command execution. They're automatically skipped if validation fails or an exception occurs.
public record OrderCreatedEvent(Guid OrderId) : IDomainEvent;
// Defer in handler
Mediator.DeferEvent(new OrderCreatedEvent(order.Id));
// Handle elsewhere
public class SendEmailHandler : IEventHandler<OrderCreatedEvent>
{
public Task Handle(OrderCreatedEvent e, CancellationToken ct) =>
emailService.SendConfirmation(e.OrderId, ct);
}
Pipeline Behaviors
Add cross-cutting concerns without modifying handlers:
builder.Services.AddAsyncMediator(cfg => cfg
.AddOpenGenericBehavior(typeof(LoggingBehavior<,>))
.AddOpenGenericBehavior(typeof(ValidationBehavior<,>)));
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
Console.WriteLine($"Handling {typeof(TRequest).Name}");
var response = await next();
Console.WriteLine($"Handled {typeof(TRequest).Name}");
return response;
}
}
Performance
| Operation | Latency | Memory |
|---|---|---|
| Send command | ~163 ns | ~488 B |
| Query | ~105 ns | ~248 B |
| Defer event | ~575 ns | 0 B |
Pipeline behaviors add zero overhead when not registered.
When to Use
Good fit:
- CQRS architectures
- Domain-driven design with domain events
- Clean architecture / vertical slices
- Applications requiring testability
Not a fit:
- Simple CRUD (direct repository access is clearer)
- Event sourcing (use specialized frameworks)
- Real-time streaming (use message brokers)
Documentation
| Resource | Description |
|---|---|
| Working Demo | Run it locally and see the flow |
| Architecture | Design decisions and internals |
| Pipeline Behaviors | Logging, validation, unit of work examples |
| Migration Guide | Upgrading from v2.x |
Advanced Topics
<details> <summary><strong>Returning data from commands</strong></summary>
var result = CommandWorkflowResult.Ok();
result.SetResult(order);
return result;
// Caller
var order = result.Result<Order>();
</details>
<details> <summary><strong>TransactionScope (opt-in)</strong></summary>
public class TransferHandler(IMediator mediator) : CommandHandler<TransferCommand>(mediator)
{
protected override bool UseTransactionScope => true;
}
</details>
<details> <summary><strong>Excluding handlers from discovery</strong></summary>
[ExcludeFromMediator]
public class DraftHandler : CommandHandler<MyCommand> { ... }
</details>
<details> <summary><strong>Manual registration (without source generator)</strong></summary>
services.AddScoped<IMediator>(sp => new Mediator(
type => sp.GetServices(type),
type => sp.GetRequiredService(type)));
services.AddTransient<ICommandHandler<CreateOrderCommand>, CreateOrderHandler>();
</details>
Contributing
Found a bug or have a feature request? Open an issue.
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 is compatible. 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. |
-
net10.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (2)
Showing the top 2 NuGet packages that depend on AsyncMediator:
| Package | Downloads |
|---|---|
|
AsyncMediator.Extensions.DependencyInjection
Setup helpers for configuring AsyncMediator with Microsoft Dependency Injection. |
|
|
AsyncMediator.Extensions.Autofac
Setup helpers for configuring AsyncMediator with Autofac. |
GitHub repositories
This package is not used by any popular GitHub repositories.
v3.1.0 - Added pipeline behaviors with behaviorFactory for DI integration, expression-compiled delegates for event publishing. See PIPELINE.md.