Featured image of post Introduction to Azure Service Bus

Introduction to Azure Service Bus

Learn how to get started with Azure Service Bus messaging. This step‑by‑step demo shows how to create a queue, send messages with .NET, and build a Blazor app to process them in real time.

The full source code for this demo is available on my GitHub repository.

💬 Introduction

In this post, we’ll explore Azure Service Bus - setting it up in Azure, sending data to a queue, and retrieving messages from it.

By the end, you’ll see how Azure Service Bus helps you build scalable, decoupled applications that can handle real‑world workloads reliably.

Stock ticker application, retrieving messages from Azure Service Bus

❓ What is Azure Service Bus?

Azure Service Bus is Microsoft’s version of enterprise cloud messaging.

It enables applications and services to communicate with each other asynchronously via messages.

Messages are stored in queues or topics until an application or service processes them. This ensures reliable delivery even when a service is temporarily unavailable.

⚙️ How does it Work?

In messaging, a producer creates and sends a message to the service bus, and a consumer retrieves the message.

There are two types of entities in Azure Service Bus:

Queues

When a producer sends a message to a queue, Azure Service Bus will store it until it is retrieved by a consumer.

This is known as point-to-point messaging.

Here’s a high-level sequence:

  1. Producer sends a message to a queue.
  2. Service Bus stores the message.
  3. Consumer retrieves and processes the message.

Azure Service Bus Queue Messaging Sequence

Topics

Topics behave differently to queues in that they store the message for multiple consumers via subscriptions.

This is known as publish/subscribe messaging.

When a producer sends a message to a topic, Azure Service Bus will store it until it is retrieved by all subscriptions.

Here’s a high-level sequence:

  1. Producer sends a message to a topic.
  2. Service Bus stores the message.
  3. Consumer from one subscription retrieves and processes the message.
  4. Consumer from a different subscription retrieves and processes the message.

Azure Service Bus Topic Messaging Sequence

📽️ Demo

In this demo, I will show you how to set up a queue in Azure Service Bus, send messages to it via a console app, and retrieve messages in a Blazor SSR web app.

Setting Up Azure Service Bus

It is very easy to get started with Azure Service Bus in Azure.

  1. Firstly, search for ‘service bus’ and select it in the results. You will be taken to the home page for Azure Service Bus.

Azure Service Bus Home

  1. Click on ‘Create’ to create a new namespace in Azure Service Bus.

You will need to create a new resource group for it or select an existing one.

There are three pricing tiers for Azure Service Bus:

FeatureBasicStandardPremium
Queues
Scheduled messages
Topics
Transactions
De-duplication
Sessions
ForwardTo/SendVia
Message Size256 KB256 KB100 MB
Resource isolation
Geo-Disaster Recovery (Geo-DR)✅*
Java Messaging Service (JMS) 2.0
Availability Zones (AZ) support

*Requires additional Service Bus Premium namespaces in another region.

Fill in the required fields and create the namespace.

Creating a new namespace in Azure Service Bus

Go to the resource once it has finished creating.

The namespace home page in Azure Service Bus

  1. Add a Queue in the Entities page

In the sidebar, expand Entities and select Queues. Click on the plus button to add a queue.

There are several options for queues:

  • Max queue size: Choose between 1-5 GB for the queue, depending on how many messages are produced and how quickly they are processed.
  • Max delivery count: The limit of attempts to send a message before it is moved to the dead-letter queue.
  • Message time to live: Determines how long a message can sit in a queue before it is expired or moved to the dead-letter queue.
  • Lock duration: Determines how long a consumer has to process a message before it unlocks and becomes available to other consumers to process.
  • Enable dead lettering on message expiration: When the message’s time to live expires, move to dead-letter queue.
  • Enable partitioning: Distributes messages across multiple message brokers for high-volume workloads.

For the purpose of this demo, I have stuck with the default options.

Creating a queue in Azure Service Bus

Sending Messages to a Queue

Now that we have a queue, we can create a producer application to send messages to it.

I have created a stock ticker console application that posts the prices for 10 stocks every second.

Access Keys

ℹ️ I would recommend creating a key with appropriate claim(s) for each application that accesses the queue.

First, we need to take a copy of the connection string that we will use to connect to the queue:

  1. In the queue’s page, expand Settings and click on Shared access policies.
  2. Add a key for each application and set the appropriate claims for them.

I have created two keys:

  • One for the producer app that will send the messages - this has the Send claim.
  • One for the consumer app that will listen and process the messages. This has the Listen claim.

Copy the primary connection string for each key, as these will be required in the applications.

Shared keys for an Azure Service Bus queue

The Producer Application

The application uses the Azure.Messaging.ServiceBus NuGet package. This package provides helpful classes to establish a connection with the queue and send messages to it.

I have added the key for the producer and the queue name to a User Secrets file.

The application creates a client with the key, and then creates a sender for the queue:

1
2
ServiceBusClient _client = new(config["ServiceBus:ConnectionString"]);
ServiceBusSender _sender = _client.CreateSender(config["ServiceBus:Queue"]);

Then, it generates random prices for the 10 stock tickers before creating a message containing the prices and sending it to the queue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
while (true)
{
    foreach (StockPrice stock in _stocks)
    {
        // Random increment of price.
        stock.UpdatePrice();

        // Create a message to send to service hub.
        ServiceBusMessage message = CreateMessageForServiceHub(stock);

        // Send the message to service hub.
        await _sender.SendMessageAsync(message);

        Console.WriteLine($"Sent stock price update: {stock.Symbol} @ {stock.Price}");
    }

    // Retrieve updated stock prices every second.
    await Task.Delay(1000);
}

static ServiceBusMessage CreateMessageForServiceHub(StockPrice stock)
    => new(JsonSerializer.Serialize(stock))
    {
        Subject = "StockPriceUpdate",
        ApplicationProperties =
        {
            ["Symbol"] = stock.Symbol,
            ["Price"] = stock.Price
        }
    };

It is as simple as that to send messages to a queue from an application.

Retrieving Messages from a Queue

I have also created a Blazor app to demonstrate retrieving these messages.

As a Blazor Server app, it uses SignalR to update the UI in real time, as soon as the messages are received.

Once again, the Azure.Messaging.ServiceBus is required to retrieve the messages.

Program.cs

In Program.cs, the service bus classes are implemented, along with the StockPriceService class, which is used to retrieve the messages:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
builder.Services.AddSingleton(new ServiceBusClient(
    builder.Configuration["ServiceBus:ConnectionString"]));

builder.Services.AddSingleton<StockPriceService>(
    x =>
    new(
        x.GetRequiredService<ServiceBusClient>(),
        builder.Configuration["ServiceBus:Queue"] ?? ""));

builder.Services.AddHostedService(x => x.GetRequiredService<StockPriceService>());

StockPriceService.cs

StockPriceService inherits IHostedService to run as a background service for the Blazor app. This interface enforces two methods: StartAsync and StopAsync.

Several fields have been added to process messages and enable the UI to update in real time:

1
2
3
4
5
6
7
private ServiceBusProcessor? _processor;
private readonly ServiceBusClient _client = client;
private readonly string _queue = queue;
private readonly JsonSerializerOptions _jsonSerialiserOptions = new() { PropertyNameCaseInsensitive = true };

public List<Ticker> Stocks { get; } = [];
public event Func<Task>? OnChange;
  • _processor is the class that retrieves the messages.
  • _client is the client registered in Program.cs. It is used to create the _processor.
  • OnChange is the function that the UI subscribes to, ensuring it’s notified whenever new messages are processed.
StartAsync

StartAsync creates the processor via the client and begins processing the messages:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public async Task StartAsync(CancellationToken cancellationToken)
{
    _processor = _client.CreateProcessor(
        _queue,
        new ServiceBusProcessorOptions());

    _processor.ProcessMessageAsync += OnMessageReceivedAsync;
    _processor.ProcessErrorAsync += OnErrorAsync;

    await _processor.StartProcessingAsync(cancellationToken);
}
OnMessageReceivedAsync

OnMessageReceivedAsync deserialises a message, updates the list of stocks with new prices, and invokes the OnChange function, before completing the message:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private async Task OnMessageReceivedAsync(ProcessMessageEventArgs args)
{
    StockPriceDto? dto = JsonSerializer.Deserialize<StockPriceDto>(
        args.Message.Body,
        _jsonSerialiserOptions);

    if (dto != null)
    {
        Ticker? existing = Stocks.FirstOrDefault(
            x =>
            x.Symbol == dto.Symbol);

        if (existing != null)
        {
            existing.Direction = dto.Price > existing.Price
                ? PriceDirection.Up
                : dto.Price < existing.Price
                    ? PriceDirection.Down
                    : PriceDirection.None;

            existing.Price = dto.Price;
        }
        else
        {
            Stocks.Add(new Ticker
            {
                Symbol = dto.Symbol,
                Price = dto.Price,
                Direction = PriceDirection.None
            });
        }

        if (OnChange != null)
            await OnChange.Invoke();
    }

    await args.CompleteMessageAsync(args.Message);
}
StopAsync

StopAsync stops processing messages and disposes of the processor:

1
2
3
4
5
6
7
public async Task StopAsync(CancellationToken cancellationToken)
{
    if (_processor == null) return;

    await _processor.StopProcessingAsync(cancellationToken);
    await _processor.DisposeAsync();
}

Home.razor

The home page lists the stock prices.

It subscribes to the OnChange function from StockService and invokes StateHasChanged to refresh the UI whenever new prices are processed:

1
2
3
4
5
6
protected override void OnInitialized() => StockService.OnChange += HandleChangeAsync;

// Re-renders when changes are made to the stock service.
private async Task HandleChangeAsync() => await InvokeAsync(StateHasChanged);

public void Dispose() => StockService.OnChange -= HandleChangeAsync;

🎬 And… Action!

Here we can see the producer sending messages to the queue, and the consumer retrieving and displaying the messages in real time:

Running the producer and consumer applications

The graphs on the service bus namespace show the usage for each queue. We can see that 1.52k messages have been sent to the queue and 1.52k messages have been processed by the consumer:

Messages sent and processed in the queue

📘 Further Reading

For more in-depth information on Azure Service Bus, Microsoft provides detailed documentation:

In case you missed it, the full source code for this demo is available on my GitHub repository.