• Multi User Chat Room Using ASP.NET 2.0 and AJAX


    Screenshot - chat.jpg

    Introduction

    Building an interactive chat room requires keeping the users up to date with the messages and status of other users. Using ASP.NET AJAX to implement the chat room will remove the unnecessary post backs and will provide a seamless chat experience to the user.

    Background

    You can read my previous article on how to build a one to one chat room, this article was built using ASP.NET 1.1 and a third party AJAX library. Most of the Chatting Logic (rooms, users, messages sending, etc..) is used here.

    Code Walkthrough

    The App_Code folder contains all the classes that will be used to create our chat application. Let us start with the ChatUser

    Collapse Copy Code
    //ChatUser.cs
    public class ChatUser:IDisposable
    {
        #region Members
        public string UserID;
        public string UserName;
        public bool IsActive;
        public DateTime LastSeen;
        public int LastMessageReceived;
        #endregion 
    
        #region Constructors
        public ChatUser(string id,string userName)
        {
            this.UserID=id;
            this.IsActive=false;
            this.LastSeen=DateTime.MinValue ;
            this.UserName=userName;
            this.LastMessageReceived=0;
        }
        #endregion 
    
        #region IDisposable Members
        public void Dispose()
        {
            this.UserID="";
            this.IsActive=false;
            this.LastSeen=DateTime.MinValue ;
            this.UserName="";
            this.LastMessageReceived=0;
        }
        #endregion
    }
    

    This class represents the user that will join the chat room. The properties we are interested in are IsActive, LastSeen and LastMessageReceived. Note that in your application you may be using the ASP.NET membership providers. You can easily use this with our chat application. You will find that the MembershipUser class contains two properties IsOnline and LastActivityDate that can be used to replace the properties IsActive and LastSeen respectively.

    Each message that appears in the chat room is represented by the following class:

    Collapse Copy Code
    //ChatMessage.cs
    public class Message 
    {
        #region Members
        public string user;
        public string msg;
        public MsgType type;
        #endregion 
    
        #region Constructors
        public Message(string _user, string _msg, MsgType _type) 
        {
            user = _user;
            msg = _msg;
            type = _type;
        }
        public Message(string _user, MsgType _type) : 
                    this(_user, "", _type) { }
        public Message(MsgType _type) : this("", "", _type) { }
        #endregion 
    
        #region Methods
        public override string ToString()
        {
            switch(this.type)
            {
                case MsgType.Msg:
                return this.user+" says: "+this.msg;
                case MsgType.Join :
                return this.user + " has joined the room";
                case MsgType.Left :
                return this.user + " has left the room";
            }
            return "";
        }
        #endregion 
    }
    
    public enum MsgType { Msg, Start, Join, Left, Action}
    

    Each chat room contains a hashtable of users and a list of messages.

    Collapse Copy Code
    //ChatRoom.cs
    public class ChatRoom : IDisposable
    {
        #region Members
        
        public List<message /> messages = null;
        public string RoomID;
        private Dictionary<string,chatuser /> RoomUsers;
        private int userChatRoomSessionTimeout;
            
        #endregion
        #region IDisposable Members
        public void Dispose()
        {
            this.messages.Clear();
            this.RoomID="";
            foreach(object key in RoomUsers.Keys)
            {
                this.RoomUsers[key.ToString()].Dispose ();
            }
        }
        #endregion
            
        #region Constructors
        public ChatRoom(string roomID) 
        {
            this.messages = new List<message />();
            this.RoomID=roomID;
        userChatRoomSessionTimeout = Int32.Parse
        (System.Configuration.ConfigurationManager.AppSettings
                ["UserChatRoomSessionTimeout"]);
            RoomUsers = new Dictionary<string,chatuser />
        (Int32.Parse(System.Configuration.ConfigurationManager.AppSettings
                        ["ChatRoomMaxUsers"]));
        }
        #endregion
        .
        .
        .
        }
    

    The main methods in the ChatRoom class are sending a message, joining a room and leaving a room.

    Collapse Copy Code
    //ChatRoom.cs
    #region Operations Join,Send,Leave
        /// Marks the user as inactive
        public void LeaveRoom(string userID)
        {
            //deactivate user 
            ChatUser user=this.GetUser(userID);
            if (user == null)
            return ;
            user.IsActive=false;
            user.LastSeen=DateTime.Now;
                this.RoomUsers.Remove(userID);
    
            //Add leaving message
            Message msg = new Message(user.UserName ,"",MsgType.Left);
            this.AddMsg(msg);
                
        if (IsEmpty())
        ChatEngine.DeleteRoom(this.RoomID);
        }
    
        /// Activates the user and adds a join message to the room
        /// <returns />All the messages sent in the room</returns />
        public string JoinRoom(string userID,string userName)
        {
            //activate user 
            ChatUser user=new ChatUser(userID,userName);
            user.IsActive=true;
            user.UserName=userName;
            user.LastSeen=DateTime.Now;
            if (!this.RoomUsers.ContainsKey(userID))
            {
                //Add join message
            Message msg=new Message(user.UserName ,"",MsgType.Join);
                this.AddMsg(msg);
                //Get all the messages to the user
                int lastMsgID;
                List<message /> previousMessages=
                    this.GetMessagesSince(-1,out lastMsgID);
                user.LastMessageReceived=lastMsgID;
                //return the messages to the user
                string str=GenerateMessagesString(previousMessages);
                this.RoomUsers.Add(userID,user);
                return str;
            }
            return "";
        }
    
        /// Adds a message in the room
        /// <returns />All the messages sent from the other user from the last time 
        ///the user sent a message</returns />
        public string SendMessage(string strMsg,string senderID)
        {
            ChatUser user=this.GetUser(senderID);
            Message msg=new Message(user.UserName ,strMsg,MsgType.Msg);
            user.LastSeen=DateTime.Now;
            this.ExpireUsers(userChatRoomSessionTimeout);
            this.AddMsg(msg);
            int lastMsgID;
            List<message /> previousMsgs= this.GetMessagesSince
                ( user.LastMessageReceived,out lastMsgID);
            if (lastMsgID!=-1)
                user.LastMessageReceived=lastMsgID;
            string res=this.GenerateMessagesString(previousMsgs);
            return res;
        }
    
        /// Removes the users that hasn't sent any message during the 
        /// last window seconds 
        /// 
        public void ExpireUsers(int window) 
        {
            lock(this) 
            {
        foreach (object key in RoomUsers.Keys)
        {
                ChatUser usr = this.RoomUsers[key.ToString()];
                    lock (usr)
                    {
                      if (usr.LastSeen != System.DateTime.MinValue)
                      {
                        TimeSpan span = DateTime.Now - usr.LastSeen;
                        if (span.TotalSeconds > window && usr.IsActive != false)
                            {
                              this.LeaveRoom(usr.UserID);
                            }
                       }
                     }
                    }
        }
        #endregion 
     

    To keep the user updated, the following function retrieves all the messages sent in the room up to the last message that was received by the user.

    Collapse Copy Code
    //ChatRoom.cs
    <summary />    /// Returns all the messages sent since the last message 
        /// the user received
        public string UpdateUser(string userID)
        {
            ChatUser user=this.GetUser(userID);
            user.LastSeen=DateTime.Now;
            this.ExpireUsers(userChatRoomSessionTimeout);
            int lastMsgID;
            List<message /> previousMsgs= this.GetMessagesSince
                ( user.LastMessageReceived,out lastMsgID);
            if (lastMsgID!=-1)
                user.LastMessageReceived=lastMsgID;
            string res=this.GenerateMessagesString(previousMsgs);
            return res;
        }
        /// Returns a list that contains all messages sent after the 
        ///             message with id=msgid
        ///     ///            the message will be retuned 
        /// 
        public List<message /> GetMessagesSince(int msgid,out int lastMsgID) 
        {
            lock(messages) 
            {
            if ((messages.Count) <= (msgid+1))
                lastMsgID=-1;
            else
                lastMsgID=messages.Count-1;
            return messages.GetRange(msgid+1 , messages.Count - (msgid+1));
            }
        }
        
    

    The ChatEngine class acts as the container of the chat rooms.

    Collapse Copy Code
    //ChatEngine.cs
        public static class ChatEngine
        {
            #region Members
            private static Dictionary<string, /> Rooms = 
                    new Dictionary<string, />
            (Int32.Parse(System.Configuration.ConfigurationManager.AppSettings
                                ["MaxChatRooms"]));
            private static int userChatRoomSessionTimeout = 
            Int32.Parse(System.Configuration.ConfigurationManager.AppSettings
                        ["UserChatRoomSessionTimeout"]);
            #endregion 
                    
            #region Methods
        /// Cleans all the chat roomsDeletes the empty chat rooms
        public static void CleanChatRooms(object state)
            {
                lock (Rooms) 
                {
                    foreach (object key in Rooms.Keys)
                    {
                        ChatRoom room = Rooms[key.ToString()];
                        room.ExpireUsers(userChatRoomSessionTimeout);
                        if (room.IsEmpty())
                        {
                            room.Dispose();
                            Rooms.Remove(key.ToString());
                        }
                    }
                }
            }
    
        /// Returns the chat room for this two users or create a new one 
        /// if nothing exists
        public static ChatRoom GetRoom(string roomID)
        {
            ChatRoom room=null;
                lock (Rooms)
                {
                    if (Rooms.ContainsKey (roomID))
                        room = Rooms[roomID];
                    else
                    {
                        room = new ChatRoom(roomID);
                        Rooms.Add(roomID, room);
                    }
                }
                return room;
            }
    
        /// Deletes the specified room
        public static void DeleteRoom(string roomID)
        {
                if (!Rooms.ContainsKey(roomID))
                    return;
                lock (Rooms)
                {
                    ChatRoom room = Rooms[roomID];
                    room.Dispose();
                    Rooms.Remove(roomID);
                }
            }
            #endregion
        }
    

    An application level timer is set when the application starts to periodically clean the empty chat rooms.

    Collapse Copy Code
    //Global.asax
    void Application_Start(object sender, EventArgs e) 
        {
            System.Threading.Timer ChatRoomsCleanerTimer = 
            new System.Threading.Timer(new TimerCallback
            (ChatEngine.CleanChatRooms), null, 1200000, 1200000);
        }
    

    When you run the application, it will take you to the default.aspx page, where you are prompted to enter the user name that you will use in the chat room. After that the user chooses the chat room that he will join.

    The chatting page consists of a text area that displays all the messages in the chat room and a list box that shows all the online users in the room. The user writes his message in the text box and presses enter or clicks the send button to send the message. A text box looks like this:

    Chat Room

    Client Side AJAX

    The Chat.aspx page contains javascript that calls methods on the server using AJAX. To call a method on the server asynchoronously in AJAX you can:

    • Either place this method inside a web service and apply the ScriptService attribute to this web service
    • Or place this method in the aspx page as a static public method and apply the WebMethod attribute to it

    I used the second approach in calling the server side methods.

    All the javascript code is placed in the scripts.js file, there are four asynchronous requests that are made from the javascript to the server, the first two are sent periodically using a javascript timer, the updateUser request is used to get all the messages that were sent by the other users and updates the text area with these messages. The updateMembers request is used to get all the online members in the room and updates the members list box such that the members that left are removed from the list and the newly joined members are added to the list. The javascript code for these two requests is shown below:

    Collapse Copy Code
    //scripts.js
            var msgTimer = "";
               var membersTimer = "";
            
            startTimers();
          
            function startTimers()
            {
                msgTimer = window.setInterval("updateUser()",3000);
                membersTimer = window.setInterval("updateMembers()",10000);
            } 
            function updateUser()
            {
                PageMethods.UpdateUser($get("hdnRoomID").value, UpdateMessages);
            }
            function updateMembers()
            {
                PageMethods.UpdateRoomMembers($get("hdnRoomID").value, 
                                UpdateMembersList);
            }
            function UpdateMessages(result)
            {
                $get("txt").value=$get("txt").value+result;
                $get("txt").doScroll();
            }
            function UpdateMembersList(result)
            {
               // alert(result);
                var users=result.split(",");
               // alert(users.length);
                var i=0;
                                    
                    $get("lstMembers").options.length=0;
                     var i=0;
                    while (i < users.length)
                    {
                        if (users[i]!="");
                        {
                            var op=new Option(users[i],users[i]);
                            $get("lstMembers").options[$get("lstMembers").
                        options.length]= op;
                        }
                        i+=1;
                    }
                   }
    

    The PageMethods class is generated by the AJAX script manager control and provides a proxy for all your server methods so that you can call the method by passing it your parameters and passing the name of the script callback function (the function that will be called when the result of your method arrives from the server).

    The corresponding server side methods for these two requests are shown below:

    Collapse Copy Code
    //Chat.aspx.cs
      
        /// This function is called peridically called from the user to update 
        /// the messages
        [WebMethod]
        static public string UpdateUser(string roomID)
        {
            try
            {
                ChatRoom room = ChatEngine.GetRoom(roomID);
                if (room != null)
                {
                    string res = "";
                    if (room != null)
                    {
                        res = room.UpdateUser(HttpContext.Current.Session
                        ["UserName"].ToString());
                    }
                    return res;
                }
            }
            catch (Exception ex)
            {
    
            }
            return "";
        }
        /// Returns a comma separated string containing the names of the users 
        /// currently online
        [WebMethod]
        static public string UpdateRoomMembers(string roomID)
        {
            try
            {
                ChatRoom room = ChatEngine.GetRoom(roomID);
                if (room != null)
                {
                    IEnumerable<string /> users=room.GetRoomUsersNames ();
                    string res="";
    
                    foreach (string  s in users)
                    {
                        res+=s+",";
                    }
                    return res;
                }
            }
            catch (Exception ex)
            {
            }
            return "";
        }
    

    The third request is sent when the user presses the send button. This request sends the user message to the room.

    Collapse Copy Code
    //scripts.js
        function button_clicked()
        {
            PageMethods.SendMessage($get("txtMsg").value,$get
            ("hdnRoomID").value, UpdateMessages, errorCallback);
            $get("txtMsg").value="";
            $get("txt").scrollIntoView("true");
        }
        
        function errorCallback(result)
        {
            alert("An error occurred while invoking the remote method: " 
            + result);
        }
    

    The corresponding server side method for this request is:

    Collapse Copy Code
    //Chat.aspx.cs
    <summary />    /// This function is called from the client script 
        [WebMethod]
        static public string SendMessage(string msg, string roomID)
        {
            try
            {
                ChatRoom room = ChatEngine.GetRoom(roomID);
                string res = "";
                if (room != null)
                {
                    res = room.SendMessage
             (msg, HttpContext.Current.Session["UserName"].ToString());
                }
                return res;
            }
            catch (Exception ex)
            {
    
            }
            return "";
        }
    

    The last request is called during the onunload event of the <body> element. This method notifies the server that the user left the room.

    Collapse Copy Code
    //scripts.js
        function Leave()
        {
            stopTimers();
            PageMethods.LeaveRoom($get("hdnRoomID").value);
        }
    

    The corresponding server side method for this request is:

    Collapse Copy Code
    //Chat.aspx.cs
    <summary />    /// This function is called from the client when the user is about to 
        /// leave the room
        [WebMethod]
        static public string LeaveRoom(string roomID)
        {
            try
            {
                ChatRoom room = ChatEngine.GetRoom(roomID);
                if (room != null)
                    room.LeaveRoom(HttpContext.Current.Session["UserName"].
                                ToString());
            }
            catch (Exception ex)
            {
    
            }
            return "";
        }
    

    License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here

    About the Author

    Mosalem


    Member

    Occupation: Web Developer
    Location: Egypt Egypt
  • 相关阅读:
    Simple Microservices Architecture on AWS
    信封加密 Envelope
    AWS::Serverless::Application
    API Gateway Stage variables
    重新开始继续准备AWS Dev认证考试:AWS Lambda 环境变量
    MXnet 转 OpenVino,在树莓派上做的图片推理
    OpenVino的MXnet模型转换
    Grafana 6.3.3发布 系统指标监控与分析平台
    【小工具】鼠标右键 图片转文字
    【Shiro】Shiro登录验证失败问题
  • 原文地址:https://www.cnblogs.com/wuhenke/p/1755948.html
Copyright © 2020-2023  润新知