- 
                Notifications
    
You must be signed in to change notification settings  - Fork 12
 
Polymorphic Dispatch
Polymorphic dispatch is an advanced feature of LiteBus that allows a single handler to process a hierarchy of related messages. By creating a handler for a base message type, you can automatically handle any message that derives from it.
Polymorphic dispatch leverages C# contravariance (in keyword) in handler interfaces. It allows you to write common, cross-cutting logic once and apply it to an entire family of commands, queries, or events.
Important: This feature applies to pre-handlers, post-handlers, and error handlers. It does not apply to main handlers, as they are resolved to the most specific message type to ensure a single, definitive handler for each message.
Consider a set of commands that all relate to auditable actions.
First, define a base class or interface that all related messages will implement.
// Base interface for all auditable commands
public interface IAuditableCommand : ICommand
{
    Guid CorrelationId { get; }
    string UserId { get; }
}
// Concrete commands that implement the base interface
public sealed class CreateProductCommand : IAuditableCommand, ICommand<Guid>
{
    // ... properties
}
public sealed class DeleteUserCommand : IAuditableCommand, ICommand
{
    // ... properties
}Now, create a single pre-handler that targets the base IAuditableCommand interface.
// This single pre-handler will run for ANY command implementing IAuditableCommand.
public sealed class AuditingPreHandler : ICommandPreHandler<IAuditableCommand>
{
    private readonly IAuditLogger _auditLogger;
    public AuditingPreHandler(IAuditLogger auditLogger)
    {
        _auditLogger = auditLogger;
    }
    public Task PreHandleAsync(IAuditableCommand command, CancellationToken cancellationToken = default)
    {
        // Log that an auditable action is about to occur.
        _auditLogger.Log(
            $"Audit: Action of type '{command.GetType().Name}' initiated by user '{command.UserId}'."
        );
        return Task.CompletedTask;
    }
}When you send a derived command, LiteBus automatically discovers and executes the handler for the base type.
// Sending a CreateProductCommand...
await _commandMediator.SendAsync(new CreateProductCommand { ... });
// ...will trigger AuditingPreHandler.
// Sending a DeleteUserCommand...
await _commandMediator.SendAsync(new DeleteUserCommand { ... });
// ...will also trigger AuditingPreHandler.This behavior is enabled by the contravariant type parameter (in TMessage) on the handler interfaces:
// The 'in' keyword allows a handler for a base type to accept a derived type.
public interface IAsyncMessagePreHandler<in TMessage> { ... }
public interface IAsyncMessagePostHandler<in TMessage> { ... }When LiteBus resolves handlers, its ActualTypeOrFirstAssignableTypeMessageResolveStrategy finds handlers for both the concrete message type and any of its base types or implemented interfaces.
- 
Auditing: Create a single post-handler for an 
IAuditableinterface to log all state-changing operations. - 
Authorization: Implement a single pre-handler for a 
ISecuredOperationinterface to check user permissions for a family of related commands or queries. - 
Validation: A pre-handler for a base 
IPaginatedQuerycould validate that pagination parameters (PageNumber,PageSize) are within valid ranges for all queries that support pagination. - 
Tenant Isolation: A pre-handler for an 
ITenantSpecificinterface can ensure the request is scoped to the correct tenant. 
- Define Clear Base Contracts: The base message interface or class should define the common data needed by the polymorphic handler.
 - Use for Cross-Cutting Concerns: Polymorphic dispatch is ideal for logic that applies uniformly across a set of related messages, such as security, logging, or validation.
 - Remember the Scope: This feature is for pre-handlers, post-handlers, and error handlers. Each concrete command, query, or event must still have its own specific main handler.