Service Bus Integration with Azure Functions
Azure Functions integrates natively with Azure Service Bus through Service Bus Triggers and Service Bus Output Bindings. A Service Bus Trigger automatically invokes a function whenever a message arrives in a queue or topic subscription — no polling code required. An Output Binding lets a function send messages to Service Bus as a simple return value or output object, without creating a sender client manually. This combination makes Azure Functions the most efficient way to build event-driven, serverless message processors on top of Service Bus.
Integration Architecture
TRIGGER — Function invoked when message arrives:
[Service Bus Queue / Subscription]
|
| message arrives
v
[Azure Function - ServiceBusTrigger]
|
| processes message
v
[Business logic + downstream services]
OUTPUT BINDING — Function sends message to Service Bus:
[HTTP Trigger / Timer / Event Grid]
|
v
[Azure Function]
|
| return value / IAsyncCollector
v
[Service Bus Queue / Topic]
SDK and Tool Setup
# Install Azure Functions Core Tools npm install -g azure-functions-core-tools@4 # Create a new function app (.NET isolated worker) func init OrderProcessingApp --worker-runtime dotnet-isolated # Navigate to app folder cd OrderProcessingApp # Add Service Bus extension dotnet add package Microsoft.Azure.Functions.Worker.Extensions.ServiceBus dotnet add package Azure.Identity
1. Service Bus Trigger — Queue
The [ServiceBusTrigger] attribute connects a function to a queue. The function runs each time a message arrives. Azure Functions manages the receiver, lock, and completion automatically.
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
public class OrderProcessor
{
private readonly ILogger<OrderProcessor> _logger;
public OrderProcessor(ILogger<OrderProcessor> logger)
{
_logger = logger;
}
[Function("ProcessOrder")]
public void Run(
[ServiceBusTrigger("orders", Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage message,
ServiceBusMessageActions messageActions)
{
_logger.LogInformation("Order received: {MessageId}", message.MessageId);
_logger.LogInformation("Body: {Body}", message.Body.ToString());
_logger.LogInformation("DeliveryCount: {Count}", message.DeliveryCount);
// Read custom properties
if (message.ApplicationProperties.TryGetValue("Region", out var region))
_logger.LogInformation("Region: {Region}", region);
// Auto-complete is on by default — message deleted after function returns
// To manually control settlement, set AutoCompleteMessages = false in host.json
}
}
Connection String Configuration
// local.settings.json (development)
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage" : "UseDevelopmentStorage=true",
"ServiceBusConnection" : "Endpoint=sb://myshopns.servicebus.windows.net/;SharedAccessKeyName=...;SharedAccessKey=...;",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
}
}
// For production in Azure App Settings:
// ServiceBusConnection = "Endpoint=sb://myshopns.servicebus.windows.net/;..."
//
// For Managed Identity (no connection string):
// ServiceBusConnection__fullyQualifiedNamespace = "myshopns.servicebus.windows.net"
2. Service Bus Trigger — Topic Subscription
[Function("ProcessInventoryEvent")]
public void Run(
[ServiceBusTrigger(
"order-events", // topic name
"inventory-sub", // subscription name
Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage message,
ILogger log)
{
log.LogInformation($"Inventory event: {message.Body}");
log.LogInformation($"Subject: {message.Subject}");
}
3. Manual Message Settlement
By default, the function runtime completes the message automatically when the function returns without error, and abandons it when the function throws an exception. For full manual control, disable auto-complete in host.json and use ServiceBusMessageActions.
Disable AutoComplete in host.json
{
"version" : "2.0",
"extensions" : {
"serviceBus" : {
"prefetchCount" : 0,
"messageHandlerOptions" : {
"autoComplete" : false,
"maxConcurrentCalls" : 10,
"maxAutoRenewDuration": "00:05:00"
}
}
}
}
Manual Settlement in Function Code
[Function("ManualSettlementExample")]
public async Task Run(
[ServiceBusTrigger("orders", Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage message,
ServiceBusMessageActions messageActions,
ILogger log)
{
string body = message.Body.ToString();
try
{
if (!body.Contains("orderId"))
{
// Invalid message — dead-letter it immediately
await messageActions.DeadLetterMessageAsync(
message,
deadLetterReason: "SchemaValidationFailed",
deadLetterErrorDescription: "Missing required field: orderId"
);
log.LogWarning("Message dead-lettered: invalid schema.");
return;
}
// Process the order...
log.LogInformation($"Processing order: {body}");
await messageActions.CompleteMessageAsync(message);
log.LogInformation("Message completed.");
}
catch (Exception ex)
{
log.LogError($"Processing failed: {ex.Message}. Abandoning.");
await messageActions.AbandonMessageAsync(message);
}
}
4. Output Binding — Send Message to Queue
The [ServiceBus] output binding sends a message to a queue or topic as a return value — no sender client code needed.
HTTP Trigger → Send to Queue
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Azure.Messaging.ServiceBus;
public class OrderIngestion
{
[Function("SubmitOrder")]
[ServiceBusOutput("orders", Connection = "ServiceBusConnection")]
public ServiceBusMessage Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
ILogger log)
{
string body = new StreamReader(req.Body).ReadToEnd();
log.LogInformation($"Order received from HTTP: {body}");
var message = new ServiceBusMessage(body)
{
MessageId = Guid.NewGuid().ToString(),
Subject = "NewOrder",
ContentType = "application/json"
};
message.ApplicationProperties["Source"] = "HTTP";
return message; // Azure Functions sends this to "orders" queue automatically
}
}
Timer Trigger → Send Batch to Queue
[Function("DailyBatchDispatch")]
public async Task Run(
[TimerTrigger("0 0 9 * * *")] TimerInfo timer, // Every day at 9 AM
[ServiceBus("orders", Connection = "ServiceBusConnection")]
IAsyncCollector<ServiceBusMessage> outputQueue,
ILogger log)
{
var pendingOrders = GetPendingOrdersFromDatabase(); // your data source
foreach (var order in pendingOrders)
{
var msg = new ServiceBusMessage(System.Text.Json.JsonSerializer.Serialize(order))
{
MessageId = $"batch-{order.Id}",
ContentType = "application/json"
};
await outputQueue.AddAsync(msg);
log.LogInformation($"Queued order {order.Id}");
}
log.LogInformation($"Batch dispatch complete. {pendingOrders.Count} orders queued.");
}
5. Using Managed Identity (No Connection String)
// In Azure Function App Settings, set: // ServiceBusConnection__fullyQualifiedNamespace = myshopns.servicebus.windows.net // Assign the Function App's Managed Identity these roles on the namespace: // Azure Service Bus Data Receiver (for trigger) // Azure Service Bus Data Sender (for output binding) // No connection string in settings — no secrets to rotate.
6. Concurrency and Scale Configuration
| Setting | Location | Purpose |
|---|---|---|
| maxConcurrentCalls | host.json | Max parallel messages per function instance |
| maxAutoRenewDuration | host.json | How long to auto-renew message locks |
| prefetchCount | host.json | Messages to pre-fetch for performance |
| FUNCTIONS_WORKER_PROCESS_COUNT | App Settings | Worker processes per instance |
| WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT | App Settings | Max scale-out instances (Consumption plan) |
{
"version": "2.0",
"extensions": {
"serviceBus": {
"prefetchCount": 10,
"messageHandlerOptions": {
"maxConcurrentCalls" : 16,
"maxAutoRenewDuration": "00:10:00",
"autoComplete" : true
}
}
}
}
7. Scale Behavior on Consumption Plan
Queue message count: 0 --> Function: 0 instances (scale to zero) Queue message count: 1,000 --> Function: scales out automatically (up to max instances) Queue message count: 10,000 --> Function: at max instances (e.g., 200) all processing in parallel Queue empties: 0 messages --> Function: scales back to 0 (Consumption plan)
8. Error Handling and Retry Policy
// host.json — configure retry policy for failed function executions
{
"version": "2.0",
"retry": {
"strategy" : "exponentialBackoff",
"maxRetryCount" : 5,
"minimumInterval" : "00:00:05",
"maximumInterval" : "00:02:00"
}
}
// If all retries fail:
// Message DeliveryCount exceeds MaxDeliveryCount on the queue
// --> Message moves to Dead Letter Queue automatically
Trigger + Output Binding — Chained Processing Pattern
[Function("ValidateAndRoute")]
[ServiceBusOutput("validated-orders", Connection = "ServiceBusConnection")]
public ServiceBusMessage? Run(
[ServiceBusTrigger("raw-orders", Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage message,
ServiceBusMessageActions actions,
ILogger log)
{
string body = message.Body.ToString();
if (!IsValid(body))
{
actions.DeadLetterMessageAsync(message, "ValidationFailed", "Schema mismatch");
return null; // Don't forward invalid messages
}
// Forward valid message to next queue in pipeline
return new ServiceBusMessage(body)
{
MessageId = message.MessageId,
Subject = "ValidatedOrder"
};
}
// Pipeline:
// [raw-orders] --> Function(ValidateAndRoute) --> [validated-orders]
Summary
Azure Functions integrates with Service Bus through Triggers (automatic invocation on message arrival) and Output Bindings (sending messages without client code). Triggers work for queues and topic subscriptions, support manual message settlement via ServiceBusMessageActions, and scale automatically from zero to hundreds of instances based on queue depth. Output bindings let any function type — HTTP, Timer, Event Grid — produce messages to Service Bus queues or topics with a simple return value or IAsyncCollector. Using Managed Identity with fullyQualifiedNamespace app settings removes all connection string secrets. Azure Functions and Service Bus together form the foundation of scalable, serverless, event-driven architectures on Azure.
