Skip to content

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 via context.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.

MemberDescription
HasRespondedWhether 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 key

Context 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.

PropertyLocationDescription
DestinationAddressMessageContextTransport URI (e.g. kafka://broker:9092/orders)
SourceAddressMessageContextSender address, propagated via headers
MessageMessageContext<T>The typed message payload
MessageIdMessageContextUnique message identifier
TimestampMessageContextWhen the message was created or received
HeadersConsumeContext<T>Message headers (delegated from TransportContext)
RetryAttemptConsumeContext<T>Current retry count (0 on first attempt)
TransactionConsumeContext<T>Active transaction context, if any
TransportContextConsumeContext<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);