Feature Collection & Payload Bag
Every MessageContext carries two extension mechanisms for sharing data across middleware without coupling to a specific transport:
- Feature collection (
IFeatureCollection): a type-safe dictionary of named capabilities, accessed viacontext.Features. - Payload bag (
SetPayload/TryGetPayload): a type-keyed dictionary for transport-specific objects, accessed directly on the context.
Feature collection
Reading a feature
var feature = context.Features.Get<IResponseFeature>();if (feature is not null){ await feature.RespondAsync(result);}Get<T> returns null if the feature is not present. Features are optional by design; middleware that reads them should always null-check before using them.
Writing a feature
context.Features.Set<IMyCustomFeature>(new MyCustomFeature(value));Later middleware in the same pipeline can read it. Features are scoped to the lifetime of the context.
Built-in features
IResponseFeature
For request-response patterns via the mediator. Carries the response back to the caller. Not present for broker consumers.
| Member | Description |
|---|---|
HasResponded | Whether a response has already been set |
RespondAsync<TResponse> | Sets the response and completes the request |
Payload bag
The payload bag stores transport-specific objects keyed by their CLR type. Unlike features (which use interface keys), the payload bag stores concrete types directly. Use it when you want to pass a fully-typed object that does not need an interface contract.
Writing a payload
context.SetPayload(myObject);Reading a payload
var payload = context.TryGetPayload<MyType>();if (payload is not null){ // use it}Built-in payloads
KafkaTransportContext<TKey>
On the consume side, KafkaTransportContext<TKey> is set as the TransportContext on ConsumeContext<T>. It carries all Kafka-specific metadata plus the deserialized message key:
if (context.TransportContext is KafkaTransportContext<string> kafka){ logger.LogDebug("Key: {Key}, Topic: {Topic}, Partition: {Partition}, Offset: {Offset}", kafka.Key, kafka.Topic, kafka.Partition, kafka.Offset);}On the produce side, the same type is stored as a payload on SendContext<T> so the terminal producer can extract the key:
var kafka = context.TryGetPayload<KafkaTransportContext<string>>();// kafka.Key contains the message keyContext properties
Most metadata middleware needs is available directly on the context, without going through features or payloads. This table is the authoritative reference; the consumers page links here rather than maintaining its own copy.
| Property | Location | Description |
|---|---|---|
DestinationAddress | MessageContext | Transport URI (e.g. kafka://broker:9092/orders) |
SourceAddress | MessageContext | Sender address, propagated via headers |
Message | MessageContext<T> | The typed message payload |
MessageId | MessageContext | Unique message identifier |
Timestamp | MessageContext | When the message was created or received |
Headers | ConsumeContext<T> | Message headers (delegated from TransportContext) |
RetryAttempt | ConsumeContext<T> | Current retry count (0 on first attempt) |
Transaction | ConsumeContext<T> | Active transaction context, if any |
TransportContext | ConsumeContext<T> | Full transport metadata: raw bytes, headers, provider ID |
Extracting transport info from URIs
Middleware can derive transport-agnostic information from DestinationAddress without knowing which transport is in use:
// Get the transport scheme (e.g. "kafka")var provider = EmitEndpointAddress.GetScheme(context.DestinationAddress);
// Get the entity name (e.g. topic name "orders")var destination = EmitEndpointAddress.GetEntityName(context.DestinationAddress);