﻿# Configuration


In NServiceBus.Persistence.AzureStorage XML-based configuration is no longer available. Configuring the behavior of the persister is done using the code configuration API.

For sagas and subscriptions:

<!-- snippet: AzurePersistenceAllConnectionsCustomization -->

```cs
var persistence = endpointConfiguration.UsePersistence<AzureTablePersistence>();
persistence.ConnectionString("connectionString");
```

<!-- endsnippet -->

### Token-credentials

Enables usage of Microsoft Entra ID authentication such as [managed identities for Azure resources](https://learn.microsoft.com/en-us/azure/storage/tables/authorize-access-azure-active-directory) instead of the shared secret in the connection string.

Use the corresponding [`TableServiceClient`](https://learn.microsoft.com/en-us/dotnet/api/azure.data.tables.tableserviceclient.-ctor?view=azure-dotnet#azure-data-tables-tableserviceclient-ctor(system-uri-azure-core-tokencredential-azure-data-tables-tableclientoptions)) constructor overload when creating the client passed to the persistence.

### Saga configuration

<!-- snippet: AzurePersistenceSagasCustomization -->

```cs
var persistence = endpointConfiguration.UsePersistence<AzureTablePersistence, StorageType.Sagas>();
persistence.ConnectionString("connectionString");

// or

TableServiceClient tableServiceClient = new TableServiceClient("connectionString");
persistence.UseTableServiceClient(tableServiceClient);
```

<!-- endsnippet -->

The following settings are available for changing the behavior of saga persistence section:

 * `ConnectionString`: Sets the connection string for the storage account to be used for storing saga information.
 * `UseTableServiceClient`: Allows to set a fully pre-configured Table Service client instead of using a connection string.

### Subscription configuration

<!-- snippet: AzurePersistenceSubscriptionsCustomization -->

```cs
var persistence = endpointConfiguration.UsePersistence<AzureTablePersistence, StorageType.Subscriptions>();
persistence.ConnectionString("connectionString");
persistence.TableName("tableName");

// Added in Version 1.3
persistence.CacheFor(TimeSpan.FromMinutes(1));
```

<!-- endsnippet -->

The following settings are available for changing the behavior of subscription persistence:

 * `ConnectionString`: Sets the connection string for the storage account to be used for storing subscription information.
 * `UseTableServiceClient`: Allows to set a fully pre-configured Table Service client instead of using a connection string.

### Configuring a Table Service Client Provider

A fully preconfigured `TableServiceClient` can be registered in the container through a custom provider.

Create a customer provider:

<!-- snippet: CustomClientProvider -->

```cs
class CustomTableClientProvider : IProvideTableServiceClient
{
    // get fully configured via DI container
    public CustomTableClientProvider(TableServiceClient tableServiceClient)
    {
        Client = tableServiceClient;
    }
    public TableServiceClient Client { get; }
}

// optionally when subscriptions used
class CustomSubscriptionTableClientProvider : IProvideTableServiceClientForSubscriptions
{
    // get fully configured via DI container
    public CustomSubscriptionTableClientProvider(TableServiceClient tableServiceClient)
    {
        Client = tableServiceClient;
    }
    public TableServiceClient Client { get; }
}
```

<!-- endsnippet -->

Then register the provider in the container:

<!-- snippet: CustomClientProviderRegistration -->

```cs
builder.Services.AddSingleton<CustomTableClientProvider>();

// optionally when subscriptions used
builder.Services.AddSingleton<CustomSubscriptionTableClientProvider>();
```

<!-- endsnippet -->

### Table name configuration and creation

The default table name will be used for Sagas, Outbox and Subscription storage and can be set as follows:

<!-- snippet: SetDefaultTable -->

```cs
var persistence = endpointConfiguration.UsePersistence<AzureTablePersistence>();
persistence.ConnectionString("connectionString");
persistence.DefaultTable("TableName");

endpointConfiguration.EnableInstallers();
```

<!-- endsnippet -->

#### Configuring the table name

To provide a table at runtime or override the default table, the table information needs to be set as part of the message handling pipeline.

A behavior at the stage of the`ITransportReceiveContext`:

<!-- snippet: CustomTableNameUsingITransportReceiveContextBehavior -->

```cs
class ContainerInfoTransportReceiveContextBehavior
    : Behavior<ITransportReceiveContext>
{
    public override async Task Invoke(ITransportReceiveContext context, Func<Task> next)
    {
        context.Extensions.Set(
            new TableInformation(
                tableName: "tableName"));

        await next();
    }
}
```

<!-- endsnippet -->

A behavior at the stage of the `IIncomingLogicalMessageContext` can be used as well:

<!-- snippet: CustomTableNameUsingIIncomingLogicalMessageContextBehavior -->

```cs
class ContainerInfoLogicalReceiveContextBehavior
    : Behavior<IIncomingLogicalMessageContext>
{
    public override async Task Invoke(IIncomingLogicalMessageContext context, Func<Task> next)
    {
        context.Extensions.Set(
            new TableInformation(
                tableName: "tableName"));

        await next();
    }
}
```

<!-- endsnippet -->

#### Enabling automatic table creation

To enable table creation on endpoint start or during runtime, the `EnableInstallers` API needs to be called on the endpoint configuration.

Note that when the default table is set, the table will be created on endpoint-start. When the table information is provided as part of the message handling pipeline, the tables will be created at runtime.

<!-- snippet: EnableInstallersConfiguration -->

```cs
var persistence = endpointConfiguration.UsePersistence<AzureTablePersistence>();
persistence.ConnectionString("connectionString");
persistence.DefaultTable("TableName");

endpointConfiguration.EnableInstallers();
```

<!-- endsnippet -->

#### Opting out from table creation when installers are enabled

In case installers are enabled, but there's a need to opt out from creating the tables, the `DisableTableCreation`-API may be used:

<!-- snippet: EnableInstallersConfigurationOptingOutFromTableCreation -->

```cs
var persistence = endpointConfiguration.UsePersistence<AzureTablePersistence>();
persistence.ConnectionString("connectionString");
persistence.DefaultTable("TableName");

// make sure the table name specified in the DefaultTable exists when calling DisableTableCreation
endpointConfiguration.EnableInstallers();
persistence.DisableTableCreation();
```

<!-- endsnippet -->

### Partitioning and compatibility mode helpers

During a given message handling pipeline, multiple data storage operations may occur. To commit them atomically, in a single transaction, they must share a partition key. In conversations involving a saga, the saga ID is a good candidate for a partition key. Unfortunately, saga IDs are determined late in the message handling process and are not exposed to user code.

This makes it difficult to:

* Enable transactional saga data storage for existing sagas that were stored with the previous version of the persister.
* Allow other data operations to participate in the saga data transaction.

To support the above scenarios, `IProvidePartitionKeyFromSagaId` may be injected into behaviors at the logical pipeline stage:

<!-- snippet: BehaviorUsingIProvidePartitionKeyFromSagaId -->

```cs
class OrderIdAsPartitionKeyBehavior : Behavior<IIncomingLogicalMessageContext>
{
    public OrderIdAsPartitionKeyBehavior(IProvidePartitionKeyFromSagaId partitionKeyFromSagaId) =>
        this.partitionKeyFromSagaId = partitionKeyFromSagaId;

    public override async Task Invoke(IIncomingLogicalMessageContext context, Func<Task> next)
    {
        var correlationProperty = SagaCorrelationProperty.None;

        if (context.Message.Instance is IProvideOrderId provideOrderId)
        {
            Log.Debug($"Order ID: '{provideOrderId.OrderId}'");

            correlationProperty = new SagaCorrelationProperty("OrderId", provideOrderId.OrderId);
        }

        await partitionKeyFromSagaId.SetPartitionKey<OrderSagaData>(context, correlationProperty);

        Log.Debug($"Partition key: {context.Extensions.Get<TableEntityPartitionKey>().PartitionKey}");

        if (context.Headers.TryGetValue(Headers.SagaId, out var sagaId))
        {
            Log.Debug($"Saga ID: {sagaId}");
        }

        if (context.Extensions.TryGet<TableInformation>(out var tableInformation))
        {
            Log.Debug($"Table name: {tableInformation.TableName}");
        }

        await next();
    }

    public class Registration : RegisterStep
    {
        public Registration() :
            base(nameof(OrderIdAsPartitionKeyBehavior),
                typeof(OrderIdAsPartitionKeyBehavior),
                "Determines the PartitionKey from the logical message",
                provider => new OrderIdAsPartitionKeyBehavior(provider.GetRequiredService<IProvidePartitionKeyFromSagaId>())) =>
            InsertBefore(nameof(LogicalOutboxBehavior));
    }

    readonly IProvidePartitionKeyFromSagaId partitionKeyFromSagaId;
    static readonly ILog Log = LogManager.GetLogger<OrderIdAsPartitionKeyBehavior>();
}
```

<!-- endsnippet -->

`IProvidePartitionKeyFromSagaId` does the folllowing:

* Sets the partition key on the `IIncomingLogicalMessageContext` based on the following algorithm:
  * Use the saga ID header value if present. Otherwise:
  * When compatibility mode is enabled, and the correlation property is not `SagaCorrelationProperty.None`, look up the saga ID either using the secondary index if present, or by table scanning the saga data if that is [enabled](/persistence/azure-table/configuration.md#saga-configuration). Otherwise:
  * Calculate the saga ID based on the specified correlation property.
* If the table in which all data, including saga data, will be stored is not [already set](/persistence/azure-table/configuration.md#table-name-configuration-and-creation), set it using the saga data name as the table name.




For more information on connection string configuration, see [Configuring Azure Connection Strings](https://learn.microsoft.com/en-us/azure/storage/storage-configure-connection-string).
