This page provides real-world examples of how to implement and use the Command Pipeline.
public record CreateUserCommand(string Email, string Name) : CommandBase, ICommand;
public class CreateUserHandler : ICommandHandler<CreateUserCommand, Result>
{
public async Task<Result> HandleAsync(CreateUserCommand command, CancellationToken ct)
{
// Business logic here
return ResultFactory.Success();
}
}
Scenario: A command with validation, logging, result transformation, and auditing.
[Order(1)] // Outermost
public class LoggingBehavior<TCommand, TResult>(ILogger logger)
: IPreBehavior<TCommand, TResult>
where TCommand : ICommand
where TResult : Result
{
public async Task<TResult> HandleAsync(TCommand command, Func<TCommand, CancellationToken, Task<TResult>> next, CancellationToken ct)
{
logger.LogInformation("Executing command {Name}", typeof(TCommand).Name);
var result = await next(command, ct);
logger.LogInformation("Command {Name} finished with {Code}", typeof(TCommand).Name, result.ResultCode);
return result;
}
}
[Order(2)]
public class ValidationBehavior<TCommand, TResult>(IEnumerable<IValidator<TCommand>> validators)
: IPreBehavior<TCommand, TResult>
where TCommand : ICommand
where TResult : Result
{
public async Task<TResult> HandleAsync(TCommand command, Func<TCommand, CancellationToken, Task<TResult>> next, CancellationToken ct)
{
var context = new ValidationContext<TCommand>(command);
var failures = validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
{
return (TResult)ResultFactory.Failed("Validation failed", 400);
}
return await next(command, ct);
}
}
public class EnvelopeTransformer<TCommand, TResult>
: IPostTransformer<TCommand, TResult>
where TCommand : ICommand
where TResult : Result
{
public async Task<TResult> HandleAsync(TCommand command, TResult current, Func<TCommand, TResult, CancellationToken, Task<TResult>> next, CancellationToken ct)
{
// Add extra metadata to the result if needed
return await next(command, current, ct);
}
}
public class AuditBehavior<TCommand, TResult>(IAuditService audit)
: IPostBehavior<TCommand, TResult>
where TCommand : ICommand
where TResult : Result
{
public async Task<Result> HandleAsync(TCommand command, TResult result, CancellationToken ct)
{
await audit.RecordAsync(command, result);
return ResultFactory.Success();
}
}
public class CachingBehavior<TCommand, TResult>(ICache cache)
: IPreBehavior<TCommand, TResult>
where TCommand : ICommand
where TResult : Result
{
public async Task<TResult> HandleAsync(TCommand command, Func<TCommand, CancellationToken, Task<TResult>> next, CancellationToken ct)
{
var cacheKey = command.GetHashCode().ToString();
if (cache.TryGet<TResult>(cacheKey, out var cachedResult))
{
return cachedResult;
}
var result = await next(command, ct);
if (result.IsSuccess)
{
cache.Set(cacheKey, result);
}
return result;
}
}