A Fully Functional Chat Room — Free!






A Fully Functional Chat Room Free!


By Steve C. Orr


Nearly every Web site could benefit from a chat room to help users socialize or sort out important issues. However, creating a chat room is usually more effort than it s worth unless somebody s already done the work for you. This month we present a WebChat control that you can drop onto any ASP.NET Web page to get an instant, fully functional chat room.


This article will teach you how the free WebChat control works and how to use it. And during the process you just might learn some cutting-edge development techniques, such as how to implement ASP.NET 2.0 client-side callbacks (AJAX), how to use the new Visual Studio 2005 Resource Manager, and how to work with Generics. You might also learn some valuable tips about how to create custom controls, how to work with application state, and how to write server code that emits client-side JavaScript.


The WebChat control works a lot like you might expect. It allows multiple people to join a conversation and have a text conversation with each other. The control indirectly supports emoticons and the ability to filter bad words (see Figure 1). AJAX keeps the user interface running smoothly while conversation requests happen inconspicuously in the background. The control consists of standard HTML and JavaScript on the client to send and receive requests, with server-side code centrally coordinating conversations among users.


Figure 1: The WebChat control allows end users to interact with each other. It indirectly supports bad-word filtering, emoticons, and other HTML-based special effects.


User Guide

To use this Firefox-compatible control, download the sample code (see end of article for details) and add the included WebChat.DLL to your Visual Studio 2005 toolbox. Then drag it onto any WebForm that s it. Run the project and the chat window should be immediately functional. Of course, you can customize it with various properties, such as those shown in Figure 2.


Unique WebChat Members


Chatter event

This event is raised to the page anytime someone adds to the conversation. This provides an opportunity to filter and/or alter the text.

CallBackInterval property

Sets or gets the frequency that the browser requests conversation updates from the server. Default: 2 (seconds).

ChatTopic property

Sets or gets the conversation with which this instance of the control is associated. This provides the ability for a Web site to have multiple chat rooms, each with different topics. Default: general .

HistoryCapacity property

Sets or gets the number of chat messages that will be cached in application state. Default: 10.

InitialFocus property

A flag that specifies whether or not this control should receive focus upon page load. Default: True.

UserName property

This run-time property sets or gets the name of the user that s chatting in this instance of the control. Default: Anonymous .

Figure 2: The WebChat control provides several important members that allow versatile usages.


The ASPX declaration looks like this:


 ChatTopic="Cooking" HistoryCapacity="20" />


As illustrated in Figure 2, the WebChat control provides several unique properties, and one event. The ChatTopic property permits multiple separate chat conversations to occur on a Web site. Some users might be interested in talking about cooking; others might be more interested in muscle cars. This allows the option to have the WebChat control active on every cooking-related page, for example, so users can keep up with the conversation as they travel from page to page.


The CallBackInterval property is more technical in nature, and is related to AJAX. As you might know, AJAX provides the ability for a page to call back to the server to update its content without having to refresh the entire page. This property specifies how many seconds should elapse between each such request. This is useful for adjusting bandwidth demands. Set it too low and your server is more likely to get bogged down when large numbers of users are chatting. Set it too high and users could end up twiddling their thumbs while needlessly waiting for new messages from other users. The default of 2 seconds is usually a good starting point.


The HistoryCapacity property specifies how much of the conversation should be kept in the server s memory. Another effect of this property is that if this property is left at the default of 10 when a user enters the chat room they ll see the last 10 messages of that conversation as they join the conversation. It also caches the conversation between AJAX requests; if the value is set too low, users might miss bits of the conversation.


The InitialFocus property specifies whether the text entry area of this control should attempt to grab focus immediately upon page load.


The Chatter event is raised to the page every time a user submits a new message, but before the message is officially added to the conversation. Keep in mind that this event is firing as the result of an AJAX request, and so page rendering will not happen after this event. Therefore, attempting to set properties of other controls in the page during this event will be futile. Rather, this event is intended to let the application developer peek at the text and make any desired changes before the text is added to the conversation. This can be useful for filtering out undesirable words or replacing specific pieces of text with images (like emoticons) or other interesting bits of HTML; again, see Figure 1. Here s a code sample:


Protected Sub WebChat1_Chatter(ByRef ChatText As String) _

   Handles WebChat1.Chatter

   'You can replace bad words...

   ChatText = ChatText.Replace("hell", "h***")

   '...or spruce things up with emoticons

   ChatText = ChatText.Replace(":)", _


End Sub


Don t forget to set the UserName property with the name (or nickname) of the user that s chatting in this instance of the control. For example, if you ve already got an authentication system in place, this line of code may do the trick:


WebChat1.UserName = User.Identity.Name


That s all you really need to know to use the WebChat control. Feel free to stop reading here, download it, and try it out. On the other hand, because you re reading this magazine, you re probably the curious type and you want to know more about how the control works from the inside out. In that case, read on...



A good deal of JavaScript is necessary for all this to work. The two primary client-side functions are used to send the AJAX request back to the server, and to process the new messages that are retrieved from the server. The JavaScript functions must be customized somewhat to reflect the property values that have been set by the application developer. What s the best way to dynamically generate such JavaScript? There are a couple different techniques used by the WebChat control; both are demonstrated in Figure 3.


Private Sub WebChat_Load(ByVal sender As Object, _

 ByVal e As System.EventArgs) Handles Me.Load

If Me.Visible Then

 'Retrieve the embedded JavaScript functions

 'that will recieve the result

 Dim sCallBack As String = _

 My.Resources.Callback.Replace("WebChat1", Me.ID)

 If Me.CallBackInterval <> 2 Then

  sCallBack = sCallBack.Replace("2000", _

     (Me.CallBackInterval * 1000).ToString())

 End If

 Page.ClientScript.RegisterClientScriptBlock(Me.GetType, _

  "CalledBack", sCallBack, True)

 'Now generate the function that initiates

 'the client side callback

 Dim sb As New StringBuilder()


 sb.Append("function DoChatCallBack(txt)")



 sb.Append("var msg = ''; if (txt != null) msg=txt.value; ")


 sb.Append(Page.ClientScript.GetCallbackEventReference(Me, _

  "WebChatMaxMsgID + '||' + msg", _

  "CalledBack", "ErrCalledBack"))



 sb.Append("if (txt != null) txt.value='';")



 Dim sDoCallBack As String = sb.ToString()

 Page.ClientScript.RegisterClientScriptBlock(Me.GetType, _

  "DoChatCallBack", sDoCallBack, True)

 'set initial focus (if configured to do so)

 If Me.InitialFocus AndAlso Me.Enabled Then

   Page.ClientScript.RegisterStartupScript(Me.GetType, _

     "ChatFocus", "document.getElementById('" _

     & _txt.ClientID & "').focus();", True)

 End If

 'emit JavaScript function call that initializes

 'the control and joins the user into the conversation

 Page.ClientScript.RegisterStartupScript(Me.GetType, _

   "ChatEnter", "ChatEnter();", True)

End If

End Sub

Figure 3: The WebChat control s Page_Load subroutine emits nearly all the necessary JavaScript to make the control work.


The first technique takes advantage of a compiled resource. In the project you ll find a file named Callback.js that contains most of the JavaScript to handle new messages received from the server. This file also contains some other important variables and initialization routines. In this case, the JavaScript file is added as a resource. By right-clicking on the project in solution explorer, you can open the properties dialog of the project. Visual Studio 2005 s new Resource Manager (shown in Figure 4) makes it obscenely easy to work with virtually any kind of file and compile them directly into the assembly. VB.NET s My.Resources namespace takes advantage of the Resource Manager. With a single line of strongly typed code the resource is retrieved and used, as shown in the first code block of Figure 3. (In C# the syntax is similarly easy: Properties.Resources.) The few bits of the JavaScript file that need to be dynamic are customized with a simple String.Replace method call before the contents are rendered into the page.


Figure 4: Visual Studio 2005 s new Resource Manager makes it mind-numbingly easy to work with resources in a strongly typed manner.


The second JavaScript generation technique is a bit more standard, using a StringBuilder object to concatenate the required JavaScript together, piece by piece. This is demonstrated in the second code block of Figure 3. Within this code block, you might want to take note of the GetCallbackEventReference method call, which tells ASP.NET to emit the AJAX client-side callback code. By utilizing this method you can ensure that appropriate callback JavaScript code will be generated for virtually every browser that supports such functionality. This ASP.NET 2.0 feature shields you from messy issues, such as browser sniffing and evolving Web 2.0 standards.


The WebChat control essentially consists of a table to position the sub-controls, a panel for the display of the conversation, a textbox to allow message entry, and a button to submit new messages. These sub-controls are configured from within the RenderContents subroutine shown in Figure 5.


Protected Overrides Sub RenderContents(ByVal output _

 As HtmlTextWriter)

 'create the containing table

 Dim tbl As Table = New Table

 tbl.Height = Me.Height

 tbl.Width = Me.Width

 tbl.ToolTip = Me.ToolTip

 tbl.Style.Add("overflow", "scroll")

 tbl.Style.Add("position", "absolute")

 Dim td As TableCell = New TableCell

 td.ColumnSpan = 2

 td.Height = New Unit(95, UnitType.Percentage)

 Dim tr As TableRow = New TableRow



 'create the message display panel

 _pnl.Width = New Unit(99, UnitType.Percentage)

 _pnl.Height = New Unit(100, UnitType.Percentage)

 _pnl.BorderStyle = WebControls.BorderStyle.Solid

 _pnl.BorderWidth = New Unit(1, UnitType.Pixel)

 _pnl.Style.Add(HtmlTextWriterStyle.Overflow, "scroll")


 'create a new table row for textbox & button

 tr = New TableRow


 td = New TableCell

 td.Width = New Unit(95, UnitType.Percentage)


 'create the button

 Dim btn As Button = New Button

 btn.ID = Me.ID & "_button"

 btn.UseSubmitBehavior = False

 btn.Text = "Send"

 'create the textbox

 _txt.Width = New Unit(99, UnitType.Percentage)

 _txt.BorderColor = Drawing.Color.Black

 _txt.BorderWidth = New Unit(1, UnitType.Pixel)

 _txt.Attributes("autocomplete") = "off"

 _txt.Attributes("onkeydown") = _

  "if ((event.keyCode == 13)) {document.getElementById('" _

  & btn.ClientID & _

  "').click();return false;} else return true;"


 'create the final table cell

 td = New TableCell



 'Attach button's client event to the JavaScript functions

 btn.Attributes("onclick") = _

   "DoChatCallBack(document.getElementById('" _

   & _txt.ClientID & "'));"


End Sub

Figure 5: The RenderContents subroutine configures all the constituent controls of which the WebChat control is comprised.


The bulk of this code is fairly boilerplate instantiating controls, positioning them in relation to each other, and configuring the appropriate properties. The more interesting tidbits are near the end where client-side events are defined for the controls. For example, the textbox will handle a client-side OnKeyDown event that will automatically click the submit button when the user presses the Enter key. The button s OnClick event, in turn, will call the previously emitted DoChatCallback JavaScript function to initiate the AJAX call to the server. The DoChatCallback JavaScript function sends the new text message to the server and requests any new messages that were entered by other users.


Server-side Callback Code

If you tinkered with the client-side callback functionality in beta 2 of the .NET Framework version 2, you ll notice a few things have changed in the final release. The most noticeable difference is that there are now two subroutines (instead of one) that must be implemented to support client-side callbacks. The RaiseCallback subroutine shown in Figure 6 is called first, as soon as the request arrives at the server. The new GetCallbackResult subroutine is subsequently called, just before a response is sent back to the client.


Public Sub RaiseCallbackEvent(ByVal _

 eventArgument As String) Implements _


'extract the last message ID that was received by the client

'so that we can pass back only the new messages

Dim aryArgs As String() = _


_LastMsgID = CType(aryArgs(0), Long)

eventArgument = aryArgs(2).Trim

Dim WebChatTopic As String = _

 "WebChat_Conversation_" & Me.ChatTopic

'Raise Chatter event if new message was sent

If eventArgument.Length > 0 Then

 RaiseEvent Chatter(eventArgument)

End If

'Massage the message cosmetically

Dim ModifiedMsgText As String = "" & _

 Me.UserName & ": " & eventArgument

'Does the conversation need instantiation?

If Context.Application(WebChatTopic) Is Nothing Then

 'no existing queue found so create a new queue

 _Messages = New _

    System.Collections.Generic.Queue(Of ChatMessage)

 If eventArgument.Trim.Length > 0 Then

   Dim Msg As New _

     ChatMessage(1, Date.Now, _

     ModifiedMsgText, Me.UserName)


 End If


 Context.Application(WebChatTopic) = _Messages

 Context.Application(WebChatTopic & "_MAX") = 1


Else 'existing Queue found, so use it


 _Messages = CType(Context.Application(WebChatTopic), _

   System.Collections.Generic.Queue(Of ChatMessage))

 If eventArgument.Trim.Length > 0 Then

   Dim Max As Long = _

     CType(Context.Application(WebChatTopic & _

     "_MAX"), Long)

   Max += 1

   Dim Msg As New ChatMessage(Max, _

    Date.Now, ModifiedMsgText, Me.UserName)

   'keep track of the max message id

   Context.Application(WebChatTopic _

     & "_MAX") = Max

   'place new message on the stack


   'purge stale messages

   If _Messages.Count > _HistoryCapacity Then

     Msg = _Messages.Dequeue()

   End If

 End If

 'put the queue back into application state

 Context.Application(WebChatTopic) = _Messages


End If

End Sub

Figure 6: The RaiseCallbackEvent method is called by ASP.NET when the client makes an AJAX call to the server. The WebChat control accepts incoming messages and adds them to the existing conversation that s kept in a generic queue that s cached in application state.


When a control implements ICallbackEventHandler, the RaiseCallbackEvent is fired whenever the client makes an AJAX request to the server. The code in Figure 6 first checks to see if there is an incoming message and, if so, parses it to retrieve the message and the ID of the last message that the client received. (This is so the server knows to return all messages that have been created since then.) If there is an incoming message, the Chatter event is raised so the application developer may alter the incoming text.


The code then branches based on if the requested conversation already exists or if it needs to be instantiated. If this is the first message of a conversation, a queue is instantiated that will accept only ChatMessage objects. (ChatMessage is a very simple custom class that contains information about each chat message.) This specialized queue utilizes Generics (a new feature of .NET 2.0) to ensure it only contains ChatMessage objects and nothing else. This technique is more efficient than using a standard Queue object because less casting is necessary. A new ChatMessage object is then instantiated, and all relevant information about the message is passed to its constructor before the ChatMessage is added to the queue. This generic queue is then added to application state (along with a separate entry that contains the maximum MessageID so far) so it will be saved between calls to the server.


The Else block handles the case where the conversation already exists and doesn t need to be instantiated. In this situation, the generic queue of ChatMessage is retrieved from application state, and the new ChatMessage (if any) will be added to the queue. Stale messages are also removed from the queue at this time. Finally, the updated queue is placed back into application state.


The GetCallbackResult function shown in Figure 7 loops through each ChatMessage in the conversation. Any messages that are newer than the last message the client received are packaged together into a string and returned to the client. Fields are delimited with two pipe characters (||) and each row is delimited by two tilde characters (~~). The custom JavaScript function CalledBack (contained within Callback.js) splits that data apart again once it s received at the client, and then displays the new messages.


Public Function GetCallbackResult() As String Implements _


 Dim retval As String = ""

 Dim sb As New StringBuilder

 For Each Msg As ChatMessage In _Messages

     If Msg.MessageID > _LastMsgID Then

         'Build the return string

         'containing recent chat messages


         sb.Append("||") 'column delimiter


         sb.Append("~~") 'row delimiter

     End If


 retval = sb.ToString()

 Return retval

End Function

Figure 7: The GetCallbackResult function concatenates all new messages together into a single string and returns it to the client for display.


The End Is Just the Beginning

Although the primary functionality has been explained, it s impossible to cover in a single article all the details of a control this complex. I encourage you to download and explore the code more thoroughly. From it you may learn more about how to create custom controls, how to utilize client-side callbacks (AJAX), and how to use client-side JavaScript to improve the user experience and gain efficiencies not otherwise possible.


Though this WebChat control covers most of the basics you d expect from a chat room, I can think of at least a dozen interesting ways to extend the control with cool new features. Can you? Send feature requests my way, and maybe there will be an enhanced version in the future. Meanwhile, feel free to enhance the control yourself with new features, and let me know what you come up with I d love to hear about them!


The source code for the WebChat control is available for download.


Steve C. Orr is an MCSD and a Microsoft MVP in ASP.NET. He s been developing software solutions for leading companies in the Seattle area for more than a decade. When he s not busy designing software systems or writing about them, he can often be found loitering at local user groups and habitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.net or e-mail him at mailto:[email protected].




Hide comments


  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.