Building Web Chat From Scratch Using Asp .Net Ajax Timer

Asp .Net Ajax Timer is useful control to do a repetitive task like constantly viewing current information, auto save etc.
Its event is tick with time interval. So every period of interval time the tick event will be executed.
This Asp .Net Timer control is much like windows form timer control.

I already have post about this Timer control on my previous post, please refer to this Introduction To Asp .Net Ajax 4 (Timer)

One of advantage using Timer is that we code more on server script instead of Js client script. So that we can use Asp .Net server script feature.
But other developers might be more comfortable using client script and then it is more like a choice. Remember that every server side script that update the UI will use server’s resources.
So it is better to consider your code, traffic and related resources.

However, this Asp .Net Timer control is a good option to go and in this post I give you an example to make web chat from scratch using it.

Web Chat

Chat user sees their friend messages by constantly refresh a message board, checking whether there are any new incoming messages or not. Timer is used to do this.

Because of this is Ajax enabled site then I have to put ScriptManager control on aspx source and then I use several UpdatePanels to load each async process separately among them.
Using separate UpdatePanel and with help of UpdateMode attribute then each async process inside one of UpdatePanel control will not refresh another controls inside another UpdatePanels.

RDBMS Structure

Before go to the source code, Here I list the RDBMS table structure:

  1. ChatUser Table to store Chat User. There is a flag to indicate that user is currently active or not
  2. ChatSession Table to store Chat Session ID, related users and creation date
  3. ChatMsg Table to store users messages. This has msg id, source from and recipient users, chat messages, submitted msg timestamp, chat id and a bit flag to indicate that a message has been showed on recipient user or not.
  4. ChatUserStatus Table to store User status like log in, log out, typing etc. However, this example only show log in/out status for simplicity and educational purpose

Login Page

Login is needed before chat. This login page example don’t ask a user password and it sets to whom user want to chat with. Login screen shot

And here is the login.aspx code:

<form id="form1" runat="server">
    <div>
    Sign In as : &nbsp;<asp:DropDownList ID="ddl_user" runat="server" AutoPostBack="true">
         <asp:ListItem Text="-Select-" Value=""></asp:ListItem>
        </asp:DropDownList><br /><br />
    Chat With : &nbsp;
        <asp:Label ID="lbl1" runat="server" Text="-"></asp:Label>
        <asp:DropDownList ID="ddl_chatwith" runat="server" Visible="false">       
        </asp:DropDownList><br /><br />
    <asp:Button ID="btn1" runat="server" Text="Continue" />
      
    </div>
    </form>

Sign In DropDownList is filled by un-active UserIDs only, so already active users will not be shown.

Chat With DropDownList is filled by available UserIDs to chat. It is basically users other than a Sign In user.

If continue button is clicked then user will be redirected to Chat Page.

Lets take a look at Login.aspx.vb code as a function to load User and redirect process to chat page:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        connstr = "Data source=.\SQLEXPRESS; Initial Catalog=TestDB;MultipleActiveResultSets=true;User ID=sa;Password=pwd;"
        Dim sql As String
        Dim Conn As New SqlConnection(connstr)
        Conn.Open()
        If Not Page.IsPostBack Then
            sql = "select UserID from ChatUser where ActiveChat=0"
        Else
            sql = "select UserID from ChatUser where UserID<>'" & ddl_user.SelectedValue & "'"
            ddl_chatwith.Visible = True
            lbl1.Visible = False
        End If

        Dim Comm As New SqlCommand(sql, Conn)
        Dim dr As SqlDataReader = Comm.ExecuteReader()
        While dr.Read()
            If Not Page.IsPostBack Then
                ddl_user.Items.Add(dr(0).ToString())
            Else
                ddl_chatwith.Items.Add(dr(0).ToString())
            End If

        End While
        dr.Close()
        Comm.Dispose()
        Conn.Close()
    End Sub

    Protected Sub btn1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btn1.Click
        Session("UserID") = ddl_user.SelectedValue
        Session("ChatWith") = ddl_chatwith.SelectedValue
        Session("LogTime") = DateTime.Now
        Dim Conn As New SqlConnection(connstr)
        Conn.Open()
        Dim sql As String = "Update ChatUser set ActiveChat=1 where UserID='" & ddl_user.SelectedValue & "'"
        Dim Comm As New SqlCommand()
        Comm.Connection = Conn
        Comm.CommandText = sql
        Comm.ExecuteNonQuery()
        sql = "select ChatSessID from ChatSession Where ((UserID_1='" & ddl_user.SelectedValue & "' and UserID_2='" & ddl_chatwith.SelectedValue & "') or (UserID_2='" & ddl_user.SelectedValue & "' and UserID_1='" & ddl_chatwith.SelectedValue & "'))" & _
                " and Year(ChatDate) = " & Year(DateTime.Now) & " And Month(Chatdate) = " & Month(DateTime.Now) & " And Day(chatdate) = " & Day(DateTime.Now)
        Comm.CommandText = sql
        Dim da As New SqlDataAdapter(Comm)
        Dim dt As New DataTable()
        da.Fill(dt)
        If dt.Rows.Count = 0 Then
            Session("SessID") = ddl_user.SelectedValue & "_" & ddl_chatwith.SelectedValue & "_" & DateTime.Now.ToString()
            sql = "insert into ChatSession (ChatSessID, UserID_1, UserID_2, ChatDate) values ('" & Session("SessID").ToString() & "','" & ddl_user.SelectedValue & "','" & ddl_chatwith.SelectedValue & "','" & DateTime.Now.ToString() & "')"
            Comm.CommandText = sql
            Comm.ExecuteNonQuery()

            sql = "insert into ChatUserStatus (ChatSessID, UserID, Status, Showed, LastUpdate) values ('" & Session("SessID").ToString() & "','" & ddl_user.SelectedValue & "','',1,getdate())"
            Comm.CommandText = sql
            Comm.ExecuteNonQuery()

            sql = "insert into ChatUserStatus (ChatSessID, UserID, Status, Showed, LastUpdate) values ('" & Session("SessID").ToString() & "','" & ddl_chatwith.SelectedValue & "','',1,getdate())"
            Comm.CommandText = sql
            Comm.ExecuteNonQuery()

        Else
            Session("SessID") = dt.Rows(0)(0).ToString()

            sql = "Update ChatUserStatus set Status='log in', Showed=0, LastUpdate=getdate() Where ChatSessID='" & Session("SessID").ToString() & "' and UserID='" & ddl_user.SelectedValue & "'"
            Comm.CommandText = sql
            Comm.ExecuteNonQuery()

        End If
        da.Dispose()
        Comm.Dispose()
        Conn.Close()

        Session("LastText") = ""
        Response.Redirect("Default.aspx")
    End Sub

Above code store UserID, ChatWith user and LogTime to sessions. This login process create a chat id if no chat session created before.
Also above code set status to ‘log in’ to related chat id and user id.

Chat Page

User need a textbox to write his/her message and chat panel board to show complete chat msg. Chat screenshot

 <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
       <script type="text/javascript">
           function write_msg() {
               var objDiv = $get("chatpanel");
               var strPanel1 = $get("Panel1").innerHTML;
               
               if (objDiv.innerHTML.trim() == "") {
                   objDiv.innerHTML = strPanel1;
               } else {
                   objDiv.innerHTML = objDiv.innerHTML + strPanel1;
               }
               objDiv.scrollTop = objDiv.scrollHeight;
               
           }          
        
    </script>
        <div id="chatpanel" style="width:250px;height:150px;overflow:auto;overflow-x:hidden;overflow-y: scroll;"></div>
        <asp:UpdatePanel ID="updpanel_txtmsg" runat="server" UpdateMode="Conditional">
                    <ContentTemplate>
                     <asp:TextBox ID="txtmsg" runat="server" Width="250" ></asp:TextBox>   
                    </ContentTemplate>
         </asp:UpdatePanel>
        <asp:UpdatePanel ID="updpanel_timer" runat="server" UpdateMode="Conditional">
        <ContentTemplate>           
           <asp:Timer ID="Timer1" runat="server" Interval="300"></asp:Timer>
        </ContentTemplate>
        </asp:UpdatePanel>
        <asp:LinkButton ID="btnout" runat="server" Text="Logout" ></asp:LinkButton>

        <asp:UpdatePanel ID="updpanel_literal" runat="server" UpdateMode="Always">
            <ContentTemplate>
                <asp:Panel ID="Panel1" runat="server" Width="1" Height="1" style="visibility:hidden">
                <asp:Literal ID="ipttimer" runat="server"></asp:Literal>
                </asp:Panel>
            </ContentTemplate>
        </asp:UpdatePanel>
    </div>
    </form>

write_msg() is a client function to write current message submitted by user and make div element ‘chatpanel’ scroll bar always scrolls to its bottom.

I have several UpdatePanels. Each of these has different purposes. ‘updpanel_txtmsg’ contains a control to write a chat text by user. Its updatemode is Conditional, so that other ajax process do not disturb writing msg activity.
‘updpanel_timer’ contains timer control to check and return new messages periodically and writes them to literal control inside ‘updpanel_literal’ updatepanel. Its updatemode is also Conditional so that checking periodically is not disturbing other control.
‘updpanel_literal’ contains literal control as a temporary new messages container. Text in this literal will be written on ‘chatpanel’ div element. Its updatemode is Always which means this literal control will be always updated by user submitted new message and other messages comes from timer control.
I do this ‘updpanel_literal’ and ‘chatpanel’ div element strategy because I need a way to make a bridge between server side method & control to a client ‘chatpanel’ div element.

Here is Code behind file .aspx.vb code:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        connstr = "Data source=.\SQLEXPRESS; Initial Catalog=TestDB;MultipleActiveResultSets=true;User ID=sa;Password=pwd"
        If IsNothing(Session("UserID")) Then
            Response.Redirect("Login.aspx")
        End If

        If Page.IsPostBack Then
            If Request.Form("__EVENTTARGET") <> "Timer1" And txtmsg.Text.Trim <> "" Then
                Timer1.Enabled = False
                InsertMsg()
                ipttimer.Text = "<table><tr><td>" & Session("UserID").ToString() & "</td><td>" & txtmsg.Text & "</td></tr></table>"
                strLastMsg = ipttimer.Text
                
                Timer1.Enabled = True
            End If
        End If
    End Sub


    Protected Sub InsertMsg()
        Dim Conn As New SqlConnection(connstr)
        Conn.Open()
        Dim sql As String = "Insert into ChatMsg (MsgTxt, UserID_FR, UserID_TO,ShowedOnTO) Values ('" & txtmsg.Text & "','" & Session("UserID").ToString() & "','" & Session("ChatWith").ToString() & "',0)"

        Dim Comm As New SqlCommand(sql, Conn)
        Comm.ExecuteNonQuery()
        Comm.Dispose()
        Conn.Close()
    End Sub

    Protected Function ShowMsg() As String
        Dim sql As String
        Dim Conn As New SqlConnection(connstr)
        Conn.Open()

        sql = "select UserID_FR, MsgTxt, MsgID from ChatMsg where ShowedOnTO=0 and UserID_TO='" & Session("UserID") & "' order by MsgTimeStamp desc"

        Dim Comm As New SqlCommand()
        Comm.Connection = Conn
        Comm.CommandText = sql
        Dim dr As SqlDataReader = Comm.ExecuteReader()
        Dim sb As New StringBuilder()

        Dim Comm1 As New SqlCommand()
        Comm1.Connection = Conn
        While dr.Read()
            sb.Append("<table><tr><td>" & dr(0).ToString() & "</td><td>" & dr(1).ToString() & "</td></tr></table>")
            Comm1.CommandText = "Update ChatMsg set ShowedOnTO=1 where MsgID=" & dr(2).ToString()
            Comm1.ExecuteNonQuery()
        End While
        dr.Close()

        Comm.CommandText = "select Status, LastUpdate, StatType from ChatUserStatus where ChatSessID='" & Session("SessID").ToString() & "' and UserID='" & Session("ChatWith").ToString() & "' and Status is not null and Status <> '' and showed=0"
        dr = Comm.ExecuteReader()
        If dr.Read() Then
            If CDate(Session("LogTime")) <= CDate(dr(1)) Then
                
                sb.Append("<div class='sign'><i>" & Session("ChatWith").ToString() & " is " & dr(0).ToString() & "</i></div>")           

                Comm1.CommandText = "Update ChatUserStatus set Showed=1 where ChatSessID='" & Session("SessID").ToString() & "' and UserID='" & Session("ChatWith").ToString() & "' and Status is not null and Status <> ''"
                Comm1.ExecuteNonQuery()
            End If

        End If
            Comm.Dispose()
            Comm1.Dispose()

            Conn.Close()
            Return sb.ToString()
    End Function

    Protected Sub Timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        strLastMsg = ShowMsg()

        If strLastMsg <> "" Then            
            ipttimer.Text = strLastMsg
        End If

    End Sub
    
    Protected Sub Panel1_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Panel1.PreRender
        If strLastMsg <> "" Then
            ScriptManager.RegisterStartupScript(Panel1, Panel1.GetType(), "msgload", "write_msg();", True)
        End If


    End Sub

    Protected Sub btnout_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnout.Click
        Dim Conn As New SqlConnection(connstr)
        Conn.Open()
        Dim sql As String = "Update ChatUser set ActiveChat=0 where UserID='" & Session("UserID").ToString() & "'"
        Dim Comm As New SqlCommand()
        Comm.Connection = Conn
        Comm.CommandText = sql
        Comm.ExecuteNonQuery()

        Comm.CommandText = "Update ChatUserStatus Set Status='log out', StatType=1, Showed=0, LastUpdate=getdate() where ChatSessID='" & Session("SessID").ToString() & "' and UserID='" & Session("UserID").ToString() & "'"
        Comm.ExecuteNonQuery()
        Comm.Dispose()
        Conn.Close()

        Session.Clear()
        Response.Redirect("Login.aspx")
    End Sub

Only one thing I want to explain is how to write new messages in literal control and transfer them to client div element.
Please take a look at Panel1_PreRender method. If strLastMsg variable is not empty then Startup Script is registered. This means Js function write_msg() is executed.
Panel1 contains literal control. This ‘Panel1’ ID can be accessed by client method by using $get('Panel1'). This client Panel1 innerHTML value attached to ‘chatpanel’ innerHTML.
So every time server method Panel1_PreRender is executed, client ‘chatpanel’ element will be filled with new chat messages.

Below is screen shot of User_A chat board. FYI, I have two chat users, User_A and User_B.

Off course we can make look and feel of this chat board more professional but this is example only.

Summary

Asp .Net Ajax Timer control makes easy to develop client rich web application that refresh periodically. I have a lot of server side script instead of client Js script. So that I can use full features of Asp .Net server framework with tons of VS intellisense.
Other developers might be more comfortable using a lot of client script to make a web chat but it is matter of choice and consideration to your server resources.

Regards,
Agung Gugiaji

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s