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
相关产品推荐
相关产品推荐

