-
Notifications
You must be signed in to change notification settings - Fork 12
Handler Filtering
LiteBus provides powerful mechanisms to selectively execute handlers based on the runtime context. This allows the same message to be processed differently depending on factors like the request source, user role, or environment. This is a core feature for building flexible and context-aware applications.
There are two primary ways to filter handlers: Tags and Predicates.
Tags are static labels applied to handlers at compile time. You can then specify which tags are active during mediation.
Use the [HandlerTag]
or [HandlerTags]
attributes.
using LiteBus.Messaging.Abstractions;
// This handler will only run if the "PublicAPI" tag is active.
[HandlerTag("PublicAPI")]
public class StrictValidationPreHandler : ICommandPreHandler<CreateUserCommand>
{
// ... logic for strict validation
}
// This handler is untagged and will always be considered for execution.
public class CommonValidationPreHandler : ICommandPreHandler<CreateUserCommand>
{
// ... logic for common validation
}
// This handler runs if either "Admin" or "Internal" tags are active.
[HandlerTags("Admin", "Internal")]
public class AdminDataEnrichmentHandler : IQueryPostHandler<GetUserQuery>
{
// ... logic to add sensitive data
}
You can specify tags when sending a message using either the extension methods or the MediationSettings
object.
// Using the extension method (for a single tag)
await _commandMediator.SendAsync(command, "PublicAPI");
// Using the settings object (for one or more tags)
var settings = new CommandMediationSettings
{
Filters = { Tags = new[] { "Admin", "HighPriority" } }
};
await _commandMediator.SendAsync(command, settings);
When you mediate with a set of tags:
- Untagged handlers are always executed. They are considered universal.
- Tagged handlers are executed only if at least one of their tags matches one of the tags provided during mediation.
- Handlers whose tags do not match any of the provided tags are skipped.
For more dynamic or complex filtering logic, you can provide a predicate function. The predicate is evaluated for each potential handler and receives an IHandlerDescriptor
object, which contains rich metadata about the handler.
This feature is configured via EventMediationSettings
but the principle can be adapted for other message types with custom mediators.
The HandlerPredicate
on EventMediationSettings.Routing
allows you to define a filter function.
var settings = new EventMediationSettings
{
Routing = new EventMediationRoutingSettings
{
// The predicate receives a descriptor for each potential handler.
// It should return true if the handler should be executed.
HandlerPredicate = descriptor =>
{
// Example: Only execute handlers from the "Core" namespace.
return descriptor.HandlerType.Namespace?.StartsWith("MyProject.Core") ?? false;
}
}
};
await _eventPublisher.PublishAsync(myEvent, settings);
The descriptor provides access to:
-
HandlerType
: TheSystem.Type
of the handler class. -
MessageType
: TheSystem.Type
of the message it handles. -
Priority
: The handler's priority value. -
Tags
: A collection of tags applied to the handler.
A common pattern is to filter handlers based on a marker interface, which is cleaner than checking type names.
// 1. Define a marker interface
public interface IHighPriorityEventHandler { }
// 2. Apply it to a handler
public class CriticalNotificationHandler : IEventHandler<SystemAlertEvent>, IHighPriorityEventHandler
{
// ...
}
// 3. Filter using the predicate
var settings = new EventMediationSettings
{
Routing =
{
HandlerPredicate = descriptor => descriptor.HandlerType.IsAssignableTo(typeof(IHighPriorityEventHandler))
}
};
await _eventPublisher.PublishAsync(alert, settings);
Tags and predicates can be used together. The filtering logic in LiteBus applies tags first to create an initial set of candidate handlers, and then the predicate is applied to further refine that set.
- Use Tags for Static Contexts: Tags are ideal for well-defined, static contexts like "PublicAPI", "InternalService", or "Reporting".
- Use Predicates for Dynamic Logic: Predicates are better for runtime decisions, feature flags, or complex rules that can't be expressed with simple labels.
- Define Constants for Tags: Avoid magic strings by defining your tags as constants in a shared class.
- Document Your Filters: Maintain clear documentation for what your tags and predicates represent to ensure your team uses them consistently.