AotCqrs 1.0.4

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

AotCqrs

NuGet Downloads

A blazing fast, reflection-free, AOT-friendly CQRS Mediator library for modern .NET, powered by C# Source Generators.

What is AotCqrs?

AotCqrs is a library that provides a simple way to implement the Mediator and CQRS patterns in your .NET applications, much like the popular MediatR library.

However, its core design principle is performance and compatibility with modern .NET technologies, especially Ahead-Of-Time (AOT) compilation and assembly trimming. It completely avoids runtime reflection by leveraging C# Source Generators to create highly optimized "glue" code at compile time.

This makes it an ideal choice for:

  • High-performance ASP.NET Core Minimal APIs.
  • Applications targeting NativeAOT.
  • Any scenario where startup time and runtime performance are critical.

Key Features

  • 🚀 High Performance: Zero runtime reflection. Requests are dispatched via a compile-time generated, highly optimized switch expression.
  • 📦 AOT & Trimming Safe: Designed from the ground up to work flawlessly with .NET's trimming and NativeAOT compilation.
  • ✍️ Simple & Familiar API: If you've used MediatR, you'll feel right at home with IRequest, IRequestHandler, and IMediator.
  • 🧩 Flexible Handler Registration: Register handlers individually with an attribute, or group them in a central "context" class for better organization.
  • 🔗 Powerful Pipeline Behaviors: Intercept and augment request handling with cross-cutting concerns like logging, validation, caching, and transactions using a flexible, contract-based pipeline model.
  • Compile-Time Safety: The source generator connects requests to their handlers and builds the behavior pipeline during compilation, reducing runtime errors.

Why AotCqrs over MediatR?

Feature MediatR (Reflection-based) AotCqrs (Source-Generated)
Dispatch Mechanism Runtime Reflection, Assembly Scan Compile-time generated switch
Pipeline Behaviors Runtime DI resolution Compile-time pipeline weaving
AOT/Trimming Can be problematic; requires configuration Fully Compatible & Safe
Performance Good Excellent / Blazing Fast
Startup Cost Scans assemblies on startup Zero

Installation

Install the package from NuGet:

dotnet add package AotCqrs

Getting Started: A Four-Step Guide

1. Define your Requests and Handlers

First, create a request (a command or query) and its corresponding handler.

// Features/Products/CreateProduct.cs
using AotCqrs;

// A command is a request that typically returns nothing (Unit).
public record CreateProductCommand(string Name) : IRequest<int>;

// The handler contains the business logic.
// Mark it with [AotCqrsHandler] so the generator can find it.
[AotCqrsHandler]
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, int>
{
    public Task<int> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        Console.WriteLine($"---> Creating product: {request.Name}");
        // ... your logic here ...
        var newProductId = new Random().Next();
        return Task.FromResult(newProductId);
    }
}

Tip: For better organization, you can also register handlers in bulk using a "context" class. See the "Advanced Handler Registration" section below.

2. Register Services in Program.cs

In your Program.cs (or wherever you configure services), call the AddAotCqrs() extension method. The source generator automatically creates this method for you.

// Program.cs
using AotCqrs; // <-- Make sure to add this using directive

var builder = WebApplication.CreateBuilder(args);

// This one line registers the mediator and all discovered handlers and behaviors.
builder.Services.AddAotCqrs();

var app = builder.Build();
// ...

3. Send Requests from your API

Inject IMediator into your Minimal API endpoints, controllers, or other services and use the Send method to dispatch requests.

// Program.cs
app.MapPost("/products", async (string name, IMediator mediator) =>
{
    var command = new CreateProductCommand(name);
    var productId = await mediator.Send(command);
    return Results.Ok(new { Id = productId });
});

app.Run();

That's it! You now have a fully functional, high-performance CQRS setup.


Advanced: Pipeline Behaviors

Pipeline behaviors are the most powerful feature of AotCqrs. They allow you to add cross-cutting concerns (logging, validation, transactions, etc.) to your requests in a clean, reusable, and AOT-safe way.

The system is based on "marker interfaces" (contracts). You define what a request is (e.g., ITransactional), and the pipeline adapts automatically.

Example: Adding Validation and Auditing

Let's build a pipeline that first audits a request and then validates it.

Step 1: Define "Marker Interface" Contracts

Create interfaces that describe the capabilities or requirements of your requests.

// Contracts/IAuditable.cs
public interface IAuditable { }

// Contracts/IValidatable.cs
public interface IValidatable { }
Step 2: Create the Behaviors

Each behavior class implements IPipelineBehavior and is decorated with [AotCqrsBehavior], specifying which contract it targets and its execution order. Lower Order numbers run first (further out in the pipeline).

// Behaviors/AuditBehavior.cs
using AotCqrs;
using MyWebApp.Contracts;

// This behavior targets any request that is IAuditable.
// It runs first because its order is -10.
[AotCqrsBehavior(typeof(IAuditable), Order = -10)]
public class AuditBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>, IAuditable
{
    /* ... implementation ... */
}

// Behaviors/ValidationBehavior.cs
using AotCqrs;
using MyWebApp.Contracts;

// This behavior targets any request that is IValidatable.
// It runs after auditing (Order 0 > -10) but before the handler.
[AotCqrsBehavior(typeof(IValidatable), Order = 0)]
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>, IValidatable
{
    /* ... implementation ... */
}

The source generator automatically finds these behaviors and registers them with the DI container for you.

Step 3: Mark Your Request

Now, simply have your request implement the marker interfaces. The pipeline will be built for it automatically at compile time.

using AotCqrs;
using MyWebApp.Contracts;

// This command will now pass through the AuditBehavior and then the ValidationBehavior
// before reaching its handler.
public record CreateUserCommand(string Email, string Password) 
    : IRequest, IAuditable, IValidatable;
Step 4: Register Dependencies (if any)

If your behaviors have dependencies (like IValidator<T> for the ValidationBehavior), register them as you normally would in Program.cs. AddAotCqrs() will handle the rest.

// Program.cs
// Example for FluentValidation
builder.Services.AddValidatorsFromAssemblyContaining<Program>();

// AddAotCqrs handles handler and behavior registration.
builder.Services.AddAotCqrs();

Advanced Handler Registration

For better organization in large projects, you can register handlers in a central "context" class instead of attributing every handler.

// Features/Users/UserFeature.cs
using AotCqrs;

[AotCqrsContext(
    typeof(CreateUserCommandHandler),
    typeof(GetUserQueryHandler),
    typeof(UpdateUserCommandHandler)
)]
public static partial class UserFeature { }

The handlers listed here no longer need the [AotCqrsHandler] attribute. The generator will find them through the context.

How It Works: The Magic of Source Generators

When you build your project, the AotCqrs source generator:

  1. Scans for registrations: It finds all handlers ([AotCqrsHandler], [AotCqrsContext]) and all contract-based behaviors ([AotCqrsBehavior]).
  2. Generates DependencyInjection.g.cs: This file contains the AddAotCqrs() extension method, which explicitly registers IMediator, every handler, and every discovered behavior.
  3. Generates Mediator.g.cs: This file contains the IMediator implementation. For each request, it generates a custom, highly-optimized pipeline by:
    • Finding the specific handler for the request.
    • Checking which marker interfaces the request implements.
    • Weaving in the corresponding behaviors in the correct order (Order property).
    • Creating a chain of calls that executes the complete pipeline.

This compile-time process results in extremely fast, AOT-safe code with zero reflection.

Contributing

Contributions are welcome! If you find a bug or have a feature request, please open an issue.

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 was computed.  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
1.0.4 131 8/18/2025
1.0.3 107 8/15/2025