JGUZDV.CQRS 2.4.3

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

JGUZDV.CQRS

This implements a simple CQRS style command and query structure. Combine it with JGUZDV.CQRS.AspNetCore for simple usage in AspNetCore scenarios.

The package contains classes describing a HandlerResult, a basic QueryHandler and a CommandHandler as well as interfaces for queries, commands, and the aforementioned handlers.

Both handlers will run a 'default pipeline' that allows you to provide initialization code (commands only), command or query object normalization, authorization, validation, execution and result authorization (queries only).
You can return results, that will easily translate to http results from the execution method of both handlers and you may throw CommandExceptions in command handlers to stop the handler running during any point in the pipeline. It's written async only and will check your CancellationTokens between all steps.

Command handlers will faciliate context objects to better handle nullability, that can be used, if initialization is necessary. Authorization of commands will return false, if you do not override them, whilst authorization of queries will return true. Validation will in both cases return valid (e.g: an empty list).

QuerySample

using JGUZDV.CQRS;

namespace MyNamespace;

public class WeatherQuery : IQuery<MyResult> {
    public string Location { get; set; }
}

public class WeatherQueryHandler : QueryHandler<WeatherQuery, MyResult> {
    private IWeatherApi _weatherApi;
    private ILogger<WeatherQueryHandler> _logger;

    // You need to override the logger
    public override ILogger Logger => _logger;
    
    public WeatherQueryHandler(IWeatherApi weatherApi, ILogger<WeatherQueryHandler> logger)
    {
        _weatherApi = weatherApi;
        _logger = logger;
    }

    protected override NormalizeQuery(WeatherQuery query, ClaimsPrincipal? principal)
    {
        query.Location = query.Location.Trim();
        return query;
    }

    protected override Task<bool> AuthorizeExecuteAsync(WeatherQuery query, ClaimsPrincipal? principal, CancellationToken ct) 
    {
        // If you choose to not override this, it will automatically return `true`
        return Task.FromResult(principal != null && principal.Name == "Johannes");
    }

    protected override Task<List<ValidationResult>> ValidateAsync(WeatherQuery query, ClaimsPrincipal? principal, CancellationToken ct)
    {
        // If you choose to not override this, it will automatically be valid.
        var result = new List<ValidationResult>();
        if(query.Location == string.Empty) {
            result.Add(new ("Location may not be empty"));
        }

        return result;
    }

    // You need to override at least this method
    protected override async Task<QueryResult<TValue>> ExecuteInternalAsync(WeatherQuery query, ClaimsPrincipal? principal, CancellationToken ct) {
        var result = await _weatherApi.GetWeatherOfLocation(query.Location, ct);

        return new QueryResult(result);
    }

    protected override Task<bool> AuthorizeValueAsync(WeatherQuery query, MyResult value, ClaimsPrincipal? principal, CancellationToken ct) 
    {
        // If you choose to not override this, it will automatically return `true`
        return Task.FromResult(principal != null && value.DoesNotContainSecrets);
    }
}

CommandSample

using System.ComponentModel.DataAnnotations;
using System.Security.Claims;

using Microsoft.Extensions.Logging;

namespace JGUZDV.CQRS.Commands;

// Sample class - DBSet<Counter> Counters
public class Counter
{
    public int Id { get; set; }

    public int Value { get; set; }
    public string Reasons { get; set; }
}


// Command object
public class IncrementCounter : ICommand
{
    public IncrementCounter(int counterId, int incValue, string reason)
    {
        CounterId = counterId;
        IncValue = incValue;
        Reason = reason;
    }

    public int CounterId { get; }
    public int IncValue { get; }
    public string Reason { get; }
}


internal class IncrementCounterHandler : CommandHandler<IncrementCounter, IncrementCounterHandler.CounterContext>
{
    private readonly CounterDb _dbContext;
    private readonly ILogger<IncrementCounterHandler> _logger;

    public override ILogger Logger => _logger;

    public IncrementCounterHandler(CounterDb dbContext, ILogger<IncrementCounterHandler> logger)
    {
        _dbContext = dbContext;
        _logger = logger;
    }


    protected override async Task<CounterContext> InitializeAsync(IncrementCounter command, ClaimsPrincipal? principal, CancellationToken ct)
    {
        var counter = await _dbContext.Counters.FirstOrDefaultAsync(command.CounterId, ct);
        if (counter == null)
            throw new CommandException(HandlerResult.NotFound());

        return new CounterContext(counter);
    }

    protected override IncrementCounter NormalizeCommand(IncrementCounter command, CounterContext context, ClaimsPrincipal? principal)
        => new IncrementCounter(
            command.CounterId,
            command.IncValue,
            command.Reason.Trim()
        );

    protected override Task<bool> AuthorizeAsync(IncrementCounter command, CounterContext context, ClaimsPrincipal? principal, CancellationToken ct)
        // If you do not override authorize async, authorization of the command will fail. You can skip authZ by setting `SkipAuthorization` to `true`
        => Task.FromResult(principal?.HasClaim(c => c.Type == "CounterAccess" && c.Value == $"{command.CounterId}") ?? false);


    protected override Task<List<ValidationResult>> ValidateAsync(IncrementCounter command, CounterContext context, ClaimsPrincipal? principal, CancellationToken ct)
    {
        // If you do not override validation, the command will be considered valid.
        var result = new List<ValidationResult>();

        
        if (command.IncValue <= 0)
            result.Add(new("Value may not be negative"));

        if(command.IncValue > 10)
            result.Add(new("Value needs to be <= 10"));

        
        return Task.FromResult(result);
    }


    protected override async Task<HandlerResult> ExecuteInternalAsync(IncrementCounter command, CounterContext context, ClaimsPrincipal? principal, CancellationToken ct)
    {
        if (context.Counter.Value >= 100)
            return HandlerResult.Conflict("Counter already at maximum");

        context.Counter.Value += command.IncValue;
        if (!string.IsNullOrEmpty(command.Reason))
            context.Counter.Reasons += $"\r\n{command.Reason}";

        await _dbContext.SaveChangesAsync(ct);

        return HandlerResult.Success();

        //If you would want to communication successfull creation, there is:
        return HandlerResult.Created<int>(context.Counter.Id);
    }


    // Command Context object (not neccessarily nested)
    public class CounterContext
    {
        public Counter Counter { get; set; }

        public CounterContext(Counter c)
        {
            Counter = c;
        }
    }
}
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 (1)

Showing the top 1 NuGet packages that depend on JGUZDV.CQRS:

Package Downloads
JGUZDV.CQRS.AspNetCore

Extension methods to handle CQRS with AspNetCore

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.4.3 259 3/26/2026
2.4.2 142 3/18/2026
2.4.1 143 3/9/2026
2.4.0 242 7/28/2025
2.3.3 298 4/29/2025
2.3.2 599 3/26/2025
2.3.1 308 3/18/2025
2.3.0 290 3/17/2025
2.2.0 408 10/25/2023
2.1.2 320 7/19/2023
2.1.1 297 6/13/2023
2.1.0 307 6/12/2023
2.0.0 370 4/13/2023
1.0.0 411 1/23/2023