Skip to main content

Azure Service Bus with C#: Complete Tutorial (2026)

Learn Azure Service Bus with C# in this hands-on tutorial. Master queues, topics, and event-driven architecture with runnable .NET code examples. Start now!

If you are building distributed systems on Microsoft's cloud, learning Azure Service Bus with C# is one of the highest-value skills you can add to your toolkit. As applications move away from monolithic designs toward microservices and event-driven architecture, a reliable message broker becomes the backbone that keeps services loosely coupled, resilient, and scalable. In this Azure Service Bus tutorial, you'll learn exactly how to send and receive messages using queues and topics with the modern Azure.Messaging.ServiceBus .NET SDK, understand why asynchronous messaging matters, and avoid the pitfalls that trip up most developers.

Whether you're a beginner searching "how to use Azure Service Bus," an intermediate developer looking for best practices, or a senior engineer designing advanced event-driven systems in C#, this guide covers the full journey with practical, runnable code.

What Is Azure Service Bus and Why Use It?

Azure Service Bus is a fully managed enterprise message broker that enables reliable, asynchronous communication between decoupled application components. Instead of Service A calling Service B directly over HTTP — which fails the moment Service B is down or overloaded — Service A drops a message into a queue, and Service B processes it whenever it's ready.

This decoupling is the heart of event-driven architecture. Here's why it matters:

  • Resilience: If a consumer crashes, messages wait safely in the queue instead of being lost.
  • Load leveling: A sudden traffic spike fills the queue rather than overwhelming your database. Consumers drain it at a steady pace.
  • Loose coupling: Producers and consumers don't need to know about each other, run at the same time, or even be written in the same language.
  • Guaranteed delivery: Service Bus supports at-least-once delivery, dead-lettering, and transactions.

Compared to Azure Storage Queues, Azure Service Bus offers advanced features like topics (publish/subscribe), sessions (FIFO ordering), message deferral, and dead-letter queues — making it the right choice for enterprise messaging in C#.

Getting Started: Setting Up Azure Service Bus in C#

First, create a Service Bus namespace in the Azure Portal (the Standard or Premium tier supports topics). Then add a queue named orders. Once that's ready, install the official NuGet package into your .NET project:

// Install via the .NET CLI
// dotnet add package Azure.Messaging.ServiceBus

using Azure.Messaging.ServiceBus;
using System.Text.Json;

The modern Azure.Messaging.ServiceBus library replaces the older Microsoft.Azure.ServiceBus package. Always use this newer SDK — it's faster, supports the latest features, and integrates cleanly with dependency injection and ServiceBusClient as a singleton.

Best Practice: Reuse a Single ServiceBusClient

The ServiceBusClient manages the underlying AMQP connection. Creating a new one per message is a common and expensive mistake. Register it once as a singleton so the connection is pooled across your entire application.

// Program.cs (ASP.NET Core dependency injection)
builder.Services.AddSingleton(sp =>
    new ServiceBusClient(builder.Configuration["ServiceBus:ConnectionString"]));

How to Send Messages to an Azure Service Bus Queue

Sending a message is straightforward. You create a ServiceBusSender, serialize your payload (JSON is the common choice), and call SendMessageAsync. Here's a complete, runnable example that sends an order message:

public record Order(int OrderId, string Customer, decimal Amount);

public class OrderPublisher
{
    private readonly ServiceBusClient _client;

    public OrderPublisher(ServiceBusClient client) => _client = client;

    public async Task SendOrderAsync(Order order)
    {
        ServiceBusSender sender = _client.CreateSender("orders");

        string body = JsonSerializer.Serialize(order);
        var message = new ServiceBusMessage(body)
        {
            ContentType = "application/json",
            MessageId = order.OrderId.ToString(), // enables duplicate detection
            Subject = "OrderCreated"
        };

        await sender.SendMessageAsync(message);
        Console.WriteLine($"Sent order {order.OrderId}");

        await sender.DisposeAsync();
    }
}

Notice the MessageId. If you enable duplicate detection on the queue, Service Bus uses this ID to discard duplicate sends within a time window — critical for building idempotent systems.

Sending Messages in Batches for High Throughput

When sending thousands of messages, batching dramatically improves performance by reducing network round-trips. Use CreateMessageBatchAsync, which respects the maximum batch size automatically:

public async Task SendBatchAsync(IEnumerable<Order> orders)
{
    ServiceBusSender sender = _client.CreateSender("orders");
    using ServiceBusMessageBatch batch = await sender.CreateMessageBatchAsync();

    foreach (Order order in orders)
    {
        var msg = new ServiceBusMessage(JsonSerializer.Serialize(order));
        if (!batch.TryAddMessage(msg))
            throw new Exception($"Order {order.OrderId} is too large for the batch.");
    }

    await sender.SendMessagesAsync(batch);
    await sender.DisposeAsync();
}

How to Receive and Process Messages in C#

For production workloads, use the ServiceBusProcessor. It handles concurrency, automatic message completion, error handling, and prefetching for you — far superior to manually polling with a receiver. This is the recommended pattern for reliable message processing in .NET.

public class OrderConsumer
{
    private readonly ServiceBusClient _client;
    private ServiceBusProcessor _processor;

    public OrderConsumer(ServiceBusClient client) => _client = client;

    public async Task StartAsync()
    {
        _processor = _client.CreateProcessor("orders", new ServiceBusProcessorOptions
        {
            AutoCompleteMessages = false,   // we complete manually after success
            MaxConcurrentCalls = 5          // process 5 messages in parallel
        });

        _processor.ProcessMessageAsync += MessageHandler;
        _processor.ProcessErrorAsync += ErrorHandler;

        await _processor.StartProcessingAsync();
    }

    private async Task MessageHandler(ProcessMessageEventArgs args)
    {
        try
        {
            Order order = args.Message.Body.ToObjectFromJson<Order>();
            Console.WriteLine($"Processing order {order.OrderId} for {order.Customer}");

            // ... your business logic here (save to DB, call an API, etc.)

            await args.CompleteMessageAsync(args.Message); // remove from queue
        }
        catch (Exception ex)
        {
            // Abandon so the message is retried; after max retries it dead-letters
            await args.AbandonMessageAsync(args.Message);
            Console.WriteLine($"Failed: {ex.Message}");
        }
    }

    private Task ErrorHandler(ProcessErrorEventArgs args)
    {
        Console.WriteLine($"Service Bus error: {args.Exception.Message}");
        return Task.CompletedTask;
    }
}

Why set AutoCompleteMessages = false? Because you only want to remove a message from the queue after your business logic succeeds. If your code throws, calling AbandonMessageAsync returns the message to the queue for another attempt. This is the foundation of at-least-once delivery.

Azure Service Bus Topics: Publish/Subscribe in C#

Queues are point-to-point — one message, one consumer. But real event-driven architecture often needs one event delivered to many subscribers. That's where Azure Service Bus topics shine.

A topic is like a queue with multiple subscriptions. When you publish an OrderCreated event, the billing service, the shipping service, and the analytics service each get their own copy — without the publisher knowing they exist.

// Publishing to a topic is identical to a queue, just point to the topic name
ServiceBusSender sender = _client.CreateSender("order-events");
await sender.SendMessageAsync(new ServiceBusMessage(JsonSerializer.Serialize(order))
{
    Subject = "OrderCreated"
});

// Each subscriber reads from its OWN subscription
ServiceBusProcessor billingProcessor =
    _client.CreateProcessor("order-events", "billing-subscription");

You can even attach subscription filters (SQL-like rules) so a subscription only receives messages it cares about — for example, only orders over $1,000 flow to the fraud-review subscription. This keeps consumers focused and efficient.

Dead-Letter Queues: Handling Poison Messages

What happens to a message that fails every retry? Service Bus automatically moves it to the dead-letter queue (DLQ), a sub-queue where problem messages wait for investigation instead of blocking healthy traffic. Monitoring and draining the DLQ is essential for production reliability.

// Read dead-lettered messages for diagnostics
ServiceBusReceiver dlqReceiver = _client.CreateReceiver("orders",
    new ServiceBusReceiverOptions { SubQueue = SubQueue.DeadLetter });

ServiceBusReceivedMessage msg = await dlqReceiver.ReceiveMessageAsync();
if (msg is not null)
{
    Console.WriteLine($"Dead-lettered: {msg.DeadLetterReason}");
}

Best Practices and Common Pitfalls

  • Do reuse the ServiceBusClient as a singleton. Don't open a connection per message — it's slow and leaks resources.
  • Make consumers idempotent. Because delivery is at-least-once, the same message may arrive twice. Use the MessageId to detect and skip duplicates.
  • Keep messages small. The Standard tier caps messages at 256 KB (Premium at 100 MB). Store large payloads in Blob Storage and send only a reference — the "claim check" pattern.
  • Always dead-letter and monitor. Set an alert on DLQ depth so poison messages don't go unnoticed.
  • Don't forget message locks. If processing takes longer than the lock duration, the message becomes visible again and gets processed twice. Renew the lock or increase the timeout.
  • Use managed identity instead of connection strings in production for secure, secret-free authentication with DefaultAzureCredential.

Conclusion: Key Takeaways

Mastering Azure Service Bus with C# unlocks the reliable, scalable messaging patterns that power modern cloud applications. By decoupling your services through queues and topics, you build systems that survive failures, absorb traffic spikes, and evolve independently — the core promise of event-driven architecture.

Here are the key points to remember from this Azure Service Bus tutorial:

  • Use the modern Azure.Messaging.ServiceBus SDK and share a single ServiceBusClient.
  • Queues handle point-to-point work; topics enable one-to-many publish/subscribe.
  • The ServiceBusProcessor is the production-grade way to receive messages with concurrency and error handling built in.
  • Design idempotent consumers, monitor your dead-letter queue, and keep messages small.

Start small — send and receive a single message from a queue — then layer in topics, filters, and dead-letter handling as your event-driven system grows. With these patterns in place, your C# applications will be ready for real-world cloud scale.

About csharp-coder.com
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.

Comments

Popular posts from this blog

Angular 14 : 404 error during refresh page after deployment

In this article, We will learn how to solve 404 file or directory not found angular error in production.  Refresh browser angular 404 file or directory not found error You have built an Angular app and created a production build with ng build --prod You deploy it to a production server. Everything works fine until you refresh the page. The app throws The requested URL was not found on this server message (Status code 404 not found). It appears that angular routing not working on the production server when you refresh the page. The error appears on the following scenarios When you type the URL directly in the address bar. When you refresh the page The error appears on all the pages except the root page.   Reason for the requested URL was not found on this server error In a Multi-page web application, every time the application needs to display a page it has to send a request to the web server. You can do that by either typing the URL in the address bar, clicking on the Me...

Angular 14 CRUD Operation with Web API .Net 6.0

How to Perform CRUD Operation Using Angular 14 In this article, we will learn the angular crud (create, read, update, delete) tutorial with ASP.NET Core 6 web API. We will use the SQL Server database and responsive user interface for our Web app, we will use the Bootstrap 5. Let's start step by step. Step 1 - Create Database and Web API First we need to create Employee database in SQL Server and web API to communicate with database. so you can use my previous article CRUD operations in web API using net 6.0 to create web API step by step. As you can see, after creating all the required API and database, our API creation part is completed. Now we have to do the angular part like installing angular CLI, creating angular 14 project, command for building and running angular application...etc. Step 2 - Install Angular CLI Now we have to install angular CLI into our system. If you have already installed angular CLI into your system then skip this step.  To install angular CLI ope...

Send an Email via SMTP with MailKit Using .NET 6

How to Send an Email in .NET Core This tutorial show you how to send an email in .NET 6.0 using the MailKit email client library. Install MailKit via NuGet Visual Studio Package Manager Console: Install-Package MailKit How to Send an HTML Email in .NET 6.0 This code sends a simple HTML email using the Gmail SMTP service. There are instructions further below on how to use a few other popular SMTP providers - Gmail, Hotmail, Office 365. // create email message var email = new MimeMessage(); email.From.Add(MailboxAddress.Parse("from_address@example.com")); email.To.Add(MailboxAddress.Parse("to_address@example.com")); email.Subject = "Email Subject"; email.Body = new TextPart(TextFormat.Html) { Text = "<h1>Test HTML Message Body</h1>" }; // send email using var smtp = new SmtpClient(); smtp.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls); smtp.Authenticate("[Username]", "[Password]"); smtp.Se...