Command Validation
The Arc provides built-in support for validating commands without executing them. This enables pre-flight validation to provide early feedback to users before performing potentially expensive or state-changing operations.
Overview
Command validation allows you to check authorization and validation rules without executing the command handler. This is useful for:
- Early User Feedback: Show validation errors before the user submits a form
- UX Improvements: Enable/disable submit buttons based on validation state
- Authorization Checks: Verify user permissions without side effects
- Progressive Validation: Validate fields as users interact with forms
For frontend usage of command validation, see:
- Core Command Validation - TypeScript/JavaScript API
- React Command Validation - React-specific patterns and hooks
Backend Support
ICommandPipeline.Validate
The ICommandPipeline interface provides a Validate method that runs only authorization and validation filters:
public interface ICommandPipeline
{
/// <summary>
/// Validates the given command without executing it.
/// </summary>
Task<CommandResult> Validate(object command);
}
Key Characteristics:
- Runs all command filters (authorization, validation)
- Does not invoke the command handler
- Returns a
CommandResultwith validation and authorization status - No side effects on the system
Example Usage
public class OrderService
{
private readonly ICommandPipeline _commandPipeline;
public OrderService(ICommandPipeline commandPipeline)
{
_commandPipeline = commandPipeline;
}
public async Task<bool> CanCreateOrder(CreateOrder command)
{
var result = await _commandPipeline.Validate(command);
return result.IsSuccess;
}
public async Task CreateOrder(CreateOrder command)
{
// Optionally validate first
var validationResult = await _commandPipeline.Validate(command);
if (!validationResult.IsSuccess)
{
// Handle validation errors
return;
}
// Execute the command
var result = await _commandPipeline.Execute(command);
// Process result...
}
}
Model-Bound Commands
For model-bound commands, validation endpoints are automatically created alongside execute endpoints:
Execute Endpoint: POST /api/orders/create-order
Validate Endpoint: POST /api/orders/create-order/validate
The validation endpoint accepts the same payload as the execute endpoint but only runs filters.
Controller-Based Commands
For controller-based commands, validation endpoints are automatically discovered and created at application startup. The system scans all controller actions that:
- Are POST methods
- Have a single
[FromBody]parameter (the command)
For each matching controller action, a corresponding /validate endpoint is automatically registered.
Example Controller:
[Route("api/carts")]
public class Carts : ControllerBase
{
[HttpPost("add")]
public Task AddItemToCart([FromBody] AddItemToCart command)
{
// Execute the command
}
}
Automatically Created Endpoints:
- Execute:
POST /api/carts/add - Validate:
POST /api/carts/add/validate(automatically created)
Key Points:
- Validation endpoints are automatically created for all controller command actions
- No attributes or special configuration required
- The system detects commands by looking for POST actions with
[FromBody]parameters - The route pattern for validation is:
{controller-action-route}/validate - Only validation and authorization filters run; the action method is not executed
How It Works:
During application startup, the ControllerCommandEndpointMapper service:
- Discovers all controller actions using
IActionDescriptorCollectionProvider - Identifies command actions (POST methods with
[FromBody]parameter) - Creates corresponding
/validateendpoints using Minimal APIs - Routes validation requests through the command pipeline's
Validatemethod
This approach provides the same validation functionality as model-bound commands without requiring developers to manually create validation actions.
Validation Filters
The validation pipeline runs all registered command filters:
Built-in Filters
- AuthorizationFilter: Checks user permissions
- DataAnnotationValidationFilter: Validates data annotations
- FluentValidationFilter: Runs FluentValidation validators
For more information, see Command Filters.
Best Practices
When to Implement Validation
✅ Good Use Cases:
- Commands that modify critical business data
- Commands with complex authorization requirements
- Commands with expensive validation logic that benefits from early feedback
- Commands used in interactive forms
❌ Less Beneficial:
- Simple CRUD operations with minimal validation
- Commands only executed by background processes
- Commands with very fast execution times
Performance Considerations
- Validation runs all filters, which may include database queries
- Optimize validator implementations for performance
- Consider caching authorization checks where appropriate
- Use appropriate indexes for validation queries
Security Considerations
- Validation endpoints run the same authorization filters as execute endpoints
- Unauthorized users receive 401/403 responses from validation endpoints
- Validation does not expose sensitive data since handlers aren't executed
- Validation results may reveal authorization policies (by design)
- Always implement proper authorization filters for commands
Troubleshooting
Validation endpoint returns 404
Cause: The validation endpoint may not be properly registered for controller-based commands.
Solution: Ensure the controller action follows the pattern:
- Is a POST method
- Has a single
[FromBody]parameter - The validation endpoint should be automatically created at
{route}/validate
Validation is slow
Cause: Complex validation logic or database queries in validators.
Solution:
- Optimize validator implementations
- Add appropriate database indexes
- Consider caching validation results where appropriate
- Profile validator performance to identify bottlenecks
Validation passes but execute fails
Cause: State may have changed between validate and execute calls, or the handler encountered an error.
Solution: This is expected behavior in concurrent systems. Validation is a pre-flight check, not a guarantee of execution success.