ASP.NET OutputCache 和 Cookie

发布于 2025-01-08 08:18:27 字数 1476 浏览 4 评论 0原文

有谁知道为什么如果我的页面上有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 中运行时,两种方式都可以正常工作。

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

Does anyone know why if is have cookies on my page, the output cache does not work !

Example page

<%@ 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>

Example code behind:

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

when deployed to iis 6 or 7 this does not cache, however if i comment out the 3 Response.Cookies lines it does.

When run up in VS it works fine both ways.

Is there some setting in iis/web.config etc to allow outputcache while i set response.cookies. I understand the cookie content will be cached as well as it is just a part of the http response that is cached.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

删除→记忆 2025-01-15 08:18:27

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

输出缓存不能很好地与 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")
    });
};

After doing a fair bit of research into this problem, I came to understand and work-around the problem.

The reason output cache doesn't play nice with cookies

So the reason the output cache will not cache a response with cookies is that a cookie could be user-specific (e.g. authentication, analytical tracking, etc.). If one or more cookies with the property HttpCookie.Shareable = false, then the output cache considers the response uncacheable.

Including cookies with a cached response

This is where it get's tricky. The output cache caches the response headers and content together and doesn't provide any hooks to modify these before sending them back to the user.
However, I wrote the following custom output cache provider to provide the ability to modify a cached response's headers before they are sent back to the user (requires the Fasterflect nuget package):

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")));
            }
        }
    }
}

You would wire it up like this:

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

And could use it like this to insert cookies:

HeaderModOutputCacheProvider.RequestServedFromCache += RequestServedFromCache;

HeaderModOutputCacheProvider.RequestServedFromCache += (sender, e) =>
{
    e.AddCookies(new HttpCookieCollection
    {
        new HttpCookie("key", "value")
    });
};
泼猴你往哪里跑 2025-01-15 08:18:27

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

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

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

You try to cache this on server side, and at the same time you try to set the cookie on the client - this is not working together.

Why: When you set a page on cache on server side the code behind is not run when the cached version is served (send to client). This is the point of caching on server. To not run anything and give it from the cache as it is.

Maybe you need to just set the cache on header and not cache the full page on server.

百思不得你姐 2025-01-15 08:18:27

这是由于.NET Framework版本不同造成的。基本上,某些版本永远不会缓存设置了 cookie 的页面。

查看此博文

It's caused by different versions of .NET framework. Basically, some versions will never cache page with cookie set.

See this blog posting.

初相遇 2025-01-15 08:18:27

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

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

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

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

我们还致力于改变与 cookie 交互的方式,以便我们可以保留在 2.0 框架上(毕竟,测试我们的 cookie 处理程序类应该更容易,而不是测试整个应用程序)。存在许多障碍,而且最终产品充满了“黑客”的味道,所以这是不可能的。

Check to see if you're running .NET 2.0 SP1 and if you've applied MS11-100 (released December 2012).

We faced similar issues and ended up reaching out to Microsoft Support. They confirmed MS11-100 breaks output caching but claimed it was by design (due to the nature of the security vulnerabilities fixed in the patch) and there is currently nothing being done to restore output cache functionality.

A simple test: if you find you have the patch installed, simply uninstall that patch and reboot. You should see that output caching begins to work. I don't think anyone would recommend this as a production solution due to the security implications so only use this as a means to isolate the problem.

We ended up testing a newer framework (you have to go to 4.0; 3.5 is just an extension of the 2.0 framework and not a standalone framework on its own) and, after resolving all compilation errors, output caching began working immediately.

We also worked on changing the way we interact with cookies so that we could stay on the 2.0 framework (after all, it should be easier to test our cookie handler classes instead of testing the whole entire application). There are a number of hurdles and the final product reeked of "hacks" so that was a no-go.

爱冒险 2025-01-15 08:18:27

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

I was having the same problem and I tested the scenario given by Aristos by setting Location="ServerAndClient" and it works. If I only use Location="Server" then it didn't worked.

野心澎湃 2025-01-15 08:18:27

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

There is a workaround which might work in some scenarios:
If the cookie does not depend heavily on the page code but can be calculated with some standalone code, you can set the cookie in the Application_EndRequest
The Application_EndRequest is processed after the OutputCache and therefore the cache is stored with no cookie but then the set cookie header is added before the request is delivered to the client.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文