Passing information between Handlers in the Pipeline
A key constraint of the Pipes and Filters architectural style is that Filters do not share state. One reason is that this limits your ability to recompose the pipeline as steps must follow other steps.
However, when dealing with Handlers that implement orthogonal concerns it can be useful to pass context along the chain. Given that many orthogonal concerns have constraints about ordering anyway, we can live with the ordering constraints imposed by passing context.
The first thing is to avoid adding extra properties to the Command to support handling state for these orthogonal Filter steps in your pipeline. This couples your Command to orthogonal concerns and you really only want to bind it to your Target Handler.
Instead we provide a Request Context and Context Bag as part of the Command Dispatcher which is injected into each Handler in the Pipeline. The lifetime of this Request Context is the lifetime of the Request (although you will need to take responsibility for freeing any unmanaged resources you place into the Context Bag for example when code called after the Handler that inserts the resource into the Bag returns to the Handler).
Using the Context Bag
The Context Bag is a ConcurrentDictionary<string, object> that allows you to pass arbitrary data between handlers in the pipeline.
public class MyContextAwareCommandHandler : RequestHandler<MyCommand>
{
public static string TestString { get; set; }
public override MyCommand Handle(MyCommand command)
{
LogContext();
return base.Handle(command);
}
private void LogContext()
{
TestString = (string)Context.Bag["TestString"];
Context.Bag["MyContextAwareCommandHandler"] = "I was called and set the context";
}
}Internally we use the Context Bag in a number of the Quality of Service supporting Attributes we provide. See Fallback for example.
Request Context Capabilities
Setting Request Context Explicitly
You can set the RequestContext explicitly when calling Send, Publish, or DepositPost methods. This allows you to set properties of the RequestContext for transmission to the RequestHandler instead of having a new context created by the RequestContextFactory for that pipeline.
Partition Key
The PartitionKey allows you to control message routing to specific partitions in messaging systems like Kafka, Azure Service Bus, or AWS Kinesis. This is useful for ensuring related messages are processed in order.
Setting Partition Key via Context Bag:
Or using a PartitionKey object:
Important Notes:
This value is optional and may be ignored by the transport if partitioning isn't supported
Custom message mappers may choose to ignore this value entirely
When not set, the transport's default partitioning strategy will be used
Default Brighter mappers will use this value when present
Custom Headers
You can add custom headers to messages dynamically via the Request Context. These headers are merged with any static headers configured on the Publication.
Important Notes:
Headers set here take precedence over static header configurations
Custom message mappers may ignore these headers entirely
Merged with
Publication.DefaultHeadersby default Brighter mappers
CloudEvents Extensions
When using CloudEvents, you can add custom extension properties via the Request Context.
These properties will be serialized as CloudEvent extensions in the generated message envelope.
Originating Message
For consumers (subscribers to queues or streams), the Request Context now provides access to the OriginatingMessage. This allows you to examine properties of the message that was received, which is useful for debugging and accessing message metadata.
Use Cases:
Debugging: Understand how we got to this request
Auditing: Log message metadata for compliance
Routing: Access original message routing keys
Headers: Read custom headers that weren't mapped to the command
Important: This is not thread-safe; the assumption is that you set this from a single thread and access the message from multiple threads. It is not intended to be set from multiple threads.
OpenTelemetry Span
The Request Context provides access to the current Span (Activity) for adding custom OpenTelemetry attributes, events, and tags.
See Telemetry for more information on OpenTelemetry integration.
Destination Override
You can override the destination topic and CloudEvents type for a message using the Destination property.
This allows runtime routing decisions based on command content or business rules.
Resilience Context
The Request Context integrates with Polly's ResiliencePipeline (V8+) for resilience operations. The ResilienceContext can be used to share data across different resilience policies during execution.
See Polly Resilience Pipeline for more information.
Resilience Pipeline Registry
Access pre-configured resilience pipelines by name:
Well-Known Context Bag Keys
Brighter provides well-known keys for the Context Bag via the RequestContextBagNames class:
Using these constants ensures consistency and avoids magic strings:
Best Practices
1. Use Well-Known Keys
2. Clean Up Resources
If you place unmanaged resources in the Context Bag, ensure they are properly disposed:
3. Document Custom Context Keys
If you use custom context bag keys, document them clearly:
4. Check for Null Before Accessing Properties
5. Use Explicit RequestContext for Important Metadata
Related Documentation
Dispatching Requests - How to dispatch commands and events
Fallback - Using context for fallback scenarios
Telemetry - OpenTelemetry integration with Span
Polly Resilience Pipeline - Resilience context integration
Cloud Events Support - CloudEvents extension properties
Summary
The Request Context provides a powerful mechanism for passing information between handlers in a pipeline:
Context Bag: Pass arbitrary data between handlers
Partition Key: Control message routing to specific partitions
Custom Headers: Add dynamic headers to messages
CloudEvents Extensions: Add custom CloudEvents properties
Originating Message: Access original message metadata in consumers
OpenTelemetry Span: Add custom traces and attributes
Destination Override: Dynamic message routing
Resilience Context: Integration with Polly resilience pipelines
Use these capabilities judiciously to avoid coupling your commands to orthogonal concerns while enabling necessary cross-cutting functionality.
Last updated
Was this helpful?
