Skip to content

Handler Filtering

A. Shafie edited this page Sep 26, 2025 · 1 revision

Handler Filtering (Tags & Predicates)

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.

1. Tag-Based Filtering

Tags are static labels applied to handlers at compile time. You can then specify which tags are active during mediation.

Applying Tags to Handlers

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
}

Mediating with Tags

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);

Tag Selection Logic

When you mediate with a set of tags:

  1. Untagged handlers are always executed. They are considered universal.
  2. Tagged handlers are executed only if at least one of their tags matches one of the tags provided during mediation.
  3. Handlers whose tags do not match any of the provided tags are skipped.

2. Predicate-Based Filtering

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.

Using a Predicate

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);

IHandlerDescriptor Properties

The descriptor provides access to:

  • HandlerType: The System.Type of the handler class.
  • MessageType: The System.Type of the message it handles.
  • Priority: The handler's priority value.
  • Tags: A collection of tags applied to the handler.

Use Case: Filtering by Marker Interface

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);

Combining Tags and Predicates

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.

Best Practices

  1. Use Tags for Static Contexts: Tags are ideal for well-defined, static contexts like "PublicAPI", "InternalService", or "Reporting".
  2. Use Predicates for Dynamic Logic: Predicates are better for runtime decisions, feature flags, or complex rules that can't be expressed with simple labels.
  3. Define Constants for Tags: Avoid magic strings by defining your tags as constants in a shared class.
  4. Document Your Filters: Maintain clear documentation for what your tags and predicates represent to ensure your team uses them consistently.
Clone this wiki locally