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 ActionFilterAttributeExtending 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
        }

    }

 

 

StringWriterWithEncoding

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值