02 Feb

Simple .NET chat application with Azure Service Bus Topics and SignalR

Azure Service Bus
The heyday of chat rooms are long past us, but nowadays almost every popular application has some sort of chat functionality implemented. Some even make a separate application just to improve and create new features.
So if you ever felt like creating your own chat functionality for your website or application, Azure Service Bus Topics is your man.


 
 

Azure Service Bus Topics

Lets see what Microsoft has to say about Topics:

Service Bus topics and subscriptions support a publish/subscribe messaging communication model. When using topics and subscriptions, components of a distributed application do not communicate directly with each other; instead they exchange messages via a topic, which acts as an intermediary.
In contrast with Service Bus queues, in which each message is processed by a single consumer, topics and subscriptions provide a “one-to-many” form of communication, using a publish/subscribe pattern. It is possible to register multiple subscriptions to a topic. When a message is sent to a topic, it is than made available to each subscription to handle/process independently.

Azure Service Bus Topic

So this is just what we need, a one to many sort of queue. One user sends a message, and many other user want to receive it.
In the next steps, I’ll show you how this is done.



Getting started

To start you’ll need to create and Azure Service Bus on the Azure Portal.
You will need to choose the Standard pricing, which is a bit more expensive than the Basic tier, because Basic will not allow you to create Topics.
After that’s done, you’ll just need to go and fetch your connection string, located in the “Shared access policies”-tab.

Now it’s time to code.

 

The code

ChatService

At first we’ll start off with our Chat service.
This will show you how to create Topics, add Subscriptions, send and read messages.

For purpose of keeping it simple we’ll create on topic and add one subscription to it. To add more “chat rooms” you could either create a topic for each room or you can play around with subscriptions and filters to show the correct messages for the correct chat rooms. There is however a limit of 10.000 Topics per Service Namespace and 2.000 Subscriptions per Topic.

using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Messaging;
using System;
using System.Configuration;

namespace Website.Services
{
    public class TopicService
    {
        private const string TopicName = "chat";
        private readonly TopicClient _topicClient;
        public TopicService()
        {
            var mngr = NamespaceManager.CreateFromConnectionString(ConfigurationManager.AppSettings.Get("ServiceBusConnectionString"));
            if (!mngr.TopicExists(TopicName))
            {
                var td = new TopicDescription(TopicName);
                td.MaxSizeInMegabytes = 5120;
                td.DefaultMessageTimeToLive = new TimeSpan(0, 0, 10);
                mngr.CreateTopic(TopicName);
            }

            if (!mngr.SubscriptionExists(TopicName, "all"))
            {
                mngr.CreateSubscription(TopicName, "all");
            }

            _topicClient = TopicClient.CreateFromConnectionString(ConfigurationManager.AppSettings.Get("ServiceBusConnectionString"), TopicName);
        }

        public void Send(string message)
        {
            _topicClient.Send(new BrokeredMessage(message));
        }

        public SubscriptionClient GetClient()
        {
            return SubscriptionClient.CreateFromConnectionString(ConfigurationManager.AppSettings.Get("ServiceBusConnectionString"), TopicName, "all");
        }
    }
}

 

MessageDataObject

Since the body of a BrokeredMessage is a string I am planning to serialize an object containing the name of the person and the message itself.

namespace Website.Models.ServiceBus
{
    public class MessageDo
    {
        public MessageDo(string message, string name)
        {
            Message = message;
            Name = name;
        }

        public string Message { get; set; }
        public string Name { get; set; }
    }
}

ChatHub

For this part you will need to have SignalR in your Visual Studio, this is by Default in Visual Studio 2015.
If not you can always import the NuGet Packages.

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Newtonsoft.Json;
using Website.Models.ServiceBus;
using System;
using Website.Services;

namespace Website.Hubs
{
    [HubName("ServiceBusTopicHub")]
    public class ServiceBusTopicHub : Hub
    {
        private readonly TopicService _topicService;

        public ServiceBusTopicHub()
        {
            _topicService = new TopicService();
        }

        public void Start()
        {
            try
            {
                _topicService.GetClient().OnMessageAsync(async brokerMessage =>
                {
                    try
                    {
                        var message = brokerMessage.GetBody<string>();
                        if (!string.IsNullOrEmpty(message))
                        {
                            var context = GlobalHost.ConnectionManager.GetHubContext<ServiceBusTopicHub>();
                            var obj = JsonConvert.DeserializeObject<MessageDo>(message);
                            await context.Clients.All.SendMessage(obj.Message, obj.Name);
                        }
                        brokerMessage.Complete();
                    }
                    catch (Exception e)
                    {
                        brokerMessage.Abandon();
                    }
                });
            }
            catch
            {
                Start();
            }
        }

        public void Send(string message, string name)
        {
            if (string.IsNullOrEmpty(message)) return;
            if (string.IsNullOrEmpty(name))
                name = "Anonymous";
            if ((message.Contains("<") && message.Contains(">")) || (name.Contains("<") && name.Contains(">"))) return;
            _topicService.Send(JsonConvert.SerializeObject(new MessageDo(message, name)));
        }
    }
}


StartUp

Since we’re using SignalR, we have start it up and let compiler build the correct mappings.
This can be done easily by adding a StartUp.cs class.

using Owin;
using Microsoft.Owin;
[assembly: OwinStartup(typeof(Website.Startup))]

namespace Chat
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

UI

I’m using .Net MVC and I started of with the default MVC website which has a bunch of stuff added by default. After that, I’ve just added an ActionResult which calls this View. All the other logic is done in JavaScript.

@{
    ViewBag.Title = "Chat";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Chat</h2>

<div>
    Naam: <input type="text" id="name" />
</div>
<div id="chat">

</div>
<div class="input">
    <input type="text" id="message" />
    <input type="button" id="sendmessage" value="Send" class="btn btn-default" />
</div>

@section scripts{
    <script src="~/Scripts/jquery.signalR-2.1.2.min.js"></script>
    <script src="~/signalr/hubs"></script>

    <script>
        $(function () {
            var queue = $.connection.ChatHub;
            //This function has to have the same name as in your hub.
            //Your hub will call this function every time a message is received. 
            queue.client.SendMessage = function (message, name) {
                $('#chat').append('<p class="chatmessage"><h5>' + name + ':</h5>' + message + '</p>');
                //I used this workaround to make sure my chat window is always scrolled down to the bottom.
                //There is probably a nicer workaround than this, I was more interested in the logic behind the chat then actual UX.
                var objDiv = document.getElementById("chat");
                objDiv.scrollTop = objDiv.scrollHeight;
            };
            $.connection.hub.start().done(function () {
                //We'll call the hub so it can start checking for new messages.
                //Notice how this name is also the same as in your hub on the server.
                queue.server.start();

                //Logic used when you click the send button.
                $('#sendmessage').click(<span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre="">function () {
                    send</span></span></span></span></span></span></span></span>();
                });

                //I've added a simple EventListener that will detect when you press the enter button. This will send the message.
                var chat = document.getElementById("message");
                chat.addEventListener("keydown", function (e) {
                    if (e.keyCode === 13) {
                        send();
                    }
                });

                function send() {
                    //This function will send you message to the server so it can be processed and send to the Azure Service Bus
                    //Notice that this function has also the same name in your hub.
                    queue.server.send($('#message').val(), $('#name').val());
                    $('#message').val('').focus();
                }
            })
        });
    </script>
}

 

All done

That’s it, all done.
Combine this code in your MVC application and you’ll have your own chat functionality using Azure Service Bus.

If you have any more questions or comments, feel free to add them below.

Sources



GitHub


https://github.com/JeffreyRosselle/net-chat-application-azure-service-bus-topics-signalr

5 thoughts on “Simple .NET chat application with Azure Service Bus Topics and SignalR

    • My apologies Merrick, ServiceBusTopicHub should have been changed to ChatHub.
      This was error from my part since I forgot the edit the piece of code.

      Code has now been changed to correct name.

  1. Can you post the code on git ?
    The topic is like a “group” in Whatsapp and the Subscriptions are like the users ? If i have a Chat with just one big group (4.000 users) and chats 1:1 , do you think the Azure standard 1 is enough ??

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.