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

