ASP.NET MVC 实现页面静态化
作者:Dreamer
出处:http://www.dreamerlzy.com/blog/article/detail/asp.net-mvc-staticize
说明:本文版权归作者所有,欢迎转载,但未经作者同意时,请在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。
参考:
ASP.NET MVC 页面静态化操作的思路 ASP.NET MVC 使用视图引擎实现页面静态化 利用ResultFilter实现asp.net mvc3 页面静态化 关于ASP.NET MVC生成纯静态后如何不再走路由直接访问静态页面 asp.net mvc3的静态化实现
静态化的几种思路
使用html模板搭配自定义占位符标识 如vtemplate等
有额外的学习成本
程序代码中拼接html生成
此方式代码繁琐,后期维护困难,且前后端耦合严重。
模拟访问自身站点后获取源码
需要准备2个站点 一个用于访问的原网站,一套供用户访问的静态网站
但此方式有较严重的性能问题 原理与爬虫抓取网站类似
通过asp.net webform或asp.net mvc本身的绑定,在输出流阶段时获取源码另存为html文件
较完美,但此方案mvc中博主没有实践成功,存在上下文(context)的问题
webform中可使用Server.Execute实现
在用户访问时首先会检测站点是否存在对应的静态文件,存在则直接输出静态文件内容,否则程序往下至输出流阶段,在此时存储流为静态页面
较完美,且生成文件的操作只有在用户首次访问时才会进行(页面过期等操作可通过代码进行更细致的控制),但存在所有请求仍然会走IIS的问题。
本文介绍的是方案5的实践 站点方式为asp.net mvc
1、首先在Global.asax中的Application_BeginRequest方法时检测html文件是否已存在
protected void Application_BeginRequest(object sender, EventArgs e) { StaticContentRewrite(); } //检测html文件是否已存在 存在则直接输出 否则程序往下进行 private void StaticContentRewrite() { LogMethod.WriteLog("Debug", "StaticContentRewrite"); string nowpath = Context.Request.FilePath.ToLower(); if (nowpath == "/" || nowpath.StartsWith("/index.html", StringComparison.OrdinalIgnoreCase))//首页 { if (File.Exists(Server.MapPath("index.html"))) { Context.RewritePath("index.html"); } else { Context.RewritePath("/index/index.html"); } } else { nowpath = nowpath.LastIndexOf("/") == nowpath.Length - 1 ? nowpath.Substring(0, nowpath.Length - 2) : nowpath; LogMethod.WriteLog("Debug", "StaticContentRewriteReadHtml" + nowpath); string temp = "/html" + nowpath; if (File.Exists(Server.MapPath(temp))) { Context.RewritePath(temp); } } }
2、实现特性类
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] public class GenerateStaticFileAttribute : ActionFilterAttribute { #region 公共属性 /// <summary> /// 过期时间,以小时为单位 /// </summary> public int Expiration { get; set; } /// <summary> /// 文件后缀名 /// </summary> public string Suffix { get; set; } /// <summary> /// 缓存目录 /// </summary> public string CacheDirectory { get; set; } /// <summary> /// 指定生成的文件名 /// </summary> public string FileName { get; set; } #endregion #region 构造函数 /// <summary> /// 默认构造函数 /// </summary> public GenerateStaticFileAttribute() { Expiration = 720; //Suffix = "html"; CacheDirectory = AppDomain.CurrentDomain.BaseDirectory; } #endregion #region 方法 public override void OnResultExecuted(ResultExecutedContext filterContext) { if (filterContext.RequestContext.HttpContext.Request.Url.ToString().Contains(".html") == false) { return; } var fileInfo = GetCacheFileInfo(filterContext); if ((fileInfo.Exists && fileInfo.CreationTime.AddHours(Expiration) < DateTime.Now) || !fileInfo.Exists) { var deleted = false; try { if (fileInfo.Exists) { fileInfo.Delete(); } deleted = true; } catch (Exception ex) { LogMethod.WriteLog("错误", string.Format("删除文件:{0}发生异常:{1}", fileInfo.FullName, ex.StackTrace)); } var created = false; try { if (!fileInfo.Directory.Exists) { fileInfo.Directory.Create(); } created = true; } catch (IOException ex) { LogMethod.WriteLog("错误", string.Format("创建目录:{0}发生异常:{1}", fileInfo.DirectoryName, ex.StackTrace)); } if (deleted && created) { FileStream fileStream = null; StreamWriter streamWriter = null; try { var viewResult = filterContext.Result as ViewResult; if (viewResult != null) { fileStream = new FileStream(fileInfo.FullName, FileMode.CreateNew, FileAccess.Write, FileShare.None); streamWriter = new StreamWriter(fileStream); var viewContext = new ViewContext(filterContext.Controller.ControllerContext, viewResult.View, viewResult.ViewData, viewResult.TempData, streamWriter); viewResult.View.Render(viewContext, streamWriter); } } catch (Exception ex) { LogMethod.WriteLog("错误", string.Format("生成缓存文件:{0}发生异常:{1}", fileInfo.FullName, ex.StackTrace)); } finally { if (streamWriter != null) { streamWriter.Close(); } if (fileStream != null) { fileStream.Close(); } } } } } /// <summary> /// 生成文件Key /// </summary> /// <param name="controllerContext">ControllerContext</param> /// <returns>文件Key</returns> protected virtual string GenerateKey(ControllerContext controllerContext) { var url = controllerContext.HttpContext.Request.Url.ToString(); if (string.IsNullOrWhiteSpace(url)) { return null; } string key = url.GetHtmlUrlByCA(); //var th = new TigerHash(); //var data = th.ComputeHash(Encoding.Unicode.GetBytes(url)); //var key = Convert.ToBase64String(data, Base64FormattingOptions.None); //var key = Guid.NewGuid().ToString(); //key = HttpUtility.UrlEncode(key); return key; } /// <summary> /// 获取静态的文件信息 /// </summary> /// <param name="controllerContext">ControllerContext</param> /// <returns>缓存文件信息</returns> protected virtual FileInfo GetCacheFileInfo(ControllerContext controllerContext) { var fileName = string.Empty; if (string.IsNullOrWhiteSpace(FileName)) { var key = GenerateKey(controllerContext); if (!string.IsNullOrWhiteSpace(key)) { fileName = Path.Combine(CacheDirectory, string.IsNullOrWhiteSpace(Suffix) ? key : string.Format("{0}.{1}", key, Suffix)); } } else { fileName = Path.Combine(CacheDirectory, FileName); } return new FileInfo(fileName); } }
3、在需要静态化的地方注册特性 可对整个controller或单个action注册
说明:后缀可任意修改,页面链接的控制可通过扩展urlhelper类实现