我已经根据我在 Internet 上找到的示例实现了一个简单的基于文件的自定义 OutputCacheProvider。
代码如下:
using System;
using System.Configuration;
using System.IO;
using System.Web;
using System.Web.Caching;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace SimpleCachedProvider
{
public class FileCacheProvider : OutputCacheProvider {
private string _cachePath;
void WriteToFile(String filename, String contents) {
FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write);
StreamWriter w = new StreamWriter(fs, System.Text.Encoding.GetEncoding(1253));
w.BaseStream.Seek(0, SeekOrigin.Begin);
w.BaseStream.SetLength(0);
w.Write(contents);
w.Flush();
w.Close();
}
void AppendToFile(String filename, String contents) {
if (contents.ToLower().IndexOf("ss2.aspx") >= 0 || contents.ToLower().IndexOf("default.aspx") >= 0) {
FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write);
StreamWriter w = new StreamWriter(fs, System.Text.Encoding.GetEncoding(1253));
w.BaseStream.Seek(0, SeekOrigin.End);
w.Write(contents);
w.Flush();
w.Close();
}
}
private string CachePath {
get {
if (!string.IsNullOrEmpty(_cachePath))
return _cachePath;
_cachePath = ConfigurationManager.AppSettings["OutputCachePath"];
var context = HttpContext.Current;
if (context != null) {
_cachePath = context.Server.MapPath(_cachePath);
if (!_cachePath.EndsWith("\\"))
_cachePath += "\\";
}
return _cachePath;
}
}
public override object Add(string key, object entry, DateTime utcExpiry) {
var path = GetPathFromKey(key);
AppendToFile(CachePath + "info.txt", "ADD: " + key + " (" + path + ")\r\n");
if (File.Exists(path)) {
AppendToFile(CachePath + "info.txt", "ADD: " + key + " (" + path + ") already exists. Will be returned.\r\n");
return entry;
}
AppendToFile(CachePath + "info.txt", "ADD: " + key + " (" + path + ") does not exists. Will be created.\r\n");
using (var file = File.OpenWrite(path)) {
var item = new CacheItem { Expires = utcExpiry, Item = entry };
var formatter = new BinaryFormatter();
formatter.Serialize(file, item);
AppendToFile(CachePath + "info.txt", "ADD: " + key + " (" + path + ") saved to disk.\r\n");
}
return entry;
}
public override void Set(string key, object entry, DateTime utcExpiry) {
var item = new CacheItem { Expires = utcExpiry, Item = entry };
var path = GetPathFromKey(key);
AppendToFile(CachePath + "info.txt", "Set: " + key + " (" + path + ") requested.\r\n");
using (var file = File.OpenWrite(path)) {
var formatter = new BinaryFormatter();
formatter.Serialize(file, item);
AppendToFile(CachePath + "info.txt", "Set: " + key + " (" + path + "): " + utcExpiry.ToLocalTime().ToString("dd/MM/yyyy HH:mm:ss") + " saved to disk.\r\n");
}
}
public override object Get(string key) {
var path = GetPathFromKey(key);
AppendToFile(CachePath + "info.txt", "Get: Querying " + key + " (" + path + ")\r\n");
if (!File.Exists(path)) {
AppendToFile(CachePath + "info.txt", "Get: " + key + " (" + path + ") not found.\r\n");
return null;
}
CacheItem item = null;
using (var file = File.OpenRead(path)) {
var formatter = new BinaryFormatter();
item = (CacheItem)formatter.Deserialize(file);
AppendToFile(CachePath + "info.txt", "Get: " + key + " (" + path + ") retrieved.\r\n");
}
if (item == null || item.Expires <= DateTime.Now.ToUniversalTime()) {
AppendToFile(CachePath + "info.txt", "Get: " + key + " (" + path + ") deleted due to expiration.\r\n");
Remove(key);
return null;
}
AppendToFile(CachePath + "info.txt", "Get: " + key + " (" + path + ") retrieved and used\r\n");
return item.Item;
}
public override void Remove(string key) {
var path = GetPathFromKey(key);
AppendToFile(CachePath + "info.txt", "Remove: " + key + " (" + path + ") requested.\r\n");
if (File.Exists(path)) {
AppendToFile(CachePath + "info.txt", "Remove: " + key + " (" + path + ") executed.\r\n");
File.Delete(path);
}
}
private string GetPathFromKey(string key) {
return CachePath + MD5(key) + ".txt";
}
private string MD5(string s) {
MD5CryptoServiceProvider provider;
provider = new MD5CryptoServiceProvider();
byte[] bytes = Encoding.UTF8.GetBytes(s);
StringBuilder builder = new StringBuilder();
bytes = provider.ComputeHash(bytes);
foreach (byte b in bytes)
builder.Append(b.ToString("x2").ToLower());
return builder.ToString();
}
}
}
然后我创建了一个带有标题的 .aspx
<%@ OutputCache Duration="3600" Location="Server" VaryByParam="*" %>
我已将默认输出缓存提供程序更改为我的 web.config。
奇怪的行为是页面没有被缓存。相反,这是我的调试信息的示例输出。看起来:
- 从缓存中检索页面并发送回 ASP.Net
- 紧接着 ASP.Net 调用 Remove() 方法到我的页面
最后 ASP.Net 调用 Set() 并更新页面 - 没有有效的缓存
获取:查询a2/ss2.aspx (C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\7394fd15241e5b7f5c437ddf28dcd0e5.txt)
获取:a2/ss2.aspx (C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\7394fd15241e5b7f5c437ddf28dcd0e5.txt) 已检索。
获取:a2/ss2.aspx (C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\7394fd15241e5b7f5c437ddf28dcd0e5.txt) 检索并使用
获取:查询a2/ss2.aspxHQFCNmycustom2VDE(C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\3e72454ab3f36e4cfe3964e5063be622.txt)
获取:a2/ss2.aspxHQFCNmycustom2VDE (C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\3e72454ab3f36e4cfe3964e5063be622.txt) 检索。
获取:a2/ss2.aspxHQFCNmycustom2VDE (C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\3e72454ab3f36e4cfe3964e5063be622.txt) 检索并使用
删除:a2/ss2.aspxHQFCNmycustom2VDE (C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\3e72454ab3f36e4cfe3964e5063be622.txt) 请求。
删除:已执行 a2/ss2.aspxHQFCNmycustom2VDE (C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\3e72454ab3f36e4cfe3964e5063be622.txt)。
添加:a2/ss2.aspx (C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\7394fd15241e5b7f5c437ddf28dcd0e5.txt)
添加:a2/ss2.aspx (C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\7394fd15241e5b7f5c437ddf28dcd0e5.txt) 已经存在。将被退回。
设置:a2/ss2.aspxHQFCNmycustom2VDE (C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\3e72454ab3f36e4cfe3964e5063be622.txt) 请求。
设置:a2/ss2.aspxHQFCNmycustom2VDE(C:\eShopKey\ASP.Net\Shops\myshoe_dev\Cache\3e72454ab3f36e4cfe3964e5063be622.txt):30/05/2012 15:07:27 保存到磁盘。
所以我的问题:
- 为什么 ASP.Net 不断使我的页面无效?
- ASP.Net 何时调用 Remove() 和 Set() 方法?我还没有找到任何相关信息。
- 如果我重命名页面并使用此变体缓存工作!这太奇怪了。
请注意,如果我使用默认的 ASP.Net outputcacheprovider 缓存按预期工作。
我发现发生了什么但无法修复它:
假设我打开页面:http ://www.mydomain.com/mypage.aspx?param1=1
ASP.Net 向我的 OutputCacheProvider 发送 2 个连续的 GET 请求:
- 一个用于页面 mypage.aspx
- 另一个用于同一页面,但附加了查询字符串参数
在我看来,第一个请求与第二个请求以某种方式相关,例如标头。
只要我使用相同的查询字符串连续调用同一页面,缓存就会按预期工作。
如果我调用下一页:http ://www.mydomain.com/mypage.aspx?param1=2
然后初始化相同的 2 步 GET 序列。ASP.Net 发送 2 个 GET 请求,一个用于不带参数的页面,一个用于带参数的页面。
然后在缓存中找到第一个 GET 请求(对于没有参数的页面)并返回给 ASP.Net。但不知何故与第二个无关。它与调用的第一个变体有关(param1=1)。
因此,尽管如此,如果第二个请求之前已经被缓存,ASP.Net 认为缓存的页面无效并再次请求添加/设置。
总而言之,在给定时刻,您似乎只能将页面的一个变体放入缓存中。所有先前缓存的变体都将失效,因为该页面将再次使用其他参数调用。
无法检查第一个 GET 请求与什么相关,因为 ASP.NET 使用相同的密钥来检索它。
所以我的新问题:
- 为什么 ASP.Net 将每个页面的 2 个请求发送到自定义输出缓存提供程序?有人知道吗?
- 我怎样才能克服这种奇怪的行为?
- AspNetInternalProvider 是否具有相同的行为?