Introduction
This article will present you with a Web chatting application, which is based on the famous open source AjaxPro.NET library. Here, we will establish ourselves a nice little multi-user chat application, much like the famous MSN Messenger. We will build the application heavily on JavaScript programming (which is decided by the AjaxPro.NET Framework which supports accessing .NET library through the client-side JavaScript way) under ASP.NET 2.0 and SQL Server Express Edition environments.
Testing Environment
- Windows XP Professional (with IIS installed)
- Visual Studio 2005 (plus SQL Server 2005 Express Edition)
- AjaxPro.NET Framework
Also, you are to be familiar with ASP.NET 2.0 C# programming, SQL Server programming and JavaScript+ DOM. To follow along with the examples, you should download the source files accompanying this article (see the download URL given at the end of the article).
The sample project named AjaxProChat in this article will achieve the listed basic goals.
- View the on-line chatting friends in real-time
- Send instantaneous messages to the on-line friends
- Receive messages in real-time and pop up a friendly MSN-like hint window
- Switch between the different states—online and offline
Thus, we can easily describe the general flow chart of this chatting application, as shown in Figure 1.
Figure 1
No doubt that the key lies in the real-time features—state switching in real-time, sending and receiving messages instantaneously, etc. There are many difficulties in tackling all these merely with the traditional web techniques, but now AJAX comes to rescue— all the troubles can be whiled away. Let us now roll up our sleeves by beginning with the database design.
Database Design
With the general goals introduced above, we can now shift our attention to the database design.
In this case, we will design a database named AjaxProChat, which contains three tables—users for holding users info, message for the detailed chatting info, and global_info for recording the changing times in tableusers.
Designing the Tables
Since the source code file with this article provides a whole database, I will omit the relevant creating-table SQL scripts. So, let us directly delve into the table structures. The following three tables give their corresponding structure definitions and field notes, respectively.
Table 1: Structure for Table Users
Field name |
Type |
Notes |
username |
varchar(50) |
NOT NULL, Primary KeyThe login user name |
nickname |
varchar(50) |
Can be nullThe nickname associated with the user name defined above |
password |
varchar(50) |
NOT NULL |
status |
int |
NOT NULL, DEFAULT (0)The user login status—0: offline; 1: online; 2: hidden |
logintime |
datetime |
Can be NULLThe datetime of the user logging in the last time |
logouttime |
datetime |
Can be NULLThe datetime of the user logging out the last time |
last_msg_id |
int |
NOT NULL, DEFAULT (0)The last received message id of the user |
Table 2: Structure for Table Message
Field name |
Type |
Notes |
msg_id |
int |
IDENTITY (1,1) NOT NULL, Primary KeyThe auto incremental message id |
sender |
varchar(50) |
NOT NULLThe user name of the sender |
receiver |
varchar(50) |
NOT NULLThe user name of the receiver |
content |
varchar(255) |
NOT NULLThe corresponding chatting content |
sendtime |
datetime |
NOT NULLThe datetime of sending the message |
Table 3: Structure for Table global_info
Field name |
Type |
Notes |
UserChanged |
int |
NOT NULL, DEFAULT (0) |
Besides these tables, we have also defined a trigger in table users. This means that whenever there are appending, deleting or updating with the records in it, the trigger should be invoked to add 1 to the fieldUserChanged in table global_info.
Designing the Stored Procedures
To improve the efficiency and simplify the database access, we design five stored procedures inside databaseAjaxProChat. Below we enumerate them and their corresponding functions:
- GetNewMessage—Fetch the most recent messages which accord with specified conditions
- GetRecentMsg—Return the chatting info of the two specified most recent users
- SendMessage—Save the sent messages (the sent and received ones) as new records into the database
- UserLogin—Log into the system—return zero if successful, otherwise return 1
- UserLogout—Perform the inverse action of UserLogin
Since you will begin a long journey and I have provided the commented source code for you to download, we will not list their SQL scripts herein any more. But with the story going on, you will quickly conclude that nearly all the .aspx pages are filled with the following call route as depicted in Figure 2.
Figure 2
Detailing the Solution
Before starting to put AjaxPro.NET into action, let me give you a brief introduction.
AjaxPro.NET is a well-known open source framework, which is based on the server-side techniques and provides support for constructing different versions of .NET Web applications. The framework supports the server-side .NET SDK by a few means via the client-side JavaScript. It can direct the JavaScript requests to the related server-side .NET methods and then the server returns the newly-generated special JavaScript to the browser. Its main functions are listed as below:
- Access the Session and Application data from the client-side JavaScript
- Cache the required results
- Freely use source code
- Add and modify new methods and properties in the framework without modifying any source code
- All the classes support the client-side JavaScript to return data, and DataSet can be used from inside the JavaScript
- Use the HTML controls to access and return data
- Do not need reload the pages and use the event delegate to access data n
- Supply only one method for call and dramatically decrease the CPU usage
Now, to use AjaxPro.NET in our ASP.NET 2.0 web applications we should properly configure the web.configfile. Since the downloaded materials have been shipped with a guide and an excellent Visual Studio Add-in—AjaxProVSTemplate.vsi, we will not talk much about the usage of this framework, but focus on the main topic.
In this demo application we mainly provide three web pages—login.aspx, main.aspx, and chatroom.aspx. There is, however, a small trick worthy to be mentioned first.
Index.html—a broker
To hide the browser's menu bar and toolbars in our chatting pages, we use some tricks -building a temporary broker page index.html. The client-side JavaScript in Listing 1 below shows the how-to.
Listing 1
<script language="javascript">
window.open("Login.aspx", "_blank", "location=no;menubar=no;status=no;toolbar=no");
Close();
function Close()
{
var ua = navigator.userAgent;
var ie = navigator.appName == "Microsoft Internet Explorer" ? true : false;
if (ie)
{
var IEversion = parseFloat(ua.substring(ua.indexOf("MSIE ") + 5, ua.indexOf
(";", ua.indexOf("MSIE "))))if (IEversion < 5.5)
{
var str = '<object id=noTipClose classid=
"clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11">';
str += '<param name="Command" value="Close"></object>';
document.body.insertAdjacentHTML("beforeEnd", str);
document.all.noTipClose.Click();
}
else
{
window.opener = null;
window.close();
}
}
else
{
window.close()
}
}
</script>
Login.aspx
Next, comes up the normal login page, login.aspx, and Figure 3 shows you the first impress.
Figure 3
This web page is the entry point of this web-versioned application, which merely calls the stored procedureUserLogin described before so as to accomplish logging into the system. This procedure is a typical one and we will not depict it, but only one thing should be noted: If the user name entered is not empty and there is not an old one inside the database, then we will simply store it whether its relevant password is empty or not. For more details about this please check out the stored procedure's SQL scripts. So next, we will move to the key code in the file login.aspx.cx.
Listing 2: The Typical ASP.NET 2.0 Server-Side Database Connection Programming for the Login Page
public partial class login: System.Web.UI.Page
{
private string myConnectionString = ConfigurationManager.ConnectionStrings[
"msn_Data_ConnStr"].ConnectionString;
protected System.Data.SqlClient.SqlConnection sqlConnection;
protected System.Data.SqlClient.SqlCommand sqlCommand;
protected void Page_Load(object sender, EventArgs e){}
protected override void OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
}
private void InitializeComponent()
{
this.sqlConnection = new SqlConnection(myConnectionString);
this.sqlCommand = new System.Data.SqlClient.SqlCommand();
this.sqlCommand.CommandText = "dbo.[UserLogin]";
this.sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
this.sqlCommand.Connection = this.sqlConnection;
this.sqlCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter(
"@RETURN_VALUE", System.Data.SqlDbType.Int, 4,
System.Data.ParameterDirection.ReturnValue, false, ((System.Byte)(0)), (
(System.Byte)(0)), "", System.Data.DataRowVersion.Current, null));
this.sqlCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter(
"@username", System.Data.SqlDbType.VarChar, 50));
this.sqlCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter(
"@password", System.Data.SqlDbType.VarChar, 50));
this.sqlCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter(
"@status", System.Data.SqlDbType.Int, 4));
}
protected void btnLogin_Click(object sender, EventArgs e)
{
int iRet = - 1;
sqlCommand.Parameters["@username"].Value = tbUsername.Text;
sqlCommand.Parameters["@password"].Value = tbPassword.Text;
sqlCommand.Parameters["@status"].Value = Convert.ToInt32
(ddlStatus.SelectedValue);
try
{
sqlConnection.Open();
sqlCommand.ExecuteNonQuery();
iRet = Convert.ToInt32(sqlCommand.Parameters["@RETURN_VALUE"].Value);
}
catch (SqlException)
{
Response.Write("<script>alert('Here,in SqlException');</script>");
}
finally
{
sqlConnection.Close();
}
if (iRet == 0)
{
FormsAuthentication.RedirectFromLoginPage(tbUsername.Text, false);
Response.Redirect("Main.aspx");
}
if (iRet == 2)
{
FormsAuthentication.RedirectFromLoginPage(tbUsername.Text, false);
Response.Redirect("Main.aspx");
}
else
{
lblMessage.Text = "iRet =" + iRet.ToString() +
"The login failed, please check out your password!";
}
}
}
Main.aspx—The Headquarters
Now, let us pay attention to the main controlling page of the chatting system, main.aspx, whose run-time snapshot is shown in Figure 4.
Figure 4
From the developer's point view, we can divide this part into three different components: login status switching, the users list viewing, and the newest message hinting. So, let us dig into their inner workings one by one.
Switching Between the Login States
At the top of the main page lie the current user name and its login status. It is easy for the user to switch between items Online and Offline via the ListBox control. When the user click the Logout button, the user will log off and the control is switched to the initial login page. Now, let us examine the related inner logic. First, we define an Ajax method—SetUserStatus on the server side, which directly updates the user login status in table users according to the passed arguments—strUsername and iStatus. Listing 3 gives the corresponding coding:
Listing 3
[AjaxPro.AjaxMethod]
public int SetUserStatus(string strUsername, int iStatus) {
//hold the returned value
int iRet = 0;
//Obtain the database connection string from the file web.config and set up the connection
string strConnection = ConfigurationManager.ConnectionStrings["msn_Data_ConnStr"].ConnectionString;
SqlConnection conn = new SqlConnection(strConnection);
//create a new SQLCommand object
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = string.Format(
"UPDATE users SET status = {0} WHERE username='{1}'",
iStatus, strUsername
);
try{
//open the data connection
conn.Open();
//execute the SQL command-- set the user's status
iRet = cmd.ExecuteNonQuery();
}
catch (SqlException){}
finally
{
//close the database connection
conn.Close();
}
return iRet;
}
Next comes up the relevant client-side function, setUserStatus, which will call the server-side methodsetUserStatus to update the current user login status, as is shown in Listing 4.
Listing 4: The Client-Side JavaScript Method Calls the Server-Side Ajax One
Collapse Copy Codefunction setUserStatus()
{
var username = el("lblUsername").innerText;
var status = el("user_status").value;
AjaxProChat.SetUserStatus(username, status);
}
As for logoff, this is performed inside the button Logout-related event handler, lblExit_Click, via the routine ASP.NET event response mechanism. Here we leave out the code listing.
Viewing the User List
Note that, in this case, we take the action of inquiring the database on time to list all the on-line users stuff. Therefore, we have to earnestly count the cost between the user real-time characteristics and the server-side load. Obviously, if the interval for inquiring the database is shorter, then the real-time features of the application will be acceptable, which will put a heavier burden on the server's shoulder. However, if the interval is longer, then there is a lighter load on the server and the merciful real-time characteristics on the client side. So, here we use some sleight: in the database we add a sign—the field UsersChanged in the tableglobal_info which is used to record the times table users to be changed. So, in the code we can judge on time whether the sign has been changed to decide whether to send requests to the server side for updating the on-line users list or not. Here, for brevity, we select to omit the simple server-side code while making room for the client-side programming.
Listing 5
Collapse Copy Code
function refreshUserlist()
{
var changed = AjaxProChat.StatusChanged().value;
if (changed > changedTimes)
{
changedTimes = changed;
var arUserlist = AjaxProChat.GetOnlineUserList().value.Tables[0];
var divUserlist = el("userlist");
while (divUserlist.childNodes.length > 0)
{
divUserlist.removeChild(divUserlist.childNodes[0]);
}
for (var i = 0; i < arUserlist.Rows.length; i++)
{
var username = arUserlist.Rows[i].username;
var nickname = arUserlist.Rows[i].nickname;
var result = document.createElement("div");
result.style.cursor = "pointer";
result.style.padding = "2px 0px 2px 0px";
result.onclick = function()
{
if (!sendWindow)
{
window.showModelessDialog("chatroom.aspx?username=" + username,
window, "help:no;unadorned:yes;edge:sunken;resizable:yes;status:no")
;
}
};
var result1 = document.createElement("span");
result1.onmouseover = function()
{
this.style.color = "#205288";
};
result1.onmouseout = function()
{
this.style.color = "#000000";
};
result1.style.textAlign = "left";
result1.style.fontWeight = "bold";
result1.style.fontFamily = "Arial, Verdana";
result1.innerHTML = username + " (" + nickname + ")";
result.appendChild(result1);
divUserlist.appendChild(result);
}
}
}
Since we have added so many comments on the import line, we only sum up the general process: obtain the most recent users list inside the database via the Ajax method—AjaxProChat.GetOnlineUserList(); fill in the user name and related nickname in turn. As is seen from above, we use much subtle JavaScript programming around the DOM object—div and span.
A MSN-like Popping up Window
To conveniently accept the new messages, we have designed a small and friendly MSN-like hint window.
Figure 5: The run-time screenshot of the MSN-like hint window
Page main.aspx is responsible for checking on time whether there have been new messages coming in; if so, this small hint window is to be popped up and the current user can click the link in it to reply. As with the functions of the main page, there are also two steps—both the server side and the client side—needed to accomplish such a prompt action. First, we have defined an Ajax method, mainGetNewMessage, who serves to obtain a DataSet of the newest chatting messages. We can see the typical database connection in ASP.NET 2.0 from Listing 6.
Listing 6: Code for the Server-Side Method mainGetNewMessage
Collapse Copy Code[AjaxPro.AjaxMethod]
public DataSet mainGetNewMessage()
{
DataSet ds = new DataSet();
SqlConnection conn = new SqlConnection
(ConfigurationManager.ConnectionStrings["msn_Data_ConnStr"].ConnectionString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = string.Format("GetNewMessage '{0}'", User.Identity.Name);
SqlDataAdapter da = new SqlDataAdapter(cmd);
try
{
da.Fill(ds);
}
catch (SqlException){}
finally
{
conn.Close();
}
return ds;
}
Since the code above is the common database operation and is provided with so many notations, we will not give unnecessary details any more.
On the client side, there is accordingly defined a method, checkNewMessage, who is to invoke the server-side method, mainGetNewMessage, and will pop up a hint box whenever there comes a new message. Listing 7 shows the related code snippet.
Listing 7: Code for the Client-Side Method checkNewMessage
Collapse Copy Codefunction checkNewMessage()
{
if (!sendWindow)
{
var dt = AjaxProChat.mainGetNewMessage().value.Tables[0];
if (dt.Rows.length > 0)
{
var sender = dt.Rows[0].sender;
var content = DealBrackets(dt.Rows[0].content);
var MSG1 = new CLASS_MSN_MESSAGE("aa", 200, 120, "Hint:", sender +
" says: ", content);
MSG1.oncommand = function()
{
if (!sendWindow)
{
window.showModelessDialog("chatroom.aspx?username=" + sender, window,
"help:no;unadorned:yes;edge:sunken;status:no");
}
};
MSG1.rect(null, null, null, screen.height - 50);
MSG1.speed = 10;
MSG1.step = 5;
MSG1.show();
}
}
}
Here shows the typical flow of calling an Ajax method on the server side from within JavaScript on the client side. As you may have guessed, the true secret for this hint window lies in the following code snippet.
Listing 8
Collapse Copy Codevar MSG1 = new CLASS_MSN_MESSAGE("aa",200,120,"Hint:",sender + " says: ",content);
Inside the downloadable source code, we define a pop up window class in JavaScript in the fileCLASS_MSN_MESSAGE.js. We are creating an instance of the class CLASS_MSN_MESSAGE and the last line above show the pop up window by calling the show method of the instance. For more details please do research into the relevant JavaScript coding.
Chatroom.aspx
Lastly, we come to the meat and potatoes of this Ajax-based chatting application. The really important window to start chatting is page chatroom.aspx. Let us first look at its run-time screenshot in Figure 6.
Figure 6
As is seen from Figure 6, it is rather simple. Most of the page is for chatting with friends and the corresponding messages, with a TEXTBOX control for entering the message to send and a basic Send button at the bottom line. You may have already guessed the similar JavaScript programming skill is used for arranging the <div> object and its inner sub items.
For easier comprehension, we have also divided the part into three components: examining the recent messages, sending the messages, and receiving the messages. Again, let us analyze them one after the other.
Examining the recent messages
When the chat room window is loaded, there is a specified number of latest chatting messages to be loaded. For this purpose, an AJAX method—GetRecentMsg is defined on the server side which then invokes the stored procedure GetRecentMsg and finally the required items of chat records return. Listing 9 gives the specific coding of method GetRecentMsg.
Listing 9
Collapse Copy Code[AjaxPro.AjaxMethod]
public DataSet GetRecentMsg(string strUsername)
{
DataSet ds = new DataSet();
SqlConnection conn = new SqlConnection
(ConfigurationManager.ConnectionStrings["msn_Data_ConnStr"].ConnectionString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = string.Format("GetRecentMsg '{0}','{1}', {2}",
User.Identity.Name, strUsername, 8);
SqlDataAdapter da = new SqlDataAdapter(cmd);
try
{
da.Fill(ds);
}
catch (SqlException){}
finally
{
conn.Close();
}
return ds;
}
Sending the messages
For this purpose, an AJAX method—SendMessage is defined on the server side which then invokes the stored procedure GetRecentMsg and finally the required items of chat records return. Listing 10 gives the specific coding of method GetRecentMsg.
Listing 10
Collapse Copy Code[AjaxPro.AjaxMethod]
public DataSet GetRecentMsg(string strUsername)
{
DataSet ds = new DataSet();
SqlConnection conn = new SqlConnection
(ConfigurationManager.ConnectionStrings["msn_Data_ConnStr"].ConnectionString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = string.Format("GetRecentMsg '{0}','{1}', {2}",
User.Identity.Name, strUsername, 8);
SqlDataAdapter da = new SqlDataAdapter(cmd);
try
{
da.Fill(ds);
}
catch (SqlException){}
finally
{
conn.Close();
}
return ds;
}
Receiving the messages
Here, certainly should we receive the messages in real time, while the whole process is very much similar to that of the main page, so we do not say more than is needed. But there is still one thing to be noticed, if the chat room window has been open, the new message will be shown directly in it; otherwise there will pop up a small hinting window (see Figure 5) for this new message. This is why the global variable sendWindow is defined inside page main.aspx's client-side JavaScript, as is shown below.
Listing 11: The initial definition of the variable sendWindow
Collapse Copy Code
<script language="javascript">
var sendWindow = false;
var changedTimes = 0;
mainLoop();
Although variable sendWindow is defined inside page main.aspx for identifying whether chat room has been already open, it is also used inside page chatroom.aspx. In connection with the definition above, in pagechatroom.aspx there exist the following code snippets.
Listing 12: Variable sendWindow is also used in page chatroom.aspx
Collapse Copy Code
<body onunload="dialogArguments.sendWindow=false;" onload="dialogArguments.sendWindow=true;">
When page chatroom.aspx is opened for the first time, variable sendWindow is set to true, when functioncheckNewMessage in page main.aspx will not query new messages and the message be displayed directly in the chat room. However, when the web page chatroom.aspx is close, variable sendWindow is set to false, when function checkNewMessage in page main.aspx will invoke the relevant method GetNewMessage in the server side which will inquire new messages via the stored procedure GetNewMessage and finally show the result in the pop up hinting box.
Author's Notes: until now, we have introduced all the web pages and their rough functions in this sample. Due to the strict demands of SQL Server 2005 Express Edition (and of course including SQL Server 2005), I suggest you download and decompress the source code and then configure the database engine with great care. As for the AjaxPro.NET framework, maybe you are a newbie to it, but there is surely noy much difficulty for you in grasping its general usage. Only one thing is to be noticed—I use the latest version of AjaxPro.NET framework in this sample, but it will be ok to follow me with the related schema. Last but not the least, I have tested the demo project under hosted mode as well as under web mode using Microsoft IE. That is all!
Conclusion
OK, in this single article we have achieved our final goal—a simple MSN-like web-based chatting application. On the other hand, as we have seen in this article, only through the prevalent WEB 2.0 technique—AJAX—can this kind of instantaneous software be developed successfully and ultimately put into practical use. And for brevity, there are still quite a few related techniques to be observed thoroughly, such as more strict and practical login approach, far more JavaScript coding to be analyzed, only the surface of what AjaxPro.NET has offered has been scratched, strictly debugging and deployment, etc. Joyfully, this article has just shown you a web 2.0 based demo, so there are still much for you to research into.