0

So I've recently gotten the need to use Service Bus Topic and Subscriptions and I've followed many articles and tutorials. I've been able to successfully implement Microsoft's Get started with Service Bus topics and also successfully used Visual Studio 2017's Worker Role template to access a database.

However, I'm confused as to how to properly "combine" the two. While the Get started with Service Bus topics article shows how to create 2 apps, one to send and one to receive and then quits, the Worker Role template seems to loops endlessly with await Task.Delay(10000);.

I'm not sure how to "mesh" the two properly. Essentially, I want my Worker Role to stay alive and listen for entries into it's subscription forever (or until it quits obviously).

Any guidance would be great!

P.S.: I've asked a related question concerning proper technology I should use for my case scenario at StackExchange - Software Engineering if you are interested.

Update #1 (2018/08/09)

Based on Arunprabhu's answer, here is some code of how I'm sending a Message based on articles I've read and receiving using Visual Studio 2017's Worker Role with Service Bus Queue template.

Sending (based on Get started with Service Bus topics)

using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus;

namespace TopicsSender {
    internal static class Program {
        private const string ServiceBusConnectionString = "<your_connection_string>";
        private const string TopicName = "test-topic";
        private static ITopicClient _topicClient;

        private static void Main(string[] args) {
            MainAsync().GetAwaiter().GetResult();
        }

        private static async Task MainAsync() {
            const int numberOfMessages = 10;
            _topicClient = new TopicClient(ServiceBusConnectionString, TopicName);

            Console.WriteLine("======================================================");
            Console.WriteLine("Press ENTER key to exit after sending all the messages.");
            Console.WriteLine("======================================================");

            // Send messages.
            await SendMessagesAsync(numberOfMessages);

            Console.ReadKey();

            await _topicClient.CloseAsync();
        }

        private static async Task SendMessagesAsync(int numberOfMessagesToSend) {
            try {
                for (var i = 0; i < numberOfMessagesToSend; i++) {
                    // Create a new message to send to the topic
                    var messageBody = $"Message {i}";
                    var message = new Message(Encoding.UTF8.GetBytes(messageBody));

                    // Write the body of the message to the console
                    Console.WriteLine($"Sending message: {messageBody}");

                    // Send the message to the topic
                    await _topicClient.SendAsync(message);
                }
            } catch (Exception exception) {
                Console.WriteLine($"{DateTime.Now} :: Exception: {exception.Message}");
            }
        }
    }
}

Receiving (based on Worker Role with Service Bus Queue template)

using System;
using System.Diagnostics;
using System.Net;
using System.Threading;
using Microsoft.ServiceBus.Messaging;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace WorkerRoleWithSBQueue1 {
    public class WorkerRole : RoleEntryPoint {
        // The name of your queue
        private const string ServiceBusConnectionString = "<your_connection_string>";
        private const string TopicName = "test-topic";
        private const string SubscriptionName = "test-sub1";

        // QueueClient is thread-safe. Recommended that you cache 
        // rather than recreating it on every request
        private SubscriptionClient _client;
        private readonly ManualResetEvent _completedEvent = new ManualResetEvent(false);

        public override void Run() {
            Trace.WriteLine("Starting processing of messages");

            // Initiates the message pump and callback is invoked for each message that is received, calling close on the client will stop the pump.
            _client.OnMessage((receivedMessage) => {
                try {
                    // Process the message
                    Trace.WriteLine("Processing Service Bus message: " + receivedMessage.SequenceNumber.ToString());
                    var message = receivedMessage.GetBody<byte[]>();
                    Trace.WriteLine($"Received message: SequenceNumber:{receivedMessage.SequenceNumber} Body:{message.ToString()}");
                } catch (Exception e) {
                    // Handle any message processing specific exceptions here
                    Trace.Write(e.ToString());
                }
            });

            _completedEvent.WaitOne();
        }

        public override bool OnStart() {
            // Set the maximum number of concurrent connections 
            ServicePointManager.DefaultConnectionLimit = 12;

            // Initialize the connection to Service Bus Queue
            _client = SubscriptionClient.CreateFromConnectionString(ServiceBusConnectionString, TopicName, SubscriptionName);
            return base.OnStart();
        }

        public override void OnStop() {
            // Close the connection to Service Bus Queue
            _client.Close();
            _completedEvent.Set();
            base.OnStop();
        }
    }
}

Update #2 (2018/08/10)

After a few suggestions from Arunprabhu and knowing I was using different libraries, below is my current solution with pieces taken from several sources. Is there anything I'm overlooking, adding that shouldering be there, etc? Currently getting an error that may be for another question or already answered so don't want to post it yet before further research.

using System;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace WorkerRoleWithSBQueue1 {
    public class WorkerRole : RoleEntryPoint {
        private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
        private readonly ManualResetEvent _runCompleteEvent = new ManualResetEvent(false);

        // The name of your queue
        private const string ServiceBusConnectionString = "<your_connection_string>";
        private const string TopicName = "test-topic";
        private const string SubscriptionName = "test-sub1";

        // _client is thread-safe. Recommended that you cache 
        // rather than recreating it on every request
        private SubscriptionClient _client;

        public override void Run() {
            Trace.WriteLine("Starting processing of messages");

            try {
                this.RunAsync(this._cancellationTokenSource.Token).Wait();
            } catch (Exception e) {
                Trace.WriteLine("Exception");
                Trace.WriteLine(e.ToString());
            } finally {
                Trace.WriteLine("Finally...");
                this._runCompleteEvent.Set();
            }
        }

        public override bool OnStart() {
            // Set the maximum number of concurrent connections 
            ServicePointManager.DefaultConnectionLimit = 12;

            var result = base.OnStart();

            Trace.WriteLine("WorkerRole has been started");

            return result;
        }

        public override void OnStop() {
            // Close the connection to Service Bus Queue
            this._cancellationTokenSource.Cancel();
            this._runCompleteEvent.WaitOne();

            base.OnStop();
        }

        private async Task RunAsync(CancellationToken cancellationToken) {
            // Configure the client
            RegisterOnMessageHandlerAndReceiveMessages(ServiceBusConnectionString, TopicName, SubscriptionName);

            _runCompleteEvent.WaitOne();

            Trace.WriteLine("Closing");
            await _client.CloseAsync();
        }

        private void RegisterOnMessageHandlerAndReceiveMessages(string connectionString, string topicName, string subscriptionName) {
            _client = new SubscriptionClient(connectionString, topicName, subscriptionName);

            var messageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler) {
                // Maximum number of concurrent calls to the callback ProcessMessagesAsync(), set to 1 for simplicity.
                // Set it according to how many messages the application wants to process in parallel.
                MaxConcurrentCalls = 1,

                // Indicates whether MessagePump should automatically complete the messages after returning from User Callback.
                // False below indicates the Complete will be handled by the User Callback as in `ProcessMessagesAsync` below.
                AutoComplete = false,
            };

            _client.RegisterMessageHandler(ProcessMessageAsync, messageHandlerOptions);
        }

        private async Task ProcessMessageAsync(Message message, CancellationToken token) {
            try {
                // Process the message
                Trace.WriteLine($"Received message: SequenceNumber:{message.SystemProperties.SequenceNumber} Body:{Encoding.UTF8.GetString(message.Body)}");
                await _client.CompleteAsync(message.SystemProperties.LockToken);
            } catch (Exception e) {
                // Handle any message processing specific exceptions here
                Trace.Write(e.ToString());
                await _client.AbandonAsync(message.SystemProperties.LockToken);
            }
        }

        private static Task ExceptionReceivedHandler(ExceptionReceivedEventArgs exceptionReceivedEventArgs) {
            Console.WriteLine($"Message handler encountered an exception {exceptionReceivedEventArgs.Exception}.");
            var context = exceptionReceivedEventArgs.ExceptionReceivedContext;
            Console.WriteLine("Exception context for troubleshooting:");
            Console.WriteLine($"- Endpoint: {context.Endpoint}");
            Console.WriteLine($"- Entity Path: {context.EntityPath}");
            Console.WriteLine($"- Executing Action: {context.Action}");
            return Task.CompletedTask;
        }
    }
}
RoLYroLLs
  • 3,113
  • 4
  • 38
  • 57
  • Update #2 looks good. I think it will work :) – Arunprabhu Aug 10 '18 at 14:48
  • @Arunprabhu Thank you. I'll try to resolve current issue "Could not load file or assembly 'Microsoft.Azure.Amqp" and report back. My main concern is my `RunAsync()` method. Is this how to properly keep it alive `_runCompleteEvent.WaitOne()` forever? – RoLYroLLs Aug 10 '18 at 14:51
  • I am not sure about _runCompleteEvent.WaitOne(), RegisterMessageHandler will keep the method alive forever. – Arunprabhu Aug 10 '18 at 14:59
  • @Arunprabhu if that's the case, then I'll remove that line `_runCompleteEvent.WaitOne()`. Reason I added it, was because the MS Article (https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-topics-subscriptions) has `Console.ReadKey();` but I don't want `Console.ReadKey();` to be the holder of Keep-Alive. – RoLYroLLs Aug 10 '18 at 15:09
  • Yeah, just remove that and check whether the method is alive. – Arunprabhu Aug 10 '18 at 15:16
  • @Arunprabhu when I remove, the `WorkerRole` seem to recycle over and over again. When I add it back it seems to work just fine. "seems" because I'm not sure what would make it continue when i don't want it to do so. =) – RoLYroLLs Aug 10 '18 at 17:39
  • @Arunprabhu also, I'm concerned with the cancellation token in my `RunAsync()` method. Is that only really needed if I had an ongoing while loop, like the base template creates? – RoLYroLLs Aug 10 '18 at 17:50

2 Answers2

3

Considering the complexity of the updated question Update #1 (2018/08/09), I am providing a separate answer.

The sender and receiver are using different libraries.

Sender - Microsoft.Azure.ServiceBus

Receiver - WindowsAzure.ServiceBus

Microsoft.Azure.ServiceBus has the message object as Message, where WindowsAzure.ServiceBus has BrokeredMessage.

There is a method RegisterMessageHandler available in Microsoft.Azure.ServiceBus, this is the alternative for client.OnMessage() in WindowsAzure.ServiceBus. By using this, the listener receives the message as Message object. This library supports asynchronous programming as you expect.

Refer here for samples from both the libraries.

Arunprabhu
  • 1,454
  • 9
  • 15
  • Thanks! I did notice the different libraries and some differences in code and was trying to mesh things into one. Thanks for the samples, but I've already been there =) In any case, I'll be updating my `client.OnMessage()` to `RegisterMessegeHandler` on the receiver and I'll let you know the status. Thanks again for your help in my tangled confusion. – RoLYroLLs Aug 10 '18 at 14:00
  • 1
    Of course =) I always do, but I just ran into trouble and now I know why i was using different libraries. I'll comment in a bit. – RoLYroLLs Aug 10 '18 at 14:11
  • I'm going to add my current solution as Update #2, would you be able to take a look and make sure the pieces I've taken for many sources doesn't conflict? Thank you. – RoLYroLLs Aug 10 '18 at 14:30
  • I'm going to accept this one, because I was able to accomplish this without creating a `Worker Role with Service Bus Queue ` since currently it seems to be using `WindowsAzure.ServiceBus` instead of the newer `Microsoft.Azure.ServiceBus` and supports `async/await`. Thanks for your help! – RoLYroLLs Aug 10 '18 at 17:44
  • With all this discussion, and finally finished my small WorkerRole, I've noticed that `Service Fabrics` are the *net gen* of `Cloud Services`. I'll be working on making this into a `Service Fabric` – RoLYroLLs Aug 17 '18 at 14:16
1

If you are using Visual Studio, there is a default template available for creating Azure Cloud Service and Worker Role with Service Bus Queue. There you need to change the QueueClient with SubscriptionClient in WorkerRole.cs.

Then, the worker role will stay active, listening for the messages from Topic Subscription.

You can find the samples here. You should create Worker role with Service Bus Queue inside the Cloud Service

enter image description here

Arunprabhu
  • 1,454
  • 9
  • 15
  • Thanks! I have played around with this, and thought since it was for `Queues`, it wasn't what I wanted. Though, later I've realized they are both essentially the same thing, except `Topics` can have multiple subscribers and (I think) work as *FIFO*. I'll go back to trying this out. – RoLYroLLs Aug 09 '18 at 17:29
  • Yes it is FIFO and it will work as expected. I got this running seamlessly in my production environment :) – Arunprabhu Aug 09 '18 at 17:35
  • Thanks! So I played around with this template and it works! (I can receive messages). However, the `client.OnMessage(...)` uses a `BrokeredMessage`. The sender I wrote based on articles sends a `Message` type, as articles state this is the "new" way to do it. How can I get this template to read `Message`s vs `BrokeredMessage`s? – RoLYroLLs Aug 09 '18 at 18:09
  • Also, I'd actually like to have all this run with `async/await`. While I have some familiarity, I'm not totally sure how to get all this to work properly. Would love it if you can help with that if you show some code. Thanks! =) – RoLYroLLs Aug 09 '18 at 18:50
  • I have provided a separate answer due to its complexity :) – Arunprabhu Aug 10 '18 at 03:28