Command Validation
Command validation enables pre-flight validation of commands without executing them. This provides early feedback to users before performing potentially expensive or state-changing operations.
Purpose
The validation mechanism allows you to check authorization and validation rules without executing the command handler. This is essential 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
How It Works
When you validate a command, the request is sent to the backend validation endpoint where:
- All command filters run (authorization, validation)
- The command handler is not executed
- A
CommandResultis returned with validation and authorization status - No side effects occur on the system
For details on the backend validation pipeline, see Backend Command Validation.
Command.validate() Method
All generated TypeScript command proxies include a validate() method alongside the execute() method:
interface ICommand<TCommandContent, TCommandResponse> {
/**
* Validate the command without executing it.
* Returns validation and authorization status.
*/
validate(): Promise<CommandResult<TCommandResponse>>;
/**
* Execute the command.
*/
execute(): Promise<CommandResult<TCommandResponse>>;
}
Basic Usage
import { CreateOrder } from './generated/commands';
async function validateOrder() {
const command = new CreateOrder();
command.orderNumber = 'ORD-12345';
command.customerId = '550e8400-e29b-41d4-a716-446655440000';
// Validate without executing
const result = await command.validate();
if (result.isSuccess) {
console.log('Command is valid and authorized');
} else {
if (!result.isAuthorized) {
console.log('User not authorized');
}
if (!result.isValid) {
console.log('Validation errors:', result.validationResults);
}
}
}
CommandResult Structure
Both execute() and validate() return the same CommandResult structure:
interface CommandResult<TResponse> {
correlationId: string;
isSuccess: boolean; // Overall success (authorized + valid + no exceptions)
isAuthorized: boolean; // Authorization status
isValid: boolean; // Validation status
hasExceptions: boolean; // Whether exceptions occurred
validationResults: ValidationResult[];
exceptionMessages: string[];
exceptionStackTrace: string;
response?: TResponse; // Only populated on execute()
}
interface ValidationResult {
message: string;
members: string[];
severity: 'Error' | 'Warning' | 'Info';
}
Important: The response property will be null or undefined when using validate() since the handler is not executed.
Validation Filters
The validate() method runs all registered command filters on the backend:
- AuthorizationFilter: Checks user permissions
- DataAnnotationValidationFilter: Validates data annotations
- FluentValidationFilter: Runs FluentValidation validators
For more information, see Backend Command Filters.
Best Practices
When to Use Validate
✅ Good Use Cases:
- Form validation as users type or blur fields
- Enabling/disabling submit buttons based on validation state
- Showing validation messages before submission
- Checking authorization before showing UI elements
❌ Avoid:
- Calling validate() immediately before execute() (execute already validates)
- Over-validating (don't validate on every keystroke for performance)
- Using validate() as a substitute for client-side validation
Performance Considerations
- Validation makes a server round-trip, so use judiciously
- Consider debouncing validation calls for real-time feedback
- Client-side validation is still important for immediate feedback
- Server validation ensures security and data integrity
Example: Debounced Validation
let validationTimeout: NodeJS.Timeout;
function debounceValidation(command: ICommand<any, any>, onResult: (result: CommandResult<any>) => void) {
clearTimeout(validationTimeout);
validationTimeout = setTimeout(async () => {
const result = await command.validate();
onResult(result);
}, 500);
}
// Usage
const command = new CreateOrder();
command.orderNumber = 'ORD-12345';
debounceValidation(command, (result) => {
if (!result.isSuccess) {
console.log('Validation errors:', result.validationResults);
}
});
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)
Framework-Specific Usage
For React-specific patterns and hooks, see React Command Validation.