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

由于公司现在所采用的是一套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);
            }
        }
    }
View Code

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(200))
        {
            //重写筛选器对象
            filterContext.HttpContext.Response.Filter = new WriteHtmlAttributeWrapper(filterContext.HttpContext.Response.Filter, filterContext);
        }
    }

    /// <summary>
    /// 在操作结果执行之前调用
    /// </summary>
    public void OnResultExecuting(ResultExecutingContext filterContext)
    {
        
    }
}
View Code

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

第一:需要对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(0))
        {
            string htmlPath = context.Request.HtmlFilePath();
            if (File.Exists(htmlPath))
            {
                context.Response.WriteFile(htmlPath);
                context.Response.End();
            }
        }
    }

    public void Dispose() { }
}
View Code

然后需要在配置文件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>
View Code
 下一篇将会提到后来我在实现过程中将ResultFilter自定义筛选器实现的功能转移到IHttpModule中来实现 Asp.Net MVC页面静态化功能实现一:利用IHttpModule,摒弃ResultFilter

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

 

posted @ 2015-11-03 15:00  Tracine0513  阅读(667)  评论(2编辑  收藏  举报