Fidget.Commander 3.0.0

dotnet add package Fidget.Commander --version 3.0.0
NuGet\Install-Package Fidget.Commander -Version 3.0.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="Fidget.Commander" Version="3.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Fidget.Commander --version 3.0.0
#r "nuget: Fidget.Commander, 3.0.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.
// Install Fidget.Commander as a Cake Addin
#addin nuget:?package=Fidget.Commander&version=3.0.0

// Install Fidget.Commander as a Cake Tool
#tool nuget:?package=Fidget.Commander&version=3.0.0

Fidget.Commander

This is a simple dispatcher for executing commands in a CQRS-ish manner. In Fidget, however, everything is a command. In my day job as both a database administrator and API developer, I've found that commands frequently end up needing to return a value, and queries frequently require auditing.

Consequently, Fidget has a unified interface for defining commands, handlers, and decorators.

Basics

Once you've added the Fidget.Commander package to your project, you'll want to familiarize yourself with the following interfaces:

  • ICommand<TResult> is a marker interface for the command itself.
  • ICommandHandler<TCommand,TResult> defines a handler for executing the command.
  • ICommandDecorator<TCommand,TResult> allows you to modify a handler's behavior without modifying the handler itself.
  • ICommandDispatcher is implemented for you, and true to its name, it dispatches commands to be executed by their handlers.

Each of the generic types above have a variant without the TResult argument for commands that return no result. These commands will return the Unit structure.

As of version 3, there are abstract types to reduce the amount of typing required to implement command handlers and command decorators. CommandHandler and CommandDecorator types both:

  • Perform argument null checking.
  • Check the cancellation token.
  • Eliminate the need to return Unit.Default on commands that return no result.

To get started, you'll need to tell your favorite DI container how to find the implementations of those interfaces.

Microsoft.Extensions.DependencyInjection + Scrutor

Here's a simple registration example using the standard dependency injection container in ASP.NET Core. It is augmented using Scrutor to scan the assembly for handlers and decorators that we would otherwise have to register individually.

    public void ConfigureServices( IServiceCollection services )
    {
        // register the commander
        services.AddFidgetCommander()
            // note: this example uses Scrutor for assembly scanning
            .Scan( scanner => scanner.FromAssemblyOf<Startup>()
                .AddClasses( _ => _.AssignableTo( typeof( ICommandHandler<,> ) ) )
                    .AsImplementedInterfaces()
                    .WithTransientLifetime()
                .AddClasses( _ => _.AssignableTo( typeof( ICommandDecorator<,> ) ) )
                    .AsImplementedInterfaces()
                    .WithTransientLifetime()
            );
                
        services.AddMvc();
    }

StructureMap.AspNetCore

Here's a registration example using StructureMap.AspNetCore:

    public void ConfigureServices( IServiceCollection services )
    {
        services.AddFidgetCommander();
        services.AddMvc();
    }

    public void ConfigureContainer( Registry registry )
    {
        // add all your handlers and decorators
        registry.Scan( scanner =>
        {
            scanner.AssemblyContainingType<Startup>();
            scanner.ConnectImplementationsToTypesClosing( typeof( ICommandHandler<,> ) );
            scanner.ConnectImplementationsToTypesClosing( typeof( ICommandDecorator<,> ) );
        });
    }

CommandAdapterFactory uses the IServiceProvider interface that most of the common DI containers support. For those that don't, a simple wrapper or a custom implementation of ICommandAdapterFactory can be used.

Usage

Here's a simple controller, command, handler, and decorator example. It returns a hello or goodbye greeting based on HTTP method, and a decorator to add some attitude:

    [Route( "" )]
    public class HelloController : ControllerBase
    {
        /// <summary>
        /// Fidget command dispatcher.
        /// </summary>

        readonly ICommandDispatcher Dispatcher;

        /// <summary>
        /// Constructs a controller that says hello.
        /// </summary>
        /// <param name="dispatcher">Fidget command dispatcher.</param>

        public HelloController( ICommandDispatcher dispatcher )
        {
            Dispatcher = dispatcher ?? throw new ArgumentNullException( nameof( dispatcher ) );
        }

        /// <summary>
        /// Command for creating a greeting.
        /// </summary>
        public class GreetCommand : ICommand<string> 
        {
            /// <summary>
            /// Gets or sets the name to include in the greeting.
            /// </summary>
            public string Name { get; set; }

            /// <summary>
            /// Gets or sets the method used to invoke the command.
            /// </summary>
            public string Method { get; set; }
        }

        /// <summary>
        /// Handles the greet command.
        /// </summary>
        public class GreetCommandHandler : ICommandHandler<GreetCommand,string>
        {
            public Task<string> Handle( GreetCommand command, CancellationToken cancellationToken )
            {
                var greeting = "delete".Equals( command.Method, StringComparison.OrdinalIgnoreCase )
                    ? "Goodbye"
                    : "Hello";

                var name = string.IsNullOrWhiteSpace( command.Name )
                    ? "World"
                    : command.Name;
                
                return Task.FromResult( $"{greeting}, {name}!" );
            }
        }

        /// <summary>
        /// Decorates the greet command.
        /// </summary>
        public class GreetCommandDecorator : ICommandDecorator<GreetCommand, string>
        {
            public async Task<string> Execute( GreetCommand command, CancellationToken cancellationToken, CommandDelegate<GreetCommand, string> continuation ) =>
                $"I've been commanded to say '{await continuation( command, cancellationToken )}'!";
        }

        /// <summary>
        /// Says hello.
        /// </summary>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <param name="name">Optional name to include in the message.</param>

        [HttpGet]
        [HttpDelete]
        public async Task<IActionResult> Greet( CancellationToken cancellationToken, string name = null )
        {
            var command = new GreetCommand
            {
                Name = name,
                Method = HttpContext.Request.Method,
            };

            return Ok( await Dispatcher.Execute( command, cancellationToken ) );
        }
    }
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. 
.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 (1)

Showing the top 1 NuGet packages that depend on Fidget.Commander:

Package Downloads
Fidget.Validation.Addresses

Address validation and metadata exploration via the Google Address Data Service. Note: The author is not affiliated with Google.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.0.0 1,204 12/17/2017
2.1.0 981 11/19/2017
2.0.0 1,078 8/14/2017
1.0.1 1,354 5/8/2017
1.0.0 989 5/5/2017