Fidget.Commander
3.0.0
dotnet add package Fidget.Commander --version 3.0.0
NuGet\Install-Package Fidget.Commander -Version 3.0.0
<PackageReference Include="Fidget.Commander" Version="3.0.0" />
paket add Fidget.Commander --version 3.0.0
#r "nuget: Fidget.Commander, 3.0.0"
// 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 | Versions 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. |
-
.NETStandard 2.0
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.