AsyncMediator 3.1.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package AsyncMediator --version 3.1.1
                    
NuGet\Install-Package AsyncMediator -Version 3.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AsyncMediator" Version="3.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AsyncMediator" Version="3.1.1" />
                    
Directory.Packages.props
<PackageReference Include="AsyncMediator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AsyncMediator --version 3.1.1
                    
#r "nuget: AsyncMediator, 3.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AsyncMediator@3.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AsyncMediator&version=3.1.1
                    
Install as a Cake Addin
#tool nuget:?package=AsyncMediator&version=3.1.1
                    
Install as a Cake Tool

AsyncMediator

A lightweight, high-performance mediator for .NET 9/10. Zero runtime dependencies. Minimal allocations.

What is a Mediator?

A mediator decouples the "what" from the "how" in your application. Instead of controllers calling services directly, they send messages through a mediator that routes them to the right handler.

Controller → Mediator → Handler → Database/Services

This indirection enables clean architecture, testability, and cross-cutting concerns (logging, validation, caching) without polluting your business logic.

When to Use AsyncMediator

Great for:

  • CQRS architectures (separate read/write paths)
  • Domain-driven design with domain events
  • Decoupling handlers from HTTP/messaging infrastructure
  • Adding cross-cutting behaviors without modifying handlers
  • Applications where testability matters

Not a fit:

  • Simple CRUD where direct repository access is clearer
  • Real-time event streaming (use a message bus)
  • Event sourcing (use specialized frameworks)

Quick Start

Install

dotnet add package AsyncMediator

1. Create a Command and Handler

// The command (what you want to do)
public record CreateOrderCommand(Guid CustomerId, List<OrderItem> Items) : ICommand;

// The handler (how it's done)
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);
        return CommandWorkflowResult.Ok();
    }
}

2. Wire Up DI

services.AddScoped<IMediator>(sp => new Mediator(
    type => sp.GetServices(type),
    type => sp.GetRequiredService(type)));

services.AddTransient<ICommandHandler<CreateOrderCommand>, CreateOrderHandler>();

3. Send Commands

public class OrderController(IMediator mediator) : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> Create(CreateOrderCommand command, CancellationToken ct)
    {
        var result = await mediator.Send(command, ct);
        return result.Success ? Ok() : BadRequest(result.ValidationResults);
    }
}

That's it. You're running.

Queries

For read operations, use queries instead of commands:

public record OrderSearchCriteria(Guid? CustomerId);

public class OrderQuery(IOrderRepository repo) : IQuery<OrderSearchCriteria, List<Order>>
{
    public Task<List<Order>> Query(OrderSearchCriteria c, CancellationToken ct) =>
        repo.Search(c.CustomerId, ct);
}

// Usage
var orders = await mediator.Query<OrderSearchCriteria, List<Order>>(criteria, ct);

Events

Defer side effects until after your main operation completes:

public record OrderCreatedEvent(Guid OrderId) : IDomainEvent;

// In your command handler
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));  // Queued, not executed yet
    return CommandWorkflowResult.Ok();
}

// Event handler (executed after DoHandle completes)
public class SendConfirmationEmailHandler(IEmailService email) : IEventHandler<OrderCreatedEvent>
{
    public Task Handle(OrderCreatedEvent e, CancellationToken ct) =>
        email.SendOrderConfirmation(e.OrderId, ct);
}

Safe by default: Deferred events only execute when DoHandle returns a successful result. If the command fails (validation errors or result.Success == false), events are automatically skipped. When UseTransactionScope is enabled, events execute after the transaction commits successfully.

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 no behaviorFactory is provided.

Advanced Usage

Automatically discover and register all handlers at compile time:

dotnet add package AsyncMediator.SourceGenerator
// Zero-config: handlers auto-discovered
services.AddAsyncMediator();

// With behaviors
services.AddAsyncMediator(cfg => cfg
    .AddOpenGenericBehavior(typeof(LoggingBehavior<,>))
    .AddOpenGenericBehavior(typeof(ValidationBehavior<,>)));

Manual Registration (No Source Generator)

If you prefer explicit control or can't use source generators:

// Register handlers manually
services.AddTransient<ICommandHandler<CreateOrderCommand>, CreateOrderHandler>();
services.AddTransient<IQuery<OrderSearchCriteria, List<Order>>, OrderQuery>();

// Register behaviors
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));

// Wire up mediator with behavior factory
services.AddScoped<IMediator>(sp => new Mediator(
    multiInstanceFactory: type => sp.GetServices(type),
    singleInstanceFactory: type => sp.GetRequiredService(type),
    behaviorFactory: type => sp.GetServices(type)));  // Resolves behaviors from DI

Pipeline Behaviors

Behaviors wrap handler execution for cross-cutting concerns. They execute in registration order, like middleware.

Logging Behavior:

public class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
    : IPipelineBehavior<TRequest, TResponse>
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        logger.LogInformation("Handling {Request}", typeof(TRequest).Name);
        var sw = Stopwatch.StartNew();
        var response = await next();
        logger.LogInformation("Handled {Request} in {Elapsed}ms", typeof(TRequest).Name, sw.ElapsedMilliseconds);
        return response;
    }
}

Validation with DataAnnotations:

public record CreateOrderCommand(
    [property: Required] Guid CustomerId,
    [property: Required, MinLength(1)] List<OrderItem> Items) : ICommand;

public class ValidationBehavior<TRequest> : IPipelineBehavior<TRequest, ICommandWorkflowResult>
    where TRequest : ICommand
{
    public Task<ICommandWorkflowResult> Handle(
        TRequest request, RequestHandlerDelegate<ICommandWorkflowResult> next, CancellationToken ct)
    {
        var context = new System.ComponentModel.DataAnnotations.ValidationContext(request);
        var results = new List<ValidationResult>();

        if (!Validator.TryValidateObject(request, context, results, validateAllProperties: true))
            return Task.FromResult<ICommandWorkflowResult>(new CommandWorkflowResult(results));

        return next();
    }
}

Unit of Work Behavior:

public class UnitOfWorkBehavior<TRequest>(IUnitOfWork uow)
    : IPipelineBehavior<TRequest, ICommandWorkflowResult>
    where TRequest : ICommand
{
    public async Task<ICommandWorkflowResult> Handle(
        TRequest request, RequestHandlerDelegate<ICommandWorkflowResult> next, CancellationToken ct)
    {
        var result = await next();
        if (result.Success)
            await uow.CommitAsync(ct);
        return result;
    }
}

TransactionScope

Opt-in for operations requiring ACID guarantees:

public class TransferFundsHandler(IMediator mediator) : CommandHandler<TransferFundsCommand>(mediator)
{
    protected override bool UseTransactionScope => true;  // Wraps DoHandle in TransactionScope
}

Documentation

Breaking Changes (v3.0)

  • Requires .NET 9 or .NET 10
  • CancellationToken added to all async interfaces
  • TransactionScope now opt-in (override UseTransactionScope => true)

License

MIT

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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.

Version Downloads Last Updated
3.1.2 163 12/28/2025
3.1.1 143 12/28/2025
3.1.0 152 12/27/2025
3.0.0 140 12/27/2025
2.1.0 25,804 11/8/2020
2.0.0 39,061 11/16/2015
1.0.2 1,743 10/22/2015
1.0.1 2,776 8/27/2015
1.0.0 1,718 8/26/2015

v3.1.0 - Added pipeline behaviors with behaviorFactory for DI integration, expression-compiled delegates for event publishing. See PIPELINE.md.