Command Context
The CommandContext is a core component of the non-controller-based command pipeline in Cratis ApplicationModel. It provides contextual information and values that are available throughout the command execution lifecycle.
Overview
The CommandContext is a record that encapsulates all the necessary information about a command being executed:
public record CommandContext(
CorrelationId CorrelationId,
Type Type,
object Command,
IEnumerable<object> Dependencies,
CommandContextValues Values,
object? Response);
Properties
- CorrelationId: A unique identifier for tracking the command execution across the system
- Type: The type of the command being executed
- Command: The actual command instance
- Dependencies: The resolved dependencies required to handle the command
- Values: A collection of key-value pairs providing additional context
- Response: The response, if any, that is returned as part of the command result.
Command Context Values
The Values property is a CommandContextValues instance that acts as a case-insensitive dictionary of contextual information. These values are populated through implementations of ICommandContextValuesProvider.
How Values Are Populated
The command pipeline uses the CommandContextValuesBuilder to collect values from all registered ICommandContextValuesProvider implementations.
Each provider receives the command instance being executed and contributes its values. If there are overlapping keys, the last provider's value takes precedence.
Extending with Custom Values
To add your own values to the command context, implement the ICommandContextValuesProvider interface:
public interface ICommandContextValuesProvider
{
CommandContextValues Provide(object command);
}
The command parameter provides access to the command instance being executed, allowing providers to customize their values based on the specific command type or content.
Example Implementation
Here's an example of a custom provider that adds audit tracking information:
public class AuditContextValuesProvider : ICommandContextValuesProvider
{
private readonly IDateTimeProvider _dateTimeProvider;
private readonly IUserAccessor _userAccessor;
public AuditContextValuesProvider(IDateTimeProvider dateTimeProvider, IUserAccessor userAccessor)
{
_dateTimeProvider = dateTimeProvider;
_userAccessor = userAccessor;
}
public CommandContextValues Provide(object command)
{
var values = new CommandContextValues();
values["ExecutedAt"] = _dateTimeProvider.UtcNow;
values["ExecutedBy"] = _userAccessor.Current?.Id ?? "System";
values["TraceId"] = Activity.Current?.TraceId.ToString() ?? Guid.NewGuid().ToString();
// Example of using command information
values["CommandType"] = command.GetType().Name;
return values;
}
}
Registration
The provider will be automatically discovered and registered by the dependency injection system if it's in the application's assembly, or you can register it manually:
services.AddSingleton<ICommandContextValuesProvider, AuditContextValuesProvider>();
Accessing Command Context
Within command handlers, filters, or other components in the command pipeline, you can access the current command context through the ICommandContextAccessor:
public class MyCommandHandler : ICommandHandler<MyCommand>
{
private readonly ICommandContextAccessor _contextAccessor;
public MyCommandHandler(ICommandContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public async Task<object> Handle(CommandContext context)
{
// Access values from the context
if (context.Values.TryGetValue("ExecutedBy", out var executedBy))
{
// Use the execution user information for logging or business logic
}
// Or access through the accessor
var currentContext = _contextAccessor.Current;
return new { Success = true };
}
}
Non-Controller-Based Pipeline
The CommandContext is specifically designed for the non-controller-based command pipeline. In this pipeline:
- Commands are executed through the
ICommandPipeline - The context is created automatically with resolved dependencies
- Values are populated from all registered providers
- The context is made available throughout the execution chain
- Filters can inspect and modify the execution based on context values
- Response value handlers can use context information for processing results
This differs from the controller-based approach where ASP.NET Core's built-in dependency injection and model binding handle much of the context management.
Best Practices
- Keep value providers lightweight and fast
- Use descriptive keys for your context values
- Avoid storing large objects in context values
- Consider the lifetime of your providers (typically singleton)
- Handle cases where expected values might not be present
- Use the context values for cross-cutting concerns like auditing, logging, and authorization