Josupeit.Practices.Commands 1.0.8

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

Josupeit.Practices.Commands

Implementing both Execute and ExecuteAsync on every command is tedious and error-prone. If the two implementations diverge, callers that happen to use the synchronous path silently get different behavior. This package solves that by providing abstract base classes where you only implement one variant — sync or async — and the other is derived automatically.

The bridging works in both directions. If you override ExecuteAsync, then Execute will block on it. If you override Execute, then ExecuteAsync will wrap it. If you accidentally call the base implementation from an override without actually implementing the other direction, the base class detects the recursion and throws an InvalidOperationException with a clear message rather than causing a stack overflow.

dotnet add package Josupeit.Practices.Commands

How to implement a command

Override either Execute (sync) or ExecuteAsync(CancellationToken) (async), but not both unless your implementation genuinely differs between the two. Never call base.Execute or base.ExecuteAsync from an override.

Parameterless command (no return value)

public sealed class SendWelcomeEmailCommand : Command
{
    private readonly IEmailService _email;

    public SendWelcomeEmailCommand(IEmailService email) => _email = email;

    // Override the async path. Execute() is bridged automatically.
    public override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        await _email.SendAsync("welcome@example.com", cancellationToken);
    }
}

// Both call sites work without any extra effort.
new SendWelcomeEmailCommand(email).Execute();
await new SendWelcomeEmailCommand(email).ExecuteAsync(cancellationToken);

Command with input

public sealed class DeleteUserCommand : Command<Guid>
{
    private readonly IUserRepository _repository;

    public DeleteUserCommand(IUserRepository repository) => _repository = repository;

    public override async Task ExecuteAsync(Guid userId, CancellationToken cancellationToken)
    {
        await _repository.DeleteAsync(userId, cancellationToken);
    }
}

Command with output

public sealed class CreateOrderCommand : CommandWithOutput<Order>
{
    private readonly IOrderRepository _repository;

    public CreateOrderCommand(IOrderRepository repository) => _repository = repository;

    public override async Task<Order> ExecuteAsync(CancellationToken cancellationToken)
    {
        return await _repository.CreateAsync(cancellationToken);
    }
}

Query (pure, no side effects)

Queries are expected to be pure. Implement the synchronous path when the implementation does not involve I/O, and the base class will handle the async path.

public sealed class GetUserQuery : Query<Guid, User>
{
    private readonly IUserRepository _repository;

    public GetUserQuery(IUserRepository repository) => _repository = repository;

    public override User Execute(Guid id) => _repository.Find(id);
}

Available base classes

Base class Input Output
Command
Command<TInput> TInput
CommandWithOutput<TOutput> TOutput
CommandWithOutput<TInput, TOutput> TInput TOutput
Query<TOutput> TOutput
Query<TInput, TOutput> TInput TOutput

The interfaces that these classes implement live in Josupeit.Practices.Commands.Abstractions, which is pulled in automatically as a transitive dependency.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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.8 120 3/10/2026