Asp.Net MVC页面静态化功能实现一:利用IHttpModule和ResultFilter

时间:2021-02-16 16:21:40

由于公司现在所采用的是一套CMS内容管理系统的框架,所以最近项目中有一个需求提到要求实现页面静态化的功能。在网上查询了一些资料和文献,最后采用的是小尾鱼的池塘提供的 利用ResultFilter实现asp.net mvc3 页面静态化实现方式,然后结合IHttpModule过滤并判断当前请求。实现代码如下:

实现思路:当服务器接收到一条Http请求后,会先进入IHttpModule并分析出当前请求的Url对应的静态页面文件路径。如果该文件存在,直接返回静态页面给客户端,终止本次请求。如果静态页面不存在,则通过路由进入对应的Controller和Action,在服务器即将将html代码返回给客户端的时候,捕获html代码,并保存为html文件。

一、 添加一个自定义一个筛选器

1、定义一个类WriteHtmlAttributeWrapper,该类继承自Stream。该代码主要是实现了重写Write方法,将Response.Filter中流的数据保存为html文件(Response.Filter包含即将返回给本次请求客户端的html代码)。

/// <summary>
/// 重写筛选器,将返回客户端的html代码保存到本地文件
/// </summary>
/// <remarks>
/// 作者: Traicne
/// 时间: 2015-10-19
/// </remarks>
public class WriteHtmlAttributeWrapper : System.IO.Stream
{
private System.IO.Stream inner;
private ControllerContext context;
public WriteHtmlAttributeWrapper(System.IO.Stream s, ControllerContext context)
{
this.inner = s;
this.context = context;
} public override bool CanRead
{
get { return inner.CanRead; }
} public override bool CanSeek
{
get { return inner.CanSeek; }
} public override bool CanWrite
{
get { return inner.CanWrite; }
} public override void Flush()
{
inner.Flush();
} public override long Length
{
get { return inner.Length; }
} public override long Position
{
get { return inner.Position; }
set { inner.Position = value; }
} public override int Read(byte[] buffer, int offset, int count)
{
return inner.Read(buffer, offset, count);
} public override long Seek(long offset, System.IO.SeekOrigin origin)
{
return inner.Seek(offset, origin);
} public override void SetLength(long value)
{
inner.SetLength(value);
} public override void Write(byte[] buffer, int offset, int count)
{
inner.Write(buffer, offset, count);//当前请求不是后台管理;并且返回客户端是html才生成静态页面
if (!context.HttpContext.Request.FilePath.ToLower().StartsWith("/admin") && context.HttpContext.Response.ContentType.Equals("text/html"))
{
//静态页面保存路径信息
string htmlPath = context.HttpContext.Request.HtmlFilePath();
string direcHtmlPath = Path.GetDirectoryName(htmlPath);
if (!Directory.Exists(direcHtmlPath))
{
Directory.CreateDirectory(direcHtmlPath);
}
//获取返回客户端的html代码,并进行压缩处理
string htmlCode = System.Text.Encoding.UTF8.GetString(buffer);
htmlCode = Regex.Replace(htmlCode, "^\\s*", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
htmlCode = Regex.Replace(htmlCode, "\\r\\n", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
htmlCode = Regex.Replace(htmlCode, "<!--*.*?-->", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
//保存文件,这里不能用File.WriteAllText
File.AppendAllText(htmlPath, htmlCode);
}
}
}

2、添加一个自定义类WriteHtmlAttribute,该类继承自FilterAttribute, IResultFilter。

/// <summary>
/// 自定义筛选器
/// </summary>
/// <remarks>
/// 作者: Traicne
/// 时间: 2015-10-19
/// </remarks>
public class WriteHtmlAttribute : FilterAttribute, IResultFilter
{
/// <summary>
/// 在操作结果执行后调用
/// </summary>
public void OnResultExecuted(ResultExecutedContext filterContext)
{
//当View()中存在@Html.Action时,会出现“不允许进行筛选”的错误,这时需要判断当前Filter是否为空
//当Action中存在重定向RedirectToAction时,会出现“Object Move To Here”的错误信息,这时需要判断当前返回给客户端的状态码
//if (filterContext.HttpContext.Response.Filter != null)
if (filterContext.HttpContext.Response.Filter != null && filterContext.HttpContext.Response.StatusCode.Equals())
{
//重写筛选器对象
filterContext.HttpContext.Response.Filter = new WriteHtmlAttributeWrapper(filterContext.HttpContext.Response.Filter, filterContext);
}
} /// <summary>
/// 在操作结果执行之前调用
/// </summary>
public void OnResultExecuting(ResultExecutingContext filterContext)
{ }
}

该类有两点点需要注意的地方

第一:需要对filterContext.HttpContext.Response.Filter是否为Null的判断。因为当你的Action返回的视图View()中存在子Action时,OnResultExecuted()会被执行多次(自定义筛选器WriteHtmlAttribute是对于每一个Action都有效)

第二:需要对返回给客户端的状态码做一个判断,即filterContext.HttpContext.Response.StatusCode.Equals(200)。因为当的Action中存在某个重定向操作RedirectToAction到另外一个Action的时候,会出现重定向到的页面出现“Object Move To Here”这样的内容信息。

3、添加自定义筛选器

添加自定义筛选器可以在Global.asax文件的Application_Start方法中添加,也可以在FilterConfig类中的RegisterGlobalFilters方法中添加。二者都是向GlobalFilterCollection中添加一个筛选器,都是一样的效果。

//FilterConfig类中的RegisterGlobalFilters方法中添加如下代码
filters.Add(new WriteHtmlAttribute());
//或者在Global.asax文件Application_Start方法添加这段代码
GlobalFilters.Filters.Add(new WriteHtmlAttribute());

二、添加一个自定义IHttpModule类

添加一个类RouterHttpModule,该类继承自 IHttpModule接口。该类主要功能是对Http请求进行过滤(比如过滤掉资源文件或表单请求),然后根据Url分析出静态页面文件地址,并判断静态页面html文件是否存在,如果存在直接返回静态页面给客户端,否则进入应用程序。

/// <summary>
/// 自定义IHttpModule,分析当前请求的Url信息
/// </summary>
/// <remarks>
/// 作者: Traicne
/// 时间: 2015-10-20
/// </remarks>
public class RouterHttpModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.BeginRequest += this.Application_BeginRequest; //注册事件
} private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
string filePath = context.Request.FilePath;
string fileExtension = VirtualPathUtility.GetExtension(filePath);
//如果当前请求的不是资源文件、不是后台管理、请求中没有表单信息、静态页面存在,则返回静态页面
if (string.IsNullOrEmpty(fileExtension) && !filePath.ToLower().StartsWith("/admin") && context.Request.Form.Count.Equals())
{
string htmlPath = context.Request.HtmlFilePath();
if (File.Exists(htmlPath))
{
context.Response.WriteFile(htmlPath);
context.Response.End();
}
}
} public void Dispose() { }
}

然后需要在配置文件Web.config中添加节点,应用程序才会执行刚刚自定义的IHttpModule对象。IIS6和IIS7添加节点的方式也不同

<!--IIS6-->
<system.web>
<httpModules>
<add name="RouterHttpModule" type="GTA.CMS.Site.Web.Common.RouterHttpModule,GTA.CMS.Site.Web"/>
</httpModules>
</system.web> <!--IIS7-->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="RouterHttpModule" type="GTA.CMS.Site.Web.Common.RouterHttpModule,GTA.CMS.Site.Web"/>
</modules>
</system.webServer>
 下一篇将会提到后来我在实现过程中将ResultFilter自定义筛选器实现的功能转移到IHttpModule中来实现 Asp.Net MVC页面静态化功能实现一:利用IHttpModule,摒弃ResultFilter

最后这里要注意的是:IHttpModule通过Url分析出来的静态页面文件路径和自定义筛选器通过Url分析出来的静态页面文件路径要一致