Whilst working on a project in which we we using the Topics on Windows Azure Service Bus, we noticed that our subscription queues (when viewed from the Windows Azure Management portal) didn’t seem to be empty even though our subscription queue processing code was working correctly. On closer inspection we found that our subscription queue was empty and the numbers in the management portal against the subscription were messages that had automatically faulted and had been moved into the Dead Letter queue.
The deadletter queue is a separate queue that allows messages that fail to be processed to be stored and analysed. The address of the deadletter queue is slightly different from your subscription queue and is the form:
YourTopic/Subscriptions/YourSubscription/ $DeadLetterQueue
for a subscription and
YourQueue/$DeadLetterQueue for a queue
Luckily you don’t have to remember this as there are helpful methods to retrieve the address for you:
SubscriptionClient.FormatDeadLetterPath(subscriptionClient.TopicPath, messagesSubscription.Name);
To create a subscription to the deadletter queue you need to append /$DeadLetterQueue to the subscription name when you create the subscription client
Once you have this address you can connect to the dead letter queue in the same way you would connect to the subscription queue. Once a deadletter brokered message is received the properties of the message should contain error information highlighting why it has failed. The message should also contain the message body from the original message. By default the subscription will move a faulty message to the dead letter queue after 10 attempts to deliver. You can also move the message yourself and put in sensible data in the properties if it fails to be processed by calling the DeadLetter method on the BrokeredMessage. The DeadLetter method allows you to pass in your own data to explain why the message has failed.
The DeadLetter can be deleted in the same was as a normal message by calling the Complete() method on the received dead letter message
Here is an example of retrieving a dead lettered message from a subscription queue
var baseAddress = Properties.Settings.Default.ServiceBusNamespace;
var issuerName = Properties.Settings.Default.ServiceBusUser;
var issuerKey = Properties.Settings.Default.ServiceBusKey;
Uri namespaceAddress = ServiceBusEnvironment.CreateServiceUri("sb", baseAddress, string.Empty);
this.namespaceManager = new NamespaceManager(namespaceAddress,
TokenProvider.CreateSharedSecretTokenProvider(issuerName, issuerKey));
this.messagingFactory = MessagingFactory.Create(namespaceAddress,
TokenProvider.CreateSharedSecretTokenProvider(issuerName, issuerKey));
var topic = this.namespaceManager.GetTopic(Properties.Settings.Default.TopicName);
if (topic != null)
{
if (!namespaceManager.SubscriptionExists(topic.Path,
Properties.Settings.Default.SubscriptionName))
{
messagesSubscription = this.namespaceManager.CreateSubscription(topic.Path,
Properties.Settings.Default.SubscriptionName);
}
else
{
messagesSubscription = namespaceManager.GetSubscription(topic.Path,
Properties.Settings.Default.SubscriptionName);
}
}
if (messagesSubscription != null)
{
SubscriptionClient subscriptionClient = this.messagingFactory.CreateSubscriptionClient(
messagesSubscription.TopicPath,
messagesSubscription.Name, ReceiveMode.PeekLock);
// Get the Dead Letter queue path for this subscription
var dlQueueName = SubscriptionClient.FormatDeadLetterPath(subscriptionClient.TopicPath,
messagesSubscription.Name);
// Create a subscription client to the deadletter queue
SubscriptionClient deadletterSubscriptionClient = messagingFactory.CreateSubscriptionClient(
subscriptionClient.TopicPath,
messagesSubscription.Name + "/$DeadLetterQueue");
// Get the dead letter message
BrokeredMessage dl = deadletterSubscriptionClient.Receive(new TimeSpan(0, 0, 300));
// get the properties
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("Enqueue Time {0}", dl.EnqueuedTimeUtc));
foreach (var props in dl.Properties)
{
sb.AppendLine(string.Format("{0}:{1}", props.Key, props.Value));
}
dl.Complete();
}