ASP.NET MVC 实现页面静态化
作者:Dreamer
出处:https://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类实现