ASP.NET MVC全局统计Action执行耗时并存储至数据库的实现方案
嘿,这个需求太常见了,完全不用在每个Action里重复写Stopwatch代码!ASP.NET MVC提供了全局Action过滤器这个完美的解决方案,能自动拦截所有Action的执行前后,轻松实现全项目的耗时统计和存储。下面给你一步步拆解实现方式:
核心思路:自定义全局Action过滤器
ASP.NET MVC的ActionFilterAttribute是专门用来拦截Action执行生命周期的扩展点,我们可以通过重写它的方法,在Action执行前启动计时,执行后停止计时并存储数据,而且只需要注册一次就能作用于所有Action。
第一步:实现自定义耗时统计过滤器
创建一个继承自ActionFilterAttribute的类,重写执行前后的方法,完成计时和数据存储逻辑。这里推荐用异步版本,避免数据库操作阻塞请求响应:
using System.Diagnostics; using System.Web.Mvc; using System.Threading.Tasks; public class ActionDurationTrackingFilter : ActionFilterAttribute { // Action执行前:启动计时器,存在当前请求上下文(每个请求独立,不会冲突) public override async Task OnActionExecutingAsync(ActionExecutingContext filterContext, CancellationToken cancellationToken) { var stopwatch = Stopwatch.StartNew(); filterContext.HttpContext.Items["ActionStopwatch"] = stopwatch; await base.OnActionExecutingAsync(filterContext, cancellationToken); } // Action执行后:停止计时,收集信息并存库 public override async Task OnActionExecutedAsync(ActionExecutedContext filterContext, CancellationToken cancellationToken) { if (filterContext.HttpContext.Items.TryGetValue("ActionStopwatch", out var stopwatchObj) && stopwatchObj is Stopwatch stopwatch) { stopwatch.Stop(); long durationMs = stopwatch.ElapsedMilliseconds; // 收集Action的关键信息 string controllerName = filterContext.RouteData.Values["controller"].ToString(); string actionName = filterContext.RouteData.Values["action"].ToString(); bool isExecutionSuccess = filterContext.Exception == null; string requestIp = filterContext.HttpContext.Request.UserHostAddress; string currentUserId = filterContext.HttpContext.User.Identity.IsAuthenticated ? filterContext.HttpContext.User.Identity.Name : null; // 存储到数据库(替换成你的实际数据库操作逻辑) try { using (var dbContext = new YourAppDbContext()) { dbContext.ActionExecutionLogs.Add(new ActionExecutionLog { ControllerName = controllerName, ActionName = actionName, DurationMilliseconds = durationMs, ExecutedTimeUtc = DateTime.UtcNow, IsSuccess = isExecutionSuccess, RequestIpAddress = requestIp, UserId = currentUserId }); await dbContext.SaveChangesAsync(cancellationToken); } } catch (Exception ex) { // 注意:不要因为存库失败影响原Action的响应,这里可以记录日志(比如用NLog/Serilog) // Log.Error("保存Action耗时日志失败", ex); } } await base.OnActionExecutedAsync(filterContext, cancellationToken); } } // 配套的实体类(根据你的数据库设计调整) public class ActionExecutionLog { public int Id { get; set; } public string ControllerName { get; set; } public string ActionName { get; set; } public long DurationMilliseconds { get; set; } public DateTime ExecutedTimeUtc { get; set; } public bool IsSuccess { get; set; } public string RequestIpAddress { get; set; } public string UserId { get; set; } }
第二步:注册为全局过滤器
把刚才的过滤器注册到全局过滤器集合里,这样所有Action都会自动触发它:
- 打开
FilterConfig.cs(如果没有就新建一个):
using System.Web.Mvc; public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); // 添加我们的耗时统计过滤器 filters.Add(new ActionDurationTrackingFilter()); } }
- 在
Global.asax.cs的Application_Start方法里调用注册:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); // 其他初始化代码... }
可选:跳过特定Action的统计
如果某些Action不需要统计,可以加一个跳过标记特性:
public class SkipDurationTrackingAttribute : Attribute { }
然后在过滤器的OnActionExecutingAsync方法开头加判断:
var skipTracking = filterContext.ActionDescriptor .GetCustomAttributes(typeof(SkipDurationTrackingAttribute), false) .Any(); if (skipTracking) { await base.OnActionExecutingAsync(filterContext, cancellationToken); return; }
之后在不需要统计的Action上标记即可:
[SkipDurationTracking] public ActionResult IgnoreThisAction() { // ... }
内容的提问来源于stack exchange,提问作者Morteza Jangjoo
相关产品推荐
相关产品推荐

