ASP.NET Ajax 聊天应用程序






4.15/5 (22投票s)
使用 Ajax 和 LINQ to XML 的聊天室页面。
引言
我开发了一个简单的聊天 Web 应用程序,它使用 XML 文件来存储有关在线聊天者的信息,使用了 ASP.NET、C#、LINQ to XML、Ajax -Anthem Framework-。 
 
技术
该应用程序包含一个类,该类通过一些方法来处理 XML 文件,以创建 XML 或加载它,保存信息、修改它或删除它。 该类的 public 方法是
void Join(string userName, DateTime dateTime)
void Say(string userName, string msg, DateTime dateTime)
void Leave(string userName, DateTime dateTime)
List< string > GetOnlineNames()
int GetNumberOfOnlines()
List< string > GetMessagesHistory()
客户端调用 (使用 Ajax 异步地) 这些方法 Join()、Say()、Leave()。
服务器端的一个定时器每隔一段时间(在我们的应用程序中为 2 秒)触发一次,读取 XML(通过调用这些方法 GetOnlineNames()、GetNumberOfOnlines()、GetMessagesHistory())并刷新我们的控件(异步地)。
代码
从我们的 XHandle.cs 类开始,它就像一个聊天室,让我们看看它的成员
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;
public class XHandle
{
    private string _path;
    private string _xPageName;
    private string _FullPath;
    private XDocument _xDoc;
    private bool _xDocCreated;
    private XElement _usersRoot;
    private int _onlineUsers;
    ....
}
构造函数的工作是
了解 App_Data 文件夹的路径 (_path) 和 XML 文件的名称 (_xPageName),以设置我们稍后将使用的 _FullPath。
检查 XML 文件是否已经创建,如果未创建,则创建它。
public XHandle(string _path)
    {
        this._path = _path;
        this._xDocCreated = false;
        this._xPageName = "Default";
        this._FullPath = this._path + @"\" + this._xPageName + ".xml";
        this._onlineUsers = 0;
        //Check the XML page if already exists
        LoadXPage();
        //or create it if it doesnt
        if (!_xDocCreated)
        {
            CreateXPage();
        }
    }
    public XHandle(string _path, string ChatRoomName)
    {
        this._path = _path;
        this._xDocCreated = false;
        this._xPageName = ChatRoomName;
        this._FullPath = this._path + @"\" + this._xPageName + ".xml";
        //Check the XML page if already exists
        LoadXPage();
        //or create it if it doesnt
        if (!_xDocCreated)
        {
            CreateXPage();
        }
    }
私有方法
LoadXPage() 和 CreateXPage() 是 private 方法,用于加载或创建 XML 文件并将 _xDocCreated 布尔值设置为 true 或 false。
    private void CreateXPage()
    {
        _xDoc = new XDocument();
        XDeclaration dec = new XDeclaration("1.0", "utf-8", "yes");
        _xDoc.Declaration = dec;
        _usersRoot = new XElement("users");
        _xDoc.Add(_usersRoot);
        _xDoc.Save(_FullPath);
        _xDocCreated = true;
    }
    private void LoadXPage()
    {
        try
        {
            _usersRoot = XElement.Load(_FullPath);
            _xDocCreated = true;
        }
        catch
        {
            _xDocCreated = false;
        }
    }
创建 XML 文件将得到以下结果
 
 
公共方法
当用户加入聊天时,我们将他们的信息保存为姓名、消息和日期时间,当用户说些什么时,我们也将其信息保存为姓名、消息和日期时间,当用户离开时,我们只删除与其姓名对应的任何信息。
这是一个名为 Ahmed 的用户加入聊天时的 XML 文件 
 
这是一个名为 Ahmed 的用户说 hello 时的 XML 文件 
 
最后,这是用户离开时的 XML 文件 
 
三个方法 Join(..)、void Say(..)、void Leave(..)
    public void Say(string userName, string msg, DateTime dateTime)
    {
        XElement user = new XElement("user");
        XElement elementName = new XElement("name", userName);
        XElement elementLastMsg = new XElement("message", msg);
        XElement elementDate = new XElement("date", dateTime.ToString());
        user.Add(elementName);
        user.Add(elementLastMsg);
        user.Add(elementDate);
        _usersRoot.Add(user);
        _usersRoot.Save(_FullPath);
    }
    public void Join(string userName, DateTime dateTime)
    {
        string systemMsg = userName + " joined chat room.";
        this.Say(userName, systemMsg, dateTime);
    }
    public void Leave(string userName, DateTime dateTime)
    {
        var user = from o in _usersRoot.Elements("user")
                   where (string)o.Element("name").Value == userName
                   select o;
        user.Remove();
        _usersRoot.Save(_FullPath);
    }
其他的 public 方法是获取姓名、所有在线聊天者数量以及消息历史记录的方法。
    public List< string> GetOnlineNames()
    {
        List< string> names = new List< string>();
        var users = (from o in _usersRoot.Elements("user")
                    select o).Distinct();
        foreach (var user in users)
        {
            if (!names.Contains(user.Element("name").Value))
            {
                names.Add(user.Element("name").Value);
            }
        }
        _onlineUsers = names.Count;
        return names;
    }
    public int GetNumberOfOnlines()
    {
        var users = (from o in _usersRoot.Elements("user")
                     select o).Distinct();
        List< string> names = new List< string>();
        foreach (var user in users)
        {
            //Filter the names to avoid duplicates
            if (!names.Contains(user.Element("name").Value))
            {
                names.Add(user.Element("name").Value);
            }
        }
        if (names.Count > 0)
        {
            _onlineUsers = names.Count;
            return names.Count;
        }
        _onlineUsers = 0;
        return 0;
    }
    public List< string> GetMessagesHistory()
    {
        List< string> messages = new List< string>();
        var users = (from o in _usersRoot.Elements("user")
                     where o.Element("message").Value != string.Empty
                     orderby DateTime.Parse(o.Element("date").Value) ascending
                     select o).Distinct();
        foreach (var user in users)
        {
            string fullString = user.Element("name").Value +
				" : " + user.Element("message").Value;
            if (!messages.Contains(fullString))
            {
                messages.Add(fullString);
            }
        }
        return messages;
    }
好了,现在我们完成了我们的类,我们有能力获取在线姓名、他们的数量和消息历史记录(这意味着如果有人在 x 时间开始聊天,而另一个人在 x 时间 30 分钟后加入聊天,则后者将获得自聊天开始以来的消息历史记录...)。
移动到 ChatRoom.aspx
 
 
在 Page_Load 事件中,我们将
- 注册页面到 Anthem Manager,以允许从客户端脚本调用它。
- 获取 XHandle类的一个实例。
- 如果存在在线聊天者,则获取其数量。
- 设置一个固定时间(可能是 2 秒)作为计时器间隔。
- 订阅 Tick 事件。
- 将刷新控件方法附加到委托,以便我们可以异步调用它们。
public partial class ChatRoom : System.Web.UI.Page
{
    XHandle xmll;
    List< string> names;
    List< string> msgs;
    private delegate void AsyncCallingNames();
    private delegate void AsyncCallingMessages();
    AsyncCallingNames callerNames;
    AsyncCallingMessages callerMessages;
    protected void Page_Load(object sender, EventArgs e)
    {
        Anthem.Manager.Register(this);
        xmll = new XHandle(Server.MapPath("App_Data"), "FirstChatRoom");
        //Get chat room name from user or chat admin
        int x = xmll.GetNumberOfOnlines();
        if (Session["userName"] != null)
        {
            LabelError.Text = "Online, Users Online: " + x.ToString();
        }
        else
        {
            LabelError.Text = "Offline, Users Online: " + x.ToString();
        }
        Timer1.Interval = 2;
        //I set it to 1 second, and it worked well
        Timer1.Tick += new EventHandler(Timer1_Tick);
        callerNames = new AsyncCallingNames(this.RefreshListNames);
        callerMessages = new AsyncCallingMessages(this.RefreshMessages);
    }
    .....
}
现在我们有两项工作
- 加入、说和离开,这通过以下方式完成- 加入按钮点击事件处理程序
- 发送按钮点击事件处理程序
- onunload 客户端脚本事件处理程序
 protected void ButtonJoin_Click(object sender, EventArgs e) { if (Session["userName"] == null) { Session["userName"] = TextBoxName.Text.ToString(); xmll.Join(Session["userName"].ToString(), DateTime.Now); TextBoxName.Enabled = false; Timer1.StartTimer(); TextBoxType2.Focus(); } } protected void ButtonSend_Click(object sender, EventArgs e) { if (Session["userName"] != null) { string name = (string)Session["userName"]; string msg = TextBoxType2.Text.ToString(); xmll.Say(name, msg, DateTime.Now); TextBoxType2.Text = ""; TextBoxType2.Focus(); } else { LabelError.Text = "You have to join with a name first.."; } } [Anthem.Method] public void Leave() { Timer1.StopTimer(); if (Session["userName"] != null) { string name = (string)Session["userName"]; xmll.Leave(name, DateTime.Now); LabelError.Text = "Offline"; } }
- 在计时器 tick 事件处理程序中刷新我们的控件- 聊天者姓名的 ListBox
- 消息的 ListBox
 void Timer1_Tick(object sender, EventArgs e) { if (Session["userName"] != null) { IAsyncResult resultN = callerNames.BeginInvoke(null, null); if (!resultN.IsCompleted) { callerNames.EndInvoke(resultN); } IAsyncResult resultM = callerMessages.BeginInvoke(null, null); if (!resultM.IsCompleted) { callerMessages.EndInvoke(resultM); } TextBoxType2.Focus(); } else { Timer1.StopTimer(); TextBoxType2.Text = "You have to join with a name first.."; } } private void RefreshListNames() { ListBox1.Items.Clear(); names = xmll.GetOnlineNames(); foreach (var name in names) { ListBox1.Items.Add(name); } } private void RefreshMessages() { ListBox2.Items.Clear(); msgs = xmll.GetMessagesHistory(); foreach (var msg in msgs) { ListBox2.Items.Add(msg); } }
- 聊天者姓名的 
Leave()
最后一件事是如何从客户端脚本调用 Leave() 方法
< body  önunload="Leave(); return false;">
< script type="text/javascript">
function Leave()
{
    Anthem_InvokePageMethod('Leave', null, null);
}
< /script>
报告
我将非常感谢您尝试此代码并反馈您可能遇到的任何问题,以使此应用程序更好。


