Asp.Net 4.0 新特性之 使用自定义OutputCache Provider

在Asp.Net 4.0 的web.config文件中添加了关于缓存的配置节,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
<system.web>
  <compilation debug="true" targetFramework="4.0" />
  <caching>
    <outputCache defaultProvider="SmartOutputCache">
      <providers>
        <add name="SmartOutputCache" type="OutputCacheTest.Caching.SmartOutputCacheProvider"
             memoryCacheLimit="00:30:00"
             />
      </providers>
    </outputCache>
  </caching>
</system.web>

我们可以在Web.config中配置自定义的OutputCacheProvider,并将自定义Provider指定为默认的Provider。

1.自定义OutputCacheProvider需要实现System.Web.Cacheing. OutputCacheProvider抽象类,网上有很多例子都用文件缓存做例子。这个例子太俗了,我写了一个新的例子,在设置的缓存时间小于指定阀值时,缓存到HttpRuntime.Cache中,否则缓存到文件中,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
 
namespace OutputCacheTest.Caching
{
    /// <summary>
    /// OutputCache精灵,如果缓存时间小于设置时间时则缓存到内存,否则缓存到文件
    /// </summary>
    public class SmartOutputCacheProvider : OutputCacheProvider
    {
        private const string KEY_PREFIX = "__outputCache_";
 
        /// <summary>
        /// 初始化SmartOutputCacheProvider,读取配置文件中配置的MemoryCacheLimit和FileCacheRoot的值
        /// </summary>
        /// <param name="name">provider名字</param>
        /// <param name="config">配置</param>
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            string memoryLimit = config["memoryCacheLimit"];
            if (memoryLimit == null)
            {
                MemoryCacheLimit = new TimeSpan(0, 30, 0);
            }
            else
            {
                MemoryCacheLimit = TimeSpan.Parse(memoryLimit);
            }
 
            string fileCacheRoot = config["fileCachRoot"];
            if (string.IsNullOrEmpty(fileCacheRoot))
            {
                fileCacheRoot = AppDomain.CurrentDomain.BaseDirectory + "cache\\";
            }
            this.FileCacheRoot = fileCacheRoot;
            base.Initialize(name, config);
        }
 
        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="key">缓存的键,key的值是有asp.net内部生成的</param>
        /// <param name="entry">缓存的对象</param>
        /// <param name="utcExpiry">过期时间</param>
        /// <returns>返回缓存值</returns>
        public override object Add(string key, object entry, DateTime utcExpiry)
        {
            Set(key, entry, utcExpiry);
            return entry;
        }
 
        /// <summary>
        /// 处理缓存键值,防止在文件缓存时出现文件路径不允许的字符
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns>处理后的键</returns>
        private string ProcessKey(string key)
        {
            return KEY_PREFIX + System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(key, "MD5");
        }
 
        /// <summary>
        /// 返回缓存文件的物理路径
        /// </summary>
        /// <param name="processedKey">处理后的键</param>
        /// <returns>物理路径</returns>
        private string GetFilePath(string processedKey)
        {
            return Path.Combine(FileCacheRoot, processedKey + ".data");
        }
 
        /// <summary>
        /// 获得缓存值,如果在HttpRuntime.Cache中有则直接读取内存中的值,否则从文件读取
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns>缓存值</returns>
        public override object Get(string key)
        {
            string processedKey = ProcessKey(key);
 
            CacheDataWithExpiryTimeUtc result = HttpRuntime.Cache[processedKey] as CacheDataWithExpiryTimeUtc;
            if (result == null)
            {
                string path = GetFilePath(processedKey);
                if (!File.Exists(path))
                    return null;
 
                using (FileStream file = File.OpenRead(path))
                {
                    var formatter = new BinaryFormatter();
                    result = (CacheDataWithExpiryTimeUtc)formatter.Deserialize(file);
                }
            }
 
            if (result == null || result.ExpiryTimeUtc <= DateTime.UtcNow)
            {
                Remove(key);
                return null;
            }
            return result.Data;
        }
 
        /// <summary>
        /// 根据键移除缓存
        /// </summary>
        /// <param name="key">缓存键</param>
        public override void Remove(string key)
        {
            string processedKey = ProcessKey(key);
            HttpRuntime.Cache.Remove(processedKey);
            string path = GetFilePath(processedKey);
            if (!File.Exists(path))
                File.Delete(path);
        }
 
        /// <summary>
        /// 设置缓存
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="entry">缓存内容</param>
        /// <param name="utcExpiry">过期时间</param>
        public override void Set(string key, object entry, DateTime utcExpiry)
        {
            TimeSpan ts = utcExpiry - DateTime.UtcNow;
            string processedKey = ProcessKey(key);
 
            CacheDataWithExpiryTimeUtc cacheItem = new CacheDataWithExpiryTimeUtc
            {
                Data = entry,
                ExpiryTimeUtc = utcExpiry
            };
 
            if (ts <= MemoryCacheLimit)
            {
                HttpRuntime.Cache.Insert(processedKey, cacheItem, null, utcExpiry.ToLocalTime(), TimeSpan.Zero);
            }
            else
            {
                string cacheFilePath = GetFilePath(processedKey);
                 
                using (var fs = new FileStream(cacheFilePath,FileMode.OpenOrCreate,FileAccess.ReadWrite))
                {
                    var formatter = new BinaryFormatter();
                    formatter.Serialize(fs, cacheItem);
                }
            }
        }
 
        /// <summary>
        /// 如果缓存设定的时间超过此值则缓存到文件中,否则在HttpRuntime.Cache中做缓存
        /// </summary>
        [XmlAttribute("memoryCacheLimit")]
        public TimeSpan MemoryCacheLimit { get; set; }
 
 
        /// <summary>
        /// 文件缓存的根目录,可以指定任何可访问目录
        /// </summary>
        [XmlAttribute("fileCacheRoot")]
        public string FileCacheRoot { get; set; }
    }
 
    /// <summary>
    /// 对缓存数据和缓存的过期时间的封装
    /// </summary>
    [Serializable]
    internal class CacheDataWithExpiryTimeUtc
    {
        public object Data { get; set; }
 
        public DateTime ExpiryTimeUtc { get; set; }
    }
}

2.如何使用自定义的OutputCacheProvider

  1)在配置文件中做配置,将自定义的实现作为默认输出缓存支持,请看文章开始的配置
  2)在UserControl中指定使用Provider的名字,改名字在web.config中定义,例如

1
2
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="IamUserControl.ascx.cs" Inherits="OutputCacheTest.IamUserControl" %>
<%@ OutputCache Duration="3000" ProviderName="AspNetInternalProvider" VaryByParam="None" %>

  需要注意的是,只能在UserControl中指定Provider的名字,在Page的生明中是不允许的,在Page中默认情况会使用web.config中配置的defaultProvider,但是我们可以通过3)中介绍的方法给不同的页面使用不同的OutputCacheProvider实现。

  3)在Global.asax文件中重写GetOutputCacheProviderName(HttpContext context)方法,根据context返回不同的实现名字,如下例子

1
2
3
4
5
6
7
8
9
public override string GetOutputCacheProviderName(HttpContext context)
{
    if (context.Request.Path.StartsWith("/default.aspx",StringComparison.CurrentCultureIgnoreCase))
    {
        return "AspNetInternalProvider";
    }
             
    return base.GetOutputCacheProviderName(context);
}

总结:
可扩展的OutputCache为我们提供了无限的可能,我们可以根据需要扩展OutputCache,例如把OutputCache存储到Memcached server或者其他键值对数据存储中,从而使程序的性能达到最优的情况。

请注意:本文举例中的代码仅为示例代码,实际应用中需要考虑很多因素。

示例代码下载

Asp.net 新特性相关阅读:

1. 从页面标记<%%>说起
2. Asp.Net 4.0 中可以用自定义的Provider做OutputCache 了
3. SEO增强支持MetaKeywords,和MetaDescription,RedirectPermanant
4. SEO增强之URL Routing
5. 输出更纯净的Html代码,ViewStateMode和ClientIDMode,CheckBoxList等

posted @   玉开  阅读(6153)  评论(5编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
历史上的今天:
2008-05-16 在内网服务器中获得真正的客户端ip的方法
2007-05-16 佛经上一百八十一条做人的道理
点击右上角即可分享
微信分享提示