Friday, February 6, 2015

Real time multi person multi window chat application using SignalR

This article is to demonstrate real time chat application using SignalR. I develop this code for my social network Alap.Me and it has tested with many people. This is super fast light weight and it can use many people at a time with multiple chat window. For alap.me due to window size I limit it to 4 active window. This looks as like gmail or facebook chat. My server was in UK and I tested it from India it's response time was around 0.350 second. That means when one person send message to some one, it deliver it in a popup within 0.350 second. I develop it as stand alone and later integrate it with alap.me. Similar way any one can use this ready-made code to his existing code. I am offering this as open source and any one can enhance this code by informing me (just a mail is ok) and its totally free of cost for non-profit purpose. 

Prerequisite 
This has developed based on .Net 4.5 with Visual Studio 2013 express.
I have setup it on Windows Server 2008.
IIS version 7.5 (or higher).
MySQL Database, however these has just 2 standard table you can create same in other database and can configure DB layer.

Technology Used
This is SignalR Hub based application with C# code.
HTML5, CSS3 and JavaScript used for UI development.
MySQL stored procedure with ADO.Net has used.
Code developed in Visual Studio 2013 Express for Web version used.

Basic Overview how it is working
Describing later this section.

Source code
Below picture is showing my full code file list which I am going to share at below.


ChatHub.cs

using System;
using System.Web;
using Microsoft.AspNet.SignalR;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace SignalRChat
{
    public class ChatHub : Hub
    {
        AlapChat objChat;
        DataTable dt;
        static Hashtable htOnlineUsers = new Hashtable();
        static List<OnlineUser> OnlineUsers=new List<OnlineUser>();
        public ChatHub()
        {
            objChat = new AlapChat();
        }
        //public void Test(string str)
        //{
        //    Clients.Caller.Hello(str + " | Message received at server reply from server." + DateTime.Now.ToShortTimeString());
        //}
        //public void Friends123()
        //{
        //    dt = objChat.GetFriendLoginStatus("7");
        //    string onlineUserList = UtilityChat.DataTableToJson(dt);
        //    Clients.Caller.friendslist(onlineUserList);
        //}
        public void clean()
        {
           
            foreach (OnlineUser user in OnlineUsers)
            {
                objChat.SetOffline(user.MyId);
            }
            OnlineUsers.Clear();
           
        }
        public void Connect(string myid)
        {
            if(OnlineUsers.Count(x=>x.MyId==myid)==0)
            {
                OnlineUsers.Add(new OnlineUser { MyId = myid, ConnectionId = Context.ConnectionId });
            }
        }
        public void loginfriends(string myid,string name)
        {
            bool isOnline=false;
            if (OnlineUsers.Count(x => x.MyId == myid) == 0)
            {
                OnlineUsers.Add(new OnlineUser { MyId = myid, ConnectionId = Context.ConnectionId, Name = name });
                objChat.SetOnline(myid);              
            }
          else //User found in Array, check if ConnectionId is same or not, if not replace it in Array
            {
                OnlineUser user = OnlineUsers.FirstOrDefault(x => x.MyId == myid);
                if(user.ConnectionId!=Context.ConnectionId)
                {
                   // Clients.Caller.Hello("Remove and readd user.");
                    OnlineUsers.Remove(user);//Delete existing and add new user
                    OnlineUsers.Add(new OnlineUser { MyId = myid, ConnectionId = Context.ConnectionId, Name = name });
                }
            }
            dt = objChat.GetFriendLoginStatus(myid);
            foreach (DataRow dr in dt.Rows)
            {
                if (dr["IsOnline"].ToString().ToLower() == "true" || dr["IsOnline"].ToString() == "1")
                    isOnline = true;
                else if (dr["IsOnline"].ToString().ToLower() == "false" || dr["IsOnline"].ToString() == "0")
                    isOnline = false;              
               
                //Inform all online friends that one user has logged in.
                if (isOnline)
                {
                    //Clients.Caller.Hello(isOnline.ToString() + " | Inform start." + DateTime.Now.ToShortTimeString());
                    try
                    {
                        OnlineUser onlineFriend = OnlineUsers.FirstOrDefault(x => x.MyId == dr["FriendUId"].ToString());
                        if (onlineFriend!=null)
                            Clients.Client(onlineFriend.ConnectionId).refreshfriendlist(myid, name); //Send new logged in user's information to all friends
                        else //User is online as DB entry but not exists in Array, hence need to set offline in DB
                        {
                            objChat.SetOffline(dr["FriendUId"].ToString());
                        }
                    }
                    catch(Exception ex)
                    {
                        Clients.Caller.Hello("Error! "+ex.Message+"<br/>"+ ex.StackTrace.ToString());
                    }
                    //Clients.Caller.Hello(isOnline.ToString() + " | Inform end." + DateTime.Now.ToShortTimeString());
                }
                else
                {
                    break;
                }
            }
           
            //Send online and offline friend list to caller
            string onlineUserList = UtilityChat.DataTableToJson(dt);
            Clients.Caller.friendslist(onlineUserList);
           
        }
        public void createnewchatid(string fromuid, string touid)
        {
            string newChatOrGroupId = objChat.GetNewChatOrGroupId();
            objChat.AddUserInChat(newChatOrGroupId,fromuid);
            objChat.AddUserInChat(newChatOrGroupId, touid);
            Clients.Caller.newchatid(newChatOrGroupId, touid);
        }
        public void sendpvtmsg(string chatorgroupid,string fromid, string fromname,string toid,string pvtmsg)
        {
            objChat.AddNewMessage(chatorgroupid, fromid, pvtmsg);
            OnlineUser onlineFriend = OnlineUsers.FirstOrDefault(x => x.MyId == toid);
            if (onlineFriend != null)
                Clients.Client(onlineFriend.ConnectionId).receivepvtmsg(chatorgroupid, fromid, fromname, pvtmsg);
        }
        public void nonmessage_control_info(string fromid, string fromname,string toid,string signal)//'Signal' is used to indicate, Typing, Away etc.
        {
            OnlineUser onlineFriend = OnlineUsers.FirstOrDefault(x => x.MyId == toid);
            if (onlineFriend != null)
                Clients.Client(onlineFriend.ConnectionId).incoming_control_msg(fromid, fromname, signal);
        }
       
        public override System.Threading.Tasks.Task OnDisconnected()
        {
            OnlineUser onlineUser = OnlineUsers.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
            if (onlineUser != null)
            {
                bool isOnline = false;
                objChat.SetOffline(onlineUser.MyId);
                dt = objChat.GetFriendLoginStatus(onlineUser.MyId);
                foreach (DataRow dr in dt.Rows)
                {
                    if(dr["IsOnline"].ToString().ToLower() == "true" || dr["IsOnline"].ToString() == "1")
                        isOnline=true;
                    else if(dr["IsOnline"].ToString().ToLower() == "true" || dr["IsOnline"].ToString() == "1")
                        isOnline=false;
                    //Inform all online friends that one user has logged in.
                    if (isOnline)
                    {
                        OnlineUser onlineFriend = OnlineUsers.FirstOrDefault(x => x.MyId == dr["FriendUId"].ToString());
                        if (onlineFriend != null)
                        {
                            Clients.Client(onlineFriend.ConnectionId).logoffuser(onlineUser.MyId);
                          //  Clients.Client(onlineFriend.ConnectionId).incoming_control_msg(onlineUser.MyId, onlineUser.Name, "offline"); //Not developed, keep aside for future feature
                        }
                    }
                    else
                    {
                        break;
                    }
                }
                OnlineUsers.Remove(onlineUser);
            }
            return base.OnDisconnected();
        }      
    }
    public class OnlineUser
    {
        //public OnlineUser(string MyId, string Name, string EmailId, string ConnectionId)
        //{
        //    this.myId = MyId;          
        //    this.connectionId = ConnectionId;
        //}
        public string MyId { get; set; }
        public string ConnectionId { get; set; }
        public string Name { get; set; }
    }
}