﻿# Azure Functions with Service Bus (Isolated Worker)


The isolated worker model is a newer hosting option for Azure Functions. Running out-of-process decouples the function code from the Azure Functions runtime. For further information about the isolated worker, refer to the [Microsoft documentation](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide).

> [!NOTE]
> **The NServiceBus.AzureFunctions.Worker.ServiceBus component is feature complete**
>
> We will continue to provide support and address critical fixes, but no new features will be added.
> New projects should use the [NServiceBus.AzureFunctions.AzureServiceBus](/nservicebus/hosting/azure/functions/index.md) instead. Follow [these instructions](/nservicebus/upgrades/azure-functions-service-bus-isolated-to-azure-functions.md) to migrate.
> For information on the date when this component will stop receiving any update check our [support policy page](/nservicebus/upgrades/all-versions.md#host-packages-nservicebus-azurefunctions-worker-servicebus).

## Usage

NServiceBus supports the isolated worker hosting model via the `NServiceBus.AzureFunctions.Worker.ServiceBus` NuGet package. The following configuration enables NServiceBus:

> [!NOTE]
> An Azure function project can only contain a single `NServiceBusTriggerFunction` attribute. A project maps to a single Azure Service Bus queue to process incoming messages.

<!-- snippet: asb-function-isolated-configuration -->

```cs
[assembly: NServiceBusTriggerFunction("WorkerDemoEndpoint")]

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = FunctionsApplication.CreateBuilder(args);

        builder.AddNServiceBus();

        var host = builder.Build();

        await host.RunAsync();
    }
}
```

<!-- endsnippet -->

The Azure Service Bus trigger is automatically generated by the `NServiceBusTriggerFunction` attribute, which forwards incoming messages to the appropriate message handlers.

If customization of the Azure Service Bus trigger is required, it can be manually declared instead of relying on the auto-generated trigger. See the article on [custom Azure Functions triggers](/nservicebus/hosting/azure-functions-service-bus/custom-triggers.md) for more information.

### Dispatching outside a message handler

To send/publish messages outside a message handler, inject `IFunctionEndpoint` into custom triggers. This snippet shows an HTTP trigger sending a message via NServiceBus:

<!-- snippet: asb-function-isolated-dispatching-outside-message-handler -->

```cs
public class HttpTrigger(IFunctionEndpoint functionEndpoint)
{
    [Function("HttpSender")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequestData req,
        FunctionContext executionContext)
    {
        await functionEndpoint.Send(new TriggerMessage(), executionContext);

        return req.CreateResponse(HttpStatusCode.OK);
    }
}
```

<!-- endsnippet -->

## Configuration

`ServiceBusTriggeredEndpointConfiguration` loads certain configuration values from the Azure Function host environment in the following order:

1. `IConfiguration`
2. Environment variables

### ServiceBus connection

Connection to Azure Service Bus can be configured in multiple ways:

- Using just a `<ConnectionName>` key, with the value set to the connection string for the ServiceBus namespace to connect to.
- Using a `<CONNECTION_NAME_PREFIX>__fullyQualifiedNamespace` along with other connection properties prefixed by the same `<CONNECTION_NAME_PREFIX>`, as specified in the [Identity Based Connections](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference?tabs=blob&pivots=programming-language-csharp#common-properties-for-identity-based-connections) for Azure Functions.

If both a connection string and Identity Based connection values are specified, the connection string will take precedence. The default `<ConnectionName>` or `<CONNECTION_NAME_PREFIX>` is `AzureWebJobsServiceBus`, however an alternate value can be supplied to the `Connection` property on the `NServiceBusTriggerAttribute`

Example of a connection string based connection:

```json
{
  "IsEncrypted": false,
  "Values": {
    ...
    "AzureWebJobsServiceBus": "Endpoint=sb://<namespace>.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=<namespace-shared-access-key>",
  }
}
```

Example of an Identity Based connection:

```json
{
  "IsEncrypted": false,
  "Values": {
    ...
    "MyConnectionName__fullyQualifiedNamespace": "<namespace>.servicebus.windows.net",
    "MyConnectionName__tenantId": "00000000-0000-0000-0000-000000000000",
    "MyConnectionName__clientId": "00000000-0000-0000-0000-000000000000",
    "MyConnectionName__clientSecret": "<client-secret>"
  }
}
```

<!-- snippet: asb-function-isolated-identity-connection -->

```txt
[assembly: NServiceBusTriggerFunction("WorkerDemoEndpoint", Connection = "MyConnectionName")]
```

<!-- endsnippet -->

### Other Configuration

| Key                             | Value      | Notes     |
|---------------------------------|------------|-----------|
| `PARTICULARSOFTWARE_LICENSE`    | The NServiceBus license | Can also be provided via `serviceBusTriggeredEndpointConfig.AdvancedConfiguration.License(...)` or via `NSERVICEBUS_LICENSE` environment variable for backward compatibility reasons. |
| `ENDPOINT_NAME`                 | The name of the NServiceBus endpoint to host | Optional. By default, the endpoint name is derived from the `NServiceBusTriggerFunction` attribute. |
| `WEBSITE_SITE_NAME`             | The name of the Azure Function app. Provided when hosting the function in Azure. | Optional. Used to set the NServiceBus [host identifier](/nservicebus/hosting/override-hostid.md). Local machine name is used if not set. |

For local development, use the `local.settings.json` file. In Azure, specify a Function setting using the environment variable as the key.

```json
{
  "IsEncrypted": false,
  "Values": {
    ...
    "PARTICULARSOFTWARE_LICENSE": "<?xml version=\"1.0\" encoding=\"utf-8\"?><license id=\"1222e1d1-2222-4a46-b1c6-943c442ca710\" expiration=\"2113-11-30T00:00:00.0000000\" type=\"Standard\" LicenseType=\"Standard\" LicenseVersion=\"4.0\" MaxMessageThroughputPerSecond=\"Max\" WorkerThreads=\"Max\" AllowedNumberOfWorkerNodes=\"Max\">. . .</license>"
  }
}
```

### Transactions

The isolated worker model does not support using [`TransportTransactionMode.SendsAtomicWithReceive`](/transports/transactions.md#transaction-modes-transport-transaction-sends-atomic-with-receive). [`TransportTransactionMode.ReceiveOnly`](/transports/transactions.md#transaction-modes-transport-transaction-receive-only) is the default option.

### Startup Diagnostics

[NServiceBus startup diagnostics](/nservicebus/hosting/startup-diagnostics.md), which help troubleshoot configuration and initialization issues, are disabled by default when using Azure Functions. Diagnostics can be written to the application log file via the `LogDiagnostics()` method and will show up in the [Azure Function log stream](https://learn.microsoft.com/en-us/azure/azure-functions/streaming-logs?tabs=built-in%2Cazure-portal).

<!-- snippet: asb-function-isolated-enable-diagnostics -->

```cs
public static async Task Main(string[] args)
{
    var builder = FunctionsApplication.CreateBuilder(args);

    builder.AddNServiceBus(configuration =>
    {
        configuration.LogDiagnostics();
    });

    var host = builder.Build();

    await host.RunAsync();
}
```

<!-- endsnippet -->

Diagnostics data will be written with logger identification `StartupDiagnostics` with log level *Informational* (`LogLevel.Info`).

#### Custom Diagnostics Writer

For full control over the diagnostic log output, the `AdvancedConfiguration.CustomDiagnosticsWriter` can be used. This is advantageous when diagnostics need to be persisted beyond the function's execution lifetime or when centralized diagnostic storage is preferred for multiple function instances. As an example, the diagnostics can be written to [Azure Blob Storage](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet?tabs=visual-studio%2Cmanaged-identity%2Croles-azure-portal%2Csign-in-azure-cli%2Cidentity-visual-studio&pivots=blob-storage-quickstart-scratch):

<!-- snippet: asb-function-iso-diagnostics-blob -->

```cs
var builder = FunctionsApplication.CreateBuilder(args);

builder.AddNServiceBus(configuration =>
{
    configuration.AdvancedConfiguration.CustomDiagnosticsWriter(
        async (diagnostics, cancellationToken) =>
        {
            var connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
            var blobServiceClient = new BlobServiceClient(connectionString);

            var containerClient = blobServiceClient.GetBlobContainerClient("diagnostics");
            await containerClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken);

            var blobName = $"{endpointName}-configuration.txt";
            var blobClient = containerClient.GetBlobClient(blobName);
            await blobClient.UploadAsync(BinaryData.FromString(diagnostics), cancellationToken);
        });
});

var host = builder.Build();
```

<!-- endsnippet -->

### Error queue

By default, repeatedly failing messages are sent to the `error` queue. The error queue can be configured or disabled completely to let the native Azure Service Bus dead-lettering configuration handle failures:

<!-- snippet: asb-function-isolated-configure-error-queue -->

```cs
public static async Task Main(string[] args)
{
    var builder = FunctionsApplication.CreateBuilder(args);

    builder.AddNServiceBus(configuration =>
    {
        // Change the error queue name:
        configuration.AdvancedConfiguration.SendFailedMessagesTo("my-custom-error-queue");

        // Or disable the error queue to let ASB native dead-lettering handle repeated failures:
        configuration.DoNotSendMessagesToErrorQueue();
    });

    var host = builder.Build();

    await host.RunAsync();
}
```

<!-- endsnippet -->

### Known constraints and limitations

The configuration API exposes NServiceBus transport configuration options via the `configuration.Transport` property to allow customization. However, not all options will be [applicable to execution within Azure Functions](analyzers.md).

Use the `configuration.Routing` property to configure transport routing.

## Preparing the Azure Service Bus namespace

Function endpoints can create queues or other infrastructure in the Azure Service Bus namespace using the `configuration.AdvancedConfiguration.EnableInstallers()` method.

To manually provision the entities in the namespace for the Function endpoint, use the [`asb-transport` command line (CLI) tool](/transports/azure-service-bus/operational-scripting.md).

### Creating the endpoint queue

For more details, see the [operation scripting documentation](/transports/azure-service-bus/operational-scripting.md#available-commands-asb-transport-endpoint-create) for the `asb-transport endpoint create` command.

> [!WARNING]
> If the `asb-transport` command-line tool is not used to create the queue, set the `MaxDeliveryCount` setting to the maximum value.

### Subscribing to events

For more details, see the [operational scripting documentation](/transports/azure-service-bus/operational-scripting.md#available-commands-asb-transport-endpoint-subscribe) for the `asb-transport endpoint subscribe` command.

### Topology configuration

The [default topology](/transports/azure-service-bus/topology.md) (using topic-per-event approach) can be overridden by adding topology options to the Application configuration.

The functions integration looks for either topology options placed into `AzureServiceBus:TopologyOptions` or `AzureServiceBus:MigrationTopologyOptions` for migration scenarios.

<!-- snippet: ASBFunctionsWorker-topology-options -->

```cs
var builder = FunctionsApplication.CreateBuilder(args);

builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
builder.AddNServiceBus();

var host = builder.Build();
```

<!-- endsnippet -->

Using the default topology

<!-- snippet: ASBFunctionsWorker-topology-options-json -->

```json
{
  "AzureServiceBus": {
    "TopologyOptions": {
      "PublishedEventToTopicsMap": {
        "MyNamespace.SomeEvent": "some-event"
      },
      "SubscribedEventToTopicsMap": {
        "MyNamespace.SomeEvent": [
          "some-event",
          "some-other-event"
        ]
      },
      "QueueNameToSubscriptionNameMap": {
        "Publisher": "PublisherSubscriptionName"
      }
    }
  }
}
```

<!-- endsnippet -->

Using the migration topology

<!-- snippet: ASBFunctionsWorker-topology-migration-options-json -->

```json
{
  "AzureServiceBus": {
    "MigrationTopologyOptions": {
      "TopicToPublishTo": "TopicToPublishTo",
      "TopicToSubscribeOn": "TopicToSubscribeOn",
      "EventsToMigrateMap": [
        "MyNamespace.NotYetMigratedEvent"
      ],
      "SubscribedEventToRuleNameMap": {
        "MyNamespace.NotYetMigratedEvent": "EventRuleName"
      },
      "PublishedEventToTopicsMap": {
        "MyNamespace.MigratedEvent": "MigratedEvent"
      },
      "SubscribedEventToTopicsMap": {
        "MyNamespace.MigratedEvent": "MigratedEvent"
      },
      "QueueNameToSubscriptionNameMap": {
        "Publisher": "PublisherSubscriptionName"
      }
    }
  }
}
```

<!-- endsnippet -->


