在ASP.NET3.5下利用Linq,Ajax创建一个线上网络聊天室【转】
原文:Build a Web Chat Application using ASP.Net 3.5, LINQ and AJAX
英文水平有限,也不想用Google或者Yahoo或者其他翻译工具去逐句翻译,按俺自己的理解去写,希望不要偏离原文的意思(老天保佑~)
创建一个线上聊天室,其实蛮好玩的,又用了感兴趣的Linq,所以才把这篇文章抄到这~。
开发环境:ASP.Net 3.5, AJAX, JavaScript, C# 3.5, LINQ-to-SQL, MS SQL Server 2000/2005
聊天室的界面就是这个样子:
目标:
1)好玩,不好玩就不会做这个东东
2)聊天室不需要安装什么就可以登录聊天
3)页面无刷新
4)信息可以保存起来,下次登录还能看到聊天记录
5)使用Linq to Sql来代替存储过程,提高开发速度
6)聊天室不需要太复杂的功能,一个登录页,和一个聊天页就可以了,不想太复杂
好了,Go,GO!
1.在MS SQL Server 2000/2005上创建聊天室数据库,然后建一个聊天室的项目。数据库表结构及说明如下:
- User: 包含用户的信息. 当然你还可以给User添加诸如 address, city, 获者其他一些字段.
- Message: 记录用户的聊天信息.
- Room: 记录聊天室信息. 一个用户可以进入不同的聊天室. 老外为了省事,下面的代码里,假定用户只有一个聊天室.
- LoggedInUsers: 记录用户登录及聊天的一些信息。借助这个表,我们可以获得用户在一个指定的聊天室的一些信息.
2.打开VS2008,创建一个.NET Framework 3.5的ASP.NET网站。开发语言是:C#
3.建一个登录页,注册页以及登录用户和密码的加密就免了,Just for fun!不考虑那么多了。
4.创建聊天页,效果图及页面元素说明如下:
现在,界面上的完成了,该进入代码阶段了
首先关注下,整个聊天室的工作流程:
1.首先,打开web.config,加入用户验证的东东
50 <authentication mode="Forms">
51 <forms name=".ASPXAUTH" loginUrl="Default.aspx"/>
52 </authentication>
53
54 <authorization>
55 <deny users="?" />
56 </authorization>
2.用户登录后由登录页跳转到聊天页时,用户名及密码用Session来保存。这里在跳转后,由于之前假定用户只有一个聊天室,
因此跳转后,会进入聊天室为"room1"的聊天页。如果存在多个聊天室,那么你可以再加一个聊天室的列表页,让用户进入到 这里,由他选择进入哪个聊天室。登录的代码如下:
13 protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
14 {
15 LinqChatDataContext db = new LinqChatDataContext();
16
17 var user = (from u in db.Users
18 where u.Username == Login1.UserName
19 && u.Password == Login1.Password
20 select u).SingleOrDefault();
21
22 if (user != null)
23 {
24 e.Authenticated = true;
25 Session["ChatUserID"] = user.UserID;
26 Session["ChatUsername"] = user.Username;
27 }
28 else
29 {
30 e.Authenticated = false;
31 }
32 }
33
34 protected void Login1_LoggedIn(object sender, EventArgs e)
35 {
36 Response.Redirect("Chatroom.aspx?roomId=1");
37 }
3.根据接收到的roomId的值,来确定聊天室的名字,已经聊天室的相关信息,比如成员,聊天记录等等
16 // for simplity's sake we're going to assume that a
17 // roomId was passed in the query string and that
18 // it is an integer
19 // note: in reality you would check if the roomId is empty
20 // and is an integer
21 string roomId = (string)Request["roomId"];
22 lblRoomId.Text = roomId;
4.为了通知其他用户,该用户已进入聊天室,所以建立了LoggedInUser 来记录聊天室的一些信息。通过
这个表,我们可以获取聊天室的所有成员及信息。这个操作的代码如下:
26 this.InsertMessage(ConfigurationManager.AppSettings["ChatLoggedInText"] + " " + DateTime.Now.ToString());
5.InsertMessage的具体代码如下:
74 private void InsertMessage(string text)
75 {
76 LinqChatDataContext db = new LinqChatDataContext();
77
78 Message message = new Message();
79 message.RoomID = Convert.ToInt32(lblRoomId.Text);
80 message.UserID= Convert.ToInt32(Session["ChatUserID"]);
81
82 if (String.IsNullOrEmpty(text))
83 {
84 message.Text = txtMessage.Text.Replace("<", "");
85 message.Color = ddlColor.SelectedValue;
86 }
87 else
88 {
89 message.Text = text;
90 message.Color = "gray";
91 }
92
93 message.ToUserID = null; // in the future, we will use this value for private messages
94 message.TimeStamp = DateTime.Now;
95 db.Messages.InsertOnSubmit(message);
96 db.SubmitChanges();
97 }
同时在web.config里加入以下代码:
24 <appSettings>
25 <add key="ChatLoggedInText" value="Just logged in!"/>
26 </appSettings>
6.完成了新增Message记录后,接下来是完成显示Message的功能了:
148 /// <summary>
149 /// Get the last 20 messages for this room
150 /// </summary>
151 private void GetMessages() 152 {
153 LinqChatDataContext db = new LinqChatDataContext(); 154
155 var messages = (from m in db.Messages
156 where m.RoomID == Convert.ToInt32(lblRoomId.Text)
157 orderby m.TimeStamp descending
158 select m).Take(20).OrderBy(m => m.TimeStamp);
159
160 if (messages != null)
161 {
162 StringBuilder sb = new StringBuilder();
163 int ctr = 0; // toggle counter for alternating color
164
165 foreach (var message in messages)
166 {
167 // alternate background color on messages
168 if (ctr == 0)
169 {
170 sb.Append("<div style='padding: 10px;'>");
171 ctr = 1;
172 }
173 else
174 {
175 sb.Append("<div style='background-color: #EFEFEF; padding: 10px;'>");
176 ctr = 0;
177 }
178
179 if (message.User.Sex.ToString().ToLower() == "m")
180 sb.Append("<img src='Images/manIcon.gif' style='vertical-align:middle' alt=''> " + message.Text + "</div>");
181 else
182 sb.Append("<img src='Images/womanIcon.gif' style='vertical-align:middle' alt=''> " + message.Text + "</div>");
183 }
184
185 litMessages.Text = sb.ToString();
186 }
187 }
7.当用户登录该聊天室时,右侧的用户列表区必须显示当前聊天室参与的的用户。当你选择某位用户时,你可以和他私聊。
99 private void GetLoggedInUsers()
100 {
101 LinqChatDataContext db = new LinqChatDataContext();
102
103 // let's check if this authenticated user exist in the
104 // LoggedInUser table (means user is logged-in to this room)
105 var user = (from u in db.LoggedInUsers
106 where u.UserID == Convert.ToInt32(Session["ChatUserID"])
107 && u.RoomID == Convert.ToInt32(lblRoomId.Text)
108 select u).SingleOrDefault();
109
110 // if user does not exist in the LoggedInUser table
111 // then let's add/insert the user to the table
112 if (user == null)
113 {
114 LoggedInUser loggedInUser = new LoggedInUser();
115 loggedInUser.UserID = Convert.ToInt32(Session["ChatUserID"]);
116 loggedInUser.RoomID = Convert.ToInt32(lblRoomId.Text);
117 db.LoggedInUsers.InsertOnSubmit(loggedInUser);
118 db.SubmitChanges();
119 }
120
121 string userIcon;
122 StringBuilder sb = new StringBuilder();
123
124 // get all logged in users to this room
125 var loggedInUsers = from l in db.LoggedInUsers
126 where l.RoomID == Convert.ToInt32(lblRoomId.Text)
127 select l;
128
129 // list all logged in chat users in the user list
130 foreach (var loggedInUser in loggedInUsers)
131 {
132 // show user icon based on sex
133 if (loggedInUser.User.Sex.ToString().ToLower() == "m")
134 userIcon = "<img src='Images/manIcon.gif' style='vertical-align:middle' alt=''> ";
135 else
136 userIcon = "<img src='Images/womanIcon.gif' style='vertical-align:middle' alt=''> ";
137
138 if (loggedInUser.User.Username != (string)Session["ChatUsername"])
139 sb.Append(userIcon + "<a href=#>" + loggedInUser.User.Username + "</a><br>");
140 else
141 sb.Append(userIcon + "<b>" + loggedInUser.User.Username + "</b><br>");
142 }
143
144 // holds the names of the users shown in the chatroom
145 litUsers.Text = sb.ToString();
146 }
8.完成上叙代码,现在当你进入聊天室后,系统会把你进入聊天室的消息通知给该聊天室的当前用户。
接着,就是发送聊天信息:
- 上文提到用Textbox来输入聊天信息,要控制每次发送消息后,光标位置都在TextBox上,需要在两个位置进行控制,代码如下:
按钮"Send"点击事件:
58 ScriptManager1.SetFocus(txtMessage.ClientID);
另外还有Timer控件的监听事件:
68 ScriptManager1.SetFocus(txtMessage);
- 当然,有时候你并不会去点击按钮来发送消息,而是直接敲击键盘的回车键(Enter),那么这个时候你要保持光标定位在TextBox上,怎么办呢?代码如下:
<form id="form1" defaultbutton="btnSend" defaultfocus="txtMessage" runat="server" >
- 当你发送一条消息,需要考虑一些问题,列表如下
- 聊天消息是如何被记录到当前聊天信息区域的.
- 如何获取已发送的消息.
- 用户的性别及消息怎样可以一并显示在聊天信息区域.
- 其他用户是如何接收到你的状态及消息的.
- 用户可以查看当前参与的用户(显示除自己以外,所有该聊天室的成员). 为了便于私聊,每个用户是带链接的,点击选择该用户可以进入私聊窗口,但是目前这不是我们考虑,预计在下一个例子里我们会实现该功能.
- 由于聊天消息不断增多,所以在聊天信息区域引入滚动条,并控制滚动条始终听聊在该区域的底部,代码如下:
首先是body:
<body style="background-color: gainsboro;" onload="SetScrollPosition()" onunload="LogMeOut()">
控制代码:
function SetScrollPosition()
{
var div = document.getElementById('divMessages');
div.scrollTop = 100000000000;
}
登出聊天室:
想退出聊天室,要么点退出按钮,要么直接关了浏览器,^_^
- 点退出按钮: 当用户点击退出时,首先从LoggedInUser将该用户删除,然后向聊天消息区域发送信息通知其他用户,该用户下线。
189 protected void BtnLogOut_Click(object sender, EventArgs e)
190 {
191 // log out the user by deleting from the LoggedInUser table
192 LinqChatDataContext db = new LinqChatDataContext();
193
194 var loggedInUser = (from l in db.LoggedInUsers
195 where l.UserID == Convert.ToInt32(Session["ChatUserID"])
196 && l.RoomID == Convert.ToInt32(lblRoomId.Text)
197 select l).SingleOrDefault();
198
199 db.LoggedInUsers.DeleteOnSubmit(loggedInUser);
200 db.SubmitChanges();
201
202 // insert a message that this user has logged out
203 this.InsertMessage("Just logged out! " + DateTime.Now.ToString());
204
205 // clean the session
206 Session.RemoveAll();
207 Session.Abandon();
208
209 // redirect the user to the login page
210 Response.Redirect("Default.aspx");
211 }
- 关闭浏览器: 大多数用户可能不会老实的点退出按钮,而是直接叉掉这个浏览器窗口. 我们可以在客户端捕捉到这个事件, 只需要一点html代码和JavaScript脚本就ok了,代码如下
<body style="background-color: gainsboro;" onload="SetScrollPosition()" onunload="LogMeOut()">
JavaScript脚本:function LogMeOut()
{
LogOutUserCallBack();
}
当然,我们还可以从服务端捕捉该事件,从LoggedInUser 删除该用户信息,利用ICallbackEventHandler接口,异步完成该操作
8 public partial class Chatroom : System.Web.UI.Page, System.Web.UI.ICallbackEventHandler
利用ICallbackEventHandler提供的方法完成以下两步操作. "RaiseCallbackEvent" 用于处理以控件为目标的回调事件. 下面是利用该方法,删除用户的LoggedInUsers表的信息.
220 void System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
221 {
222 _callBackStatus = "failed";
223
224 // log out the user by deleting from the LoggedInUser table
225 LinqChatDataContext db = new LinqChatDataContext();
226
227 var loggedInUser = (from l in db.LoggedInUsers
228 where l.UserID == Convert.ToInt32(Session["ChatUserID"])
229 && l.RoomID == Convert.ToInt32(lblRoomId.Text)
230 select l).SingleOrDefault();
231
232 db.LoggedInUsers.DeleteOnSubmit(loggedInUser);
233 db.SubmitChanges();
234
235 // insert a message that this user has logged out
236 this.InsertMessage("Just logged out! " + DateTime.Now.ToString());
237
238 _callBackStatus = "success";
239 }
"GetCallbackResult" 用于获取返回以控件为目标的回调事件的结果. 我们可以声明一个变量来获取该方法返回的处理结果,这里我们对变量赋予"success" 或 "failed" 来标明当前事件的处理结果. 代码如下:
215 string System.Web.UI.ICallbackEventHandler.GetCallbackResult()
216 {
217 return _callBackStatus;
218 }
接下来,我们在Page_Load方法里注册一个脚本事件,以完成这个异步的事件处理.
29 // create a call back reference so we can log-out user when user closes the browser
30 string callBackReference = Page.ClientScript.GetCallbackEventReference(this, "arg", "LogOutUser", "");
31 string logOutUserCallBackScript = "function LogOutUserCallBack(arg, context) { " + callBackReference + "; }";
32 Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "LogOutUserCallBack", logOutUserCallBackScript, true);
结语:
老外的文章还是不怎么好翻译,要保证翻译后的文章达到信,达,雅这个高度太难了。不过因为好玩把这篇文章坚持看完,收获还是不小。原文最后还有一些话没翻译,如果想看,点击本文开头就可以找到原文。我就偷下懒了~
代码