【问题标题】:ASP.NET OutputCache and CookiesASP.NET 输出缓存和 Cookie
【发布时间】:2012-02-23 10:28:31
【问题描述】:

有谁知道为什么如果我的页面上有cookie,输出缓存不起作用!

示例页面

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="ct.aspx.vb" Inherits="ct" %>
<%@ OutputCache Duration="600" Location="Server" VaryByParam="none" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      <h1>Cache test</h1>
      <p id="rndout" runat="server"></p>
    </div>
    </form>
</body>
</html>

后面的示例代码:

Partial Class ct
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
        Dim rc As New Random()
        Dim rn As Integer
        rn = rc.Next()
        rndout.InnerHtml = rn.ToString

        Response.Cookies("sym")("hello") = "world"
        Response.Cookies("sym").Expires = DateTime.Now.AddDays(370)
        Response.Cookies("sym").Domain = Application.Get("cookieurl")

    End Sub
End Class

当部署到 iis 6 或 7 时,它不会缓存,但是如果我注释掉 3 Response.Cookies 行它会。

在 VS 中运行时,两种方式都可以正常工作。

在我设置 response.cookies 时,iis/web.config 等中是否有一些设置允许输出缓存。我知道 cookie 内容将被缓存,它只是缓存的 http 响应的一部分。

【问题讨论】:

  • 您找到解决方案了吗?
  • 我发现同样的事情是真的,但没有遇到任何官方文档。明确声明它不起作用。
  • @Allov,抱歉耽搁了——不,我没有解决方案。除了摆脱 cookie 或者如果我需要 cookie,我可以在设置 cookie 的页面上添加脚本标签或 0x0 图像。
  • 晚了几年,但这个家伙以某种方式找到了解决方法。尚无定论它有多大风险。 stackoverflow.com/a/29771803/775800

标签: asp.net cookies outputcache


【解决方案1】:

在对这个问题进行了大量研究之后,我开始理解并解决了这个问题。

输出缓存不能与 cookie 配合使用的原因

因此,输出缓存不会缓存带有 cookie 的响应的原因是 cookie 可能是用户特定的(例如身份验证、分析跟踪等)。如果一个或多个 cookie 具有属性HttpCookie.Shareable = false,则输出缓存认为响应不可缓存。

包含带有缓存响应的 cookie

这就是棘手的地方。输出缓存将响应标头和内容缓存在一起,并且在将它们发送回用户之前不提供任何挂钩来修改它们。 但是,我编写了以下自定义输出缓存提供程序,以提供在将缓存响应的标头发送回用户之前修改它们的能力(需要 Fasterflect nuget 包):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Web;
using System.Web.Caching;
using Fasterflect;

namespace CustomOutputCache
{
    /// <summary>
    /// An output cache provider that has ability to modify the http header collection before a cached response is served back to the user.
    /// </summary>
    public class HeaderModOutputCacheProvider : OutputCacheProvider
    {
        private static readonly Type OutputCacheEntryType, HttpCachePolicySettingsType;
        private static readonly Type[] ParameterTypes;

        public static event EventHandler<CachedRequestEventArgs> RequestServedFromCache;

        static HeaderModOutputCacheProvider()
        {
            var systemWeb = typeof(HttpContext).Assembly;
            OutputCacheEntryType = systemWeb.GetType("System.Web.Caching.OutputCacheEntry");
            HttpCachePolicySettingsType = systemWeb.GetType("System.Web.HttpCachePolicySettings");
            ParameterTypes = new[]{
                typeof(Guid),
                HttpCachePolicySettingsType,
                typeof(string),
                typeof(string) ,
                typeof(string[]),
                typeof(int),
                typeof(string),
                typeof(List<HeaderElement>),
                typeof(List<ResponseElement>)
            };
        }

        private readonly ObjectCache _objectCache;

        public HeaderModOutputCacheProvider()
        {
            _objectCache = new MemoryCache("output-cache");
        }

        #region OutputCacheProvider implementation

        public override object Get(string key)
        {
            var cachedValue = _objectCache.Get(key);

            if (cachedValue == null)
                return null;

            if (cachedValue.GetType() != OutputCacheEntryType)
                return cachedValue;

            var cloned = CloneOutputCacheEntry(cachedValue);

            if (RequestServedFromCache != null)
            {
                var args = new CachedRequestEventArgs(cloned.HeaderElements);
                RequestServedFromCache(this, args);
            }

            return cloned;
        }

        public override object Add(string key, object entry, DateTime utcExpiry)
        {
            _objectCache.Set(key, entry, new CacheItemPolicy { AbsoluteExpiration = utcExpiry });
            return entry;
        }

        public override void Set(string key, object entry, DateTime utcExpiry)
        {
            _objectCache.Set(key, entry, new CacheItemPolicy { AbsoluteExpiration = utcExpiry });
        }

        public override void Remove(string key)
        {
            _objectCache.Remove(key);
        }

        #endregion

        private IOutputCacheEntry CloneOutputCacheEntry(object toClone)
        {
            var parameterValues = new[]
            {
                toClone.GetFieldValue("_cachedVaryId", Flags.InstancePrivate),
                toClone.GetFieldValue("_settings", Flags.InstancePrivate),
                toClone.GetFieldValue("_kernelCacheUrl", Flags.InstancePrivate),
                toClone.GetFieldValue("_dependenciesKey", Flags.InstancePrivate),
                toClone.GetFieldValue("_dependencies", Flags.InstancePrivate),
                toClone.GetFieldValue("_statusCode", Flags.InstancePrivate),
                toClone.GetFieldValue("_statusDescription", Flags.InstancePrivate),
                CloneHeaders((List<HeaderElement>)toClone.GetFieldValue("_headerElements", Flags.InstancePrivate)),
                toClone.GetFieldValue("_responseElements", Flags.InstancePrivate)
            };

            return (IOutputCacheEntry)OutputCacheEntryType.CreateInstance(
                parameterTypes: ParameterTypes,
                parameters: parameterValues
            );
        }

        private List<HeaderElement> CloneHeaders(List<HeaderElement> toClone)
        {
            return new List<HeaderElement>(toClone);
        }
    }

    public class CachedRequestEventArgs : EventArgs
    {
        public CachedRequestEventArgs(List<HeaderElement> headers)
        {
            Headers = headers;
        }
        public List<HeaderElement> Headers { get; private set; }

        public void AddCookies(HttpCookieCollection cookies)
        {
            foreach (var cookie in cookies.AllKeys.Select(c => cookies[c]))
            {
                //more reflection unpleasantness :(
                var header = cookie.CallMethod("GetSetCookieHeader", Flags.InstanceAnyVisibility, HttpContext.Current);
                Headers.Add(new HeaderElement((string)header.GetPropertyValue("Name"), (string)header.GetPropertyValue("Value")));
            }
        }
    }
}

你可以这样连接它:

<system.web>
  <caching>
      <outputCache defaultProvider="HeaderModOutputCacheProvider">
        <providers>
          <add name="HeaderModOutputCacheProvider" type="CustomOutputCache.HeaderModOutputCacheProvider"/>
        </providers>
      </outputCache>
    </caching>
  </system.web>

并且可以像这样使用它来插入cookie:

HeaderModOutputCacheProvider.RequestServedFromCache += RequestServedFromCache;

HeaderModOutputCacheProvider.RequestServedFromCache += (sender, e) =>
{
    e.AddCookies(new HttpCookieCollection
    {
        new HttpCookie("key", "value")
    });
};

【讨论】:

    【解决方案2】:

    您尝试在服务器端缓存它,同时您尝试在客户端设置 cookie - 这不能一起工作。

    为什么:当您在服务器端设置缓存页面时,在提供缓存版本(发送到客户端)时,后面的代码不会运行。这是在服务器上缓存的要点。不运行任何东西并按原样从缓存中提供它。

    也许您只需要在标头上设置缓存,而不是在服务器上缓存整个页面。

    【讨论】:

    • 我正在 asp.net 中创建一个包含 cookie 的页面。我希望 iis 缓存此页面,而不是运行后面的代码。我正在使用标准的 .net 代码来执行此操作。但是,如果我以任何方式使用 response.cookie,那么 outputcache 指令似乎就被破坏了。这在 .net 中没有以任何方式记录。事实上,有一篇文章说记住,如果你用 cookie 缓存页面,cookie 也会被缓存。我很清楚 cookie 是 http 标头的一部分,因此将被缓存。我的问题是 iis/web.config 等中是否有启用此功能的设置。在 cassini 中运行时运行正常
    • @Symeon 这与您尝试做的事情不合逻辑(错误)。您为一个用户设置了 cookie,那么下一个没有设置 cookie 的用户呢?客户端上设置的 cookie - 您将客户端缓存与服务器缓存混淆了。当 cookie 在客户端而不是在服务器上时,cookie 也会保留在缓存中
    • 访问该页面的人将获得一个 cookie,无论它是否存在。 cookie 只是 http 标头中的文本。我明白为什么它可能会令人困惑,但我只是认为一定有一些配置,因为它没有在哪里声明它们是互斥的,并且在 cassini 中可以正常工作。看看这个 -support.microsoft.com/kb/917072 听起来它应该与 cookie 一起缓存,因为他们提供了一种解决方法来阻止它。
    【解决方案3】:

    这是由不同版本的 .NET 框架引起的。基本上,有些版本永远不会缓存设置了 cookie 的页面。

    See this blog posting.

    【讨论】:

    • 欢迎来到 Stack Overflow!感谢您发布您的答案!请务必仔细阅读FAQ on Self-Promotion。另请注意,每次链接到自己的网站/产品时,都要求发布免责声明。
    【解决方案4】:

    检查您是否正在运行 .NET 2.0 SP1 以及是否应用了 MS11-100(2012 年 12 月发布)。

    我们遇到了类似的问题,最终联系了 Microsoft 支持。他们确认 MS11-100 破坏了输出缓存,但声称这是设计使然(由于补丁中修复的安全漏洞的性质),目前没有采取任何措施来恢复输出缓存功能。

    一个简单的测试:如果您发现已安装补丁,只需卸载该补丁并重新启动即可。您应该看到输出缓存开始起作用。由于安全隐患,我认为没有人会推荐将此作为生产解决方案,因此仅将其用作隔离问题的一种手段。

    我们最终测试了一个较新的框架(您必须转到 4.0;3.5 只是 2.0 框架的扩展,而不是独立的框架),在解决所有编译错误后,输出缓存立即开始工作。

    我们还致力于改变与 cookie 交互的方式,以便我们可以继续使用 2.0 框架(毕竟,测试 cookie 处理程序类应该比测试整个应用程序更容易)。有许多障碍,最终产品散发着“黑客”的味道,所以这是不行的。

    【讨论】:

      【解决方案5】:

      我遇到了同样的问题,我通过设置 Location="ServerAndClient" 测试了 Aristos 给出的场景并且它有效。如果我只使用 Location="Server" 那么它不起作用。

      【讨论】:

      • 在您的情况下,只有客户端(HTTP 响应标头缓存)可以工作。如果您在响应中设置 cookie,则页面输出不会缓存在服务器上。
      【解决方案6】:

      有一种解决方法可能在某些情况下有效: 如果 cookie 不严重依赖页面代码,但可以用一些独立代码计算,您可以在 Application_EndRequest 中设置 cookie Application_EndRequest 在 OutputCache 之后处理,因此缓存存储时没有 cookie,但在将请求传递给客户端之前添加了设置的 cookie 标头。

      【讨论】:

      • 我试过这个方法,收到了“发送响应后无法修改标头错误”。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-16
      • 2012-08-15
      • 2011-01-26
      • 2011-01-22
      相关资源
      最近更新 更多