ASP.Net OutputCacheProvider奇怪的行为

时间:2012-05-30 11:24:50

标签: asp.net outputcache

我根据在互联网上找到的样本实现了一个简单的基于文件的自定义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();
        }
    }
}

然后我创建了带标题<。p>的.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)。

    ADD:a2 / ss2.aspx(C:\ eShopKey \ ASP.Net \ Shops \ myshoe_dev \ Cache \ 7394fd15241e5b7f5c437ddf28dcd0e5.txt)

    ADD: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保存到磁盘。

    < / LI>

    所以我的问题:

    1. 为什么ASP.Net会使我的页面无效?
    2. 当ASP.Net调用Remove()和Set()方法时?我没有找到任何相关信息。
    3. 如果我重命名页面并使用此变体缓存工作!这很奇怪。
    4. 请注意,如果我使用默认的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认为缓存页面无效并再次询问添加/设置。

      总结一下,似乎您可以在给定时刻只将页面的一个变体添加到缓存中。由于将使用其他参数再次调用页面,因此之前的所有曲线变体都将失效。

      由于ASP.NET使用相同的密钥来检索它,因此无法检查第一个GET请求的相关内容。

      所以我的新问题:

      • 为什么ASP.Net会将每个页面的2个请求发送到自定义输出缓存提供程序?有人知道吗?
      • 我如何克服这种奇怪的行为?
      • AspNetInternalProvider是否具有相同的行为?

2 个答案:

答案 0 :(得分: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哈希值)。在我的第一个发布的代码中查找实现。

答案 1 :(得分:0)

第一个请求返回一个对象System.Web.Caching.CachedVary,第二个请求返回System.Web.Caching.OutputCacheEntry。根据对象的名称,第一个用于OutputCache,第二个用于页面数据。

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

希望它可以帮到你!

Amir Sheng