最近给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:
publicclass ActionOutputCacheAttribute : ActionFilterAttribute 
{
privatestatic MethodInfo _switchWriterMethod =typeof(HttpResponse).GetMethod("SwitchWriter", BindingFlags.Instance | BindingFlags.NonPublic);
public ActionOutputCacheAttribute(int cacheDuration) 
{
_cacheDuration = cacheDuration;
}
//目前还不能设置为Client缓存,会与OutputCache同样的问题
private CachePolicy _cachePolicy = CachePolicy.Server;
privateint _cacheDuration;
private TextWriter _originalWriter;
privatestring _cacheKey;
publicoverridevoid 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, newobject[]
{ newWriter });
}
}
}
publicoverridevoid 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, newobject[]
{ _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);
}
}
}
privatestring 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();
}
privateclass CacheContainer 
{
publicstring Output;
publicstring ContentType;
public CacheContainer(string data, string contentType) 
{
Output = data;
ContentType = contentType;
}
}
publicenum CachePolicy 
{
NoCache =0,
Client =1,
Server =2,
ClientAndServer =3
}
}


{ 
encoding;



StringWriterWithEncoding
publicclass StringWriterWithEncoding : StringWriter 
{
Encoding encoding;
public StringWriterWithEncoding(Encoding encoding) 
{
this.encoding = encoding;
}
publicoverride Encoding Encoding 
{ 
get
{ return encoding; }
}
}
转自:http://www.cnblogs.com/mazq/archive/2009/05/30/1492298.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2012-10-30 win2008WEB服务器集群实践(转)
2009-10-30 总结一些做人的标准
2008-10-30 C# 内置类型表