Showing posts with label real time chat. Show all posts
Showing posts with label real time chat. Show all posts

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; }
    }
}





Tuesday, February 25, 2014

In Memory Search using Lamda Expression: Realtime Chat application on web in ASP.Net: Step 4

Welcome at  Realtime Chat application on web in ASP.Net using SignalR technology. This is step 4 and now we will learn how we can search in memory array without any loop like for, while etc. We will use Lamda expression to search an element in array list with generics. I shall not describe about these technologies from theoretical perspective, here I shall show some application of these. You can read theory of these things from MSDN site.

You may think why this is require for our chat application, really good question. We shall use this technique to find an online user in server. However we can take database help to find online users but to minimize database operation I have used this technique. In chat application I have used a public static list (array) to hold online users, and from here I am searching users to generate online friend lists. By that way I have minimize a lot database operation and this technique can improve your chat application.

Lets check the code first then shall describe the codes.

HTML/ASP.Net part
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <h1>In memory search using Lamda Expression in C#.Net</h1>
        <h3>Search country calling code</h3>
        <p>
            Country Name:
            <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
&nbsp;<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Find Calling Code" />
        </p>
        <p>
            <asp:Label ID="Label1" runat="server" Text=""></asp:Label>
        </p>
        <a href="http://en.wikipedia.org/wiki/List_of_country_calling_codes" target="_blank">Full list is available here</a>
    </form>
</body>
</html>
and the C# code as below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page
{
   
    protected void Page_Load(object sender, EventArgs e)
    {       
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        FindCallingCode();
    }
    private void FindCallingCode()
    {
        //In real use databind before searching, using by that way as demo
        List<Country> countries = new List<Country>();
        countries.Add(new Country { callingCode = "+91", name = "India" });
        countries.Add(new Country { callingCode = "+44", name = "UK" });
        countries.Add(new Country { callingCode = "+1", name = "USA" });
        countries.Add(new Country { callingCode = "+88", name = "Bangladesh" });
        countries.Add(new Country { callingCode = "+49", name = "Germany" });
        countries.Add(new Country { callingCode = "+33", name = "France" });
        countries.Add(new Country { callingCode = "+55", name = "Brazil" });

        Country country = countries.FirstOrDefault(x => x.name.ToLower() == TextBox1.Text.Trim().ToLower());
        if (country != null)
            Label1.Text = "Calling code of " + country.name + " is " + country.callingCode;
        else
            Label1.Text = TextBox1.Text + " Not Found in our Country List";
    }
}
public class Country {   
    public string callingCode { get; set; }
    public string name { get; set; }
}
Here is main method is  "FindCallingCode()" and class "Country". Here I am doing all operations. Lets look at some important code.

Country class: This class I have defined to create country objects which will store country name  and calling code. If you need extra properties you can add these easily.

List<Country> countries = new List<Country>();
In the above code I have defined a list (array) of county objects with name countries. Naming convention using as this variable holding multiple country hence its plural name of country.

countries.Add(new Country { callingCode = "+91", name = "India" });
In the above line of code I have adding element of array by creating country class object.

Country country = countries.FirstOrDefault(x => x.name.ToLower() == TextBox1.Text.Trim().ToLower());
This is actually Lamda expression "x=>x.name" this is doing searching operation with method "FirstOrDefault". This line searching all element and doing comparison with user entered country name and stored country name. When matched it will return the country object.
Label1.Text = "Calling code of " + country.name + " is " + country.callingCode;
 In previous line of code we have found country object from array of countries and now getting calling code and name from the found object.

In my chat application I have used mainly this (FirstOrDefault) method and for some cases have used "Count", "Find" methods/properties. "Find" and "FirstOrDefault" both can do a bit similar work but "Find" is very fast (in a blog I found 1000 times) than "FirstOrDefault", hence I am using as and when these are suitable.
For your knowledge you can check other methods as well.

Thanks for reading my blog please visit again for next article.

Source code is available here.