Skip to content

Sessions

Session support enables ordered, grouped message processing - all messages with the same session ID are delivered to the same handler instance in FIFO order.

Azure Service Bus prerequisite

The queue or subscription must be created with RequiresSession = true. This is an immutable property set at entity creation time - you cannot enable sessions on an existing non-session entity. You must recreate the entity.

bicep
resource sessionQueue 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = {
  name: 'session-queue'
  properties: {
    requiresSession: true
  }
}

If you use the library's topology validation (ValidateTopologyOnStartup = true), it will detect a mismatch between a session handler and a non-session entity at startup.

Session-Enabled Entities

csharp
// Session-aware queue handler
messaging.MapQueue<TMessage>("session-queue")
    .MapSessionHandler(async (
        TMessage message,
        SessionContext session,
        CancellationToken ct) =>
    {
        Console.WriteLine($"Session: {session.SessionId}, State: {session.State}");
        await session.SetStateAsync(newState, ct);
    });

// Session-aware subscription handler
messaging.MapTopic<TMessage>("topic")
    .MapSessionSubscription("subscription", handler);

INFO

When sending messages to a session-enabled entity, you must set a session ID via SendOptions:

csharp
await sender.SendAsync(message, opts => opts.SessionId = "customer-123", ct);

Azure Service Bus will reject messages without a session ID on session-enabled entities.

Session Context

SessionContext is injected into session handlers and provides SessionId, LockedUntil, and methods to get/set/clear session state (GetStateAsync, SetStateAsync, ClearStateAsync) and renew the session lock.

Session State Storage

Azure Service Bus provides up to 256 KB of binary session state per session. This state is stored server-side and survives handler restarts. Use it for lightweight checkpointing, not as a general-purpose store.

Sessions with Polymorphic Queues

Sessions work seamlessly with polymorphic queues. A single session-enabled queue can carry multiple message types - the session ID routes to the correct consumer, and the MessageType discriminator routes to the correct handler within that consumer.

csharp
// All command types share one session queue - gatewayId is the session key
var commands = messaging.MapPolymorphicQueue("gateway-commands", sessions: true);
commands.MapMessage<PingGatewayRequest>(handler);
commands.MapMessage<SetBatterySetpointRequest>(handler);
commands.MapMessage<RefreshConfigurationRequest>(handler);

See Core Concepts - Polymorphic Queues and Patterns - Polymorphic Queue for details.

Competing Consumers and Session Filtering

When multiple instances of the same process connect to a session-enabled queue, the Azure Service Bus broker makes sessions available to whichever processor calls AcceptNextSessionAsync first. Without a session filter, every instance races to acquire any available session - meaning a message targeted at device-A can end up processed by the instance meant for device-B.

WARNING

A ServiceBusSessionProcessor with no SessionIds filter accepts any session from the queue. In a multi-instance deployment where each instance should own exactly its own sessions, this causes non-deterministic mis-routing that is consistent until the session lock expires, then shifts again.

Use WithAcceptedSessionId to restrict a session processor to sessions matching a specific ID:

csharp
// Each process knows its own ID and registers a filtered processor
var gatewayId = configuration.GetValue<string>("GatewayId")!;

messaging.MapPolymorphicQueue("gateway-commands", sessions: true)
    .WithAcceptedSessionId(gatewayId)          // only accept sessions for this instance
    .MapMessage<PingGatewayRequest, PingHandler>()
    .MapMessage<SetBatterySetpointRequest, SetpointHandler>();

WithAcceptedSessionId can be called multiple times if a single process legitimately owns more than one session ID:

csharp
// A redundant pair that both handle the same gateway's sessions
commands
    .WithAcceptedSessionId("gateway-primary")
    .WithAcceptedSessionId("gateway-secondary");

INFO

The sender side does not call WithAcceptedSessionId - that is a receiver-only concern. The sender only needs to set opts.SessionId to the target ID when sending.

Why this matters for polymorphic queues

The MessageType application property (set by the sender) routes to the correct handler within a consumer. Session filtering determines which consumer instance receives the message at all. Both layers are necessary for correct multi-instance targeting:

LayerMechanismResponsibility
Which consumer instanceSessionIds filter (WithAcceptedSessionId)Routing to correct process
Which handler within the consumerMessageType application propertyRouting to correct handler type