Azka.CQRS
10.0.0-alpha3
dotnet add package Azka.CQRS --version 10.0.0-alpha3
NuGet\Install-Package Azka.CQRS -Version 10.0.0-alpha3
<PackageReference Include="Azka.CQRS" Version="10.0.0-alpha3" />
<PackageVersion Include="Azka.CQRS" Version="10.0.0-alpha3" />
<PackageReference Include="Azka.CQRS" />
paket add Azka.CQRS --version 10.0.0-alpha3
#r "nuget: Azka.CQRS, 10.0.0-alpha3"
#:package Azka.CQRS@10.0.0-alpha3
#addin nuget:?package=Azka.CQRS&version=10.0.0-alpha3&prerelease
#tool nuget:?package=Azka.CQRS&version=10.0.0-alpha3&prerelease
Azka.CQRS
A lightweight CQRS library for .NET that provides command/query pipelines with validation, before/after hooks, and dependency injection integration.
Features
- CQRS Handler - Single entry point for commands and queries.
- Validation Pipeline - Validate commands before handlers run.
- Before/After Hooks - Intercept before and after the handler.
- Standardized Responses - Error collection and
IsValidstatus. - Auto Registration - Assembly scanning for handlers/validators/hooks.
- Pagination Helper -
QueryResponse<T>supports paging.
Installation
dotnet add package Azka.CQRS
Quick Start
1. Register in DI
using System.Reflection;
using Azka.CQRS;
var builder = WebApplication.CreateBuilder(args);
// Register all handlers/validators/hooks in this assembly
builder.Services.AddCQRS(Assembly.GetExecutingAssembly());
2. Create a Command
using Azka.CQRS.Abstractions.Commands;
using Azka.CQRS.Implementations.Commands;
public record CreateUserCommand(string Username, string Email)
: ICommandRequest<CreateUserResponse>;
public class CreateUserResult
{
public Guid Id { get; set; }
public bool IsCustom { get; set; }
public bool IsGenericBehaviour { get; set; }
}
public class CreateUserResponse : CommandResponse<CreateUserResult>
{
}
public class CreateUserHandler : ICommandHandler<CreateUserCommand, CreateUserResponse>
{
public async Task<CreateUserResponse> HandleAsync(
CreateUserCommand request,
CancellationToken cancellationToken = default)
{
var result = new CreateUserResult { Id = Guid.NewGuid() };
return await Task.FromResult(new CreateUserResponse { Data = result });
}
}
3. Add Command Validation (Optional)
using Azka.CQRS.Implementations.Commands;
public class CreateUserValidation : CommandValidation<CreateUserCommand, CreateUserResponse>
{
protected override Task DoValidateAsync(
CreateUserCommand request,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(request.Username))
AddError(nameof(request.Username), "Username is required.");
if (string.IsNullOrWhiteSpace(request.Email))
AddError(nameof(request.Email), "Email is required.");
return Task.CompletedTask;
}
}
4. Before/After Hooks (Optional)
using Azka.CQRS.Abstractions.Commands;
public class CreateUserOnBefore : ICommandOnBeforeHandler<CreateUserCommand, CreateUserResponse>
{
public Task<CreateUserResponse> OnBeforeHandleAsync(
CreateUserCommand request,
CancellationToken cancellationToken = default)
{
// Add extra rule checks before handler
return Task.FromResult(new CreateUserResponse());
}
}
public class CreateUserOnAfter : ICommandOnAfterHandler<CreateUserCommand, CreateUserResponse>
{
public Task OnAfterHandleAsync(
CreateUserCommand request,
CreateUserResponse response,
CancellationToken cancellationToken = default)
{
// Logging or side-effects after handler
return Task.CompletedTask;
}
}
5. Run a Command
using Azka.CQRS.Abstractions;
public class UsersController : ControllerBase
{
private readonly ICQRSHandler _cqrs;
public UsersController(ICQRSHandler cqrs)
{
_cqrs = cqrs;
}
[HttpPost("/users")]
public async Task<IActionResult> Create(CreateUserCommand command)
{
var response = await _cqrs.RunCommandAsync<CreateUserCommand, CreateUserResponse>(command);
if (!response.IsValid) return BadRequest(response.Errors);
return Ok(response.Data);
}
}
Query Example
using Azka.CQRS.Abstractions.Queries;
using Azka.CQRS.Implementations.Queries;
public record GetUsersQuery(int Page, int PerPage)
: IQueryRequest<GetUsersResponse>;
public class GetUsersResponse : QueryResponse<UserDto>
{
public GetUsersResponse(IEnumerable<UserDto> data, int totalRows, int page, int perPage)
: base(data, totalRows, page, perPage)
{
}
}
public class GetUsersHandler : IQueryHandler<GetUsersQuery, GetUsersResponse>
{
public async Task<GetUsersResponse> HandleAsync(
GetUsersQuery request,
CancellationToken cancellationToken = default)
{
var data = new List<UserDto>();
var totalRows = 0;
return await Task.FromResult(new GetUsersResponse(data, totalRows, request.Page, request.PerPage));
}
}
Run a Query
var response = await _cqrs.RunQueryAsync<GetUsersQuery, GetUsersResponse>(
new GetUsersQuery(page: 1, perPage: 20));
if (!response.IsValid)
{
// handle error
}
var rows = response.Data;
Execution Pipeline
Command
ICommandValidation(if any)ICommandOnBeforeHandler(if any)ICommandHandlerICommandOnAfterHandler(if any)
Query
IQueryOnBeforeHandler(if any)IQueryHandler
Custom Behaviour
By default, ICommandBehaviour<TRequest, TResponse> uses CommandBehaviour<,>.
You can override behaviour for a specific command or replace it globally.
Custom Behaviour for a Specific Command
using Azka.CQRS.Abstractions.Commands;
using Azka.CQRS.Implementations.Commands;
public class CustomBehaviour
: CommandBehaviour<CreateUserCommand, CreateUserResponse>
{
public CustomBehaviour(
ICommandHandler<CreateUserCommand, CreateUserResponse> handler,
ICommandValidation<CreateUserCommand, CreateUserResponse>? validation = null,
ICommandOnBeforeHandler<CreateUserCommand, CreateUserResponse>? onBefore = null,
ICommandOnAfterHandler<CreateUserCommand, CreateUserResponse>? onAfter = null)
: base(handler, validation, onBefore, onAfter)
{
}
public override async Task<CreateUserResponse> RunBehaviourAsync(
CreateUserCommand request,
CancellationToken cancellationToken = default)
{
var response = await base.RunBehaviourAsync(request, cancellationToken);
if (response.Data != null)
{
response.Data.IsCustom = true;
}
return response;
}
}
Registration:
builder.Services.AddCQRS(Assembly.GetExecutingAssembly());
builder.Services.AddTransient<ICommandBehaviour<CreateUserCommand, CreateUserResponse>, CustomBehaviour>();
Global Behaviour (for all commands)
public class GlobalBehaviour<TRequest, TResponse> : CommandBehaviour<TRequest, TResponse>
where TRequest : ICommandRequest<TResponse>
where TResponse : ICommandResponse
{
public GlobalBehaviour(
ICommandHandler<TRequest, TResponse> handler,
ICommandValidation<TRequest, TResponse>? validation = null,
ICommandOnBeforeHandler<TRequest, TResponse>? onBefore = null,
ICommandOnAfterHandler<TRequest, TResponse>? onAfter = null)
: base(handler, validation, onBefore, onAfter)
{
}
public override async Task<TResponse> RunBehaviourAsync(
TRequest request,
CancellationToken cancellationToken = default)
{
var response = await base.RunBehaviourAsync(request, cancellationToken);
if (response is CommandResponse<CreateUserResult> user && user.Data != null)
{
user.Data.IsGenericBehaviour = true;
}
return response;
}
}
Registration:
builder.Services.AddTransient(typeof(ICommandBehaviour<,>), typeof(GlobalBehaviour<,>));
API Summary
Command
| Interface | Description |
|---|---|
ICommandRequest<TResponse> |
Marker for command request |
ICommandResponse |
Response contract with errors & IsValid |
ICommandHandler<TRequest, TResponse> |
Main command handler |
ICommandValidation<TRequest, TResponse> |
Command validation |
ICommandOnBeforeHandler<TRequest, TResponse> |
Before hook |
ICommandOnAfterHandler<TRequest, TResponse> |
After hook |
Query
| Interface | Description |
|---|---|
IQueryRequest<TResponse> |
Marker for query request |
IQueryResponse |
Response contract with errors & IsValid |
IQueryHandler<TRequest, TResponse> |
Main query handler |
IQueryOnBeforeHandler<TRequest, TResponse> |
Before hook |
Requirements
- .NET 10.0 or higher
- Microsoft.Extensions.DependencyInjection 10.0.2 or higher
License
See LICENSE.txt for details.
Links
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- Azka (>= 10.0.0-alpha3)
- Microsoft.Extensions.DependencyInjection (>= 10.0.2)
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 |
|---|---|---|
| 10.0.0-alpha3 | 32 | 2/1/2026 |
| 10.0.0-alpha2 | 32 | 1/31/2026 |
Initial release of Azka.CQRS
- Command and query handler pipeline
- Validation and before/after hooks
- Auto registration for handlers and validators
- Standardized command/query responses