2013-02-11

Creating a chat application in ASP.NET with SignalR

There are many things we can do with SignalR. It’s never been this easier to build real time web applications. In this post, I’m going to show how to build a chat application and add some additional functionality to it.

First of all, we have to get the SignalR library and include it in our ASP.NET web application. Easiest way to do this is by going to NuGet package manager and install it from there. You can go to the NuGet package manager in Project – > Manage NuGet Packages…

After you install it, you can see there are new script files in the Script folder as well as some new references. Scripts are for the SignalR client library and references are there for SignalR server library.

Now we are ready to develop our application with SignalR.

Let’s start by building the web page. Following is the body section of my HTML page. Note that I’m using a simple HTML page as my chat page.

<body style="border: solid gray; height: 90%;">
    <input type="hidden" id="displayname" />
    <div style="height: 80%;">
        <div id="chats" style="width: 80%; float: left;"></div>
        <div id="onlineList" style="width: 19%; float: right; border-left: solid red 2px; height: 100%;">
            <div style="font-size: 20px; border-bottom: double">Online Users</div>
        </div>
    </div>
    <div style="height: 19%; border-top: double black; background-color: lightgray">
        <div style="float: left; height: 90%; top: 10%; position: relative;">
            <textarea spellcheck="true" id="message" style="width: 625px; height: 80%"></textarea>
        </div>
        <div style="position: relative; top: 30%; float: left;">
            <input type="button" id="sendmessage" value="Send" />
        </div>
        <div style="position: relative; top: 30%; float: left;">
            <select id="users">
                <option value="All">All</option>
            </select>
        </div>
    </div>
</body>

I’m using a hidden field to store the username. A div called ‘chats’ to display the chat messages. A div called ‘onlineList’ to display the online users. At the bottom, I’ve placed the text area along with the send button. Also, I’ve included a dropdown list to display the online users so the user can select to whom (s)he is going to send the message or (s)he can simply select ‘All’ option to send the message to all users.

So that’s the layout of my webpage. Now let’s look at the head section of it.

<head>
    <style>
        .border {
            border-bottom: solid gray 1px;
        }

        .smileys {
            height: 15px;
        }
    </style>

    <title>Chat demo in ASP.NET using SignalR</title>

    <script src="Scripts/jquery-1.6.4.min.js"></script>

    <script src="Scripts/jquery.signalR-1.0.0-rc2.js"></script>

    <!--Reference the autogenerated SignalR hub script. -->
    <script src="/signalr/hubs"></script>

    <script type="text/javascript">
        $(function () {

            var chat = $.connection.chatHub;

            // Get the user name.
            $('#displayname').val(prompt('Enter your name:', ''));

            chat.client.differentName = function (name) {
                // Prompts for different user name
                $('#displayname').val(prompt('Please enter different username:', ''));
                chat.server.notify($('#displayname').val(), $.connection.hub.id);
            };

            chat.client.online = function (name) {
                // Update list of users
                if (name == $('#displayname').val())
                    $('#onlineList').append('<div class="border" style="color:red">You: ' + name + '</div>');
                else {
                    $('#onlineList').append('<div class="border">' + name + '</div>');
                    $("#users").append('<option value="' + name + '">' + name + '</option>');
                }
            };

            chat.client.enters = function (name) {
                $('#chats').append('<div class="border"><i>' + name + ' enters chatroom</i></div>');
                $("#users").append('<option value="' + name + '">' + name + '</option>');
                $('#onlineList').append('<div class="border">' + name + '</div>');
            };

            // Create a function that the hub can call to broadcast chat messages.
            chat.client.broadcastMessage = function (name, message) {
                //Interpret smileys
                message = message.replace(":)", "<img src=\"/emoticons/smile.png\" class=\"smileys\" />");
                message = message.replace(";)", "<img src=\"/emoticons/wink.png\" class=\"smileys\" />");
                message = message.replace(":D", "<img src=\"/emoticons/laugh.png\" class=\"smileys\" />");

                //display the message
                $('#chats').append('<div class="border"><span style="color:blue">' + name + '</span>: ' + message + '</div>');
            };

            chat.client.disconnected = function (name) {
                //Calls when someone leaves the page
                $('#chats').append('<div class="border"><i>' + name + ' leaves chatroom</i></div>');
                $('#onlineList div').remove(":contains('" + name + "')");
                $("#users option").remove(":contains('" + name + "')");
            }

            // Start the connection.
            $.connection.hub.start().done(function () {
                //Calls the notify method of the server
                chat.server.notify($('#displayname').val(), $.connection.hub.id);

                $('#sendmessage').click(function () {
                    if ($("#users").val() == "All") {
                        // Call the Send method on the hub.
                        chat.server.send($('#displayname').val(), $('#message').val());
                    }
                    else {
                        chat.server.sendToSpecific($('#displayname').val(), $('#message').val(), $("#users").val());
                    }
                    // Clear text box and reset focus for next comment.
                    $('#message').val('').focus();
                });

            });
        });
    </script>
</head>

At the head section of the HTML file, I’ve added styles, references to the jQuery library, SignalR library and auto generated SignalR hub script. Next I’ve included the client side functions which are responsible for displaying chats, notify when a user enters or leaves the chat room, sending the chats and maintaining up to date online users list.

Following is the whole code behind responsible for handling the client side events. I have written it in a CS files called ChatHub.cs.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace SingalRTest
{
    public class ChatHub : Hub
    {
        static ConcurrentDictionary<string, string> dic = new ConcurrentDictionary<string, string>();

        public void Send(string name, string message)
        {
            // Call the broadcastMessage method to update clients.
            Clients.All.broadcastMessage(name, message);
        }

        public void sendToSpecific(string name, string message, string to)
        {
            // Call the broadcastMessage method to update clients.
            Clients.Caller.broadcastMessage(name, message);
            Clients.Client(dic[to]).broadcastMessage(name, message);
        }

        public void Notify(string name, string id)
        {
            if (dic.ContainsKey(name))
            {
                Clients.Caller.differentName();
            }
            else
            {
                dic.TryAdd(name, id);

                foreach (KeyValuePair<String, String> entry in dic)
                {
                    Clients.Caller.online(entry.Key);
                }

                Clients.Others.enters(name);
            }
        }

        public override Task OnDisconnected()
        {
            var name = dic.FirstOrDefault(x => x.Value == Context.ConnectionId.ToString());
            string s;
            dic.TryRemove(name.Key, out s);
            return Clients.All.disconnected(name.Key);
        }
    }
}

Now let’s have a look at both the client side events and server side events. First, I’m referencing to my server hub using the below code in client side

var chat = $.connection.chatHub;

Note that I’m using the same name as the class name to refer to the hub. My class, ChatHub is inherited from the SignalR Hub class.

Then I’m prompting the user for a username and I’m storing the username in the ‘displayname’ hidden field. That’s what the below code do

            $('#displayname').val(prompt('Enter your name:', ''));

In code behind, I’m using a concurrent static Dictionary to maintain the online users lists.

static ConcurrentDictionary<string, string> dic = new ConcurrentDictionary<string, string>();

Whenever a user enters the chat room, I’m adding the username as the key and connection ID as the value to that dictionary. That’s what the below server side method do

        public void Notify(string name, string id)
        {
            if (dic.ContainsKey(name))
            {
                Clients.Caller.differentName();
            }
            else
            {
                dic.TryAdd(name, id);

                foreach (KeyValuePair<String, String> entry in dic)
                {
                    Clients.Caller.online(entry.Key);
                }

                Clients.Others.enters(name);
            }
        }

As you can see, I’m passing two parameters, name and id.

As soon as the connection made with the server, I’m calling that method.

$.connection.hub.start().done(function () {
    //Calls the notify method of the server
    chat.server.notify($('#displayname').val(), $.connection.hub.id);
.............................................……………………………………..

As you can see from the above code, I’m passing the username I’ve stored in the hidden field and I’m passing the unique ID which was generated when the connection has been made with the server.

If the Dictionary already contains the username entered, I’m calling the method diiferentName for the Caller. Note that I’m calling that method ONLY to the calling client. That’s why I’ve used Client.Caller. So now, it will call the differentName method in the client side for the Caller Client. Following is the differentName method in my client side script.

chat.client.differentName = function (name) {
    // Prompts for different user name
    $('#displayname').val(prompt('Please enter different username:', ''));
    chat.server.notify($('#displayname').val(), $.connection.hub.id);
};

As you can see, I’m prompting again for a different username. Then I’m again calling the Notify method in the server by passing the new username and connection ID.

If there is no user by that name, then it will add the username to the Dictionary.

dic.TryAdd(name, id);

Then I have to display the online list to the just entered client. For that, I’m calling the online method for the Caller Client. Note that, online client function will only get called for the calling client, not for all the clients.

foreach (KeyValuePair<String, String> entry in dic)
{
    Clients.Caller.online(entry.Key);
}

For each user in the list, I’m calling the online function. Below is that function

chat.client.online = function (name) {
    // Update list of users
    if (name == $('#displayname').val())
        $('#onlineList').append('<div class="border" style="color:red">You: ' + name + '</div>');
    else {
        $('#onlineList').append('<div class="border">' + name + '</div>');
        $("#users").append('<option value="' + name + '">' + name + '</option>');
    }
};

As you can see, if the name equals to the caller client, I’m adding a You: in front of the name and it will get displayed in red color. Otherwise I’m just appending the usernames to the onlineList div and adding an option to the dropdown list.

For the rest of the clients, I’m notifying that a new user enters the chat room. For that, I’m using the below code

Clients.Others.enters(name);

Note that I’m using Clients.Others, which will display the message to all the connected clients except the caller (There is no point of notifying the caller that (s)he just entered to the chat room). So I am calling the enters method in the client side and I’m passing the username so I can notify others who has just entered to the chat room. Following is the enters function.

chat.client.enters = function (name) {
    $('#chats').append('<div class="border"><i>' + name + ' enters chatroom</i></div>');
    $("#users").append('<option value="' + name + '">' + name + '</option>');
    $('#onlineList').append('<div class="border">' + name + '</div>');
};

As you can see, I’m showing username enters chatroom in the chats div. For rest of the clients, I have to update the online users list too. So I’m going to do that in the enters method by appending the new user name to the onlineList div and adding a new option to the users dropdown list.

Now that’s what happened when a client made a connection with the server, in this example, when the user joins the chat room. Now let’s see how to send the chat messages to users. For that, I’ve bound the below client side function to the Send button.

$('#sendmessage').click(function () {
    if ($("#users").val() == "All") {
        // Call the Send method on the hub.
        chat.server.send($('#displayname').val(), $('#message').val());
    }
    else {
        chat.server.sendToSpecific($('#displayname').val(), $('#message').val(), $("#users").val());
    }
    // Clear text box and reset focus for next comment.
    $('#message').val('').focus();
});

In the dropdown list, if the user has selected “All” option, then I’m calling the Send method in the server. I’m passing the username along with the text in the message textarea. Following is the send method in the server

public void Send(string name, string message)
{
    // Call the broadcastMessage method to update clients.
    Clients.All.broadcastMessage(name, message);
}

It will accept the two parameters and it will call the broadcastMessage function for all the clients. Since the user has selected “All” option in the dropdown list, now it will send the message to all the connected clients. The broadcastMessage function will get called for all the clients.

chat.client.broadcastMessage = function (name, message) {
    //Interpret smileys
    message = message.replace(":)", "<img src=\"/emoticons/smile.png\" class=\"smileys\" />");
    message = message.replace(";)", "<img src=\"/emoticons/wink.png\" class=\"smileys\" />");
    message = message.replace(":D", "<img src=\"/emoticons/laugh.png\" class=\"smileys\" />");

    //display the message
    $('#chats').append('<div class="border"><span style="color:blue">' + name + '</span>: ' + message + '</div>');
};

As you can see, it will accept the two parameters which were passed from the server. First it will check for the smiley syntaxes in the message and replace those with emoticons. Smile

After building the final message with the images, I’m appending it to the chats div along with the username. Now all the clients will get the message with the username!

Now let’s move on to the part where a user can send messages only to specific user (not all the clients). For that I’m calling the sendToSpecific server side method. As you can see from the Send button event, if the value selected in the dropdown list is not “All”, I’m calling the sendToSpecific method by passing the caller name, message and the callee name (The selected value in the dropdown list). My sendToSpecific method looks like below

public void sendToSpecific(string name, string message, string to)
{
    // Call the broadcastMessage method to update clients.
    Clients.Caller.broadcastMessage(name, message);
    Clients.Client(dic[to]).broadcastMessage(name, message);
}

Since now I need to display the message only between two users, I’m calling the Clients.Caller.broadcastMessage and Clients.Client(dic[to]).broadcastMessage. By calling the Clients.Caller.broadcastMessage, I’m displaying the message to the caller. By calling the below line, I’m displaying the message ONLY to the particular client. That’s why I’m passing the connection ID of that client to the Client method.

Clients.Client(dic[to]).broadcastMessage(name, message);

I’m passing the callee name to the Dictionary so it will return the connection ID associated for that username. Now it will broadcast the message only to that specific client.

So that’s how we can broadcast messages between two specific clients. Now let’s move to the final part, what will happen when a user leaves the chat room? Pretty simple, I will need to remove that username from the online user lists and from the dropdown list in client side and I will need to remove the particular dictionary entry associated for that user from the server side. Let’s see how to do that.

There is a server side method in the SignalR library, called OnDisconnected(), which will get called whenever the connection get closed from a client. So we can handily use that for our purpose here.

public override Task OnDisconnected()
{
    var name = dic.FirstOrDefault(x => x.Value == Context.ConnectionId.ToString());
    string s;
    dic.TryRemove(name.Key, out s);
    return Clients.All.disconnected(name.Key);
}

As you can see, first I’m removing the entry in the dictionary associated with that username. Then I’m calling the disconnected client side function for all the clients.

chat.client.disconnected = function (name) {
    //Calls when someone leaves the page
    $('#chats').append('<div class="border"><i>' + name + ' leaves chatroom</i></div>');
    $('#onlineList div').remove(":contains('" + name + "')");
    $("#users option").remove(":contains('" + name + "')");
}

From the disconnected function, I’m simply notifying all the clients that particular user leaves the chat and then remove his/her name from both the online list and dropdown list.

Well, that’s how we can simply create a chat application in ASP.NET with SignalR. SmileFollowing are some screenshots of running application

Untitled

leaves

20 comments:

Alexander Coder said...

This was a good suggestion that you put up here...dude…..hope that it benefits all the ones who land up here. 

Edmonton Mobile App Development

Ankit Sarkar said...

Great Tutorial... :) but when i run this script its not working.. Can you plz help me?

Ruchira Gamage said...

@Ankit: As for now, SignalR will only work with IIS express. Please run it on IIS instead of the visual studio development server.

Galo Doido said...

Face an awesome tutorial, I loved even more that I am a beginner in programming in general, could I replace the eval by an input prompt and soon after it is redirected to the chat page, could guide me on this issue. thank you thank you

Ankit Sarkar said...

Thak You For your Rpl. I have done all those things.. and now its working properly.

One more thing is there any option for saving the chat history between two user?

Ruchira Gamage said...

@Galo: Of course you can. Simply get the username from your input control and pass it to the next page. Use a hidden field and set it's value in the page_load method of the next page. Then access that hidden field's value from your jQuery code.

@Ankit: Yeah you can save the chat in a database table.

Anonymous said...

Hi It will be great if It will be possible to include the IP address by the side of the name being displayed..

Is it possible to achieve somehow??

Regds,
Vishal

Ruchira Gamage said...

@Vishal: Yeah you can get the IP address of the client like below

var ip;
$.getJSON("http://jsonip.appspot.com?callback=?", function (data) {
ip = data.ip;
});

Then append that IP to the user list.

Sumit said...

You just saved a lot of coding... thanks

Anonymous said...

Can you post the source from the demo?

Ruchira Gamage said...

Hi,

You can download the source code from below link

http://ruchirac.blogspot.com/2013/07/sri-lanka-net-forum-monthly-user-group.html

Divyeh said...

thanks nyc tutorial...bt wat if the user is offline and i want to store it in db..show him wen he comes online..can u plz refer me some example
Thanks

Anonymous said...

I am getting a error as type or namespace 'Hub'could not be found(are you missing a using directive or an assembly reference)

Ruchira Gamage said...

Check whether you've imported the SignalR library.

using Microsoft.AspNet.SignalR;

Anonymous said...

Nice tutorial with the help of your tutorial i created chatroom and is working fine but now after finetuning my chatroom i have landed in trouble my chat application isn't working in internet explorer 8 there are 3 errors.
1.SignalR: No JSON parser found.Please ensure json2.js is referenced before the SignalR.js file
2.SignalR is not loaded.Please ensure jquery.signalR.x.js is referenced before ~/signalr/hubs.
3.$.connection.chaHub is null or not an object

and also i want to know if there is any limitation with signalr to the number of users can get connected ? because in chrome i can use max 5 users to the chatroom. when i open new tab to add another user that page doesnt get displayed and it keeps on loading..
plz answer my queries as i am totally new to signalr

Sarah Taylor said...
This comment has been removed by the author.
kovalan Jayamurugan said...

Nice tutorial and examples code for creating chat application in dot net.
Its working great.
DOT NET Training in Chennai

christina jeni said...

Your information related to dot net is really useful for fresher. Thanks for sharing this informative blog..Keep posting..

Dot Net Training in Chennai

Sarah Taylor said...

Great Tutorial, a good suggestion that you discussed here about web development hope that it benefits all the ones who land up here.

Anonymous said...

even if i am begginer for programming jus i have got the how to create chat application thanks very much i will trie it soon and i will back soon