Which technology is used?
A web-chat is an application for which dynamically generated HTML is needed and client-server interaction. Therefore ASP.NET 2.0 has been used and for the client-server-intercation without postbacks of course Ajax. We could use Ajax.NET but to keep it simple we decided to use the Ajax-implementation from Mike Schwartz called AjaxPro.
To do all serverside calls from javascript with AjaxPro you need to do the following:
1. Add a reference to the Assembly AjaxPro.dll which you can download here.
2. Add a class to your project.
3. Add a method to that class you want to call by script and mark it with the Attribute AjaxMethod
4. Register the class in the Page_Load-handler of your aspx-page from which you want to use it. With
AjaxPro.Utility.RegisterTypeForAjax(typeof(MyAjaxClass));
That's all. Other details will be explained in the implemenation-part of the article.
The design of a Chat?

There are different kind of chats. In this example I designed a chat in which users can login with their username and password. Then they can enter the chat and send messages. The messages are visible for all other chat-users. It is a public chat, where all logged in chatters can communicate together.
![]()
Figure 1
The Session holds a CurrentChatter, a user that has just logged in. The chatter enters the chat (1) by navigating to the chat-page (Chat.aspx). After a chatter has entered, he has the possibility to send messages by typing text to a TextBox and pressing the enter-key. Then the chat-message is sent to a ChatMessageQueue (CMQ) which in our example is stored in the Application (2). Okay, now the message lies in the CMQ on the server. We have to get it from there, so that every client can see it. Therefore we have a javascript-timer running, that gets all new messages from the CMQ and displays it in an HTML-DIV for example (3).
Sounds simple, doesn't it? Here is what we need to do, to implement points 1 to 3.
(1) AddChatter (store chat-user)
Save the curent user after successful login to the Session.
(2) AddToMsgQueue
When sending a chat-message you need to store this in the CMQ. You will have to invoke this action from a javascript function. The scriptcode calls a serverside method via Ajax that has the ability to access the ASP.NET-Application.
(3) GetMsgsFromQueue
Here we need to check in a javascript-timer every n seconds, if there are new chat-messages for the client to display. So we poll the CMQ for new messages by an Ajax-method. If we have new messages, theses are transformed to a string that can be displayed as a list in a chatwindow.
The implementation
So far the theory, now the concrete coding. Here are the datatypes we need:

![]()
Figure 2
Chatter
This class has an Id to identify a chatter internally and a name for the display. The LastMsgKey is the key of the last message a chatter received. When the timer polls for new messages, this key is used to find out, if there are messages in the CMQ after that message. The messages are stored in a list in the Application. If there are messages in the list for the chatter after his LastMsgKey, these messages have to be fetched.
ChatMsg
A chat-message has a Key (Guid), the name of the chatter who sent this message and the message itself as string.
ChatMgr
The ChatMgr handles the list of all logged in chatters and the CMQ. You can add messages to the CMQ and get them back from it. It also has methods to get a list of all available chatters as Html. The code could be optimized here: The display-logic for chatters and chat-messages could be placed to another class.
The HTML-components are located on the WebForm Chat.aspx. We have
divChatBox: A DIV for all chatmessages.
divChatters: A DIV for all logged in chatters
txtMessage: A TextArea for the text that a chatter can type and send.
When the page loads, all logged in chatters and the available messages are loaded and displayed in the corresponding HTML-elements. Here is the javascript-code which is called cause we add the calls to it in the onload-handler of the Chat.aspx-page:
function fillUsers()
{
ChatMgr.GetChattersHtml( fillUsers_CallBack );
}
function fillUsers_CallBack( res )
{
var userBox = document.getElementById('divUsers');
if( res.value == '' )
{
setTimeout( "fillUsers()", 5000 );
return;
}
userBox.innerHTML = res.value;
setTimeout( "fillUsers()", 5000 );
}
function getMsgs()
{
ChatMgr.GetMsgsFromQueue( getMsgs_CallBack );
}
function getMsgs_CallBack( res )
{
var chatBox = document.getElementById('divChatBox');
chatBox.innerHTML += res.value;
scrollContentDown();
setTimeout( "getMsgs()", 2000 );
}
The _callback-functions are asynchronous callbacks. They are initiated by the Ajax-calls of the ChatMgr. In the end of a _callback-function setTimeout is called to invoke the function again after a certain period.
Now how is the ChatMgr able to call a serverside function from script? See the examle of the call GetChattersHtml:
[ AjaxMethod( HttpSessionStateRequirement.Read ) ]
public string GetChattersHtml()
{
string result = "";
if( SessionMgr.Instance.OnlineUsers == null )
return result;
foreach (Member member in SessionMgr.Instance.OnlineUsers.Values )
{
result += "
result += " result += " result += " result += "
";"; " + UserImage.GetHtml( member.ID, "" ) + " "; " + member.Name + " ";
result += "
";}
return result;
}
The method of the ChatMgr is marked with the Attribute AjaxMethod. To get access to the current Session, we add the HttpSessionStateRequirement.Read to that Attribute. Now the method can read all logged in users from the Session and give back the list as HTML. If we also want to be able to write to the HttpSession we could use HttpSessionStateRequirement.ReadWrite.
For the other code-parts it's just the same: The script calls the serverside method, the method returns the return-values which are read-out in an asynchronous callback-function in script.
Here is the logic for sending messages to the queue:
function sendMessage()
{
var newMsg = '';
var chatBox = null;
var msg = document.getElementById('txtMsg').value;
if( msg == '' )
return;
document.getElementById('txtMsg').value = '';
newMsg = ChatMgr.AddToMsgQueue( msg ).value;
chatBox = document.getElementById('divChatBox');
chatBox.innerHTML += '' +
newMsg.Name + ': ' + newMsg.Message + '
';
}
On pressing return, the function sendMessage is called and adds the message to the CMQ by calling the method AddToMsgQueue of the ChatMgr which again is an Ajax-method.
If you have further questions on how to build an ASP.NET-Ajax-Chat or want codegod to implement it for you, just send us a mail.
You can see the codegod-chat in action here after logging in. Have fun!
Introduction
I ran into the problem of creating a chat room where two users can chat directly. I didn't like the idea of using server side code and doing all these post back which is not user friendly. I found that with the use of remote scripting, we can make a simple and fast chat application.
Background
I built this application using the Remote Scripting client and server side implementation provided by Alvaro Mendez: Remote Scripting. I also used some chat room logic from Julien Couvreur.
Code Walkthrough
I'll start by examining the ChatEngine.cs file which contains all the logic. You'll find four classes in this file:
- The ChatUser that contains the user info (ID, name, active or not, last seen time, last message received):
Collapse
public class ChatUser : IDisposable
{
public string UserID;
public string UserName;
public bool IsActive;
public DateTime LastSeen;
public int LastMessageReceived;
public ChatUser(string id,string userName)
{
this.UserID=id;
this.IsActive=false;
this.LastSeen=DateTime.MinValue ;
this.UserName=userName;
this.LastMessageReceived=0;
}
public void Dispose()
{
this.UserID="";
this.IsActive=false;
this.LastSeen=DateTime.MinValue ;
this.UserName="";
this.LastMessageReceived=0;
}
}
- The Message class contains information about the message (the message text, message type, sender ID):
Collapse
public class Message
{
public string user;
public string msg;
public MsgType type;
public Message(string _user, string _msg, MsgType _type)
{
user = _user;
msg = _msg;
type = _type;
}
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 "";
}
public Message(string _user, MsgType _type) : this(_user, "", _type) { }
public Message(MsgType _type) : this("", "", _type) { }
}
public enum MsgType { Msg, Start, Join, Left }
- The ChatRoom class:
Each chat room can have two users only represented by the two members FirstUser and SecondUser. The messages are stored in the ArrayList messages of Message instances. The class contains the common operations for a chat room like joining, sending message and leaving the room.
- The ChatEngine class: contains a HashTable of chat rooms.
Other operations in the class include creating new rooms and deleting the empty chat rooms.
Building The Application
I will build a simple app to demonstrate the usage of the Chat Engine classes. First, we create a simple page (default.aspx) where the user can enter his nickname (which is saved in the session). After that, the user enters the nickname of the other user he wants to chat with. The two user names are used to create the ID of the chat room. Then the user is redirected to chat.aspx with the name of the other passed in the query string and saved in a hidden variable.
In Chat.aspx, a chat room is created for the two users:
Collapse code snippet
private void Page_Load(object sender, System.EventArgs e)
{
if (AMS.Web.RemoteScripting.InvokeMethod(this))
//if this is a callback function return
return;
txtMsg.Attributes.Add("onkeypress", "return clickButton(event,'btn')");
if (!IsPostBack)
{
if (Request.QueryString["userid"]!=null
&& Request.QueryString["userid"]!="")
{
otherUser.Value=Request.QueryString["userid"];
ChatRoom room=ChatEngine.GetRoom(Session["UserName"].ToString() ,
Request.QueryString["userid"]);
string s=room.JoinRoom(Session["UserName"].ToString(),
Session["UserName"].ToString() );
txt.InnerText=s;
string strScript="< script >startTimer();< /script >";
this.RegisterClientScriptBlock("timerScript",strScript);
}
else
{
Response.Write("User id Missing");
}
}
}
On the client side, remote scripting is used to send messages to the chat room. Using Alvaro Mendez's implementation, calling server functions from the client is very simple. For example, when the user clicks the Enter key in the text box, a message is sent to the chat room using the following script:
Collapse
//Chat.aspx
function clickButton(e, buttonid)
{
var bt = document.getElementById(buttonid);
if (typeof bt == 'object')
{
if(navigator.appName.indexOf("Netscape")>(-1)){
if (e.keyCode == 13)
{
bt.click();
return false;
}
}
if (navigator.appName.indexOf("Microsoft Internet Explorer")>(-1))
{
if (event.keyCode == 13)
{
bt.click();
return false;
}
}
}
}
function button_clicked()
{
RS.Execute("Chat.aspx", "SendMessage",document.Form1.txtMsg.value,
document.Form1.otherUser.value, callback, errorCallback);
document.Form1.txtMsg.value="";
document.Form1.txt.scrollIntoView("true");
}
function callback(result)
{
document.Form1.txt.value=document.Form1.txt.value+result;
document.Form1.txt.doScroll();
}
function errorCallback(result)
{
alert("An error occurred while invoking the remote method: "
+ result);
}
The RS object is defined in the rs.js file, so you have to include it in the aspx page. The Execute method takes the name of the page (chat.aspx) in the first parameter, and the name of the function in the second parameter. This function must be a public function in the page class you pass as the parameter of the server function to the Execute function. The last two parameters are for the callback and error callback functions. The result of the server function is passed as an argument to the callback function.
Collapse
//Chat.aspx.cs
public string SendMessage(string msg,string toUserID)
{
try
{
ChatRoom room=ChatEngine.GetRoom(Session["UserName"].ToString() ,toUserID);
string res="";
if (room!=null)
{
res=room.SendMessage(msg,Session["UserName"].ToString() ,toUserID);
}
return res;
}
catch(Exception ex)
{
}
return "";
}
As you can see, SendMessage takes the message as a parameter and the ID of the other user, adds this message to the chat room, and returns all the messages that have been sent that the user didn't receive yet.
The user can leave the room by clicking the Exit button.
Collapse
private void btnExit_Click(object sender, System.EventArgs e)
{
try
{
ChatRoom room=ChatEngine.GetRoom(Session["UserName"].ToString(),
Request.QueryString["userid"]);
room.LeaveRoom(Session["UserName"].ToString() );
Response.Redirect("default.aspx");
}
catch(Exception ex)
{
}
}
I also added a client side function that handles the onunload event of the form.
Collapse
//Chat.aspx
function Leave()
{
RS.Execute("Chat.aspx", "LeaveRoom",document.Form1.otherUser.value);
}
Collapse
//Chat.aspx.cs
public string LeaveRoom(string otherUser)
{
try
{
ChatRoom room=ChatEngine.GetRoom(Session["UserName"].ToString() ,otherUser);
if (room!=null)
room.LeaveRoom(Session["UserName"].ToString() );
}
catch(Exception ex)
{
}
return "";
}
Remaining is to periodically update the client to receive the messages sent from the other user. A simple solution is to add a timer that makes a remote call to the server function.
Collapse
//Chat.aspx
var interval = "";
var i = 5;
function startTimer()
{
interval = window.setInterval("tTimer()",1000);
}
function stopTimer()
{
window.clearInterval (interval);
interval="";
}
function tTimer()
{
RS.Execute("Chat.aspx", "UpdateUser",document.Form1.otherUser.value,
callback, errorCallback);
}
Collapse
//Chat.aspx.cs
public string UpdateUser(string otherUserID)
{
try
{
ChatRoom room=ChatEngine.GetRoom(Session["UserName"].ToString() ,
otherUserID);
if (room!=null)
{
string res="";
if (room!=null)
{
res=room.UpdateUser(Session["UserName"].ToString());
}
return res;
}
}
catch(Exception ex)
{
}
return "";
}
A timer is added on the server to periodically clean and remove the empty chat rooms.
Collapse
//Global.asax.cs
Timer ChatRoomsCleanerTimer=new System.Threading.Timer(
new TimerCallback(ChatEngine.CleanChatRooms),null,3600000,3600000);
Collapse
//ChatEngine.cs
public static void CleanChatRooms(object state)
{
Monitor.Enter(Rooms);
foreach(object key in Rooms.Keys)
{
ChatRoom room=(ChatRoom)Rooms[key.ToString()];
room.ExpireUsers(100);
if (room.IsEmpty())
{
room.Dispose();
Rooms.Remove(key.ToString());
}
}
Monitor.Exit(Rooms);
}
Points of Interest
You can easily extend the ChatRoom class to allow multiple users per chat room.
ASP.NET forums Source Code:
Here is some code.
C#
1 using System.Web;
2 using System.Web.Routing;
3
4 namespace MvcApplication1.Constraints
5 {
6 public class LocalhostConstraint : IRouteConstraint
7 {
8 public bool Match
9 (
10 HttpContextBase httpContext,
11 Route route,
12 string parameterName,
13 RouteValueDictionary values,
14 RouteDirection routeDirection
15 )
16 {
17 return httpContext.Request.IsLocal;
18 }
19
20 }
21 }
VB
1 Public Class LocalhostConstraint
2 Implements IRouteConstraint
3
4 Public Function Match( _
5 ByVal httpContext As HttpContextBase, _
6 ByVal route As Route, _
7 ByVal parameterName As String, _
8 ByVal values As RouteValueDictionary, _
9 ByVal routeDirection As RouteDirection _
10 ) As Boolean Implements IRouteConstraint.Match
11
12 Return httpContext.Request.IsLocal
13 End Function
14
15 End Class
SQL
1 SELECT TOP 1000 [EventLogID]
2 ,[Message]
3 ,[Category]
4 ,[SettingsID]
5 ,[EventID]
6 ,[EventType]
7 ,[EventDate]
8 ,[MachineName]
9 FROM [somedb].[dbo].[sometable] WHERE SettingsID = 1000 AND Message LIKE 'Top%' ORDER BY 1 desc
C# with comments and slashes
1 var targetSchemeAndHost = "http://" + targetHost;
2 //comment;
VB with comments and single quote
1 Dim s As String = "A string with ' a single quote"
2 'comment
longer C#
1 public bool public class Collection:CollectionBase
2
3 {
4
5 public Collection()
6
7 {
8
9 //
10
11 // TODO: Add constructor logic here
12
13 //
14
15 }
16
17 public T this[int index]
18
19 {
20
21 get { return (T)this.List[index]; }
22
23 set { this.List[index] = value; }
24
25 }
26
27
28
29 public int IndexOf(T item)
30
31 {
32
33 return this.List.IndexOf(item);
34
35 }
36
37
38
39 public int Add(T item)
40
41 {
42
43 return this.List.Add(item);
44
45 }
46
47
48
49 public void Remove(T item)
50
51 {
52
53 this.List.Remove(item);
54
55 }
56
57
58
59 public void CopyTo(Array array, int index)
60
61 {
62
63 this.List.CopyTo(array, index);
64
65 }
66
67
68
69 public void AddRange(Collection collection)
70
71 {
72
73 for (int i = 0; i < collection.Count; i++)
74
75 {
76
77 this.List.Add(collection[i]);
78
79 }
80
81 }
82
83 public void AddRange(T[] collection)
84
85 {
86
87 this.AddRange(collection);
88
89 }
90
91 Contains(T item)
92
93 {
94
95 return this.List.Contains(item);
96
97 }
98
99 public void Insert(int index, T item)
100
101 {
102
103 this.List.Insert(index, item);
104
105 }
106
107 }
C# generics
1 Stack
2
3 Dictionary
4
5 public void MyGenericMethod(T x, T y)
6
7 {
8
9 Console.Writeline("Parameters type is {0}", typeof(T));
10
11 }
pound sign (#) in href attribute
1 Test
expressions + ) turning into BLOCKED EXPRESSION
CTE (common table expressions)
Pasting from Word
Please my dear Aunt Sally
Terri Morton
ASP.NET Website Manager, Neudesic
Comments
Post a Comment