获取asp.net mvc中的在线用户列表

时间:2011-01-23 14:36:20

标签: asp.net asp.net-mvc

我的应用程序中有一个页面,它始终显示更新的在线用户列表。 现在,为了保持存储在应用程序对象中的列表更新,我执行以下步骤

  1. 登录时将用户添加到列表

  2. 在注销时删除用户

  3. 然后,为了处理浏览器关闭/导航情况,我有一个时间戳和集合中的用户名 每90秒调用一次ajax会更新时间戳。

  4. 问题: 我需要每隔120秒清理一次该列表,以删除带有旧时间戳的条目。

    如何在我的网络应用程序中执行此操作?即每2分钟调用一次函数。

    PS:我想过使用调度程序每2分钟调用一次web服务,但托管环境不允许任何调度。

6 个答案:

答案 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)

我自己找到了解决方案。如果有人要求,我会发布链接。

http://www.codeproject.com/KB/aspnet/ASPNETService.aspx

答案 4 :(得分:1)

使用Ajax每隔30秒向服务器发送“我仍然在线”消息。这是找到真正在线人员的最佳方式。

答案 5 :(得分:0)

所以我做了什么:

  1. 在数据库中创建表

    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]
    
  2. 覆盖 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);            
    }
    
  3. 接下来我在 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();
               }                
           }
       }
    }
    
  4. 在 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);
                 }
             }
         }            
     }
    
  5. 在继承自 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);
     }
    
  6. 在 _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 解释:

在页面加载时,我正在设置用户与我的网站交互的最后日期时间。

由于我无法跟踪用户在屏幕上注视的内容,因此与实际交互最接近的下一个是鼠标移动。 因此,当用户将鼠标移动到页面上的任意位置时,会发生以下情况:

  1. 我将上次互动日期与当前日期进行比较。
  2. 然后我检查自上次更新日期是否过去了 5 分钟。

由于用户碰巧喜欢该网站并决定在该网站上花费更多时间,所以在 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 库,然后创建一个集线器即可!