asp.net mvc Partial OutputCache在SpaceBuilder中的应用实践

最近给SpaceBuilder增加OutputCache 时发现了一些问题,贴在这做个备忘,也方便遇到类似问题的朋友查阅。

目前SpaceBuilder 表现层使用是asp.net mvc v1.0,使用了很多RenderAction(关于asp.net mvc的Partial Requests参见Partial Requests in ASP.NET MVC) 。希望对于实时性要求不高的内容区域采用客户端缓存来提升性能同时也弥补一下RenderAction对性能的损失。

使用asp.net mvc自带 的OutputCache Filter时发现了一个可怕的bug,在View中任何一个RenderAction设置OutputCache却影响了整个View。搜索发现确实是asp.net mvc目前已知的一个bug ,关于该问题的解决也有很多人提出了自己的方法。

关于asp.net mvc的缓存,Haacked写了两篇文章:

Donut Caching in ASP.NET MVC 介绍的是缓存整个页面,对于一部分内容禁用缓存,是在mvc中实现的WebForm的Substitution功能。 存在以下弊端:当前一个View中有多个区域需要禁用缓存时使用比较麻烦,另外不能实现对页面的不同的区域使用不同的过期策略。

Donut Hole Caching in ASP.NET MVC介 绍的是我想要的功能,即只缓存页面的部分区域。但是弊端也非常明显:只能通过WebForm中 的声明方式来使用用户控件(:),现在已经有点不适应这种方 式了,而且必须使用WebFormViewEngine),无法直接使用RenderPartial,而且 还必须设置强类型的ViewPage,确保在用 户控件中的Model与View中的Model相同。使用太麻烦,限制也多。

Maarten Balliauw在 Creating an ASP.NET MVC OutputCache ActionFilterAttribute 和Extending ASP.NET MVC OutputCache ActionFilterAttribute - Adding substitution   也提出了一个完整的OutputCache解决方案。但是经测试启用客户端缓存时同样会产生与RenderAction同样的问题,还没有 时间彻查这个问题,先把客户端缓存禁用,暂时使用服务器端缓存应付一阵。

以Maarten Balliauw的代码为原型,编写了SpaceBuilder 的ActionOutputCacheAttribute:

public class ActionOutputCacheAttribute : ActionFilterAttribute
    {
        private static MethodInfo _switchWriterMethod = typeof(HttpResponse).GetMethod("SwitchWriter", BindingFlags.Instance | BindingFlags.NonPublic);

        public ActionOutputCacheAttribute(int cacheDuration)
        {
            _cacheDuration = cacheDuration;
        }

        //目前还不能设置为Client缓存,会与OutputCache同样的问题
        private CachePolicy _cachePolicy = CachePolicy.Server;
        private int _cacheDuration;
        private TextWriter _originalWriter;
        private string _cacheKey;

        public override void OnActionExecuting (ActionExecutingContext filterContext)
        {
            // Server-side caching?
            if (_cachePolicy == CachePolicy.Server || _cachePolicy == CachePolicy.ClientAndServer)
            {
                _cacheKey = GenerateCacheKey(filterContext);
                CacheContainer cachedOutput = (CacheContainer)filterContext.HttpContext.Cache[_cacheKey];
                if (cachedOutput != null)
                {
                    filterContext.HttpContext.Response.ContentType = cachedOutput.ContentType;
                    filterContext.Result = new ContentResult { Content = cachedOutput.Output };
                }
                else
                {
                    StringWriter stringWriter = new StringWriterWithEncoding (filterContext.HttpContext.Response.ContentEncoding);
                    HtmlTextWriter newWriter = new HtmlTextWriter(stringWriter);
                    _originalWriter = (TextWriter)_switchWriterMethod.Invoke(HttpContext.Current.Response, new object[] { newWriter });
                }
            }
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            // Server-side caching?
            if (_cachePolicy == CachePolicy.Server || _cachePolicy == CachePolicy.ClientAndServer)
            {
                if (_originalWriter != null) // Must complete the caching
                {
                    HtmlTextWriter cacheWriter = (HtmlTextWriter)_switchWriterMethod.Invoke (HttpContext.Current.Response, new object[] { _originalWriter });
                    string textWritten = ((StringWriter)cacheWriter.InnerWriter).ToString();
                    filterContext.HttpContext.Response.Write(textWritten);
                    CacheContainer container = new CacheContainer(textWritten, filterContext.HttpContext.Response.ContentType);
                    filterContext.HttpContext.Cache.Add(_cacheKey, container, null, DateTime.Now.AddSeconds(_cacheDuration), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
                }
            }
        }

        private string GenerateCacheKey(ActionExecutingContext filterContext)
        {
            StringBuilder cacheKey = new StringBuilder("OutputCacheKey:");

             // Controller + action
            cacheKey.Append(filterContext.Controller.GetType().FullName.GetHashCode());
            if (filterContext.RouteData.Values.ContainsKey("action"))
            {
                cacheKey.Append("_");
                cacheKey.Append(filterContext.RouteData.Values["action"].ToString());
            }

            foreach (KeyValuePair<string, object> pair in filterContext.ActionParameters)
            {
                cacheKey.Append("_");
                cacheKey.Append(pair.Key);
                cacheKey.Append("=");

                if (pair.Value != null)
                    cacheKey.Append(pair.Value.ToString());
                else
                    cacheKey.Append(string.Empty);
            }

            return cacheKey.ToString();
        }

        private class CacheContainer
        {
            public string Output;
            public string ContentType;
            public CacheContainer(string data, string contentType)
            {
                Output = data;
                ContentType = contentType;
            }
        }

        public enum CachePolicy
        {
            NoCache = 0,
            Client = 1,
            Server = 2,
            ClientAndServer = 3
        }
    }

     {
 encoding;

StringWriterWithEncoding

public class StringWriterWithEncoding : StringWriter
    {
        Encoding encoding;
        public StringWriterWithEncoding(Encoding encoding)
        {
            this.encoding = encoding;
        }
        public override Encoding Encoding
        {
            get { return encoding; }
        }
}

时间: 2024-12-03 19:19:19

asp.net mvc Partial OutputCache在SpaceBuilder中的应用实践的相关文章

ASP.NET MVC做的微信WEBAPP中调用微信JSSDK扫一扫

今天做一个项目,是在微信上用的,微信WEB APP,里面用到了调用手机摄像头扫一扫二维码的功能,记得以前某个项目里写有的,但是找不到之前那个项目源码了,想复制粘贴也复制不了了,只好对着微信的那个开发文档重新再写过 ,顺便写个博客,以后碰到相同的问题直接复制博客里的代码就行了 以下是显示在微信上的页面:   以下是页面的代码,(用到了MUI):   @{     Layout = "~/Views/Shared/_Layout.cshtml"; }     <header clas

Asp.net Mvc Framework可以在Controller中使用的Url.Action方法

原本的Url.Action方法是利用RouteCollection来实现Url的Routing的. 所以这里用一个扩展方法重现一下 using System.Web.Routing; static public class CUrl { public static string Action(this Controller c, string controller, string action) { RouteValueDictionary rvd = new RouteValueDiction

[转自JeffreyZhao]不妨来做个尝试:UpdatePanel for ASP.NET MVC

原文地址:http://www.cnblogs.com/JeffreyZhao/archive/2008/04/27/try-to-build-an-updatepanel-for-asp-dot-net-mvc.html 先来发一通牢骚. 其实这是一篇迟发布近2个月的文章.事实上在ASP.NET MVC Preview 2发布之前我就已经将这篇文章的所有内容准备完毕了.当时想,就等Preview 2发布吧,而真一旦Preview 2发布之后却又懒得进行移植--移植了之后却又懒得写文章.这一拖就

通过 SignalR 类库,实现 ASP.NET MVC 的实时通信

在本文中,您将学到在现有 ASP.NET MVC 框架的 CRUD 项目中,如何使用 SignalR 类库,显示来自数据库的实时更新.在这一主题中,我们将重点放在在现有 ASP.NET MVC 框架的 CRUD 项目中,如何使用 SignalR 类库,显示来自数据库的实时更新. 本文系国内 ITOM 管理平台 OneAPM 工程师编译整理. 本主题有以下两个步骤: 我们将创建一个示例应用程序来执行 CRUD 操作. 我们将使用 SignalR 类库让应用实时. 那些不熟悉 SignalR 的,请

[转自Scott]ASP.NET MVC框架(第四部分): 处理表单编辑和提交场景

英文原文地址:http://weblogs.asp.net/scottgu/archive/2007/12/09/asp-net-mvc-framework-part-4-handling-form-edit-and-post-scenarios.aspx 翻译原文地址:http://blog.joycode.com/scottgu/archive/2007/12/10/112465.aspx 过去的几个星期内,我一直在写着讨论我们正在开发的新ASP.NET MVC框架的系列贴子.ASP.NET

ASP.NET MVC数据验证

关于ASP.NET MVC的验证,用起来很特别,因为MS的封装,使人理解起来很费解.也可能很多人都在Scott Guthrie等人写的一本<ASP.NET MVC 1.0>书中,见过NerdDinner项目中对Dinner对象修改和添加的时的数据验证.但有许多封装的地方,不知道是怎样的工作原理,今天研究了,拿出来给大家分享一下. 数据库还是上一篇blog中的库与表,同样的方法来创建news表的实体类,在自动生成的news这个实体类中,我们发现有一个特殊的分部方法: partial void O

艾伟_转载:ASP.NET MVC数据验证

关于ASP.NET MVC的验证,用起来很特别,因为MS的封装,使人理解起来很费解.也可能很多人都在Scott Guthrie等人写的一本<ASP.NET MVC 1.0>书中,见过NerdDinner项目中对Dinner对象修改和添加的时的数据验证.但有许多封装的地方,不知道是怎样的工作原理,今天研究了,拿出来给大家分享一下. 数据库还是上一篇blog中的库与表,同样的方法来创建news表的实体类,在自动生成的news这个实体类中,我们发现有一个特殊的分部方法: partial void O

给ASP.NET MVC及WebApi添加路由优先级(1)

一.为什么需要路由优先级 大家都知道我们在Asp.Net MVC项目或WebApi项目中注册路由是没有优先级的,当项目比较大.或有多个区域.或多个Web项目.或采用插件式框架开发时,我们的路由注册很可能 不是写在一个文件中的,而是分散在很多不同项目的文件中,这样一来,路由的优先级的问题就突显出来了. 比如: App_Start/RouteConfig.cs中 routes.MapRoute(      name: "Default",      url: "{controll

ASP.NET MVC的Model元数据提供机制的实现

在前面的介绍中我们已经提到过表示Model元数据的ModelMetadata对象最终是通过一个名为ModelMetadataProvider的组件提供的,接下来我们着重讨论基于ModelMetadataProvider的Model元数据提供机制及其扩展. 一. ModelMetadataProvider 在ASP.NET MVC的Model元数据相关的应用编程接口中,用于创建Model元数据的ModelMetadataProvider接继承自抽象类ModelMetadataProvider.如下