CommanderCQRS 2.0.0

dotnet add package CommanderCQRS --version 2.0.0
                    
NuGet\Install-Package CommanderCQRS -Version 2.0.0
                    
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="CommanderCQRS" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CommanderCQRS" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="CommanderCQRS" />
                    
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 CommanderCQRS --version 2.0.0
                    
#r "nuget: CommanderCQRS, 2.0.0"
                    
#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 CommanderCQRS@2.0.0
                    
#: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=CommanderCQRS&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=CommanderCQRS&version=2.0.0
                    
Install as a Cake Tool

Commander.CQRS

A high-performance, modern CQRS / mediator library for .NET — designed to be a faster and cleaner alternative to MediatR.

CI NuGet Downloads License: MIT Targets AOT

dotnet add package CommanderCQRS
# Optional: FluentValidation integration
dotnet add package CommanderCQRS.FluentValidation

Why Commander.CQRS

Feature Commander.CQRS 2.x MediatR 12.x
Multi-target net8 / net10 Yes Yes
CancellationToken everywhere Yes Yes
Pipeline behaviors (request + stream) Yes Request only
Streaming queries Yes Yes
ISender / IPublisher split Yes Yes
Pluggable notification publisher (sequential / parallel) Yes Yes
ValueTask on the hot path Yes Task only
Strongly-typed ICommandResult / IQueryResult Yes No
First-class FluentValidation pipeline behavior Yes Third party
Reflection-free dispatch (JIT-specialized) Yes Some reflection
AOT / trim friendly core (IsAotCompatible=true) Yes No
Source generator for handler registration (zero startup reflection) Yes No
Public API surface tracked via PublicAPI.Shipped.txt Yes No

The core package has a single transitive dependency (Microsoft.Extensions.DependencyInjection.Abstractions). FluentValidation is opt-in via the Commander.FluentValidation adapter — keep your core lean.

Targets

  • net8.0 (LTS)
  • net10.0 (LTS, latest)

Quick start

using Commander;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

services.AddCommander(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<Program>();
});

var sp = services.BuildServiceProvider();
var commander = sp.GetRequiredService<ICommander>();

var result = await commander.Execute<AddProductCommand, Product>(
    new AddProductCommand { Name = "Notebook" });

Console.WriteLine(result.IsSuccess);

AOT / trim friendly registration (source generator)

AddCommander(cfg => cfg.RegisterServicesFromAssembly(...)) uses runtime reflection. For trimmed or AOT-published apps, the bundled source generator emits an extension method that registers every handler in your project at compile time — zero reflection, zero startup cost:

services.AddCommander(cfg => { /* options, behaviors, publisher */ });
services.AddCommanderHandlers();   // generated by Commander.SourceGenerator at compile time

The generator ships inside the CommanderCQRS NuGet package under analyzers/dotnet/cs, so installing the package is the only step required.

Commands

public sealed class AddProductCommand : Command
{
    public string Name { get; init; } = string.Empty;
}

public sealed class AddProductHandler : ICommandHandler<AddProductCommand, Product>
{
    public ValueTask<ICommandResult<Product>> Execute(
        AddProductCommand request,
        CancellationToken cancellationToken = default)
        => CommandResult<Product>.SuccessAsync(new Product { Name = request.Name });
}

Execute<TRequest>(...) (no response) is also supported via ICommandHandler<TRequest>.

Queries

public sealed class ProductQuery : Query
{
    public ProductQuery(Guid id) => Id = id;
    public Guid Id { get; }
}

public sealed class ProductQueryHandler : IQueryHandler<ProductQuery, ProductOutput>
{
    public ValueTask<IQueryResult<ProductOutput>> ExecuteQuery(
        ProductQuery request,
        CancellationToken cancellationToken = default)
        => QueryResult<ProductOutput>.SuccessAsync(new ProductOutput { Id = request.Id });
}

Streaming queries

public sealed class TailLogsHandler : IStreamQueryHandler<TailLogsQuery, LogLine>
{
    public async IAsyncEnumerable<LogLine> Stream(
        TailLogsQuery request,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        await foreach (var line in source.ReadAllAsync(cancellationToken))
            yield return line;
    }
}

await foreach (var line in commander.Stream<TailLogsQuery, LogLine>(query, ct))
    Console.WriteLine(line);

Events / notifications

public sealed class ProductAddedEvent : Event
{
    public ProductAddedEvent(string productName) => ProductName = productName;
    public string ProductName { get; }
}

public sealed class EmailHandler : IEventHandler<ProductAddedEvent>
{
    public ValueTask<IEventResult> Publish(ProductAddedEvent e, CancellationToken ct = default)
        => EventResult.SuccessAsync();
}

await commander.Publish(new ProductAddedEvent("Notebook"));

Pipeline behaviors

public sealed class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : Message
{
    public async ValueTask<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        Console.WriteLine($"-> {typeof(TRequest).Name}");
        try
        {
            return await next(cancellationToken);
        }
        finally
        {
            Console.WriteLine($"<- {typeof(TRequest).Name}");
        }
    }
}

services.AddCommander(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<Program>();
    cfg.AddOpenBehavior(typeof(LoggingBehavior<,>));
});

Behaviors run in registration order, wrap both commands and queries, and receive the request object plus a RequestHandlerDelegate<TResponse> so they can short-circuit, retry, time, or transform the result.

Notification publisher strategies

By default events are dispatched sequentially with ForeachAwaitPublisher — deterministic, easy to reason about, no scheduler overhead. Switch to parallel fan-out:

using Commander.Internal;

services.AddCommander(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<Program>();
    cfg.UseNotificationPublisher<TaskWhenAllPublisher>();
});

Or write your own by implementing INotificationPublisher.

Validation (FluentValidation)

using Commander.FluentValidation;
using FluentValidation;

public sealed class AddProductValidator : CommanderValidator<AddProductCommand>
{
    public AddProductValidator() => RuleFor(x => x.Name).NotEmpty();
}

services.AddCommander(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<Program>();
    cfg.AddFluentValidation(); // turns on the validation pipeline behavior
});

When validation fails, the pipeline returns a failed ICommandResult / ICommandResult<T> without invoking the handler.

If you don't want FluentValidation, implement ICommandValidator<T> directly — the validation pipeline behavior accepts any implementation.

ISender and IPublisher

If a class only sends commands/queries, depend on ISender. If it only publishes events, depend on IPublisher. ICommander is the umbrella, equivalent to MediatR's IMediator. All three resolve to the same instance.

Performance

A Commander.Benchmarks project ships side-by-side with the library and compares Commander vs MediatR with BenchmarkDotNet:

dotnet run -c Release --project Commander.Benchmarks

Designed for speed:

  • Hot path is fully generic — the JIT specializes each call site, no per-call reflection
  • Result types use cached singletons for Success() to avoid allocations
  • Notification dispatch materializes handlers once per call, with a pre-bound Func per executor
  • ValueTask end-to-end avoids unnecessary Task allocations for synchronously-completing handlers
  • TryAddEnumerable registration deduplicates handlers without sacrificing event multi-cast

Migration from 1.x

1.x 2.x
services.AddCommander<TMarker>() Same call still works or use the new services.AddCommander(cfg => cfg.RegisterServicesFromAssemblyContaining<TMarker>())
Handlers without CancellationToken Add CancellationToken cancellationToken = default to handlers
CommanderValidator<T> from Commander namespace Commander.FluentValidation.CommanderValidator<T>; install CommanderCQRS.FluentValidation and call cfg.AddFluentValidation()
Task.WhenAll-based publish (hardcoded) Default is now ForeachAwaitPublisher. Restore old behavior with cfg.UseNotificationPublisher<TaskWhenAllPublisher>()
Event.AggreggateId (typo) / SetAggreggateId Renamed to AggregateId / SetAggregateId

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  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.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 51 6/8/2026
1.0.2 500 9/16/2021
1.0.1 437 9/16/2021
1.0.0 449 9/16/2021