ASP.net 4.0 的新特性: 可扩展输出缓存

Categories: 未分类
Comments: 3 Comments
Published on: 2010 年 02 月 25 日

ASP.NET 1.0 引入了输出缓存的概念,允许我们把网页,控件输出保存在内存的缓存中。然后在之后的web请求中,ASP.NET可以通过从缓存中取出和使用现成的输出(而不是再次执行网页重新从头生成输出),以实现快速地将内容输出,提高应用的性能。

ASP.NET的输出缓存是相当灵活的,它允许你根据网页/查询字符串/表单提交参数等来缓存不同版本的内容;它也允许你基于访问你的应用的客户端的浏览器类型或者用户语言选项来缓存不同版本的内容。你可以配置ASP.NET在一个特定的时间内缓存某个特定的输出缓存,你还可以配置ASP.NET基于某个外部事件(例如,缓存内容基于的数据库改变了)动态地使得缓存条目失效。

但ASP.NET V1->V3.5中的输出缓存有一个限制,就是缓存存储本身不是可扩展的,缓存的内容总是保存在内存中的。

ASP.NET 4.0 中 为输出缓存添加了扩展点,现在允许开发人员配置一个或多个定制的输出缓存提供器(output-cache provider)。输出缓存提供器可以使用任何存储机制来持久化缓存内容。比如文件,其他进程的内存,其他服务器的进程,memcached 等其他分布式缓存。

下面就是一个简单的演示例子,演示如何自己定制一个基于文件的输出缓存提供器。

要实现我们自己的输出缓存提供器,我们需要自己从 System.Web.Caching.OutputCacheProvider 抽象类派生一个自己的输出缓存提供器类,即实现这个抽象类的下面4个抽象方法:

public override object Add(string key, object entry, DateTime utcExpiry)
{
}

public override object Get(string key)
{
}

public override void Remove(string key)
{
}

public override void Set(string key, object entry, DateTime utcExpiry)
{
}

然后,你可以通过在应用的 web.config 文件中使用<outputCache>元素的新<providers>节将其注册,配置ASP.NET 4来使用你定制的输出缓存提供器:如下所示。

<configuration>
<system.web>
    <caching>
      <outputCache defaultProvider="HongjunGuoFileCache">
        <providers>
          <add name="HongjunGuoFileCache" type="WebApplication_CacheOutput.HongjunGuoOutputCache,WebApplication_CacheOutput"/>
        </providers>
      </outputCache>
    </caching>
</system.web>
</configuration>

在上面,我加了一个新的输出缓存提供器(我将其命名为“HongjunGuoFileCache”),是用WebApplication_CacheOutput.dll程序集中的“WebApplication_CacheOutput.HongjunGuoOutputCache”类实现的。我还配置ASP.NET,将我的“WebApplication_CacheOutput.HongjunGuoOutputCache”实现作为默认的输出缓存实现来使用(每当内容需要输出缓存时),- 这是通过设置<outputCache>元素的 “defaultProvider”属性实现的。

至此,当我在任意一个.aspx网页的顶部加一个OutputCache指令时,内容就会通过使用我的ScottOutputCache提供器被缓存和保存起来:

<%@ OutputCache Duration="60" VaryByParam="None" %>

我这里书写的 “WebApplication_CacheOutput.HongjunGuoOutputCache”类 的完整代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace WebApplication_CacheOutput
{
    [Serializable]
    internal class CacheItem
    {
        public DateTime Expires;
        public object Item;
    }


    public class HongjunGuoOutputCache : OutputCacheProvider
    {

        private readonly string CachePath = HttpContext.Current.Server.MapPath(@"~/CacheFiles/");


        public override object Add(string key, object entry, DateTime utcExpiry)
        {
            string fileName = Path.Combine(CachePath, string.Format("{0}.txt", key));
            if (File.Exists(fileName)) return entry;

            string path = Path.GetDirectoryName(fileName);
            if (!Directory.Exists(path)) Directory.CreateDirectory(path);

            using (var file = File.OpenWrite(fileName))
            {
                var item = new CacheItem { Expires = utcExpiry, Item = entry };
                var formatter = new BinaryFormatter();
                formatter.Serialize(file, item);
            }
            return entry;
        }


        public override object Get(string key)
        {
            string fileName = Path.Combine(CachePath, string.Format("{0}.txt", key));
            if (!File.Exists(fileName)) return null;

            CacheItem item = null;
            using (var file = File.OpenRead(fileName))
            {
                var formatter = new BinaryFormatter();
                item = (CacheItem)formatter.Deserialize(file);
            }

            // 是否过期,或者文件损毁
            if (item == null || item.Expires <= DateTime.Now.ToUniversalTime())
            {
                Remove(key);
                return null;
            }
            return item.Item;
        }



        public override void Remove(string key)
        {
            string fileName = Path.Combine(CachePath, string.Format("{0}.txt", key));

            if (File.Exists(fileName)) File.Delete(fileName);
        }


        public override void Set(string key, object entry, DateTime utcExpiry)
        {
            if (entry == null) return;
            string fileName = Path.Combine(CachePath, string.Format("{0}.txt", key));

            string path = Path.GetDirectoryName(fileName);
            if (!Directory.Exists(path)) Directory.CreateDirectory(path);

            var item = new CacheItem { Expires = utcExpiry, Item = entry };
            using (var file = File.OpenWrite(fileName))
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(file, item);
            }
        }
    }
}

这里我把要缓存的东西放入 CacheItem 实体二进制序列化到一个文件中了。

文件是保存在 Web 项目的根目录的 CacheFiles 目录下的。

由于这里传入的 key 字符中会有 / \ 这样的斜杠存在, 所以这里也为它建立了必须的目录。 你也可以把 key  md5 加密后,根据加密算法生成文件或者目录。

执行这个程序,我们在 根目录的 CacheFiles 目录下 就会看到一些文件产生了。

我们调试这个程序,就会看到缓存的工作原理,很简单明了。

参考资料:

ASP.NET 4.0: Writing custom output cache providers

http://weblogs.asp.net/gunnarpeipman/archive/2009/11/19/asp-net-4-0-writing-custom-output-cache-providers.aspx

VS 2010 和 .NET 4.0 系列之《ASP.NET 4中的可扩展输出缓存》篇

http://blog.joycode.com/scottgu/archive/2010/02/11/115877.joy

3 Comments
  1. test说道:

    ScottOutputCache????
    copy没改全…..

  2. zhaorui说道:

    学习了一下代码,请教一个问题,对于用户数量不多(<500)的办公系统(公文编辑流转)来说,使用Cache对于性能的提升是否足够明显?

    之前的一个系统运行在Windows Server 2000 上,IIS大概是5或者5.5的版本,.NET Framework 的版本是 1.1,给人的感觉似乎没有那种“一开始比较慢,后面会比较快”的感觉

  3. mbt shoes usa说道:

    今天安装PB6后创建一个ce6的项目,结果点击完成后又弹回创建窗口,在任务栏显示项目创建失败。

Comments are closed.

Welcome , today is 星期四, 2017 年 03 月 30 日