MinimalCqrs 4.1.0

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

Minimal CQRS

Formerly known as AstroCQRS.

NuGet Build Status

https://www.nuget.org/packages/MinimalCqrs

Minimal.CQRS

A clean, modern implementation of CQRS (Command Query Responsibility Segregation) and Vertical Slice Architecture, designed to simplify and scale application development in .NET.

Quite possibly the easiest and most beginner-friendly implementation out there.

It is designed to be used with:

  • .NET 9 & .NET 8
  • Minimal API
  • Azure Functions (HttpTrigger, ServiceBusTrigger and TimeTrigger)
  • Console app
  • Blazor (todo)
  • MVC (todo)

Usage

  1. Install:
dotnet add package MinimalCqrs  
  1. Configure :
builder.Services.AddMinimalCqrs();

Query

  1. Create an endpoint:
app.MapGetHandler<GetOrderById.Query, GetOrderById.Response>("/orders.getById.{id}");

☝️ Simple: we are telling what's the input, the output and the path.

  1. Create a Query
public static class GetOrderById
{
    public class Query : IQuery<IHandlerResponse<Response>>
    {
        public string Id { get; set; } = "";
    }

    public record Response(OrderModel Order);

    public record OrderModel(string Id, string CustomerName, decimal Total);

    public class Handler : QueryHandler<Query, Response>
    {
        public Handler()
        {
        }

        public override async Task<IHandlerResponse<Response>> ExecuteAsync(Query query, CancellationToken ct)
        {
            // retrive data from data store
            var order = await Task.FromResult(new OrderModel(query.Id, "Gavin Belson", 20));

            if (order is null)
            {
                return Error("Order not found");
            }

            return Success(new Response(order));
        }
    }
}

☝️ Simple: We keep the input, the output and executing method in a single file (not mandatory though).

.... and that's it!

Command

  1. Create an endpoint:
app.MapPostHandler<CreateOrder.Command, CreateOrder.Response>("/orders.create");
  1. Create a Command:
public static class CreateOrder
{
    public sealed record Command(string CustomerName, decimal Total) : ICommand<IHandlerResponse<Response>>;
    public sealed record Response(Guid OrderId, string SomeValue);

    public sealed class CreateOrderValidator : Validator<Command>
    {
        public CreateOrderValidator()
        {
            RuleFor(x => x.CustomerName)
                .NotNull()
                .NotEmpty();
        }
    }

    public sealed class Handler : CommandHandler<Command, Response>
    {
        public Handler()
        {
        }

        public override async Task<IHandlerResponse<Response>> ExecuteAsync(Command command, CancellationToken ct)
        {
            var orderId = await Task.FromResult(Guid.NewGuid());
            var response = new Response(orderId, $"{command.CustomerName}");

            return Success(response);
        }
    }
}

☝️ Simple: Same as above + the command can be flexible and return a response

Azure Functions

Here are the same query and command used in Azure Functions!

services.AddMinimalCqrsFromAssemblyContaining<ListOrders.Query>();

☝️ Ah yeah, due to the nature of Azure Functions, we need to point to the assembly where the handlers live

public class HttpTriggerFunction
{
    [Function(nameof(HttpTriggerFunction))]
    public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous,"get")] HttpRequestData req)
    {
        return await AzureFunction.ExecuteHttpGetAsync<GetOrderById.Query, GetOrderById.Response>(req);
    }
}
public class ServiceBusFunction
{
    [Function(nameof(ServiceBusFunction))]
    public async Task Run([ServiceBusTrigger("created-order", Connection = "ConnectionStrings:ServiceBus")] string json, FunctionContext context)
    {
        await AzureFunction.ExecuteServiceBusAsync<CreateOrder.Command, CreateOrder.Response>(json, JsonOptions.Defaults, context);
    }
}

Handlers

The handler always returns three types of responses, which enforce consistency:

  • Success(payload) - handler executed with success and has response
  • Success() - handler executed with success but has no response
  • Error("Error message")- handler has an error

Error("Order not found") will return Problem Details (a standard way of specifying errors in HTTP API responses)

{
    "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
    "title": "Order not found",
    "status": 400,
    "errors": {}
}

Sample Code

Check samples available in this repo.

Also check a solution template here: Astro Architecture.

Motives

I'm a big fan of:

and used them all in production environment. So why this?

Well, in all of them I was missing something:

  • MediatR - a bit of setup + always abstracted a lot in my own wrappers.
  • Wolverine - it covers a lot more that I need, it uses a lot of dependencies, has an odd way to setup query handler.
  • FastEndpoints - its command bus is amazing but the whole library enforces REPR Design Pattern (Request-Endpoint-Response) which I'm not a big fan of. It also doesn't work for Azure Functions or Blazor.

I decided to borrow the best features from existing frameworks to create an in-process messaging mechanism that features:

  • Easy setup
  • Decoupled and reusable handlers
  • Enforced consistency
  • Built-in validation
  • Out-of-the-box compatibility with multiple project types (including Minimal API, Azure Functions, Console, MVC, Blazor)
  • Unit testability

It can be seen in production here: Salarioo.com

Todo

There are few things to work out here and mainly:

  • Blazor example
  • MVC example
  • Unit test example
  • Integration test example
  • Benchmarks

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Questions, feature requests

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 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
4.1.0 459 5/28/2025
4.0.1 342 5/19/2025
4.0.0 153 5/19/2025