C# asp.net MVC:何时更新LastActivityDate?

12

我正在使用ASP.NET MVC创建一个公共网站,需要跟踪在线用户。我发现ASP.NET的标准方法是跟踪LastActivityDate。我的问题是,何时应该更新它?

如果每次用户点击某个地方都更新它,那么性能会下降。但是如果不这样做,只浏览网页的人将被列为离线状态。

在ASP.NET MVC中,最好的方法是什么?


有人知道 Facebook 是如何做到这一点的吗? - Oskar Kjellin
我假设Facebook在其许多AJAX调用之一中不断地进行检查。 - Mark Redman
10个回答

6

我遇到了同样的问题,这是我为MVC用户提供的答案:

思路是在每次页面加载时触发Membership.GetUser("..", true),这将自动更新LastActivityDate。

我将其放置在global.asax中的“RegisterGlobalFilters”下:

filters.Add(new MembershipTriggerLastActivityDate());

我创建了一个新的类,它看起来像这样:

class MembershipTriggerLastActivityDate : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            MembershipUser user = Membership.GetUser(filterContext.HttpContext.User.Identity.Name, true);
        }   
        base.OnActionExecuting(filterContext);
    }
}

谢谢这个。我喜欢这种方法。 - Andrew Garrison

6
只需在您的主页面底部放置一个ajax javascript调用来跟踪这个。不要担心性能问题,如果实施后发现有问题,请回来寻找更好的解决方案。这么简单的东西不应该是性能问题。只需将其视为Google分析。它坐落在数百万页面的底部,对这些站点的用户体验几乎没有影响。

1
很好的提示。最终我也这样做了 :) - Oskar Kjellin
1
@user594166:我把代码放在$(document).ready里面了... 在这里看 https://dev59.com/rHE85IYBdhLWcg3wkkfK#12720372。 - Leniel Maccaferri

4
我开始使用 SimpleMembershipProvider,它非常简单,没有了 LastActivityDate 跟踪功能。因此,我不得不自己创建。
我只是在用户表中添加了一个 LastActivityDate 列,就完成了...
遵循@Jab的提示,并在ASP.NET MVC应用程序中使用_Layout.cshtml页面(母版页),借助jQuery,我做到了这一点:
$(document).ready((function () {

    var isUserAuthenticated = '@User.Identity.IsAuthenticated';

    if (isUserAuthenticated) {

        $.ajax({
            type: "POST",
            url: "@Url.Action(MVC.Account.ActionNames.UpdateLastActivityDate, MVC.Account.Name)",
            data: { userName: '@User.Identity.Name' },
            cache: false
        });
    }
});

这是操作方法:
public virtual ActionResult UpdateLastActivityDate(string userName)
{
    User user = Database.Users.Single(u => u.UserName == userName);

    user.LastActivityDate = DateTime.Now;

    Database.Entry(user).State = EntityState.Modified;

    Database.SaveChanges();

    return new EmptyResult();
}

只需 133 毫秒(具体时间因人而异):-) enter image description here

2

正如@Jab所说,只需实现它,如果将来发现性能问题-那时再处理。

这是我在我的应用程序中实现的方法:

protected void Application_EndRequest()
{
    if ((Response.ContentType == "text/html") && (Request.IsAuthenticated))
    {
        var webUser = Context.User as WebUser;
        if (webUser != null)
        {
            //Update their last activity
            webUser.LastActivity = DateTime.UtcNow;

            //Update their page hit counter
            webUser.ActivityCounter += 1;

            //Save them
            var webUserRepo = Kernel.Get<IWebUserRepository>(); //Ninject
            webUserRepo.Update(webUser);
        }
    }
}

我在性能方面没有遇到任何问题。

希望对你有所帮助,
Charles


2
为什么不将LastActivityDate的更新实现为异步调用呢?这样你就可以触发更新并继续处理。

有没有在 global.asax 中可以捕获这些异步事件的事件? - Oskar Kjellin
Application_BeginRequest 在 global.asax 中被调用。理论上,您可以使用该入口点记录用户的日期。 - Steve Horn

2
我将其放入一个特殊的队列中,该队列仅允许一个给定键在其中(在本例中使用userId作为键)。然后我有一个低优先级线程,通过这个队列进行数据库更新。因此用户没有任何减慢速度,并且一个用户在一秒钟内进行100次更新也不会造成任何危害。如果这变成一个问题,我将把这些更新变成批量更新,针对数据库,但目前这种方法工作得非常好。
如果应用程序崩溃,我会失去最近几秒钟的活动数据,但这没关系。当然,我每次都要更新内存中的User对象,以便在用户界面上反映出来,即使它还没有进入数据库。通常,在他们收到完整页面之前,它已经在那里了。

1

如果您正在使用InProc SessionState,请使用SessionStateModule.End事件。当会话状态从底层缓存存储中驱逐时,就会发生这种情况。通常在20分钟的不活动后会发生这种情况,您可以在web.config中设置时间。


这个事件对我来说似乎无法正常触发。 - Oskar Kjellin

0

我尝试在 Global.asax 中像这样使用 Charlino 的代码

        protected void Application_BeginRequest(object sender, EventArgs e)
    {
        if ((Response.ContentType == "text/html") && (Request.IsAuthenticated))
        {

        }
    }

然而,我一直得到Request.IsAuthenticated的false。 所以我把代码移到了我的Site.Master页面中的一个方法中,像这样

 public void RegisterActivity()
    {
        if ((Response.ContentType == "text/html") && (Request.IsAuthenticated))
        {
            string userName = Page.User.Identity.Name;

            UserManager userManager = new UserManager();
            AppUser appUser = userManager.FindByName(userName);
            appUser.LastActivityDate = DateTime.UtcNow;
            userManager.Update(appUser);
        }
    }

我从Master页面Page_Load事件中调用了该方法,并且它起作用了。

我正在使用asp.net Identity而不是Membership,但我添加了一个继承自IdentityUser类的AppUser类,在AppUser类中我添加了LastActivityDate属性

这是一个WebForms应用程序而不是MVC


0

好问题,我也考虑过这个问题,考虑到性能如何准确地实现这些机制,有几个想法:

1)跟踪上次登录日期

2)使用LastLoginDate + 预期会话长度来设置某种LastOnlineDate,以便检查用户是否在线。


但是如果用户登录后浏览了3个小时,而会话长度为20分钟,会发生什么情况呢?那么用户将被错误地列为离线。这在大多数情况下都会发生。 - Oskar Kjellin
你可以将这个LastOnlineDate存储在会话中,并设置一些标志来根据用户操作(页面加载)更新它。 - Mark Redman

0

我认为如果您在每个请求中获取当前已登录用户并每次更新LastActivityDate字段(如果您小心并为每个http请求调用GetUser方法以获取已登录用户),则性能上不会有太大的惩罚。通过这种方式,您还可以确保始终拥有用户的最新数据,例如电子邮件、姓名等,以防他/她更新了这些数据。


将这个LastActivityDate保存在一个只包含UserID | LastActivityDate的单独表中,是否会减少一些开销? - Mark Redman
1
@Mark - 那肯定只会增加不必要的JOIN。 - Ian Mercer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接