16
votes

When I use the following:

var deadLetterPath = SubscriptionClient.FormatDeadLetterPath(topicPath,subName);
var client = SubscriptionClient.CreateFromConnectionString(connectionString, deadLetterPath, subName);

I get an InvalidOperationException

Cannot directly create a client on a sub-queue. Create a client on the main queue and use that to create receivers on the appropriate sub-queue

Some parts of the azure documentation say to use SubscriptionClient.CreateReceiver to access a sub-queue but that method doesn't exist.

5

5 Answers

20
votes

Does this approach work for you ?

MessagingFactory factory = MessagingFactory.CreateFromConnectionString(cnxString);
var deadLetterPath = SubscriptionClient.FormatDeadLetterPath(topicPath,subName);
var dlqReceiver = factory.CreateMessageReceiver(deadLetterPath, ReceiveMode.ReceiveAndDelete);

I haven't test it out here (in a meeting), but give it a try

cheers

15
votes

There is a convention naming for Dead letter queue using Azure Service Bus:

  • For Service Bus Queue: queuePath/$DeadLetterQueue
  • For Service Bus Subscription: topicPath/subscriptionName/$DeadLetterQueue

So you can access your dead letter queues the same way you access your queues.

From @SamVanhoutte answer, you can see that the ServiceBus framework provides methods to format the dead letter queue name:

  • For Service Bus Queue: QueueClient.FormatDeadLetterPath(queuePath)

  • For Service Bus Subscription: SubscriptionClient.FormatDeadLetterPath(topicPath, subscriptionName)

I wrote two small methods to create a message receiver for queue and subscription where you can set if you want the deal letter queue or not:

/// <summary>
/// Create a new <see cref="MessageReceiver"/> object using the specified Service Bus Queue path.
/// </summary>
/// <param name="connectionString">The connection string to access the desired service namespace.</param>
/// <param name="queuePath">The Service Bus Queue path.</param>
/// <param name="isDeadLetter">True if the desired path is the deadletter queue.</param>
public static MessageReceiver CreateMessageReceiver(string connectionString, string queuePath,
    bool isDeadLetter = false)
{
    return MessagingFactory.CreateFromConnectionString(connectionString)
        .CreateMessageReceiver(isDeadLetter
            ? QueueClient.FormatDeadLetterPath(queuePath)
            : queuePath);
}

/// <summary>
/// Create a new <see cref="MessageReceiver"/> object using the specified Service Bus Topic Subscription path.
/// </summary>
/// <param name="connectionString">The connection string to access the desired service namespace.</param>
/// <param name="topicPath">The Service Bus Topic path.</param>
/// <param name="subscriptionName">The Service Bus Topic Subscription name.</param>
/// <param name="isDeadLetter">True if the desired path is the deadletter subqueue.</param>
public static MessageReceiver CreateMessageReceiver(string connectionString, string topicPath,
    string subscriptionName, bool isDeadLetter = false)
{
    return MessagingFactory.CreateFromConnectionString(connectionString)
        .CreateMessageReceiver(isDeadLetter
            ? SubscriptionClient.FormatDeadLetterPath(topicPath, subscriptionName)
            : SubscriptionClient.FormatSubscriptionPath(topicPath, subscriptionName));
}
10
votes

If you are using Microsoft.Azure.ServiceBus instead of Microsoft.ServiceBus, it is slightly different.

var deadQueuePath = EntityNameHelper.FormatDeadLetterPath(your_queue_name);
var deadQueueReceiver = new MessageReceiver(connectionString, deadQueuePath);

As per EntityNameHelper class in Microsoft.Azure.ServiceBus namespace, for topics, use the subscription path instead of your_queue_name.

The name of the queue, or path of the subscription.

/// <summary>
/// Formats the dead letter path for either a queue, or a subscription.
/// </summary>
/// <param name="entityPath">The name of the queue, or path of the subscription.</param>
/// <returns>The path as a string of the dead letter entity.</returns>
public static string FormatDeadLetterPath(string entityPath)
{
    return EntityNameHelper.FormatSubQueuePath(entityPath, EntityNameHelper.DeadLetterQueueName);
}
2
votes

Those who want to do it in python.

Receive the messages from the dead letter queue:

from azure.servicebus import ServiceBusClient
import json
connectionString = "Your Connection String to Service Bus"
serviceBusClient = ServiceBusClient.from_connection_string(connectionString)
queueName = "Your Queue Name created in the Service Bus"
queueClient = serviceBusClient.get_queue(queueName)
with queueClient.get_deadletter_receiver(prefetch=5) as queueReceiver:
    messages = queueReceiver.fetch_next(timeout=100)
    for message in messages:
        # message.body is a generator object. Use next() to get the body.
        body = next(message.body)
        message.complete()

Hope it helps to someone.

2
votes

With the latest Service Bus client libraries, you can access the deadletter queue like this. Note the consistency across the languages in specifying the deadletter queue when creating the receiver:

const { ServiceBusClient } = require("@azure/service-bus");

const client = new ServiceBusClient("<connectionstring>");
const deadletterReceiver = client.createReceiver("queueName", { subQueueType: "deadLetter" });
const messages = await deadletterReceiver.receiveMessages(1);

if (messages.length > 0) {
    console.log("Received the message from DLQ - ", messages[0].body);

    // Mark message as complete, i.e. remove from DLQ
    await deadletterReceiver.completeMessage(messages[0]);
} else {
    console.log("Error: No messages were received from the DLQ.");
}

await deadletterReceiver.close();
import com.azure.messaging.servicebus.*;
import com.azure.messaging.servicebus.models.SubQueue;

ServiceBusReceiverClient deadletterReceiver = new ServiceBusClientBuilder()
    .connectionString("<connectionstring>")
    .receiver() // Use this for session or non-session enabled queue or topic/subscriptions
    .queueName("queuename")
    .subQueue(SubQueue.DEAD_LETTER_QUEUE)
    .buildClient();

IterableStream<ServiceBusReceivedMessage> messages = receiver.receiveMessages(10);
messages.forEach(message -> {
    System.out.printf("Id: %s. Contents: %s%n", message.getMessageId(),
        message.getBody().toString());

    //Remove message from DLQ
    deadletterReceiver.complete(message);
});

deadletterReceiver.close();
import asyncio
from azure.servicebus import ServiceBusMessage, ServiceBusSubQueue
from azure.servicebus.aio import ServiceBusClient

servicebus_client = ServiceBusClient.from_connection_string(conn_str="<connectionstring>")
dlq_receiver = servicebus_client.get_queue_receiver(queue_name="queuename", 
               sub_queue=ServiceBusSubQueue.DEAD_LETTER)

async with dlq_receiver:
    received_msgs = await dlq_receiver.receive_messages(max_message_count=10, max_wait_time=5)
    for msg in received_msgs:
        print(msg)
        
        # remove the message from the DLQ
        await dlq_receiver.complete_message(msg)

using Azure.Messaging.ServiceBus;

await using var client = new ServiceBusClient("<connectionstring>");
ServiceBusReceiver deadletterReceiver = client.CreateReceiver(
    "queuename",
     new ServiceBusReceiverOptions { SubQueue = SubQueue.DeadLetter }
);

ServiceBusReceivedMessage message = await deadletterReceiver.ReceiveMessageAsync();
            
if (message != null)
{
    Console.WriteLine($"DeadLetter message = {message.Body}");
    
    // remove the message from the DLQ
    await deadletterReceiver.CompleteMessageAsync(message);
}
else
{
    // DLQ was empty on last receive attempt
    Console.WriteLine("Error: No messages were received from the DLQ."); 
}

await deadletterReceiver.CloseAsync();