从零开始,搭建博客系统MVC5+EF6搭建框架(3),添加Nlog日志、缓存机制(MemoryCache、RedisCache)、创建控制器父类BaseController
一、回顾系统进度以及本章概要
目前博客系统已经数据库创建、以及依赖注入Autofac集成,接下来就是日志和缓存集成,这里日志用的是Nlog,其实还有其他的日志框架如log4,这些博客园都有很多介绍,这里就不说了,缓存机制用的是微软自带的MemoryCache和比较流行Redis,这里我也只是了解使用,没有做更升入的研究,以后好好学一下Redis,然后就是实现一个BaseController父类用来重写JsonResult方法为的是返回时间格式问题,默认json返回的时间格式是Date(84923838332223)转为常见的yyyy-MM-dd HH:mm:ss格式。
二、缓存机制实现
1、在公共程序集中创建连个文件加一个Cache用来存放缓存类,一个是Log是用来创建Nlog类,这里都使用接口来实现,以便可以以后可以多个实现。
2、首先创建一个ICacheManager接口类。
1 namespace Wchl.WMBlog.Common.Cache 2 { 3 public interface ICacheManager 4 { 5 /// <summary> 6 /// 获取 7 /// </summary> 8 /// <typeparam name="TEntity"></typeparam> 9 /// <param name="key"></param> 10 /// <returns></returns> 11 TEntity Get<TEntity>(string key); 12 //设置 13 void Set(string key, object value, TimeSpan cacheTime); 14 //判断是否存在 15 bool Contains(string key); 16 //移除 17 void Remove(string key); 18 //清除 19 void Clear(); 20 21 } 22 }
3、在实现微软缓存机制的时候需要引用System.Runtime.Caching.dll,创建一个MemoryCacheManager 类
1 namespace Wchl.WMBlog.Common.Cache 2 { 3 public class MemoryCacheManager : ICacheManager 4 { 5 public void Clear() 6 { 7 8 foreach (var item in MemoryCache.Default) 9 { 10 this.Remove(item.Key); 11 } 12 } 13 14 public bool Contains(string key) 15 { 16 return MemoryCache.Default.Contains(key); 17 } 18 19 public TEntity Get<TEntity>(string key) 20 { 21 return (TEntity)MemoryCache.Default.Get(key); 22 } 23 24 public void Remove(string key) 25 { 26 MemoryCache.Default.Remove(key); 27 } 28 29 public void Set(string key, object value, TimeSpan cacheTime) 30 { 31 MemoryCache.Default.Add(key, value, new CacheItemPolicy { SlidingExpiration = cacheTime }); 32 } 33 } 34 }
4、实现RedisCacheManager类,这里我们使用的免费的Redis客服端是StackExchange.Redis.可以在nuget中下载到。
RedisCacheManager类
1 namespace Wchl.WMBlog.Common.Cache 2 { 3 public class RedisCacheManager : ICacheManager 4 { 5 private readonly string redisConnenctionString; 6 7 public volatile ConnectionMultiplexer redisConnection; 8 9 private readonly object redisConnectionLock = new object(); 10 11 public RedisCacheManager() 12 { 13 //链接redis服务语句 14 string redisConfiguration = ConfigurationManager.ConnectionStrings["redisCache"].ToString(); 15 16 if (string.IsNullOrWhiteSpace(redisConfiguration)) 17 { 18 throw new ArgumentException("redis config is empty", nameof(redisConfiguration)); 19 } 20 this.redisConnenctionString = redisConfiguration; 21 this.redisConnection = GetRedisConnection(); 22 } 23 24 private ConnectionMultiplexer GetRedisConnection() 25 { 26 if (this.redisConnection != null && this.redisConnection.IsConnected) 27 { 28 return this.redisConnection; 29 } 30 lock (redisConnectionLock) 31 { 32 if (this.redisConnection != null) 33 { 34 //释放redis连接 35 this.redisConnection.Dispose(); 36 } 37 this.redisConnection = ConnectionMultiplexer.Connect(redisConnenctionString); 38 } 39 return this.redisConnection; 40 } 41 42 public void Clear() 43 { 44 foreach (var endPoint in this.GetRedisConnection().GetEndPoints()) 45 { 46 var server = this.GetRedisConnection().GetServer(endPoint); 47 foreach (var key in server.Keys()) 48 { 49 redisConnection.GetDatabase().KeyDelete(key); 50 } 51 } 52 } 53 54 public bool Contains(string key) 55 { 56 return redisConnection.GetDatabase().KeyExists(key); 57 } 58 59 public TEntity Get<TEntity>(string key) 60 { 61 var value = redisConnection.GetDatabase().StringGet(key); 62 if (value.HasValue) 63 { 64 return SerializeHelper.Deserialize<TEntity>(value); 65 } else 66 { 67 return default(TEntity); 68 } 69 } 70 71 public void Remove(string key) 72 { 73 redisConnection.GetDatabase().KeyDelete(key); 74 } 75 76 public void Set(string key, object value, TimeSpan cacheTime) 77 { 78 if (value != null) 79 { 80 redisConnection.GetDatabase().StringSet(key, SerializeHelper.Serialize(value), cacheTime); 81 } 82 } 83 } 84 }
这里在存储数据的时候使用到了序列化和反序列化,用的序列化工具是Newtonsoft.Json,同样也可以在nuget中找到。
SerializeHelper序列化帮助类
1 namespace Wchl.WMBlog.Common 2 { 3 public class SerializeHelper 4 { 5 /// <summary> 6 /// 序列化 7 /// </summary> 8 /// <param name="item"></param> 9 /// <returns></returns> 10 public static byte[] Serialize(object item) 11 { 12 var jsonString = JsonConvert.SerializeObject(item); 13 14 return Encoding.UTF8.GetBytes(jsonString); 15 } 16 /// <summary> 17 /// 反序列化 18 /// </summary> 19 /// <typeparam name="TEntity"></typeparam> 20 /// <param name="value"></param> 21 /// <returns></returns> 22 public static TEntity Deserialize<TEntity>(byte[] value) 23 { 24 if (value == null) 25 { 26 return default(TEntity); 27 } 28 var jsonString = Encoding.UTF8.GetString(value); 29 return JsonConvert.DeserializeObject<TEntity>(jsonString); 30 } 31 } 32 }
三、日志处理:Nlog日志框架
1、首先实现一个日子接口ILogger
1 namespace Wchl.WMBlog.Common.Log 2 { 3 public interface ILogger 4 { 5 void Debug(string message); 6 void Debug(string message, Exception exception); 7 void Error(string message); 8 void Error(string message, Exception exception); 9 void Fatal(string message); 10 void Fatal(string message, Exception exception); 11 void Info(string message); 12 void Info(string message, Exception exception); 13 void Warn(string message); 14 void Warn(string message, Exception exception); 15 } 16 }
2.在nuget中添加Nlog框架
nlog.config是日志框架的配置文件。
Nloglogger类
1 namespace Wchl.WMBlog.Common.Log 2 { 3 public class NLogLogger : ILogger 4 { 5 private readonly Logger logger = LogManager.GetCurrentClassLogger(); 6 public void Debug(string message) 7 { 8 logger.Debug(message); 9 } 10 11 public void Debug(string message, Exception exception) 12 { 13 logger.Debug(exception, message); 14 } 15 16 public void Error(string message) 17 { 18 logger.Error(message); 19 } 20 21 public void Error(string message, Exception exception) 22 { 23 logger.Error(exception, message); 24 } 25 26 public void Fatal(string message) 27 { 28 logger.Fatal(message); 29 } 30 31 public void Fatal(string message, Exception exception) 32 { 33 logger.Fatal(exception, message); 34 } 35 36 public void Info(string message) 37 { 38 logger.Info(message); 39 } 40 41 public void Info(string message, Exception exception) 42 { 43 logger.Info(exception, message); 44 } 45 46 public void Warn(string message) 47 { 48 logger.Warn(message); 49 } 50 51 public void Warn(string message, Exception exception) 52 { 53 logger.Warn(exception, message); 54 } 55 } 56 }
3、配置日志文件NLog.config,这里是在webUI层应用这个文件,因为最终日志是在web下运行。
在targets的节点下面配置,这里是以文件的方式保存日子,你也可以使用这个配置一个直接把日子写到数据库中
1 <target xsi:type ="File" 2 name="file" 3 header="------------------------------Start------------------------------" 4 footer="------------------------------End------------------------------" 5 fileName="${basedir}/App_Data/Logs/${shortdate}.log" 6 layout="${longdate} - ${level:uppercase=true}:${message} ${callsite:fileName=true} ${exception:format=Type,Message,Method,StackTrace:maxInnerExceptionLevel=5:innerFormat=ShortType,Message,Method,StackTrace}" 7 keepFileOpen="false" 8 archiveFileName="${basedir}/App_Data/Logs/Backup_${shortdate}.{##}.log" 9 archiveNumbering="Sequence" 10 archiveEvery="Day" 11 maxArchiveFiles="30"> 12 13 </target>
在rules节点下配置 <logger name="*" minlevel="Error" writeTo="file" />表示什么级别的日志对应放在哪个配置里面。
这里日志保存在发布站点App_Data\Logs下
4、日志测试
4.1在测试之前首先设置一个全局错误机制文件ExpFilter继承HandleErrorAttribute,放在Webcore下面
这里需要添加System.Web.Mvc.dll程序集。
ExpFilter类:
1 namespace Wchl.WMBlog.WebCore 2 { 3 public class ExpFilter:HandleErrorAttribute 4 { 5 public override void OnException(ExceptionContext filterContext) 6 { 7 Exception exp = filterContext.Exception; 8 9 //获取ex的第一级内部异常 10 Exception innerEx = exp.InnerException == null ? exp : exp.InnerException; 11 //循环获取内部异常直到获取详细异常信息为止 12 while (innerEx.InnerException!=null) 13 { 14 innerEx = innerEx.InnerException; 15 } 16 NLogLogger nlog = new NLogLogger(); 17 if (filterContext.HttpContext.Request.IsAjaxRequest()) 18 { 19 20 nlog.Error(innerEx.Message); 21 JsonConvert.SerializeObject(new { status = 1, msg ="请求发生错误,请联系管理员"}); 22 } 23 else 24 { 25 nlog.Error("Error",exp); 26 ViewResult vireResult = new ViewResult(); 27 vireResult.ViewName = "/Views/Shared/Error.cshtml"; 28 filterContext.Result = vireResult; 29 } 30 31 //告诉MVC框架异常被处理 32 filterContext.ExceptionHandled = true; 33 base.OnException(filterContext); 34 } 35 } 36 } 37
4.2这里对两种请求方式做处理一种是Ajax请求,一种是对链接地址做处理,另外还需要在webui下创建一个错误提醒页面。(/Views/Shared/Error.cshtml)
4.3在homecontroller控制器下写错误代码
4.4日志测试结果:这里直接开始执行(不调试)
然后在项目文件下查看web站点下的\App_Data\Logs查看日子文件
日志信息:错误信息,以及错误是那个文件多少行都有显示。
四、创建BaseController类
这里使用反序列化工具都是Newtonsoft.Json
BaseController类:
1 namespace Wchl.WMBlog.WebCore 2 { 3 public class BaseController: Controller 4 { 5 protected override JsonResult Json(object data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior) 6 { 7 return new JsonNetResult { Data = data, ContentType = contentType, ContentEncoding = contentEncoding, JsonRequestBehavior = behavior }; 8 } 9 } 10 }
JsonNetResult类:
1 namespace Wchl.WMBlog.WebCore 2 { 3 public class JsonNetResult:JsonResult 4 { 5 public override void ExecuteResult(ControllerContext context) 6 { 7 if (context==null) 8 { 9 throw new ArgumentException(nameof(context)); 10 } 11 12 var response = context.HttpContext.Response; 13 14 response.ContentType = !string.IsNullOrEmpty(ContentType) ? ContentType : "application/json"; 15 16 if (ContentEncoding != null) 17 { 18 response.ContentEncoding = ContentEncoding; 19 } 20 21 var jsonSerializerSetting = new JsonSerializerSettings(); 22 //首字母小写 23 jsonSerializerSetting.ContractResolver = new CamelCasePropertyNamesContractResolver(); 24 //日期格式化 25 jsonSerializerSetting.DateFormatString = "yyyy-MM-dd HH:mm:ss"; 26 var json = JsonConvert.SerializeObject(Data, Formatting.None, jsonSerializerSetting); 27 28 response.Write(json); 29 30 } 31 } 32 }
直接在创建的控制器下集成:
接下来就是准备实现页面布局,先做个简单的前台查看,后台分布的功能,然后在一步一步的完善,希望大家多多指点,多多支持,谢谢了。
10月10日补充说明:
全局错误处理机制需要在FilterConfig中配置,才能起作用。感谢园友1非烟的指正