简体   繁体   English

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

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

I have a page in my application which always shows updated list of online users.我的应用程序中有一个页面,它总是显示更新的在线用户列表。 Now, to keep the list-which is stored in application object- updated, i do the below steps现在,为了使存储在应用程序对象中的列表保持更新,我执行以下步骤

  1. add user to list when login登录时将用户添加到列表

  2. remove user on log off注销时删除用户

  3. Then to handle browser close/navigate away situations, I have a timestamp along with the username in the collection An ajax call every 90 seconds updates the timestamp.然后为了处理浏览器关闭/导航离开的情况,我有一个时间戳以及集合中的用户名每 90 秒一次 ajax 调用更新时间戳。

The problem: I need something to clean this list every 120 seconds to remove entries with old timestamps.问题:我需要每 120 秒清理一次此列表以删除带有旧时间戳的条目。

How do I do this within my web application?如何在我的 Web 应用程序中执行此操作? ie Call a function every 2 mins.即每 2 分钟调用一次函数。

PS: I thought of calling a webservice every 2 mins using a scheduler , but the hosting environment do not allow any scheduling. PS:我想过使用调度程序每 2 分钟调用一次 web 服务,但是托管环境不允许任何调度。

Do the following inside a global filter.在全局过滤器中执行以下操作。

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

    }
}

To retrieve the user list检索用户列表

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;

    }
}

Don't forget to Register you filter in global.asax.不要忘记在 global.asax 中注册您的过滤器。 It's probably a good idea to have an app setting to switch this off.有一个应用程序设置来关闭它可能是个好主意。

GlobalFilters.Filters.Add(new TrackLoginsFilter());

Also remove users at logoff to be more accurate.还要在注销时删除用户以更准确。

SecurityHelper.GetLoggedInUsers().Remove(WebSecurity.CurrentUserName);

In your Account Controller在您的帐户控制器中

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

in your partial view.在你的部分观点中。

@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>
    }
}

render this partial view when user log in.当用户登录时呈现这个局部视图。

use this script call ever 90 second每隔 90 秒使用此脚本调用

<script type="text/javascript">
    $(function () {
        setInterval(loginDisplay, 90000);
    });

    function loginDisplay() {
        $.post("/Account/getLoginUser", null, function (data) {

        });
    }
</script>

Here is the white elephant solution.这是白象解决方案。

Instead of maintaining this list in application object, maintain this list in database.不是在应用程序对象中维护这个列表,而是在数据库中维护这个列表。 Then you can use database jobs to work on this list periodically.然后您可以使用数据库作业定期处理此列表。 Establish SQL notification on this object so that everytime this list is purged you get refreshed data in your application.在此对象上建立 SQL 通知,以便每次清除此列表时,您都会在应用程序中获得刷新的数据。

Use Ajax to send "I am still online" message to the server in every 30 seconds.使用 Ajax 每 30 秒向服务器发送“我仍然在线”消息。 This is the best way to find who is really online.这是找出谁真正在线的最佳方式。

I found the solution myself. 我自己找到了解决方案。 I will post the link in case anyone requires it. 如果有人要求,我会发布链接。

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

So here what I did:所以在这里我做了什么:

  1. Create a table in the database在数据库中创建一个表

    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. Override the OnActionExecution method.覆盖 OnActionExecution 方法。 This method is in a separate controller in my case is called AuthController then every other controller that required authemtication inherits from this controller.这个方法在一个单独的控制器中,在我的例子中称为 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. Next I created the following classes in the Data Repository class: GetOnlineUsers接下来我在 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. In the Global.asax在 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. In one of the controllers (You can create a new one up to you) that inhering from the AuthController, create the following method:在继承自 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. In your _Layout.cshtml file at the very bottom place this Javascript code: This Javascript code will call the GetLastLoggedInUserDate() above to get the last interacted date from the database.在 _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 explanation: JavaScript 解释:

On page load I am are setting the last datetime the the user interacted with my website.在页面加载时,我正在设置用户与我的网站交互的最后日期时间。

Since I cannot track what the user stares at on the screen, the next closest thing to real interaction is mouse movement.由于我无法跟踪用户在屏幕上注视的内容,因此最接近真实交互的是鼠标移动。 So when the user moves the mouse anywhere on the page the following happens:因此,当用户将鼠标移动到页面上的任意位置时,会发生以下情况:

  1. I compare the last interacted date with the current date.我将上次交互日期与当前日期进行比较。
  2. Then I check if 5 minutes passed since the last updated date occurred.然后我检查自上次更新日期发生以来是否过去了 5 分钟。

Since the user happened to love the website and decided to spend more time on it, after the 5 minutes are passed, I send another request to the this method in my controller GetLastLoggedInUserDate() to get the date again.由于用户碰巧喜欢该网站并决定在该网站上花费更多时间,所以在 5 分钟过去后,我向控制器GetLastLoggedInUserDate()的 this 方法发送另一个请求以再次获取日期。 But before we get the date we will execute the OnActionExecuting method which will then update the records Created date and will return the current time.但是在我们获得日期之前,我们将执行OnActionExecuting方法,然后该方法将更新记录创建日期并返回当前时间。 The lastInteracted gets the updated date and we go again. lastInteracted获取更新日期,然后我们再次访问。

The idea here is that when the user is not interacting with my website he is not really online for me.这里的想法是,当用户没有与我的网站进行交互时,他对我来说并不是真的在线。 Maybe he has 100 tabs open and playing games doing other things but interacting with my website it is possible that they will not even realize they have it open in days or months depends on how often they reboot the PC.也许他打开了 100 个标签并玩游戏做其他事情,但与我的网站交互时,他们甚至可能不会意识到他们在几天或几个月内打开了它,这取决于他们重新启动 PC 的频率。 In any case I think that 10 minutes is a good threshold to work with but feel free to change it.无论如何,我认为 10 分钟是一个很好的门槛,但可以随意更改。

Finally AdminController class:最后是 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 (admin dashboard page) 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>

Since the original table will only store online users I wanted to have a bit of history/log so I create a history table in the database:由于原始表只会存储在线用户,我想要一些历史记录/日志,因此我在数据库中创建了一个历史记录表:

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]

Lastly, I created a database Trigger on insert and delete最后,我在插入和删除时创建了一个数据库触发器

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

Hope this can hep someone.希望这可以帮助某人。 One thing I would improve tho is the way the online users are displaying load in the dashboard.我要改进的一件事是在线用户在仪表板中显示负载的方式。 Now, I need to refresh the page to see the updated number.现在,我需要刷新页面以查看更新后的数字。 But if you want to see it live, you just add the SignalR library then create a hub and you good to go!但是,如果您想实时查看它,只需添加 SignalR 库,然后创建一个集线器即可!

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

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