ASP.NET OutputCache и файлы cookie - PullRequest
       3

ASP.NET OutputCache и файлы cookie

9 голосов
/ 23 февраля 2012

Кто-нибудь знает, почему, если на моей странице есть куки, кэш вывода не работает!

Пример страницы

<%@ 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, который кэшируется.

Ответы [ 6 ]

5 голосов
/ 31 марта 2016

Проведя немало исследований по этой проблеме, я понял и обошел проблему.

Причина, по которой кэш-память вывода не подходит для файлов cookie

Таким образом, причина в том, что кэш вывода не будет кэшировать ответ с помощью cookie, заключается в том, что cookie может быть специфичным для пользователя (например, аутентификация, аналитическое отслеживание и т. Д.). Если один или несколько файлов cookie со свойством HttpCookie.Shareable = false, то кэш вывода считает, что ответ не кэшируется.

Включая файлы cookie с кэшированным ответом

Вот где это сложно. Выходной кэш кэширует заголовки ответа и контент вместе и не предоставляет никаких хуков для их изменения перед отправкой обратно пользователю. Однако я написал следующий пользовательский поставщик кэша вывода, чтобы предоставить возможность изменять заголовки кэшированных ответов перед их отправкой обратно пользователю ( требует пакет nuget Fasterflect ):

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>

И может использовать это, чтобы вставить куки:

HeaderModOutputCacheProvider.RequestServedFromCache += RequestServedFromCache;

HeaderModOutputCacheProvider.RequestServedFromCache += (sender, e) =>
{
    e.AddCookies(new HttpCookieCollection
    {
        new HttpCookie("key", "value")
    });
};
4 голосов
/ 23 февраля 2012

Вы пытаетесь кэшировать это на стороне сервера, и в то же время вы пытаетесь установить cookie на клиенте - это не работает вместе.

Почему: когда вы устанавливаете страницу в кеше на стороне сервера, код не запускается при обслуживании кэшированной версии (отправка клиенту). Это точка кеширования на сервере. Чтобы ничего не запускать и отдавать из кеша как есть.

Может быть, вам нужно просто установить кеш в заголовке, а не кешировать всю страницу на сервере.

3 голосов
/ 05 октября 2012

Это вызвано различными версиями .NET Framework.По сути, некоторые версии никогда не кэшируют страницу с установленным файлом cookie.

См. Это сообщение в блоге .

1 голос
/ 08 июня 2012

Проверьте, используете ли вы .NET 2.0 SP1 и применили ли вы MS11-100 (выпущен в декабре 2012 г.).

Мы столкнулись с похожими проблемами и в итоге обратились в службу поддержки Microsoft. Они подтвердили, что MS11-100 нарушает кэширование вывода, но утверждают, что оно было разработано (из-за особенностей уязвимостей безопасности, исправленных в патче), и в настоящее время ничего не делается для восстановления функциональности кэша вывода.

Простой тест: если вы обнаружите, что у вас установлено исправление, просто удалите это исправление и перезагрузите компьютер. Вы должны увидеть, что выходное кэширование начинает работать. Я не думаю, что кто-то порекомендовал бы это в качестве производственного решения из-за проблем безопасности, поэтому используйте его только как средство для выявления проблемы.

В итоге мы протестировали новую платформу (вам нужно перейти на 4.0; 3.5 - это просто расширение платформы 2.0, а не отдельная платформа самостоятельно), и после устранения всех ошибок компиляции выходное кэширование сразу же заработало.

Мы также работали над тем, чтобы изменить способ взаимодействия с файлами cookie, чтобы мы могли оставаться в среде 2.0 (в конце концов, должно быть проще тестировать наши классы обработчиков файлов cookie, а не тестировать все приложение). Есть ряд препятствий, и конечный продукт пахнет "взломами", так что это было бесполезно.

0 голосов
/ 28 ноября 2012

Существует обходной путь, который может работать в некоторых сценариях: если файл cookie не сильно зависит от кода страницы, но может быть рассчитан с помощью некоторого автономного кода, вы можете установить файл cookie в Application_EndRequest. Application_EndRequest обрабатывается после OutputCache ипоэтому кэш хранится без cookie, но затем добавляется заданный заголовок cookie, прежде чем запрос будет доставлен клиенту.

0 голосов
/ 30 мая 2012

У меня возникла та же проблема, и я протестировал сценарий, заданный Aristos, установив Location = "ServerAndClient", и он работает.Если я только использую Location = "Server", то это не сработало.

...