You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET MVC新手如何追踪用户在控制器/Action上的停留时长?

嘿,作为ASP.NET MVC新手想追踪用户在特定页面的停留时长,你已经用ActionFilterAttribute开了个不错的头,但这里得先澄清一个关键点:服务器端的ActionFilter只能捕获请求的开始和处理完成时间,这可不是用户实际在页面上停留的时长——用户可能打开页面后逛10分钟才切走,服务器根本不知道这段时间,所以得结合客户端JavaScript来实现才行!

接下来给你一步步完善方案:

一、核心逻辑梳理

用户在页面的停留时长是纯客户端行为,得在浏览器里记录用户进入页面的时间戳,当用户离开(关闭页面、切标签页、跳转)时计算时长,再把数据传给服务器保存。服务器端的Filter用来辅助记录请求信息,但核心时长数据得靠前端捕获。

二、客户端实现(JavaScript)

在你要追踪的页面(比如Resident/Index的View)里加这段脚本,用来监听页面生命周期:

// 记录用户进入页面的时间戳
let pageEntryTime = new Date().getTime();

// 监听页面卸载(关闭/跳转)事件
window.addEventListener('beforeunload', () => {
    sendPageDuration();
});

// 监听页面隐藏(切换到其他标签页)事件
window.addEventListener('visibilitychange', () => {
    if (document.hidden) {
        sendPageDuration();
    } else {
        // 用户切回页面,重新更新进入时间
        pageEntryTime = new Date().getTime();
    }
});

function sendPageDuration() {
    const pageExitTime = new Date().getTime();
    // 计算停留时长(转成秒)
    const durationSeconds = Math.floor((pageExitTime - pageEntryTime) / 1000);

    // 只统计有效停留(比如至少1秒,避免误触发)
    if (durationSeconds > 0) {
        // 从页面获取当前控制器和Action(避免硬编码)
        const controllerName = '@ViewContext.RouteData.Values["controller"]';
        const actionName = '@ViewContext.RouteData.Values["action"]';
        // 如果有身份认证,获取当前用户ID
        const userId = '@User.Identity.IsAuthenticated ? User.Identity.GetUserId() : string.Empty';

        // 发送到服务器的日志接口
        fetch('/Audit/LogPageDuration', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                // 带上CSRF令牌,防止跨站攻击
                'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
            },
            body: JSON.stringify({
                ControllerName: controllerName,
                ActionName: actionName,
                DurationInSeconds: durationSeconds,
                UserId: userId
            })
        });
    }
}
三、服务器端实现

1. 创建接收数据的模型

public class PageDurationLogDto
{
    public string ControllerName { get; set; }
    public string ActionName { get; set; }
    public int DurationInSeconds { get; set; }
    public string UserId { get; set; }
}

2. 创建Audit控制器接收日志

假设你用EF Core操作数据库,先准备一个AuditLog实体用来存日志:

public class AuditLog
{
    public int Id { get; set; }
    public string ControllerName { get; set; }
    public string ActionName { get; set; }
    public int DurationInSeconds { get; set; }
    public string UserId { get; set; }
    public DateTime CreatedTime { get; set; } = DateTime.Now;
}

然后写控制器:

public class AuditController : Controller
{
    private readonly YourDbContext _dbContext;

    // 依赖注入你的数据库上下文
    public AuditController(YourDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult LogPageDuration(PageDurationLogDto dto)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        // 把数据转成实体存到数据库
        var log = new AuditLog
        {
            ControllerName = dto.ControllerName,
            ActionName = dto.ActionName,
            DurationInSeconds = dto.DurationInSeconds,
            UserId = dto.UserId
        };

        _dbContext.AuditLogs.Add(log);
        _dbContext.SaveChanges();

        return Ok();
    }
}

3. 完善你原来的AuditAttribute(可选)

这个Filter可以用来记录服务器处理请求的时长,作为用户停留时长的补充:

public class AuditAttribute : ActionFilterAttribute
{
    private DateTime _requestStartTime;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        _requestStartTime = DateTime.Now;
        base.OnActionExecuting(filterContext);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // 计算服务器处理请求的耗时
        var requestDuration = DateTime.Now - _requestStartTime;
        
        // 获取控制器和Action名称
        var controllerName = filterContext.RouteData.Values["controller"].ToString();
        var actionName = filterContext.RouteData.Values["action"].ToString();

        // 这里可以把请求耗时记录到日志或数据库,比如:
        // _dbContext.RequestLogs.Add(new RequestLog { ... });

        base.OnActionExecuted(filterContext);
    }
}
四、注意事项
  • beforeunload事件在浏览器崩溃、强制关闭等极端情况可能不触发,所以数据会有少量丢失,这是前端监听的正常局限
  • 一定要带上CSRF令牌,避免跨站攻击,MVC默认的表单里会生成__RequestVerificationToken,直接获取就行
  • 如果你的项目没有身份认证,可以去掉UserId相关的代码
  • 可以把JavaScript封装成一个全局脚本,在_Layout.cshtml里引用,这样所有页面都能复用追踪逻辑

内容的提问来源于stack exchange,提问作者user8107351

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:13:07