繁体   English   中英

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

[英]get a list of online users in asp.net mvc

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

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

  2. 注销时删除用户

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

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

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

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

在全局过滤器中执行以下操作。

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);

在您的帐户控制器中

   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>

这是白象解决方案。

不是在应用程序对象中维护这个列表,而是在数据库中维护这个列表。 然后您可以使用数据库作业定期处理此列表。 在此对象上建立 SQL 通知,以便每次清除此列表时,您都会在应用程序中获得刷新的数据。

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

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

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

所以在这里我做了什么:

  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 库,然后创建一个集线器即可!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM