说到网站性能优化,没有什么比“缓存”更重要了。即便是某些朋友口中念念不忘的“静态页”,说到底也只是缓存了整张页面内容而已。但是,显然这样大粒度的缓存策略,在如今“牵一发而动全身”的Web 2.0站点中几乎是无法使用的。试想,在Twitter中的某个名人被数十万人订阅,那么他发一条消息,难道此时网站要去修改数十万用户的静态页面?因此,我们需要粒度更小的缓存。而比“整页缓存”粒度小一号的缓存,便是所谓“视图片断缓存”了。
视图片断缓存非常重要,因为它缓存的也是页面内容,这表示它比更低级别的缓存更有效率,也比静态页等整页内容缓存的适用面要大得多。在ASP.NET WebForm模型中提供了控件级别的缓存,我们可以为控件标记输出缓存策略,这样控件便不会每次都完整执行一遍。当然这个策略还不够灵活,因为它缓存的最小单元是“控件”,而不是页面中任意的部分。因此我在一年多前提出了一个CachePanel,由它包装的页面内容都可以被缓存,无论其内部是控件还是普通输出的内容。在实际生产过程中,CachePanel起到了非常重要的作用,许多场景下只要在页面中包裹一个<ext:CachePanel runat="server" />,性能立即就有了质的飞跃。
只可惜,在如今ASP.NET MVC的时代无法直接使用CachePanel这样的服务器端控件。因为CachePanel需要服务器端代码的配合,而ASP.NET MVC中的页面只是“视图模板”,除了呈现之外就不应该有其他职责。因此,我们必须提出一种脱离于后端代码的“标记”方式,将视图中的内容片断进行随意地缓存。在Rails或Django中都有类似的特性,但ASP.NET MVC甚至在2.0的Road Map中还没有包含这一功能,于是我们只能自己动手丰衣足食。不过有了ASP.NET WebForm作为强大的视图引擎,加这样的功能简直是举手之劳:
public static class CacheExtensions        
{
  public static string Cache(
    this HtmlHelper htmlHelper,
    string cacheKey,
    CacheDependency cacheDependencies,
    DateTime absoluteExpiration,
    TimeSpan slidingExpiration,
    Func<object> func) 
  {
    var cache = htmlHelper.ViewContext.HttpContext.Cache;
    var content = cache.Get(cacheKey) as string;
    if (content == null) 
    {
      content = func().ToString();
      cache.Insert(cacheKey, content, cacheDependencies, absoluteExpiration, slidingExpiration);
    } 
    return content;
  } 
}