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

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都会自动触发它:

  1. 打开FilterConfig.cs(如果没有就新建一个):
using System.Web.Mvc;

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        // 添加我们的耗时统计过滤器
        filters.Add(new ActionDurationTrackingFilter());
    }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:37:29