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

MVC5菜单加载优化:替代OnActionExecuting的单次调用方案

嘿,这个场景我在MVC5项目里太熟悉了!给你两个针对性的解决方案,完美解决你这两个问题:

一、用OnResultExecuting实现视图渲染前仅调用一次的逻辑

原来的OnActionExecuting会在每个Action执行前触发,哪怕是同一个请求里的子Action(比如分部视图调用的Action)也会跑,所以才会多次调用。而MVC里的OnResultExecuting方法是在Action执行完成,准备渲染Result(比如View)之前触发的,正常情况下每个请求只会触发一次,正好符合你要的“视图即将渲染时调用一次”的需求。

具体做法是在你的基础控制器里重写这个方法,并且判断当前要执行的Result是不是ViewResult(避免在JsonResult、FileResult等场景下无效执行):

public class BaseController : Controller
{
    // 假设你注入了菜单相关的服务/仓储
    private readonly IMenuRepository _menuRepo;

    public BaseController(IMenuRepository menuRepo)
    {
        _menuRepo = menuRepo;
    }

    protected override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        // 只处理视图渲染的场景
        if (filterContext.Result is ViewResult)
        {
            // 这里写你的菜单加载逻辑,后面结合缓存优化
            // var menu = _menuRepo.GetMaintenanceMenu(User.Identity.Name);
            // ViewBag.MaintenanceMenu = menu;
        }

        // 别忘了调用基类方法
        base.OnResultExecuting(filterContext);
    }
}

这样每次页面请求,只会在视图要渲染的前一刻执行一次菜单加载逻辑,不会被多次调用。

二、同一用户的菜单缓存优化方案

既然是同一用户的菜单,完全没必要每次请求都查数据库,我们可以用缓存来存储,根据场景选不同的缓存方式:

1. Session缓存(用户专属,简单易用)

Session是每个用户独立的存储,适合存储用户专属的菜单数据,比如不同用户有不同的权限菜单:

protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
    if (filterContext.Result is ViewResult)
    {
        var userId = User.Identity.GetUserId(); // 用ASP.NET Identity获取用户ID
        var cacheKey = $"User_MaintenanceMenu_{userId}";

        // 先从Session取缓存
        var maintenanceMenu = Session[cacheKey] as List<MenuModel>;

        if (maintenanceMenu == null)
        {
            // 缓存不存在,从数据库加载
            maintenanceMenu = _menuRepo.GetMaintenanceMenuForUser(userId);
            // 存入Session,设置过期时间(比如30分钟,和Session超时一致)
            Session[cacheKey] = maintenanceMenu;
            Session.Timeout = 30;
        }

        // 把菜单传给视图
        ViewBag.MaintenanceMenu = maintenanceMenu;
    }

    base.OnResultExecuting(filterContext);
}

2. MemoryCache(应用级缓存,适合角色共享菜单)

如果你的菜单是按角色分配的(同一角色的用户菜单相同),可以用应用级的MemoryCache,这样同角色的用户共享缓存,节省资源:

using System.Runtime.Caching;

public class BaseController : Controller
{
    private readonly IMenuRepository _menuRepo;
    private readonly ObjectCache _cache = MemoryCache.Default;

    public BaseController(IMenuRepository menuRepo)
    {
        _menuRepo = menuRepo;
    }

    protected override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        if (filterContext.Result is ViewResult)
        {
            // 获取用户的角色(假设用户只有一个角色,多个的话可以组合成键)
            var userRole = User.IsInRole("Admin") ? "Admin" : "User";
            var cacheKey = $"Role_MaintenanceMenu_{userRole}";

            var maintenanceMenu = _cache.Get(cacheKey) as List<MenuModel>;

            if (maintenanceMenu == null)
            {
                maintenanceMenu = _menuRepo.GetMaintenanceMenuForRole(userRole);
                // 设置缓存过期时间,比如1小时,或者依赖数据库变更
                var cachePolicy = new CacheItemPolicy
                {
                    AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
                };
                _cache.Set(cacheKey, maintenanceMenu, cachePolicy);
            }

            ViewBag.MaintenanceMenu = maintenanceMenu;
        }

        base.OnResultExecuting(filterContext);
    }
}

缓存更新注意事项

  • 如果后台更新了菜单数据(比如新增/修改菜单权限),需要手动清空对应的缓存,否则用户会看到旧数据。比如在菜单管理的Action里,更新数据库后调用Session.Remove(cacheKey)或者_cache.Remove(cacheKey)。
  • 对于Session缓存,用户注销后Session会自动清空,不用担心残留数据;对于MemoryCache,要注意缓存键的唯一性,避免不同用户/角色的菜单互相覆盖。

这样组合起来,既解决了方法多次调用的问题,又优化了数据库访问,一举两得!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:26:45