Kmd 0.4.0

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

Kmd

A lightweight, extensible CQRS library for .NET with built-in interceptor pipeline and OpenTelemetry support.

Packages

Package Description
Kmd.Abstractions Core interfaces (commands, queries, interceptors)
Kmd Default implementations (buses, pipeline, dispatcher)
Kmd.AspNetCore ASP.NET Core dependency injection integration

Getting Started

Installation

Install the packages you need via the .NET CLI:

dotnet add package Kmd.Abstractions
dotnet add package Kmd
dotnet add package Kmd.AspNetCore

Registration

Register Kmd in your Program.cs using AddKmd:

builder.Services.AddKmd(kmdBuilder => {
    // Register global interceptors (applied to all commands and queries)
    kmdBuilder
        .AddInterceptor(typeof(LoggingInterceptor<,>))
        .AddInterceptor(typeof(TimingInterceptor<,>));

    // Auto-discover and register all handlers in the given assemblies
    kmdBuilder
        .AddCommandsFromAssemblies(typeof(Program).Assembly)
        .AddQueriesFromAssemblies(typeof(Program).Assembly);
});

Commands

Commands represent operations that change state. Inherit from Command and implement ICommandHandler<TCommand, TResult>.

Defining a Command

sealed record CreateOrderCommand(string ProductName, int Quantity) : Command;

Implementing a Handler

sealed class CreateOrderCommandHandler : ICommandHandler<CreateOrderCommand, string>
{
    public Task<string> HandleAsync(ICommandContext<CreateOrderCommand> context,
        CancellationToken cancellationToken = default)
    {
        var command = context.Command;
        // Handle the command...
        return Task.FromResult($"Order created for {command.ProductName}");
    }
}

Dispatching a Command

Inject ICommandBus<TCommand, TResult> and call SendAsync:

app.MapPost("/orders", async ([FromServices] ICommandBus<CreateOrderCommand, string> commandBus) => {
    return await commandBus.SendAsync(new CreateOrderCommand("Widget", 5));
});

Queries

Queries represent read operations that return data without modifying state. Inherit from Query and implement IQueryHandler<TQuery, TResult>.

Defining a Query

sealed record GetOrderQuery(string OrderId) : Query;

Implementing a Handler

sealed class GetOrderQueryHandler : IQueryHandler<GetOrderQuery, string>
{
    public Task<string> HandleAsync(IQueryContext<GetOrderQuery> context,
        CancellationToken cancellationToken = default)
    {
        return Task.FromResult($"Order {context.Query.OrderId}");
    }
}

Dispatching a Query

Inject IQueryBus<TQuery, TResult> and call SendAsync:

app.MapGet("/orders/{id}", async ([FromServices] IQueryBus<GetOrderQuery, string> queryBus, string id) => {
    return await queryBus.SendAsync(new GetOrderQuery(id));
});

Interceptors

Interceptors implement cross-cutting concerns such as logging, timing, validation, and exception handling. They form a middleware pipeline that wraps every command or query execution.

Global Interceptors

A global interceptor is applied to all commands and queries. It must be an open generic type implementing IInterceptor<TRequest, TResult>:

sealed class LoggingInterceptor<TRequest, TResult>(ILogger<LoggingInterceptor<TRequest, TResult>>? logger = null)
    : IInterceptor<TRequest, TResult> where TRequest : IRequest
{
    public async Task<TResult> InterceptAsync(IRequestContext<TRequest> context,
        Func<IRequestContext<TRequest>, Task<TResult>> next,
        CancellationToken cancellationToken = default)
    {
        logger?.LogInformation("Handling {RequestId}", context.Request.RequestId);
        var result = await next(context);
        logger?.LogInformation("Handled {RequestId}", context.Request.RequestId);
        return result;
    }
}

Register global interceptors using the open generic type:

kmdBuilder.AddInterceptor(typeof(LoggingInterceptor<,>));

Type-Specific Interceptors

An interceptor can also target a specific command or query type:

sealed class ValidateOrderInterceptor : IInterceptor<CreateOrderCommand, string>
{
    public async Task<string> InterceptAsync(IRequestContext<CreateOrderCommand> context,
        Func<IRequestContext<CreateOrderCommand>, Task<string>> next,
        CancellationToken cancellationToken = default)
    {
        if (context.Request.Quantity <= 0)
            throw new ArgumentException("Quantity must be greater than zero.");

        return await next(context);
    }
}

Register using the typed overload:

kmdBuilder.AddInterceptor<CreateOrderCommand, string, ValidateOrderInterceptor>();

Interceptor Ordering

Interceptors are executed in the order they are registered. The first interceptor registered wraps the outermost layer of the pipeline.

Command-Only and Query-Only Interceptors

For finer-grained control, you can implement ICommandInterceptor<TCommand, TResult> or IQueryInterceptor<TQuery, TResult> and register them with:

kmdBuilder.AddCommandInterceptor(typeof(MyCommandInterceptor<,>));
kmdBuilder.AddQueryInterceptor(typeof(MyQueryInterceptor<,>));

OpenTelemetry Integration

Kmd exposes an ActivitySource and a Meter for distributed tracing and metrics. Both are identified by the name "Kmd", available via KmdConstants:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource(KmdConstants.ActivitySourceName)
        // ...
    )
    .WithMetrics(metrics => metrics
        .AddMeter(KmdConstants.MeterName)
        // ...
    );

You can start custom activities inside handlers or interceptors using the activity source:

using var activity = Observability.ActivitySource.StartActivity("MyOperation");

Shared Request Context

Each command or query handler receives a context object that exposes the request and a shared Items dictionary. Use Items to pass data between interceptors and handlers in the same pipeline execution:

public async Task<TResult> InterceptAsync(IRequestContext<TRequest> context, ...)
{
    context.Items["startTime"] = DateTime.UtcNow;
    return await next(context);
}

Supported Frameworks

  • .NET 8.0
  • .NET 9.0
  • .NET 10.0
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 (1)

Showing the top 1 NuGet packages that depend on Kmd:

Package Downloads
Kmd.AspNetCore

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.0 156 2/18/2026
0.3.0 117 2/17/2026
0.2.0 124 2/17/2026
0.1.0 121 2/17/2026