WebChat
A Fully Functional Chat Room — Free!
October 30, 2009
ControlFreak
LANGUAGES:VB.NET | C#
ASP.NETVERSIONS: 2.x
WebChat
A Fully Functional Chat Room Free!
By Steve C. Orr
Nearly every Web site could benefit from a chat room tohelp users socialize or sort out important issues. However, creating a chat roomis usually more effort than it s worth unless somebody s already done thework for you. This month we present a WebChat control that you can drop ontoany ASP.NET Web page to get an instant, fully functional chat room.
This article will teach you how the free WebChat controlworks and how to use it. And during the process you just might learn somecutting-edge development techniques, such as how to implement ASP.NET 2.0client-side callbacks (AJAX), howto use the new Visual Studio 2005 Resource Manager, and how to work withGenerics. You might also learn some valuable tips about how to create customcontrols, how to work with application state, and how to write server code thatemits client-side JavaScript.
The WebChat control works a lot like you might expect. Itallows multiple people to join a conversation and have a text conversation witheach other. The control indirectly supports emoticons and the ability to filterbad words (see Figure 1). AJAXkeeps the user interface running smoothly while conversation requests happeninconspicuously in the background. The control consists of standard HTML andJavaScript on the client to send and receive requests, with server-side codecentrally coordinating conversations among users.
Figure 1: The WebChat control allowsend users to interact with each other. It indirectly supports bad-wordfiltering, emoticons, and other HTML-based special effects.
User Guide
To use this Firefox-compatible control, download thesample code (see end of article for details) and add the included WebChat.DLLto your Visual Studio 2005 toolbox. Then drag it onto any WebForm that s it. Runthe 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 | Description |
---|---|
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: TheWebChat 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 providesseveral unique properties, and one event. The ChatTopic property permitsmultiple separate chat conversations to occur on a Web site. Some users mightbe interested in talking about cooking; others might be more interested inmuscle cars. This allows the option to have the WebChat control active on everycooking-related page, for example, so users can keep up with the conversationas they travel from page to page. The CallBackInterval property is more technical in nature,and is related to AJAX. As youmight know, AJAX provides theability for a page to call back to the server to update its content withouthaving to refresh the entire page. This property specifies how many secondsshould elapse between each such request. This is useful for adjusting bandwidthdemands. Set it too low and your server is more likely to get bogged down whenlarge numbers of users are chatting. Set it too high and users could end uptwiddling their thumbs while needlessly waiting for new messages from otherusers. The default of 2 seconds is usually a good starting point. The HistoryCapacity property specifies how much of theconversation should be kept in the server s memory. Another effect of thisproperty is that if this property is left at the default of 10 when a userenters the chat room they ll see the last 10 messages of that conversation asthey join the conversation. It also caches the conversation between AJAXrequests; if the value is set too low, users might miss bits of theconversation. The InitialFocus property specifies whether the text entryarea of this control should attempt to grab focus immediately upon page load. The Chatter event is raised to the page every time a usersubmits a new message, but before the message is officially added to theconversation. Keep in mind that this event is firing as the result of an AJAXrequest, and so page rendering will not happen after this event. Therefore,attempting to set properties of other controls in the page during this eventwill be futile. Rather, this event is intended to let the application developerpeek at the text and make any desired changes before the text is added to theconversation. This can be useful for filtering out undesirable words orreplacing specific pieces of text with images (like emoticons) or otherinteresting bits of HTML; again, see Figure 1. Here s a code sample: Protected Sub WebChat1_Chatter(ByRef ChatText As String) _ HandlesWebChat1.Chatter 'You can replace badwords... ChatText =ChatText.Replace("hell", "h***") '...or spruce things upwith 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. Forexample, if you ve already got an authentication system in place, this line ofcode may do the trick: WebChat1.UserName = User.Identity.Name That s all you really need to know to use the WebChatcontrol. Feel free to stop reading here, download it, and try it out. On theother hand, because you re reading this magazine, you re probably the curioustype and you want to know more about how the control works from the inside out.In that case, read on... Innards A good deal of JavaScript is necessary for all this towork. The two primary client-side functions are used to send the AJAXrequest back to the server, and to process the new messages that are retrievedfrom the server. The JavaScript functions must be customized somewhat toreflect the property values that have been set by the application developer. What sthe best way to dynamically generate such JavaScript? There are a coupledifferent techniques used by the WebChat control; both are demonstrated inFigure 3. Private Sub WebChat_Load(ByVal sender As Object, _ ByVal e AsSystem.EventArgs) Handles Me.LoadIf Me.Visible Then 'Retrieve the embeddedJavaScript functions 'that will recieve theresult 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 functionthat initiates 'the client side callback Dim sb As NewStringBuilder() sb.Append(System.Environment.NewLine) sb.Append("functionDoChatCallBack(txt)") sb.Append(System.Environment.NewLine) sb.Append("{") sb.Append("var msg =''; if (txt != null) msg=txt.value; ") sb.Append(System.Environment.NewLine) sb.Append(Page.ClientScript.GetCallbackEventReference(Me,_ "WebChatMaxMsgID +'||' + msg", _ "CalledBack","ErrCalledBack")) sb.Append(";") sb.Append(System.Environment.NewLine) sb.Append("if (txt !=null) txt.value='';") sb.Append(System.Environment.NewLine) sb.Append("}") Dim sDoCallBack As String= sb.ToString() Page.ClientScript.RegisterClientScriptBlock(Me.GetType,_ "DoChatCallBack", sDoCallBack, True) 'set initial focus (ifconfigured to do so) If Me.InitialFocus AndAlsoMe.Enabled Then Page.ClientScript.RegisterStartupScript(Me.GetType, _ "ChatFocus", "document.getElementById('" _ & _txt.ClientID& "').focus();", True) End If 'emit JavaScript functioncall that initializes 'the control and joins theuser into the conversation Page.ClientScript.RegisterStartupScript(Me.GetType,_ "ChatEnter","ChatEnter();", True) End IfEnd SubFigure 3: TheWebChat control s Page_Load subroutine emits nearly all the necessaryJavaScript to make the control work. The first technique takes advantage of a compiledresource. In the project you ll find a file named Callback.js that containsmost of the JavaScript to handle new messages received from the server. Thisfile also contains some other important variables and initialization routines. Inthis case, the JavaScript file is added as a resource. By right-clicking on theproject in solution explorer, you can open the properties dialog of theproject. Visual Studio 2005 s new Resource Manager (shown in Figure 4) makes itobscenely easy to work with virtually any kind of file and compile themdirectly into the assembly. VB.NET s My.Resources namespace takes advantage ofthe Resource Manager. With a single line of strongly typed code the resource isretrieved and used, as shown in the first code block of Figure 3. (In C# thesyntax is similarly easy: Properties.Resources.) The few bits of the JavaScriptfile that need to be dynamic are customized with a simple String.Replace methodcall before the contents are rendered into the page.
Figure 4: Visual Studio 2005 s newResource Manager makes it mind-numbingly easy to work with resources in astrongly typed manner. The second JavaScript generation technique is a bit morestandard, using a StringBuilder object to concatenate the required JavaScripttogether, piece by piece. This is demonstrated in the second code block ofFigure 3. Within this code block, you might want to take note of theGetCallbackEventReference method call, which tells ASP.NET to emit the AJAXclient-side callback code. By utilizing this method you can ensure thatappropriate callback JavaScript code will be generated for virtually everybrowser that supports such functionality. This ASP.NET 2.0 feature shields youfrom messy issues, such as browser sniffing and evolving Web 2.0 standards. The WebChat control essentially consists of a table toposition the sub-controls, a panel for the display of the conversation, atextbox to allow message entry, and a button to submit new messages. Thesesub-controls are configured from within the RenderContents subroutine shown inFigure 5. Protected Overrides Sub RenderContents(ByVal output _ As HtmlTextWriter) 'create the containingtable Dim tbl As Table = NewTable 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 = NewTableCell td.ColumnSpan = 2 td.Height = New Unit(95,UnitType.Percentage) Dim tr As TableRow = NewTableRow tr.Cells.Add(td) tbl.Rows.Add(tr) 'create the messagedisplay panel _pnl.Width = New Unit(99,UnitType.Percentage) _pnl.Height = NewUnit(100, UnitType.Percentage) _pnl.BorderStyle =WebControls.BorderStyle.Solid _pnl.BorderWidth = NewUnit(1, UnitType.Pixel) _pnl.Style.Add(HtmlTextWriterStyle.Overflow, "scroll") td.Controls.Add(_pnl) 'create a new table rowfor textbox & button tr = New TableRow tbl.Rows.Add(tr) td = New TableCell td.Width = New Unit(95,UnitType.Percentage) tr.Cells.Add(td) 'create the button Dim btn As Button = NewButton 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 = NewUnit(1, UnitType.Pixel) _txt.Attributes("autocomplete") = "off" _txt.Attributes("onkeydown") = _ "if ((event.keyCode== 13)) {document.getElementById('" _ & btn.ClientID &_ "').click();returnfalse;} else return true;" td.Controls.Add(_txt) 'create the final tablecell td = New TableCell td.Controls.Add(btn) tr.Cells.Add(td) 'Attach button's clientevent to the JavaScript functions btn.Attributes("onclick") = _ "DoChatCallBack(document.getElementById('" _ & _txt.ClientID& "'));" tbl.RenderControl(output) End SubFigure 5: TheRenderContents subroutine configures all the constituent controls of which theWebChat control is comprised. The bulk of this code is fairly boilerplate instantiatingcontrols, positioning them in relation to each other, and configuring theappropriate properties. The more interesting tidbits are near the end whereclient-side events are defined for the controls. For example, the textbox willhandle a client-side OnKeyDown event that will automatically click the submitbutton when the user presses the Enter key. The button s OnClick event, inturn, will call the previously emitted DoChatCallback JavaScript function toinitiate the AJAX call to theserver. The DoChatCallback JavaScript function sends the new text message tothe server and requests any new messages that were entered by other users. Server-side Callback Code If you tinkered with the client-side callbackfunctionality in beta 2 of the .NET Framework version 2, you ll notice a fewthings have changed in the final release. The most noticeable difference isthat there are now two subroutines (instead of one) that must be implemented tosupport client-side callbacks. The RaiseCallback subroutine shown in Figure 6is called first, as soon as the request arrives at the server. The newGetCallbackResult subroutine is subsequently called, just before a response issent back to the client. Public Sub RaiseCallbackEvent(ByVal _ eventArgument As String)Implements _ System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent'extract the last message ID that was received by the client'so that we can pass back only the new messagesDim aryArgs As String() = _ eventArgument.Split("||".ToCharArray())_LastMsgID = CType(aryArgs(0), Long) eventArgument = aryArgs(2).TrimDim WebChatTopic As String = _ "WebChat_Conversation_" & Me.ChatTopic'Raise Chatter event if new message was sentIf eventArgument.Length > 0 Then RaiseEventChatter(eventArgument) End If'Massage the message cosmeticallyDim ModifiedMsgText As String = ""& _ Me.UserName & ":" & eventArgument'Does the conversation need instantiation? If Context.Application(WebChatTopic) Is Nothing Then 'no existing queue foundso create a new queue _Messages = New _ System.Collections.Generic.Queue(Of ChatMessage) IfeventArgument.Trim.Length > 0 Then Dim Msg As New _ ChatMessage(1,Date.Now, _ ModifiedMsgText,Me.UserName) _Messages.Enqueue(Msg) End If Context.Application.Lock() Context.Application(WebChatTopic) = _Messages Context.Application(WebChatTopic & "_MAX") = 1 Context.Application.UnLock()Else 'existing Queue found, so use it Context.Application.Lock() _Messages =CType(Context.Application(WebChatTopic), _ System.Collections.Generic.Queue(Of ChatMessage)) IfeventArgument.Trim.Length > 0 Then Dim Max As Long = _ CType(Context.Application(WebChatTopic & _ "_MAX"),Long) Max += 1 Dim Msg As NewChatMessage(Max, _ Date.Now, ModifiedMsgText, Me.UserName) 'keep track of the maxmessage id Context.Application(WebChatTopic _ &"_MAX") = Max 'place new message onthe stack _Messages.Enqueue(Msg) 'purge stale messages If _Messages.Count >_HistoryCapacity Then Msg =_Messages.Dequeue() End If End If 'put the queue back intoapplication state Context.Application(WebChatTopic) = _Messages Context.Application.UnLock()End IfEnd SubFigure 6: TheRaiseCallbackEvent method is called by ASP.NET when the client makes an AJAXcall to the server. The WebChat control accepts incoming messages and adds themto the existing conversation that s kept in a generic queue that s cached in applicationstate. When a control implements ICallbackEventHandler, theRaiseCallbackEvent is fired whenever the client makes an AJAXrequest to the server. The code in Figure 6 first checks to see if there is anincoming message and, if so, parses it to retrieve the message and the ID ofthe last message that the client received. (This is so the server knows toreturn all messages that have been created since then.) If there is an incomingmessage, the Chatter event is raised so the application developer may alter theincoming text. The code then branches based on if the requestedconversation already exists or if it needs to be instantiated. If this is thefirst message of a conversation, a queue is instantiated that will accept onlyChatMessage objects. (ChatMessage is a very simple custom class that containsinformation 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 Queueobject because less casting is necessary. A new ChatMessage object is theninstantiated, and all relevant information about the message is passed to itsconstructor before the ChatMessage is added to the queue. This generic queue isthen added to application state (along with a separate entry that contains themaximum MessageID so far) so it will be saved between calls to the server. The Else block handles the case where the conversationalready exists and doesn t need to be instantiated. In this situation, thegeneric queue of ChatMessage is retrieved from application state, and the newChatMessage (if any) will be added to the queue. Stale messages are alsoremoved from the queue at this time. Finally, the updated queue is placed backinto application state. The GetCallbackResult function shown in Figure 7 loopsthrough each ChatMessage in the conversation. Any messages that are newer thanthe last message the client received are packaged together into a string andreturned to the client. Fields are delimited with two pipe characters (||) andeach row is delimited by two tilde characters (~~). The custom JavaScriptfunction CalledBack (contained within Callback.js) splits that data apart againonce it s received at the client, and then displays the new messages. Public Function GetCallbackResult() As String Implements _ System.Web.UI.ICallbackEventHandler.GetCallbackResult Dim retval As String ="" Dim sb As NewStringBuilder For Each Msg AsChatMessage In _Messages If Msg.MessageID >_LastMsgID Then 'Build the return string 'containingrecent chat messages sb.Append(Msg.MessageID.ToString()) sb.Append("||") 'column delimiter sb.Append(Msg.MessageText) sb.Append("~~") 'row delimiter End If Next retval = sb.ToString() Return retvalEnd FunctionFigure 7: TheGetCallbackResult function concatenates all new messages together into a singlestring and returns it to the client for display. The End Is Just the Beginning Although the primary functionality has been explained, it simpossible to cover in a single article all the details of a control thiscomplex. I encourage you to download and explore the code more thoroughly. Fromit you may learn more about how to create custom controls, how to utilizeclient-side callbacks (AJAX), andhow to use client-side JavaScript to improve the user experience and gainefficiencies not otherwise possible. Though this WebChat control covers most of the basics you dexpect from a chat room, I can think of at least a dozen interesting ways toextend the control with cool new features. Can you? Send feature requests myway, and maybe there will be an enhanced version in the future. Meanwhile, feelfree to enhance the control yourself with new features, and let me know whatyou come up with I d love to hear about them! The source code forthe WebChat control is available for download. Steve C. Orr is anMCSD and a Microsoft MVP in ASP.NET. He s been developing software solutionsfor leading companies in the Seattlearea for more than a decade. When he s not busy designing software systems orwriting about them, he can often be found loitering at local user groups andhabitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.netor e-mail him at mailto:[email protected].
About the Author
You May Also Like