在上一篇文章中我用递归方法实现了管理菜单,在上一节我也提到要考虑用缓存,也算是学习一下.Net Core的缓存机制。
关于.Net Core的缓存,官方有三种实现:
1.In Memory Caching 我理解是在内容中实现,这种方法适用于单服务器的生产环境。
2.a Distributed Cache 分部式缓存实现。
3.Response Cache 这种方式我理解为客户端缓存。
今天我只用了第一种实现方法,内存中缓存,之所以用这种方法我是觉得我这里用缓存的初衷是为了减少访问数据库的次数,而把访问数据库频率最高的数据转变为对象而放在缓存里。对于分部式说实话目前我也只是听说和了解原理,而没有做过实现,这里先不谈它。
微软官方提供的实现In Memory Caching 方法时有一个微软标准的示例,GitHub地址是 在这里
示例是用中间件实现的,经过一番学习,用示例里面提到的方法通过中间件实现了缓存,但怎么用到Controller里成了一个难题,也可能是我对中间件的了解不够深入吧!反正我是不知道该怎么办了!无耐之下,还是仔细研究了一下代码发现在中间件里面对缓存的调用(包括设置和取出)其实只在下面这段代码里实现:
1 if(!_memoryCache.TryGetValue(cacheKey, out greeting)) 2 { 3 // fetch the value from the source 4 greeting = _greetingService.Greet("world"); 5 6 // store in the cache 7 _memoryCache.Set(cacheKey, greeting, 8 new MemoryCacheEntryOptions() 9 .SetAbsoluteExpiration(TimeSpan.FromMinutes(1))); 10 _logger.LogInformation($"{cacheKey} updated from source."); 11 } 12 else 13 { 14 _logger.LogInformation($"{cacheKey} retrieved from cache."); 15 }
也就是 _memoryCache.TryGetValue(cacheKey, out greeting)这一段负责从缓存中取出缓存内容;而_memoryCache.Set(cacheKey, greeting, new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(1)));这个方法调用是把数据放到缓存里面的!
知道了这些以后就着手改造我的程序了:
1.创建IAdminTreeService.cs接口类:
public interface IAdminTreeService { /// <summary> /// 全部节点数据树 /// </summary> AdminUserTree GetAllTreeData { get; } }
2.创建服务AdminTreeServices.cs,用于实现从数据库中取出管理菜单数据,用服务方式实现这种方式还是借鉴了官方示例的实现方式,这种实现是有其道理的。道理自己琢磨吧,最起码有得用接口统一。不再啰嗦直接代码:
1 public class AdminTreeServices:IAdminTreeService 2 { 3 /// <summary> 4 /// EF数据访问配置 5 /// </summary> 6 private readonly ApplicationDbContext _basecontext; 7 8 public AdminTreeServices(ApplicationDbContext context) 9 { 10 _basecontext = context; 11 } 12 13 /// <summary> 14 /// 建立无限极节点树-管理菜单 15 /// </summary> 16 /// <returns></returns> 17 public AdminUserTree GetAllTreeData 18 { 19 get 20 { 21 AdminUserTree result = new AdminUserTree(); 22 //初始化一个节点做为根节点 23 result.NodeID = 0; 24 result.Text = "管理员菜单"; 25 result.Url = ""; 26 result.ParentID = -1; 27 result.Location = ""; 28 result.OrderID = 0; 29 result.Comment = "来源为数据库"; 30 result.ImageUrl = ""; 31 result.PermissionID = 0; 32 result.Level = 0; 33 result.ChildNumberl = 0; 34 //把根节点传递给递归方法去创建子节点 35 result.ChildNode = BuildMenuTree(result, -1); 36 return result; 37 38 } 39 40 } 41 42 43 /// <summary> 44 /// 递归创建子节点方法 45 /// </summary> 46 /// <param name="node">要为其分配子节点的父级节点</param> 47 /// <param name="levelID">层级关系</param> 48 /// <returns></returns> 49 protected List<AdminUserTree> BuildMenuTree(AdminUserTree node, int levelID) 50 { 51 var listtree = _basecontext.Admintree; 52 53 //从数据库中取出node节点的全部子节点 条件:m.ParentID==node.NodeID 54 List<AdminUserTree> lt = listtree.Where(m => m.ParentID == node.NodeID) 55 .Select(m => new AdminUserTree() 56 { 57 NodeID = m.NodeID 58 , 59 Text = m.Text 60 , 61 Url = m.Url 62 , 63 ParentID = m.ParentID 64 , 65 Location = m.Location 66 , 67 OrderID = m.OrderID 68 , 69 Comment = m.Comment 70 , 71 ImageUrl = m.ImageUrl 72 , 73 PermissionID = m.PermissionID 74 }) 75 .ToList(); 76 77 if (lt != null) 78 { 79 //节点深度 80 node.Level = levelID + 1; 81 //子节点数量,便于前端递归输出时调用 82 node.ChildNumberl = lt.Count; 83 for (int i = 0; i < lt.Count; i++) 84 { 85 //递归调用创建子节点 86 lt[i].ChildNode = BuildMenuTree(lt[i], node.Level); 87 } 88 return lt; 89 90 } 91 else 92 { 93 return null; 94 } 95 96 97 98 99 } 100 101 }
AdminTreeServices
3.管理页面基类 AdminBase.cs:
1 public class AdminBase: Controller 2 { 3 /// <summary> 4 /// EF数据访问配置 5 /// </summary> 6 private readonly ApplicationDbContext _basecontext; 7 private readonly IAdminTreeService _adminTreeService; 8 private readonly ILogger<AdminUserTree> _logger; 9 private readonly IMemoryCache _memoryCache; 10 11 12 /// <summary> 13 /// 管理菜单 这里是基数,声明为属性以便控制器里面可以用到 14 /// </summary> 15 public AdminUserTree leftMenu { get; set; } 16 17 18 public AdminBase(ApplicationDbContext context, 19 IMemoryCache memoryCache, 20 ILogger<AdminUserTree> logger, 21 IAdminTreeService adminTreeService) 22 { 23 _basecontext = context; 24 //初始化无限极分类管理菜单 25 _logger = logger; 26 _adminTreeService = adminTreeService; 27 _memoryCache = memoryCache; 28 leftMenu = GetAdminTreeByCache(); 29 } 30 31 /// <summary> 32 /// 从缓存中读取节点树 33 /// </summary> 34 /// <param name="httpContext"></param> 35 /// <returns></returns> 36 public AdminUserTree GetAdminTreeByCache() 37 { 38 string cacheKey = "AdminTree-Base"; 39 AdminUserTree adminTree; 40 41 //获取缓存内容的一种方式 42 // greeting = _memoryCache.Get(cacheKey) as string; 43 44 //另一种获取缓存内容的方式, 45 // alternately, TryGet returns true if the cache entry was found 46 if (!_memoryCache.TryGetValue(cacheKey, out adminTree)) 47 { 48 //缓存中不存在该内容,从数据接口中通过访问数据库获取。 49 adminTree = _adminTreeService.GetAllTreeData; 50 51 // 绝对过期时间方法,在设置缓存时3分钟后过期 52 //_memoryCache.Set(cacheKey, adminTree, 53 // new MemoryCacheEntryOptions() 54 // .SetAbsoluteExpiration(TimeSpan.FromMinutes(3))); 55 //相对过期时间方法,相对于最后一次调用后3分钟过期 56 _memoryCache.Set(cacheKey, adminTree, 57 new MemoryCacheEntryOptions() 58 .SetSlidingExpiration(TimeSpan.FromMinutes(3))); 59 //记录日志 60 _logger.LogInformation($"{cacheKey} From Data."+DateTime.Now.AddMinutes(3).ToString()); 61 } 62 else 63 { 64 //记录日志,这里表示当前数据是从缓存中读取的。 65 _logger.LogInformation($"{cacheKey} From Cacha."+DateTime.Now.ToString()); 66 } 67 68 return adminTree; 69 70 } 71 }
AdminBase
4.最后不要忘记了在Startup.cs中加入以下代码 :
1 public void ConfigureServices(IServiceCollection services) 2 { 3 ………… 4 services.AddMemoryCache(); 5 6 services.AddTransient<IAdminTreeService, AdminTreeServices>(); 7 }
在这里基本就实现了通过缓存读取数据了!至于Controller中的实现没有变化请参考我的上篇随笔:
C#无限极分类树-创建-排序-读取 用Asp.Net Core+EF实现
为了方便阅读这里再放一遍菜单节点类代码:
1 /// <summary> 2 /// 无限极节点类 3 /// </summary> 4 public class AdminUserTree 5 { 6 /// <summary> 7 /// 节点信息 8 /// </summary> 9 public int NodeID { get; set; } 10 /// <summary> 11 /// 节点名称 12 /// </summary> 13 public string Text { get; set; } 14 /// <summary> 15 /// 父节点ID 16 /// </summary> 17 public int ParentID { get; set; } 18 /// <summary> 19 /// 对应的链接地址 20 /// </summary> 21 public string Url { get; set; } 22 public int? PermissionID { get; set; } 23 public int? OrderID { get; set; } 24 public string Location { get; set; } 25 public string Comment { get; set; } 26 public string ImageUrl { get; set; } 27 /// <summary> 28 /// 层级 29 /// </summary> 30 public int Level { get; set; } 31 /// <summary> 32 /// 子节点数目(很重要) 33 /// </summary> 34 public int ChildNumberl { get; set; } 35 36 /// <summary> 37 /// 子节点 (子节点是一个List)这种用法叫什么? 38 /// </summary> 39 public List<AdminUserTree> ChildNode { get; set; } 40 }
最后把运行时日志截图放出来:
首次访问:
上面有很多的数据库访问语句,日志提示数据来源与数据库
再次访问:
这里就是直接从缓存中读取出来的内容了!
好了今天的内容就写到这里。
按照先前的计划,下一步该研究一下用户及权限的内容了,这次我打算研究一下 ASP.NET Identity 身份验证和基于角色的授权,网上有很多相关的资料了,可我还是没有看明白怎么和我实际的项目相结合。估计要花几天时间了!