我的应用程序中有一个页面,它始终显示更新的在线用户列表。 现在,为了保持存储在应用程序对象中的列表更新,我执行以下步骤
登录时将用户添加到列表
在注销时删除用户
然后,为了处理浏览器关闭/导航情况,我有一个时间戳和集合中的用户名 每90秒调用一次ajax会更新时间戳。
问题: 我需要每隔120秒清理一次该列表,以删除带有旧时间戳的条目。
如何在我的网络应用程序中执行此操作?即每2分钟调用一次函数。
PS:我想过使用调度程序每2分钟调用一次web服务,但托管环境不允许任何调度。
答案 0 :(得分:8)
在全局过滤器中执行以下操作。
public class TrackLoginsFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Dictionary<string, DateTime> loggedInUsers = SecurityHelper.GetLoggedInUsers();
if (HttpContext.Current.User.Identity.IsAuthenticated )
{
if (loggedInUsers.ContainsKey(HttpContext.Current.User.Identity.Name))
{
loggedInUsers[HttpContext.Current.User.Identity.Name] = System.DateTime.Now;
}
else
{
loggedInUsers.Add(HttpContext.Current.User.Identity.Name, System.DateTime.Now);
}
}
// remove users where time exceeds session timeout
var keys = loggedInUsers.Where(u => DateTime.Now.Subtract(u.Value).Minutes >
HttpContext.Current.Session.Timeout).Select(u => u.Key);
foreach (var key in keys)
{
loggedInUsers.Remove(key);
}
}
}
检索用户列表
public static class SecurityHelper
{
public static Dictionary<string, DateTime> GetLoggedInUsers()
{
Dictionary<string, DateTime> loggedInUsers = new Dictionary<string, DateTime>();
if (HttpContext.Current != null)
{
loggedInUsers = (Dictionary<string, DateTime>)HttpContext.Current.Application["loggedinusers"];
if (loggedInUsers == null)
{
loggedInUsers = new Dictionary<string, DateTime>();
HttpContext.Current.Application["loggedinusers"] = loggedInUsers;
}
}
return loggedInUsers;
}
}
不要忘记在global.asax中注册过滤器。有一个应用程序设置来关闭它可能是一个好主意。
GlobalFilters.Filters.Add(new TrackLoginsFilter());
同时在注销时删除用户更准确。
SecurityHelper.GetLoggedInUsers().Remove(WebSecurity.CurrentUserName);
答案 1 :(得分:6)
在您的帐户控制器中
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (HttpRuntime.Cache["LoggedInUsers"] != null) //if the list exists, add this user to it
{
//get the list of logged in users from the cache
List<string> loggedInUsers = (List<string>)HttpRuntime.Cache["LoggedInUsers"];
//add this user to the list
loggedInUsers.Add(model.UserName);
//add the list back into the cache
HttpRuntime.Cache["LoggedInUsers"] = loggedInUsers;
}
else //the list does not exist so create it
{
//create a new list
List<string> loggedInUsers = new List<string>();
//add this user to the list
loggedInUsers.Add(model.UserName);
//add the list into the cache
HttpRuntime.Cache["LoggedInUsers"] = loggedInUsers;
}
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public ActionResult LogOff()
{
string username = User.Identity.Name; //get the users username who is logged in
if (HttpRuntime.Cache["LoggedInUsers"] != null)//check if the list has been created
{
//the list is not null so we retrieve it from the cache
List<string> loggedInUsers = (List<string>)HttpRuntime.Cache["LoggedInUsers"];
if (loggedInUsers.Contains(username))//if the user is in the list
{
//then remove them
loggedInUsers.Remove(username);
}
// else do nothing
}
//else do nothing
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
在部分视图中。
@if (HttpRuntime.Cache["LoggedInUsers"] != null)
{
List<string> LoggedOnUsers = (List<string>)HttpRuntime.Cache["LoggedInUsers"];
if (LoggedOnUsers.Count > 0)
{
<div class="ChatBox">
<ul>
@foreach (string user in LoggedOnUsers)
{
<li>
<div class="r_row">
<div class="r_name">@Html.Encode(user)</div>
</div>
</li>
}
</ul>
</div>
}
}
在用户登录时呈现此部分视图。
使用此脚本调用90秒
<script type="text/javascript">
$(function () {
setInterval(loginDisplay, 90000);
});
function loginDisplay() {
$.post("/Account/getLoginUser", null, function (data) {
});
}
</script>
答案 2 :(得分:1)
这是白象解决方案。
不是在应用程序对象中维护此列表,而是在数据库中维护此列表。然后,您可以使用数据库作业定期处理此列表。在此对象上建立SQL通知,以便每次清除此列表时,您都会在应用程序中获得刷新数据。
答案 3 :(得分:1)
我自己找到了解决方案。如果有人要求,我会发布链接。
答案 4 :(得分:1)
使用Ajax每隔30秒向服务器发送“我仍然在线”消息。这是找到真正在线人员的最佳方式。
答案 5 :(得分:0)
所以我做了什么:
在数据库中创建表
CREATE TABLE [dbo].[OnlineUser]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[Guid] [uniqueidentifier] NOT NULL,
[Email] [nvarchar](500) NOT NULL,
[Created] [datetime] NOT NULL,
CONSTRAINT [PK_OnlineUser] PRIMARY KEY CLUSTERED
(
[ID] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
覆盖 OnActionExecution 方法。这个方法在一个单独的控制器中,在我的例子中称为 AuthController 然后每个其他需要身份验证的控制器都从这个控制器继承。
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// session variable that is set when the user authenticates in the Login method
var accessSession = Session[Constants.USER_SESSION];
// load cookie is set when the user authenticates in the Login method
HttpCookie accessCookie = System.Web.HttpContext.Current.Request.Cookies[Constants.USER_COOKIE];
// create session from cookie
if (accessSession == null)
{
if (accessCookie != null)
{
if (!string.IsNullOrEmpty(accessCookie.Value))
accessSession = CreateSessionFromCookie(accessCookie);
}
}
// if session does not exist send user to login page
if (accessSession == null)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{"controller", "Account"},
{"action", "Login"}
}
);
return;
}
else
{
TrackLoggedInUser(accessSession.ToString());
}
}
private List<OnlineUser> TrackLoggedInUser(string email)
{
return GetOnlineUsers.Save(email);
}
接下来我在 Data Repository 类中创建了以下类:GetOnlineUsers
public static class GetOnlineUsers
{
public static List<OnlineUser> GetAll()
{
using (var db = new CEntities())
{
return db.OnlineUsers.ToList();
}
}
public static OnlineUser Get(string email)
{
using (var db = new CEntities())
{
return db.OnlineUsers.Where(x => x.Email == email).FirstOrDefault();
}
}
public static List<OnlineUser> Save(string email)
{
using (var db = new CEntities())
{
var doesUserExist = db.OnlineUsers.Where(x => x.Email.ToLower() == email.ToLower()).FirstOrDefault();
if (doesUserExist != null)
{
doesUserExist.Created = DateTime.Now;
db.SaveChanges();
}
else
{
OnlineUser newUser = new OnlineUser();
newUser.Guid = Guid.NewGuid();
newUser.Email = email;
newUser.Created = DateTime.Now;
db.OnlineUsers.Add(newUser);
db.SaveChanges();
}
return GetAll();
}
}
public static void Delete(OnlineUser onlineUser)
{
using (var db = new CEntities())
{
var doesUserExist = db.OnlineUsers.Where(x => x.Email.ToLower() == onlineUser.Email.ToLower()).FirstOrDefault();
if (doesUserExist != null)
{
db.OnlineUsers.Remove(doesUserExist);
db.SaveChanges();
}
}
}
}
在 Global.asax 中
protected void Application_EndRequest()
{
// load all active users
var loggedInUsers = GetOnlineUsers.GetAll();
// read cookie
if (Context.Request.Cookies[Constants.USER_SESSION] != null)
{
// the cookie has the email
string email = Context.Request.Cookies[Constants.USER_SESSION].ToString();
// send the user's email to the save method in the repository
// notice in the save methos it also updates the time if the user already exist
loggedInUsers = GetOnlineUsers.Save(email);
}
// lets see we want to clear the list for inactive users
if (loggedInUsers != null)
{
foreach (var user in loggedInUsers)
{
// I am giving the user 10 minutes to interact with the site.
// if the user interaction date and time is greater than 10 minutes, removing the user from the list of active user
if (user.Created < DateTime.Now.AddMinutes(-10))
{
GetOnlineUsers.Delete(user);
}
}
}
}
在继承自 AuthController 的一个控制器(您可以创建一个新的控制器)中,创建以下方法:
public JsonResult GetLastLoggedInUserDate()
{
string email = Session[Constants.USER_SESSION].ToString();
var user = GetOnlineUsers.Get(email);
return Json(new { year = user.Created.Year,
month = user.Created.Month,
day = user.Created.Day,
hours = user.Created.Hour,
minutes = user.Created.Minute,
seconds = user.Created.Second,
milliseconds = user.Created.Millisecond
}, JsonRequestBehavior.AllowGet);
}
在 _Layout.cshtml 文件的最底部放置此 Javascript 代码:此 Javascript 代码将调用上面的 GetLastLoggedInUserDate() 以从数据库中获取上次交互日期。
<script>
var lastInteracted, DifferenceInMinutes;
$(window).on('load', function (event) {
$.get("get-last-interaction-date", function (data, status) {
lastInteracted = new Date(data.year.toString() + "/" + data.month.toString() + "/" + data.day.toString() + " " + data.hours.toString() + ":" + data.minutes.toString() + ":" + data.seconds.toString());
});
});
$(window).on('mousemove', function (event) {
var now = new Date();
DifferenceInMinutes = (now.getTime() - lastInteracted.getTime()) / 60000;
if (DifferenceInMinutes > 5) {
$.get("get-last-interaction-date", function (data, status) {
lastInteracted = new Date(data.year.toString() + "/" + data.month.toString() + "/" + data.day.toString() + " " + data.hours.toString() + ":" + data.minutes.toString() + ":" + data.seconds.toString());
});
}
});
</script>
JavaScript 解释:
在页面加载时,我正在设置用户与我的网站交互的最后日期时间。
由于我无法跟踪用户在屏幕上注视的内容,因此与实际交互最接近的下一个是鼠标移动。 因此,当用户将鼠标移动到页面上的任意位置时,会发生以下情况:
由于用户碰巧喜欢该网站并决定在该网站上花费更多时间,所以在 5 分钟过去后,我向控制器 GetLastLoggedInUserDate()
中的 this 方法发送了另一个请求以再次获取日期。但是在我们获得日期之前,我们将执行 OnActionExecuting
方法,该方法将更新记录创建日期并返回当前时间。 lastInteracted
获取更新日期,然后我们再次出发。
这里的想法是,当用户不与我的网站互动时,他对我来说并不是真正在线。也许他打开了 100 个标签并玩游戏做其他事情,但与我的网站交互时,他们甚至可能不会意识到他们在几天或几个月内打开了它,这取决于他们重新启动 PC 的频率。无论如何,我认为 10 分钟是一个很好的门槛,但可以随意更改。
最后是 AdminController 类:
public ActionResult Index()
{
DashboardViewModel model = new DashboardViewModel();
// loading the list of online users to the dashboard
model.LoggedInUsers = GetOnlineUsers.GetAll();
return View("Index", "~/Views/Shared/_adminLayout.cshtml", model);
}
Index.cshtml(管理仪表板页面)
@model ILOJC.Models.Admin.DashboardViewModel
@{
ViewBag.Menu1 = "Dashboard";
}
/// some html element and styles
<h5 class="">@Model.LoggedInUsers.Count() Online Users</h5>
<div class="row">
@foreach (var user in Model.LoggedInUsers.OrderByDescending(x => x.Created))
{
<div class="col-md-12">
<h5>@user.Email</h5>
<p><span>Last Inreaction Time: @user.Created.ToString("MM/dd/yyyy hh:mm:ss tt")</span></p>
</div>
}
</div>
由于原始表只存储在线用户,我想有一些历史记录/日志,所以我在数据库中创建了一个历史表:
CREATE TABLE [dbo].[OnlineUserHistory](
[ID] [int] IDENTITY(1,1) NOT NULL,
[OnlineUserID] [int] NOT NULL,
[Guid] [uniqueidentifier] NOT NULL,
[Email] [nvarchar](500) NOT NULL,
[Created] [datetime] NOT NULL,
[Updated] [datetime] NOT NULL,
[Operation] [char](3) NOT NULL,
CONSTRAINT [PK_OnlineUserLog] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
最后,我在插入和删除时创建了一个数据库触发器
CREATE TRIGGER [dbo].[trg_online_user_history]
ON [dbo].[OnlineUser]
AFTER INSERT, DELETE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO OnlineUserHistory(
OnlineUserID,
[Guid],
Email,
Created,
Updated,
Operation
)
SELECT
i.ID,
i.[Guid],
i.Email,
i.Created,
GETDATE(),
'INS'
FROM
inserted i
UNION ALL
SELECT
d.ID,
d.[Guid],
d.Email,
d.Created,
GETDATE(),
'DEL'
FROM
deleted d;
END
希望这可以帮助某人。我要改进的一件事是在线用户在仪表板中显示负载的方式。现在,我需要刷新页面以查看更新后的数字。但是,如果您想实时查看它,只需添加 SignalR 库,然后创建一个集线器即可!