Service Bus with .NET SDK
The Azure.Messaging.ServiceBus NuGet package is the official .NET SDK for Azure Service Bus. It provides a fully async, AMQP-based API for all messaging operations. This topic covers every major SDK feature with production-ready code — client setup, sending, pull and push receiving, sessions, dead-letter queues, scheduled messages, administration, and error handling.
SDK Installation
dotnet add package Azure.Messaging.ServiceBus dotnet add package Azure.Identity
SDK Client Hierarchy
ServiceBusClient (one per application)
|-- CreateSender("queue-or-topic") --> ServiceBusSender
|-- CreateReceiver("queue") --> ServiceBusReceiver
|-- CreateReceiver("topic","sub") --> ServiceBusReceiver
|-- CreateProcessor("queue") --> ServiceBusProcessor
|-- CreateSessionProcessor("queue") --> ServiceBusSessionProcessor
|-- AcceptSessionAsync("queue","id") --> ServiceBusSessionReceiver
|-- AcceptNextSessionAsync("queue") --> ServiceBusSessionReceiver
ServiceBusAdministrationClient
|-- CreateQueueAsync / DeleteQueueAsync
|-- CreateTopicAsync / CreateSubscriptionAsync
|-- CreateRuleAsync / DeleteRuleAsync
|-- GetQueueRuntimePropertiesAsync
1. Creating the Client
Connection String (Development)
using Azure.Messaging.ServiceBus;
string connectionString =
"Endpoint=sb://myshopns.servicebus.windows.net/;" +
"SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=<key>";
await using var client = new ServiceBusClient(connectionString);
Managed Identity (Production — No Secrets)
using Azure.Identity; using Azure.Messaging.ServiceBus; string fqNamespace = "myshopns.servicebus.windows.net"; await using var client = new ServiceBusClient(fqNamespace, new DefaultAzureCredential());
Client Options — Transport and Retry
var options = new ServiceBusClientOptions
{
TransportType = ServiceBusTransportType.AmqpWebSockets,
RetryOptions = new ServiceBusRetryOptions
{
Mode = ServiceBusRetryMode.Exponential,
MaxRetries = 5,
Delay = TimeSpan.FromSeconds(1),
MaxDelay = TimeSpan.FromSeconds(30)
}
};
await using var client = new ServiceBusClient(connectionString, options);
2. Sending Messages
Single Message to a Queue
await using var sender = client.CreateSender("orders");
var order = new { orderId = 101, product = "Laptop", amount = 1200.00 };
string json = System.Text.Json.JsonSerializer.Serialize(order);
var message = new ServiceBusMessage(BinaryData.FromString(json))
{
MessageId = "order-101",
Subject = "NewOrder",
ContentType = "application/json",
TimeToLive = TimeSpan.FromHours(24)
};
message.ApplicationProperties["Region"] = "India";
message.ApplicationProperties["Amount"] = 1200.00;
await sender.SendMessageAsync(message);
Console.WriteLine("Message sent.");
Batch Sending (High Volume)
using ServiceBusMessageBatch batch = await sender.CreateMessageBatchAsync();
for (int i = 1; i <= 50; i++)
{
var msg = new ServiceBusMessage($"Order {i}") { MessageId = $"order-{i}" };
if (!batch.TryAddMessage(msg))
{
await sender.SendMessagesAsync(batch);
batch.Dispose();
batch = await sender.CreateMessageBatchAsync();
batch.TryAddMessage(msg);
}
}
await sender.SendMessagesAsync(batch);
3. Pull-Based Receiving
await using var receiver = client.CreateReceiver("orders",
new ServiceBusReceiverOptions { ReceiveMode = ServiceBusReceiveMode.PeekLock });
ServiceBusReceivedMessage msg =
await receiver.ReceiveMessageAsync(TimeSpan.FromSeconds(10));
if (msg != null)
{
try
{
Console.WriteLine($"Body: {msg.Body} | Delivery#: {msg.DeliveryCount}");
// business logic here
await receiver.CompleteMessageAsync(msg);
}
catch
{
await receiver.AbandonMessageAsync(msg);
}
}
4. Push-Based Receiving — ServiceBusProcessor
await using var processor = client.CreateProcessor("orders",
new ServiceBusProcessorOptions
{
MaxConcurrentCalls = 5,
AutoCompleteMessages = false,
MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(5)
});
processor.ProcessMessageAsync += async args =>
{
Console.WriteLine($"[{args.Message.MessageId}] {args.Message.Body}");
try
{
// process order
await args.CompleteMessageAsync(args.Message);
}
catch
{
await args.AbandonMessageAsync(args.Message);
}
};
processor.ProcessErrorAsync += args =>
{
Console.WriteLine($"Error [{args.ErrorSource}]: {args.Exception.Message}");
return Task.CompletedTask;
};
await processor.StartProcessingAsync();
Console.ReadLine();
await processor.StopProcessingAsync();
Concurrent Processing Diagram
Queue: orders MaxConcurrentCalls = 5 | |-- msg1 --> Thread 1 --> CompleteAsync |-- msg2 --> Thread 2 --> CompleteAsync |-- msg3 --> Thread 3 --> AbandonAsync (retry) |-- msg4 --> Thread 4 --> CompleteAsync |-- msg5 --> Thread 5 --> CompleteAsync
5. Session Processor
await using var sp = client.CreateSessionProcessor(
"order-session-queue",
new ServiceBusSessionProcessorOptions
{
MaxConcurrentSessions = 4,
MaxConcurrentCallsPerSession = 1,
AutoCompleteMessages = false
});
sp.ProcessMessageAsync += async args =>
{
Console.WriteLine($"Session [{args.Message.SessionId}]: {args.Message.Body}");
await args.CompleteMessageAsync(args.Message);
};
sp.ProcessErrorAsync += args =>
{
Console.WriteLine($"Error: {args.Exception.Message}");
return Task.CompletedTask;
};
await sp.StartProcessingAsync();
Console.ReadLine();
await sp.StopProcessingAsync();
6. Dead Letter Queue Access
await using var dlqRx = client.CreateReceiver("orders",
new ServiceBusReceiverOptions { SubQueue = SubQueue.DeadLetter });
var dlqMsg = await dlqRx.ReceiveMessageAsync(TimeSpan.FromSeconds(5));
if (dlqMsg != null)
{
Console.WriteLine($"Body : {dlqMsg.Body}");
Console.WriteLine($"Reason : {dlqMsg.DeadLetterReason}");
Console.WriteLine($"Desc : {dlqMsg.DeadLetterErrorDescription}");
await dlqRx.CompleteMessageAsync(dlqMsg);
}
7. Scheduled Messages
await using var sender = client.CreateSender("orders");
long seqNo = await sender.ScheduleMessageAsync(
new ServiceBusMessage("Future order") { MessageId = "future-001" },
DateTimeOffset.UtcNow.AddHours(8)
);
Console.WriteLine($"Scheduled. SeqNo: {seqNo}");
await sender.CancelScheduledMessageAsync(seqNo);
Console.WriteLine("Cancelled.");
8. Administration Client
using Azure.Messaging.ServiceBus.Administration;
var admin = new ServiceBusAdministrationClient(connectionString);
await admin.CreateQueueAsync(new CreateQueueOptions("payments")
{
MaxSizeInMegabytes = 2048,
DefaultMessageTimeToLive = TimeSpan.FromDays(7),
LockDuration = TimeSpan.FromMinutes(1),
MaxDeliveryCount = 10,
EnableDeadLetteringOnMessageExpiration = true,
RequiresDuplicateDetection = true
});
await admin.CreateTopicAsync("order-events");
await admin.CreateSubscriptionAsync("order-events", "inventory-sub");
await admin.DeleteRuleAsync("order-events", "inventory-sub", "$Default");
await admin.CreateRuleAsync("order-events", "inventory-sub",
new CreateRuleOptions
{
Name = "india-filter",
Filter = new SqlRuleFilter("Region = 'India' AND Amount > 1000"),
Action = new SqlRuleAction("SET Priority = 'Urgent'")
});
var stats = await admin.GetQueueRuntimePropertiesAsync("payments");
Console.WriteLine($"Active: {stats.Value.ActiveMessageCount}");
Console.WriteLine($"DLQ : {stats.Value.DeadLetterMessageCount}");
9. Transactions in .NET
await using var receiver = client.CreateReceiver("order-queue");
await using var sender = client.CreateSender("invoice-queue");
var orderMsg = await receiver.ReceiveMessageAsync(TimeSpan.FromSeconds(10));
if (orderMsg != null)
{
var invoiceMsg = new ServiceBusMessage($"Invoice for {orderMsg.MessageId}")
{ MessageId = $"inv-{orderMsg.MessageId}" };
using var txn = new ServiceBusTransactionContext();
try
{
await sender.SendMessageAsync(invoiceMsg, txn);
await receiver.CompleteMessageAsync(orderMsg, txn);
await txn.CommitAsync();
Console.WriteLine("Transaction committed.");
}
catch
{
await txn.RollbackAsync();
Console.WriteLine("Transaction rolled back — order stays in queue.");
}
}
10. Error Handling
| Exception Reason | Cause | Action |
|---|---|---|
| MessagingEntityNotFound | Queue/topic does not exist | Verify entity name |
| QuotaExceeded | Queue at storage limit | Drain faster or increase size |
| Unauthorized | Missing RBAC role or wrong key | Check policy rights |
| MessageSizeExceeded | Body too large for tier | Compress or use Premium |
| MessageLockLost | Lock expired mid-processing | Enable auto lock renewal |
| ServiceCommunicationError (transient) | Network blip | SDK retries automatically |
Best Practices
| Practice | Reason |
|---|---|
| One ServiceBusClient per app | Thread-safe; reuses AMQP connections |
Use await using | Properly closes AMQP links on exit |
| AutoCompleteMessages = false | Prevents message loss on handler crash |
| MaxAutoLockRenewalDuration | Avoids lock expiry without manual renewal |
| DefaultAzureCredential | Works with Managed Identity — no secrets in code |
| AmqpWebSockets transport | Port 443 bypasses corporate firewalls |
Summary
The Azure.Messaging.ServiceBus .NET SDK covers every Service Bus capability through a clean, async-first API. One ServiceBusClient per application handles all connections efficiently. The ServiceBusProcessor enables high-throughput concurrent consumption with automatic lock renewal. The ServiceBusAdministrationClient manages entity configuration programmatically. Using DefaultAzureCredential with Managed Identity eliminates secrets entirely in Azure-hosted workloads. Proper retry configuration, WebSocket transport, and explicit settlement build messaging solutions that are reliable, secure, and maintainable at production scale.
