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.
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.
Thus, we can easily describe the general flow chart of this chatting application, as shown in 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.
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.
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.
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 |
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 |
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.
To improve the efficiency and simplify the database access, we design five stored procedures inside databaseAjaxProChat. Below we enumerate them and their corresponding functions:
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.
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:
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.
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.
<script language="javascript">
//open a new window
window.open("Login.aspx", "_blank", "location=no;menubar=no;status=no;toolbar=no");
//close the parent window
Close();
// close the window without any prompt
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>
Next, comes up the normal login page, login.aspx, and Figure 3 shows you the first impress.
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.
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; //specify the parameters of the stored procedure 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>"); //merely for debugging purpose } 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!"; } } }
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.
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.
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:
[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.
Collapse Copy Codefunction setUserStatus()
{
//user name
var username = el("lblUsername").innerText;
//status
var status = el("user_status").value;
//call the relevant server-side Ajax method
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.
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.
Collapse Copy Code//update the users list
function refreshUserlist()
{
//get the value of field UsersChanged from inside table users on the server side
var changed = AjaxProChat.StatusChanged().value;
//if table users has changed
if (changed > changedTimes)
{
//write down the current value of field UsersChanged
changedTimes = changed;
//obtain the DataTable object to hold the users info
var arUserlist = AjaxProChat.GetOnlineUserList().value.Tables[0];
//the <div> object to show the users list
var divUserlist = el("userlist");
//remove the old contents
while (divUserlist.childNodes.length > 0)
{
divUserlist.removeChild(divUserlist.childNodes[0]);
}
//show the users list
for (var i = 0; i < arUserlist.Rows.length; i++)
{
//the login user name
var username = arUserlist.Rows[i].username;
//nickname
var nickname = arUserlist.Rows[i].nickname;
//create a <div> object for showing one user message
var result = document.createElement("div");
//set the cursor shape hand-like
result.style.cursor = "pointer";
//inner patches
result.style.padding = "2px 0px 2px 0px";
//mouse click handler--open the chat room window
result.onclick = function()
{
//if the chat room window has not been opened
if (!sendWindow)
{
// open the chat room window--note the passed parameters
window.showModelessDialog("chatroom.aspx?username=" + username,
window, "help:no;unadorned:yes;edge:sunken;resizable:yes;status:no")
;
}
};
//the texts for the user name and the nickname are to be shown inside the <span> object
var result1 = document.createElement("span");
//set the mouse in-and-out effect
result1.onmouseover = function()
{
this.style.color = "#205288";
};
result1.onmouseout = function()
{
this.style.color = "#000000";
};
//set show style
result1.style.textAlign = "left";
result1.style.fontWeight = "bold";
result1.style.fontFamily = "Arial, Verdana";
//show the user name plus the nickname
result1.innerHTML = username + " (" + nickname + ")";
//attach the <div> object to DOM
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.
To conveniently accept the new messages, we have designed a small and friendly 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.
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.
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.
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.
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.
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.
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.
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;
}
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.
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;
}
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.
Collapse Copy Code//……(omitted, inside main.aspx)
<script language="javascript">
//used to mark whether the chat room is open
var sendWindow = false;
//check out whether the user status has been changed
var changedTimes = 0;
//the main procedure
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.
Collapse Copy Code//……(omitted, inside page <span class=Italic>chatroom.aspx</span>)
<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!
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.