ASP.NET在线用户列表精确版——解决用户意外退出在线列表无法及时更新问题
??? 我的大概思路是,給每在線用戶增加一個(gè)RefreshTime屬性,建立一個(gè)負(fù)責(zé)將當(dāng)前用戶的RefreshTime屬性設(shè)置為當(dāng)前時(shí)間的單獨(dú)頁面(Refresh.aspx),然后在系統(tǒng)的主要頁面(也可以是所有頁面)中通過xmlhttp不斷地請(qǐng)求Refresh.aspx頁面,一旦用戶關(guān)閉了與本系統(tǒng)相關(guān)的所有窗口,即以直接關(guān)閉瀏覽器的方式退出系統(tǒng),那么該用戶的RefreshTime屬性便不會(huì)自動(dòng)更新了,我們?cè)僭O(shè)置一個(gè)自動(dòng)刷新的超時(shí)時(shí)間(這個(gè)要比會(huì)話超時(shí)短很多_refreshTimeout),當(dāng)發(fā)現(xiàn)某用戶超過_refreshTimeout的時(shí)間沒有自動(dòng)刷新,就能判定該用戶已經(jīng)以直接關(guān)閉瀏覽器的方式退出了。
??? 假設(shè)我們?cè)O(shè)置會(huì)話超時(shí)時(shí)間為60分鐘,自動(dòng)刷新超時(shí)時(shí)間為1分鐘,在客戶端通過xmlhttp每隔25秒(之所以不設(shè)1分鐘,是防止網(wǎng)速慢的時(shí)候訪問Refresh.aspx超時(shí),個(gè)人感覺,不一定正確)訪問一次Refresh.aspx頁面,在用戶登陸、用戶注銷、檢測(cè)用戶是否在線的時(shí)候都執(zhí)行清理超時(shí)用戶(包括會(huì)話超時(shí)和自動(dòng)刷新超時(shí))操作,這樣一來,在線用戶列表的統(tǒng)計(jì)誤差就由60分鐘降至1分鐘了。
==========================================
?
具體實(shí)現(xiàn)如下:
1、 新建一個(gè)名為ActiveUser的類,存儲(chǔ)單個(gè)活動(dòng)用戶數(shù)據(jù)。
/// <summary>
?/// 單個(gè)在線用戶數(shù)據(jù),無法繼承此類。
?/// </summary>
?public sealed class ActiveUser
?{?
? private readonly string _ticket;??? //票據(jù)名稱
? private readonly string _username;?? //登陸用戶名
? private readonly string _truename;?? //登陸用戶名
? private readonly string _roleid;??? //角色
? private readonly DateTime _refreshtime;? //最新刷新時(shí)間
? private readonly DateTime _activetime;? //最新活動(dòng)時(shí)間
? private readonly string _clientip;?? //登陸IP
?
? public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,string ClientIP) {
?? this._ticket=Ticket;
?? this._username=UserName;
?? this._truename=TrueName;
?? this._roleid=RoleID;
?? this._refreshtime=DateTime.Now;
?? this._activetime=DateTime.Now;
?? this._clientip=ClientIP;
? }
? public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,DateTime RefreshTime,DateTime ActiveTime,string ClientIP)? {
?? this._ticket=Ticket;
?? this._username=UserName;
?? this._truename=TrueName;
?? this._roleid=RoleID;
?? this._refreshtime=RefreshTime;
?? this._activetime=ActiveTime;
?? this._clientip=ClientIP;
? }
?
? public string Ticket? { get{return _ticket;}? }
? public string UserName? { get{return _username;}? }
? public string TrueName? { get{return _truename;}? }
? public string RoleID? { get{return _roleid;}? }
? public DateTime RefreshTime { get{return _refreshtime;} }
? public DateTime ActiveTime { get{return _activetime;} }
? public string ClientIP? { get{return _clientip;}? }
?}
?
2、 新建一個(gè)名為PassPort的類,存儲(chǔ)在線用戶列表。
/// <summary>
?/// PassPort 存儲(chǔ)在線用戶列表。
?/// </summary>
?public class PassPort
?{
? private? static? DataTable? _activeusers;
? private? int? _activeTimeout;
? private? int? _refreshTimeout;
? /// <summary>
? /// 初始化在線用戶表。
? /// </summary>
? private void userstableFormat()
? {
?? if(_activeusers==null) {
??? _activeusers? =? new? DataTable("ActiveUsers");
??? DataColumn? myDataColumn;
??? System.Type mystringtype;
??? mystringtype = System.Type.GetType("System.String");
??? System.Type mytimetype;
??? mytimetype = System.Type.GetType("System.DateTime");
??? myDataColumn? =? new? DataColumn("Ticket",mystringtype);
??? _activeusers.Columns.Add(myDataColumn);
??? myDataColumn? =? new? DataColumn("UserName",mystringtype);
??? _activeusers.Columns.Add(myDataColumn);
??? myDataColumn? =? new? DataColumn("TrueName",mystringtype);
??? _activeusers.Columns.Add(myDataColumn);
??? myDataColumn? =? new? DataColumn("RoleID",mystringtype);
??? _activeusers.Columns.Add(myDataColumn);???
??? myDataColumn? =? new? DataColumn("RefreshTime",mytimetype);
??? _activeusers.Columns.Add(myDataColumn);
??? myDataColumn? =? new? DataColumn("ActiveTime",mytimetype);
??? _activeusers.Columns.Add(myDataColumn);
??? myDataColumn? =? new? DataColumn("ClientIP",mystringtype);
??? _activeusers.Columns.Add(myDataColumn);??
?? }
? }
? public PassPort()
? {
?? userstableFormat(); //初始化在線用戶表
?? //活動(dòng)超時(shí)時(shí)間初始化 單位:分鐘
?? try { _activeTimeout=int.Parse(ConfigurationSettings.AppSettings["ActiveTimeout"]); }
?? catch{ _activeTimeout=60; }??
?? //自動(dòng)刷新超時(shí)時(shí)間初始化 單位:分鐘
?? try { _refreshTimeout=int.Parse(ConfigurationSettings.AppSettings["RefreshTimeout"]); }
?? catch{ _refreshTimeout=1; }??
? }
? //全部用戶列表
? public? DataTable? ActiveUsers
? {
?? get{return? _activeusers.Copy();}
? }
?
? /// <summary>
? /// 新用戶登陸。
? /// </summary>
? public void Login(ActiveUser user,bool SingleLogin)
? {
?? DelTimeOut();? //清除超時(shí)用戶
?? if(SingleLogin){
??? //若是單人登陸則注銷原來登陸的用戶
??? this.Logout(user.UserName,false);
?? }
?? DataRow myRow;
?? try
?? {
??? myRow? =? _activeusers.NewRow();???
??? myRow["Ticket"]? =? user.Ticket.Trim();
??? myRow["UserName"]? =? user.UserName.Trim();
??? myRow["TrueName"]? =? ""+user.TrueName.Trim();
??? myRow["RoleID"]? =? ""+user.RoleID.Trim();
??? myRow["ActiveTime"]? =? DateTime.Now;
??? myRow["RefreshTime"]? =? DateTime.Now;
??? myRow["ClientIP"]? =? user.ClientIP.Trim();
??? _activeusers.Rows.Add(myRow);
?? }
?? catch(Exception? e)
?? {
??? throw(new? Exception(e.Message));
?? }?
?? _activeusers.AcceptChanges();
??
? }
? /// <summary>
? ///用戶注銷,根據(jù)Ticket或UserName。
? /// </summary>
? private void Logout(string strUserKey,bool byTicket)
? {
?? DelTimeOut();? //清除超時(shí)用戶
?? strUserKey=strUserKey.Trim();
?? string? strExpr;??
?? strExpr =byTicket ? "Ticket=" + strUserKey +"" : "UserName=" + strUserKey + "";
?? DataRow[]? curUser;
?? curUser? =? _activeusers.Select(strExpr);
?? if? (curUser.Length? >0? )
?? {
??? for(int? i? =? 0;? i? <? curUser.Length;? i? ++)
??? {
???? curUser[i].Delete();
??? }
?? }
?? _activeusers.AcceptChanges();??
? }
? /// <summary>
? ///用戶注銷,根據(jù)Ticket。
? /// </summary>
? /// <param name="strTicket">要注銷的用戶Ticket</param>
? public void Logout(string strTicket){
?? this.Logout(strTicket,true);
? }
? /// <summary>
? ///清除超時(shí)用戶。
? /// </summary>
? private? bool DelTimeOut()
? {??
?? string? strExpr;??
?? strExpr = "ActiveTime < " + DateTime.Now.AddMinutes( 0 - _activeTimeout) + "or RefreshTime < "+DateTime.Now.AddMinutes( 0 - _refreshTimeout)+"";??
?? DataRow[]? curUser;
?? curUser? =? _activeusers.Select(strExpr);
?? if? (curUser.Length? >0? )
?? {
??? for(int? i? =? 0;? i? <? curUser.Length;? i? ++)
??? {
???? curUser[i].Delete();????
??? }
?? }
?? _activeusers.AcceptChanges();
?? return? true;
? }
? /// <summary>
? ///更新用戶活動(dòng)時(shí)間。
? /// </summary>
? public? void? ActiveTime(string? strTicket)
? {
?? DelTimeOut();
?? string? strExpr;
?? strExpr? =? "Ticket="? +? strTicket? +? "";?
?? DataRow[]? curUser;
?? curUser? =? _activeusers.Select(strExpr);
?? if? (curUser.Length? >0? )
?? {
??? for(int? i? =? 0;? i? <? curUser.Length;? i? ++)
??? {
???? curUser[i]["ActiveTime"]=DateTime.Now;
???? curUser[i]["RefreshTime"]=DateTime.Now;
??? }
?? }
?? _activeusers.AcceptChanges();
? }
? /// <summary>
? ///更新系統(tǒng)自動(dòng)刷新時(shí)間。
? /// </summary>
? public? void? RefreshTime(string? strTicket)
? {
?? DelTimeOut();
?? string? strExpr;
?? strExpr? =? "Ticket="? +? strTicket? +? "";?
?? DataRow[]? curUser;
?? curUser? =? _activeusers.Select(strExpr);
?? if? (curUser.Length? >0? )
?? {
??? for(int? i? =? 0;? i? <? curUser.Length;? i? ++)
??? {
???? curUser[i]["RefreshTime"]=DateTime.Now;
??? }
?? }
?? _activeusers.AcceptChanges();
? }
? private ActiveUser SingleUser(string strUserKey,bool byTicket)
? {
?? strUserKey=strUserKey.Trim();
?? string? strExpr;
?? ActiveUser myuser;
?? strExpr =byTicket ? "Ticket=" + strUserKey +"" : "UserName=" + strUserKey + "";
?? DataRow[]? curUser;
?? curUser? =? _activeusers.Select(strExpr);
?? if? (curUser.Length? >0? )
{
??? string myTicket=(string)curUser[0]["Ticket"];
??? string myUser=(string)curUser[0]["UserName"];
??? string myName=(string)curUser[0]["TrueName"];
??? string myRoleID=(string)curUser[0]["RoleID"];???
??? DateTime myActiveTime=(DateTime)curUser[0]["ActiveTime"];
??? DateTime myRefreshtime=(DateTime)curUser[0]["RefreshTime"];
??? string myClientIP =(string)curUser[0]["ClientIP"];
??? myuser=new ActiveUser(myTicket,myUser,myName,myRoleID,myActiveTime,myRefreshtime,myClientIP);?
?? }
?? else
?? {
??? myuser=new ActiveUser("","","","","");???
?? }
?? return? myuser;
? }
? /// <summary>
? ///按Ticket獲取活動(dòng)用戶。
? /// </summary>
? public ActiveUser SingleUser_byTicket(string strTicket)
? {
?? return this.SingleUser(strTicket,true);
? }
? /// <summary>
? ///按UserName獲取活動(dòng)用戶。
? /// </summary>
? public ActiveUser SingleUser_byUserName(string strUserName)
? {
?? return this.SingleUser(strUserName,false);
? }
? /// <summary>
? ///按Ticket判斷用戶是否在線。
? /// </summary>
? public bool IsOnline_byTicket(string strTicket)
? {
?? return (bool)(this.SingleUser(strTicket,true).UserName!="");
? }
? /// <summary>
? ///按UserName判斷用戶是否在線。
? /// </summary>
? public bool IsOnline_byUserName(string strUserName)
? {
?? return (bool)(this.SingleUser(strUserName,false).UserName!="");
? }
}
3、 新建一個(gè)繼承自PlaceHolder名為Refresh的類,執(zhí)行更新自動(dòng)刷新時(shí)間操作。
?
/// <summary>
?/// Refresh 執(zhí)行更新自動(dòng)刷新時(shí)間操作。
?/// </summary>
?public class Refresh: PlaceHolder
?{
? /// <summary>
? /// 設(shè)置存儲(chǔ)Ticket的Session名稱,默認(rèn)為Ticket。
? /// </summary>
? public virtual string SessionName
? {
?? get{
??? object obj1 = this.ViewState["SessionName"];
??? if (obj1 != null){ return ((string) obj1).Trim(); }
??? return "Ticket";
?? }
?? set{
??? this.ViewState["SessionName"] = value;
?? }
? }
? protected override void Render(HtmlTextWriter writer)
? {
?? string myTicket=(string)this.Page.Session[this.SessionName];
?? if(myTicket!=null)
?? {??
??? PassPort myPass = new PassPort();
??? myPass.RefreshTime(myTicket);
??? writer.Write("OK:"+DateTime.Now.ToString());
?? }
?? else{
??? writer.Write("Sorry:"+DateTime.Now.ToString());
?? }
?? base.Render(writer);
?}
}
4、 新建一個(gè)繼承自PlaceHolder名為Script的類,生成執(zhí)行xmlhttp的js腳本。。
/// <summary>
?/// Script 生成執(zhí)行xmlhttp的js腳本。
?/// </summary>
?public class Script: PlaceHolder
?{
? /// <summary>
? /// 設(shè)置js自動(dòng)刷新的間隔時(shí)間,默認(rèn)為25秒。
? /// </summary>
? public virtual int RefreshTime
? {
?? get
?? {
??? object obj1 = this.ViewState["RefreshTime"];
??? if (obj1 != null){return int.Parse(((string) obj1).Trim());}
??? return 25;
?? }
?? set
?? {???
??? this.ViewState["RefreshTime"] = value;
?? }
? }
? protected override void Render(HtmlTextWriter writer)
? {
?? //從web.config中讀取xmlhttp的訪問地址
?? string refreshUrl=(string)ConfigurationSettings.AppSettings["refreshUrl"];
?? string scriptString = @" <script language=""JavaScript"">"+writer.NewLine;
?? scriptString += @"? window.attachEvent(""onload"", "+this.ClientID+@"_postRefresh);"+writer.NewLine;
?? scriptString += @"? var "+this.ClientID+@"_xmlhttp=null;"+writer.NewLine;
?? scriptString += @"? function "+this.ClientID+@"_postRefresh(){"+writer.NewLine;
?? scriptString += @"?? var "+this.ClientID+@"_xmlhttp = new ActiveXObject(""Msxml2.XMLHTTP"");"+writer.NewLine;
?? scriptString += @"?? "+this.ClientID+@"_xmlhttp.Open(""POST"", """+refreshUrl+@""", false);"+writer.NewLine;
?? scriptString += @"?? "+this.ClientID+@"_xmlhttp.Send();"+writer.NewLine;
?? scriptString += @"?? var refreshStr= "+this.ClientID+@"_xmlhttp.responseText;"+writer.NewLine;
???
?? scriptString += @"?? try {"+writer.NewLine;
?? scriptString += @"??? var refreshStr2=refreshStr;"+writer.NewLine;
?? //scriptString += @"??? alert(refreshStr2);"+writer.NewLine;
?? scriptString += @"?? }"+writer.NewLine;
?? scriptString += @"?? catch(e) {}"+writer.NewLine;
?? scriptString += @"?? setTimeout("""+this.ClientID+@"_postRefresh()"","+this.RefreshTime.ToString()+@"000);"+writer.NewLine;
?? scriptString += @"? }"+writer.NewLine;
?? scriptString += @"<";
?? scriptString += @"/";
?? scriptString += @"script>"+writer.NewLine;
?? writer.Write(writer.NewLine);
?? writer.Write(scriptString);
?? writer.Write(writer.NewLine);
?? base.Render(writer);
? }
?}
注意以上四個(gè)類同屬于一個(gè)名為OnlineUser的工程,他們的命名空間為OnlineUser,編譯生成一個(gè)dll。
?
===============================================
?
下面我簡(jiǎn)單介紹一下調(diào)用方法:
1、 新建一個(gè)名為OnlineUserDemo的asp.net web應(yīng)用程序
2、 在vs的工具箱選項(xiàng)卡上右擊,選擇[添加/移除項(xiàng)],瀏覽定位到OnlineUser.dll,確定即可把Refresh 和Script添加到工具箱。
3、 把自動(dòng)生成的WebForm1.aspx刪除,并設(shè)置web.config
<appSettings>
?? <add key="ActiveTimeout" value="30" />
?? <add key="RefreshTimeout" value="1" />
?? <add key="refreshUrl" value="refresh.aspx" />
?</appSettings>
4、 添加一個(gè)名為Online.aspx的web窗體,給該窗體添加一個(gè)Script控件,一個(gè)DataGrid控件(id為DataGrid1),兩個(gè)HyperLink控件(分別鏈接到login.aspx和logout.aspx,text屬性分別設(shè)置為“登陸”和“注銷”),調(diào)整好四個(gè)控件的位置,轉(zhuǎn)到codebehind,在Page_Load中加入如下代碼:
string myTicket=(string)this.Page.Session["Ticket"];
?? if(myTicket!=null)
?? {
??? OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
??? if(myPassPort.IsOnline_byTicket(this.Session["Ticket"].ToString()))
??? {
???? myPassPort.ActiveTime(this.Session["Ticket"].ToString());
???? DataGrid1.DataSource=myPassPort.ActiveUsers;
???? DataGrid1.DataBind();
??? }
??? else{
???? //若在線用戶列表中找不到當(dāng)前用戶,則定向到注銷頁面
???? Response.Redirect("Logout.aspx");
??? }
?? }
?? else{
??? Response.Redirect("Login.aspx");
?? }
5、 添加一個(gè)名為login.aspx的web窗體,給該窗體添加一個(gè)label控件(id為L(zhǎng)abel1),設(shè)置text屬性為“輸入一個(gè)用戶名”,再添加一個(gè)textbox控件(id為TextBox1)和一個(gè)button控件(id為Button1),調(diào)整好他們的位置,雙擊Button1控件轉(zhuǎn)到codebehind,為Button1的Click事件加入如下代碼:
if(TextBox1.Text.Trim()=="")
?? {
??? //不能為空
??? String scriptString = @"<script language=JavaScript>";
??? scriptString += @"alert(""輸入一個(gè)用戶名\n"");";
??? scriptString += @"history.go(-1);";
??? scriptString += @"<";
??? scriptString += @"/";
??? scriptString += @"script>";
??? if(!this.Page.IsStartupScriptRegistered("Startup"))
???? this.Page.RegisterStartupScript("Startup", scriptString);
?? }
?? else{
??? OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
??? string myTicket=DateTime.Now.ToString("yyyyMMddHHmmss");
??? string myUser=TextBox1.Text.Trim();
??? string myClintIP=this.Request.UserHostAddress;
??? this.Session["Ticket"]=myTicket;
??? OnlineUser.ActiveUser myActiveUser=new OnlineUser.ActiveUser(myTicket,myUser,myUser,"test",myClintIP);
??? myPassPort.Login(myActiveUser,true);
??? Response.Redirect("Online.aspx");
?? }
6、 添加一個(gè)名為logout.aspx的web窗體,給該窗體添加一個(gè)HyperLink控件,指向login.aspx,text屬性設(shè)置為“重登陸”轉(zhuǎn)到codebehind,在Page_Load中加入如下代碼:
OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
? myPassPort.Logout(this.Session["Ticket"].ToString());
?this.Session["Ticket"]="";
7、 添加一個(gè)名為Refresh.txt的文本文件,設(shè)置其內(nèi)容為:
<%@ Register TagPrefix="cc2" Namespace="OnlineUser" Assembly="OnlineUser" %>
<%@ Page %>
<cc2:Refresh id="myRefresh" runat="server"></cc2:Refresh>
把Refresh.txt改名為Refresh.aspx
8、 編譯生成工程。
===============================================
下面進(jìn)行功能測(cè)試:
1、 打開瀏覽器,在地址欄輸入
http://你機(jī)器的IP地址/onlineuserdemo/Login.aspx
2、 輸入一個(gè)用戶名(假設(shè)是test1)登陸,自動(dòng)轉(zhuǎn)到online.aspx頁面
3、 找同網(wǎng)段的另外一臺(tái)機(jī)器(設(shè)你的機(jī)器為a,這臺(tái)機(jī)器為b),重復(fù)執(zhí)行第一步。
4、 輸入一個(gè)用戶名(假設(shè)是test2)登陸,自動(dòng)轉(zhuǎn)到online.aspx頁面
5、 在b機(jī)器不斷刷新online.aspx,若發(fā)現(xiàn)test1用戶RefreshTime每過25秒自動(dòng)更新一次而ActiveTime不變(這個(gè)時(shí)候a機(jī)器不要刷新頁面啊),則證明a機(jī)器的自動(dòng)刷新生效。
6、 在a機(jī)器不斷刷新online.aspx,若發(fā)現(xiàn)test2用戶RefreshTime每過25秒自動(dòng)更新一次而ActiveTime不變(這個(gè)時(shí)候b機(jī)器不要刷新頁面啊),則證明b機(jī)器的自動(dòng)刷新生效。
7、 直接關(guān)閉一臺(tái)機(jī)器(假設(shè)是a)上的online.aspx瀏覽窗口,在另一臺(tái)機(jī)器(就是b啦)上刷新online.aspx,若發(fā)現(xiàn)1分鐘后test1掉線在線用戶只剩下test2,證明通過_refreshTimeout清除在線用戶成功。
8、 若5、6、7三步正常,則大功告成,否則就再調(diào)試調(diào)試~~
==========================================================
轉(zhuǎn)載于:https://www.cnblogs.com/China-Dragon/archive/2009/12/04/1617273.html
總結(jié)
以上是生活随笔為你收集整理的ASP.NET在线用户列表精确版——解决用户意外退出在线列表无法及时更新问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LLBLGen 关于类型转换
- 下一篇: .Net 反射机制写Log