Skip to content

Message transport middleware

Orleans establishes silo-to-silo and client-to-gateway connections through message transport connectors and listeners. Transport middleware decorates those components before they create or accept a connection.

IMessageTransportConnectorMiddleware applies to outbound connectors. IMessageTransportListenerMiddleware applies to inbound listeners. Orleans applies every registered middleware instance in dependency-injection registration order.

Orleans uses transport middleware to apply TLS. See Secure Orleans connections with TLS for the built-in security configuration.

Register connector and listener middleware as singleton services:

public static void RegisterTransportMiddleware(IServiceCollection services)
{
services.AddSingleton<IMessageTransportConnectorMiddleware, LoggingConnectorMiddleware>();
services.AddSingleton<IMessageTransportListenerMiddleware, LoggingListenerMiddleware>();
}

The middleware instances can be invoked for multiple connectors or listeners, so keep per-connection state in the returned decorator or the resulting MessageTransport.

A connector middleware receives the next MessageTransportConnector and returns the connector Orleans should use. The decorator can observe connection attempts, select another transport, or wrap the returned message transport:

internal sealed class LoggingConnector(
MessageTransportConnector inner,
ILogger<LoggingConnector> logger) : MessageTransportConnector
{
public override IFeatureCollection Features => inner.Features;
public override bool IsValid => inner.IsValid;
public override async ValueTask<MessageTransport> CreateAsync(
EndPoint endpoint,
CancellationToken cancellationToken = default)
{
logger.LogInformation("Connecting Orleans transport to {Endpoint}", endpoint);
return await inner.CreateAsync(endpoint, cancellationToken);
}
public override ValueTask DisposeAsync() => inner.DisposeAsync();
}

Delegate Features, IsValid, and disposal unless the middleware deliberately changes those guarantees.

Orleans owns the connector decorators returned by middleware. During host disposal, the connection factory closes connection-establishment admission, awaits admitted attempts, and disposes the decorated connector chain. Each decorator disposes its own resources and delegates disposal to its inner connector. The service provider owns the registered transport connector and disposes it separately.

A listener middleware receives the next MessageTransportListener and returns the listener Orleans should bind and accept from:

internal sealed class LoggingListener(
MessageTransportListener inner,
ILogger<LoggingListener> logger) : MessageTransportListener
{
public override bool IsValid => inner.IsValid;
public override string ListenerName => inner.ListenerName;
public override IFeatureCollection Features => inner.Features;
public override ValueTask BindAsync(CancellationToken cancellationToken = default) =>
inner.BindAsync(cancellationToken);
public override async ValueTask<MessageTransport?> AcceptAsync(
CancellationToken cancellationToken = default)
{
var transport = await inner.AcceptAsync(cancellationToken);
if (transport is not null)
{
logger.LogInformation(
"Accepted Orleans transport on listener {ListenerName}",
ListenerName);
}
return transport;
}
public override ValueTask UnbindAsync(CancellationToken cancellationToken = default) =>
inner.UnbindAsync(cancellationToken);
public override ValueTask DisposeAsync() => inner.DisposeAsync();
}

Preserve the listener name so Orleans can select the silo and gateway listeners by role. Return null from AcceptAsync when the inner listener has stopped, matching the listener contract.

Customizations previously registered through ClientConnectionOptions, SiloConnectionOptions, or IConnectionMiddleware migrate to the connector and listener decorators shown above. Register outbound behavior as connector middleware and inbound behavior as listener middleware. Listener decorators can use the listener name to configure the silo and gateway roles independently.

The message transport request contracts own framing, completion, and buffer lifetimes. Decorators preserve those contracts while applying per-connection behavior.