Postie.Cqrs 1.0.1

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

Postie.Cqrs

A lightweight CQRS mediator for .NET. Commands and queries are separate, first-class concepts with their own handler and dispatcher interfaces, dispatched through cached compiled delegates (no per-call reflection). Part of Postiefree forever, MIT licensed.

dotnet add package Postie.Cqrs

Define requests and handlers

using Postie.Cqrs.Queries;
using Postie.Cqrs.Commands;

public record Order(int Id);

public record GetOrder(int Id) : IQuery<Order>;

public class GetOrderHandler : IQueryHandler<GetOrder, Order>
{
    public ValueTask<Order> Handle(GetOrder query, CancellationToken cancellationToken) =>
        ValueTask.FromResult(new Order(query.Id));
}

public record CreateOrder(string Customer) : ICommand<Order>;   // command that returns a response
public record DeleteOrder(int Id) : ICommand;                   // command with no response

Command handlers implement ICommandHandler<TCommand, TResponse> or ICommandHandler<TCommand>.

Register

builder.Services.AddCqrs<GetOrder>();                  // scans the marker type's assembly
builder.Services.AddCqrs(typeof(GetOrder).Assembly);   // or pass assemblies explicitly

At least one assembly is required — there is no calling-assembly fallback. Or register handlers individually with AddQueryHandler<,,> / AddCommandHandler<,,> / AddCommandHandler<,>.

Dispatch

public class OrderService(IQueryDispatcher queries, ICommandDispatcher commands)
{
    public ValueTask<Order> Get(int id, CancellationToken ct) => queries.Dispatch(new GetOrder(id), ct);
    public ValueTask<Order> Create(string customer, CancellationToken ct) => commands.Dispatch(new CreateOrder(customer), ct);
    public ValueTask Delete(int id, CancellationToken ct) => commands.Execute(new DeleteOrder(id), ct);
}

Commands that return a response are dispatched with Dispatch; those that do not are run with Execute.

Pipeline behaviors

Wrap handling with cross-cutting concerns. The query and command pipelines are separate, so a behavior can target one side without touching the other.

public class TimingBehavior<TQuery, TResponse>(ILogger<TQuery> logger) : IQueryPipelineBehavior<TQuery, TResponse>
    where TQuery : IQuery<TResponse>
{
    public async ValueTask<TResponse> Handle(TQuery query, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        var sw = Stopwatch.StartNew();
        var response = await next();
        logger.LogInformation("{Query} took {Elapsed}ms", typeof(TQuery).Name, sw.ElapsedMilliseconds);
        return response;
    }
}

builder.Services.AddQueryPipelineBehavior(typeof(TimingBehavior<,>));

Use AddCommandPipelineBehavior for command behaviors (ICommandPipelineBehavior<TCommand, TResponse> or ICommandPipelineBehavior<TCommand>). Behaviors run in registration order, each surrounding the next.

Streaming queries

For queries that return a stream of results, implement IStreamQuery<TResponse> and IStreamQueryHandler<TQuery, TResponse> (returning IAsyncEnumerable<TResponse>) and dispatch through IStreamQueryDispatcher:

public record Tail(string File) : IStreamQuery<string>;

public class TailHandler : IStreamQueryHandler<Tail, string>
{
    public async IAsyncEnumerable<string> Handle(Tail query, [EnumeratorCancellation] CancellationToken ct)
    {
        await foreach (var line in ReadLines(query.File, ct)) yield return line;
    }
}

Stream queries have their own pipeline behaviors (IStreamQueryPipelineBehavior<TQuery, TResponse>, registered with AddStreamQueryPipelineBehavior) and are mapped to endpoints with MapStreamQuery.

OpenTelemetry

The dispatcher records an activity per query, command and stream query (tagged with the request kind and type). Add the source to your tracer to collect them:

builder.Services.AddOpenTelemetry().WithTracing(t => t.AddSource(PostieDiagnostics.ActivitySourceName));

When nothing is listening the dispatch takes an allocation-free fast path, so tracing costs nothing until you opt in.

For streaming queries the activity starts when enumeration begins (not at dispatch) and parents to the enumeration-time Activity.Current, so an unenumerated stream records nothing.

For ready-made FluentValidation behaviors see Postie.Cqrs.FluentValidation.

Map to minimal API endpoints

Postie.Cqrs.AspNetCore maps these commands and queries straight to ASP.NET Core minimal API endpoints.

License

MIT. See LICENSE.

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 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.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Postie.Cqrs:

Package Downloads
Postie.Cqrs.AspNetCore

Postie.Cqrs adapter for Postie.AspNetCore — dispatches minimal API endpoints through Postie's own mediator.

Postie.Cqrs.FluentValidation

FluentValidation pipeline behaviors for Postie.Cqrs — validate commands and queries before they reach the handler.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 1,205 7/22/2026
1.0.0 217 7/22/2026