在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.创建聊天页,效果图及页面元素说明如下:
 

  • Div tag for Messages: 蓝色的Div Tag是聊天室的主要部分,显示聊天信息
  • TextBox: 输入你想说的话.
  • Send Button: 向聊天室发送你刚才输入的话.
  • Div tag for Chatters: 棕色的Div Tag是显示聊天室内的成员.
  • Log-out Button: 登出目前的聊天室.
  • Update Panel: VS2008已经集成了MS AJAX了,所以这个控件就不想说它在这起什么作用了.
  • Timer Control: 这个控件主要是为了更新聊天室的内容,每隔7秒刷新一次内容.

    现在,界面上的完成了,该进入代码阶段了

    首先关注下,整个聊天室的工作流程

    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.完成上叙代码,现在当你进入聊天室后,系统会把你进入聊天室的消息通知给该聊天室的当前用户。

    接着,就是发送聊天信息:

    1. 上文提到用Textbox来输入聊天信息,要控制每次发送消息后,光标位置都在TextBox上,需要在两个位置进行控制,代码如下:
      按钮"Send"点击事件:
         58 ScriptManager1.SetFocus(txtMessage.ClientID);

      另外还有Timer控件的监听事件: 
         68 ScriptManager1.SetFocus(txtMessage);


    2. 当然,有时候你并不会去点击按钮来发送消息,而是直接敲击键盘的回车键(Enter),那么这个时候你要保持光标定位在TextBox上,怎么办呢?代码如下:

      <form id="form1" defaultbutton="btnSend" defaultfocus="txtMessage" runat="server" >

    3. 当你发送一条消息,需要考虑一些问题,列表如下
      • 聊天消息是如何被记录到当前聊天信息区域的.
      • 如何获取已发送的消息.
      • 用户的性别及消息怎样可以一并显示在聊天信息区域. 
      • 其他用户是如何接收到你的状态及消息的.
      • 用户可以查看当前参与的用户(显示除自己以外,所有该聊天室的成员). 为了便于私聊,每个用户是带链接的,点击选择该用户可以进入私聊窗口,但是目前这不是我们考虑,预计在下一个例子里我们会实现该功能.
    4. 由于聊天消息不断增多,所以在聊天信息区域引入滚动条,并控制滚动条始终听聊在该区域的底部,代码如下:

      首先是body:

      <body style="background-color: gainsboro;" onload="SetScrollPosition()" onunload="LogMeOut()">

      控制代码:

      function SetScrollPosition()

      {

            var div = document.getElementById('divMessages');

            div.scrollTop = 100000000000;

      }

    登出聊天室:

    想退出聊天室,要么点退出按钮,要么直接关了浏览器,^_^

    1. 点退出按钮: 当用户点击退出时,首先从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         }


    2. 关闭浏览器: 大多数用户可能不会老实的点退出按钮,而是直接叉掉这个浏览器窗口. 我们可以在客户端捕捉到这个事件, 只需要一点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);

    结语:
    老外的文章还是不怎么好翻译,要保证翻译后的文章达到信,达,雅这个高度太难了。不过因为好玩把这篇文章坚持看完,收获还是不小。原文最后还有一些话没翻译,如果想看,点击本文开头就可以找到原文。我就偷下懒了~
    代码

  • posted @ 2008-10-14 19:15  KiNg.JiOnG  阅读(2494)  评论(1编辑  收藏  举报
    查看博客访问人数(点击):