0

我已经根据我在 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。

奇怪的行为是页面没有被缓存。相反,这是我的调试信息的示例输出。看起来:

  1. 从缓存中检索页面并发送回 ASP.Net
  2. 紧接着 ASP.Net 调用 Remove() 方法到我的页面
  3. 最后 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 保存到磁盘。

所以我的问题:

  1. 为什么 ASP.Net 不断使我的页面无效?
  2. ASP.Net 何时调用 Remove() 和 Set() 方法?我还没有找到任何相关信息。
  3. 如果我重命名页面并使用此变体缓存工作!这太奇怪了。

请注意,如果我使用默认的 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 是否具有相同的行为?
4

2 回答 2

1

我找到了解决方案!问题出在 Add 方法上。它必须写在所有提供者上,如下所示:

public override object Add(string key, object entry, DateTime utcExpiry) {
        String vKey = TransformKey(key);

        object res = Get(key);
        if (res == null) {
            Set(key, entry, utcExpiry);
            return entry;
        }

        return res;
    }

TransformKey 方法只是根据密钥(例如密钥的 MD5 哈希)返回一个安全字符串(没有坏字符的字符串)。在我的第一个发布的代码上寻找一个实现。

于 2012-06-01T22:26:09.587 回答
0

第一个请求返回一个对象System.Web.Caching.CachedVary,第二个请求返回System.Web.Caching.OutputCacheEntry。根据object的名字,第一个是for OutputCache,第二个是page的数据。

如果您有任何问题,请发送电子邮件至shengzhengshan@hotmail.com

希望它可以帮助你!

阿米尔·盛

于 2012-06-01T07:53:08.950 回答