Как установить время истечения срока кэширования кэшей клиентов и серверов

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

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

Пользовательский класс ActionFilterAttribute

public class ClientCacheAttribute : ActionFilterAttribute
{
    private bool _noClientCache;

    public int ExpireMinutes { get; set; }

    public ClientCacheAttribute(bool noClientCache) 
    {
        _noClientCache = noClientCache;
    }

    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        if (_noClientCache || ExpireMinutes <= 0)
        {
            filterContext.HttpContext.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
            filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false);
            filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
            filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            filterContext.HttpContext.Response.Cache.SetNoStore();
        }
        else
        {
            filterContext.HttpContext.Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(ExpireMinutes));
        }

        base.OnResultExecuting(filterContext);
    }
}

Настройки веб-конфигурации

  <outputCacheSettings>
    <outputCacheProfiles>
      <add name="Cache24Hours" location="Server" duration="86400" varyByParam="none" />
    </outputCacheProfiles>
  </outputCacheSettings>

Как я его называю:

[OutputCache(CacheProfile = "Cache24Hours")]
[ClientCacheAttribute(false, ExpireMinutes = 10)]
public class HomeController : Controller
{
  [...]
}

Но при просмотре заголовка HTTP:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/html; charset=utf-8
Expires: -1

Как я могу реализовать это правильно? Это приложение ASP.NET MVC 4.

Ответ 1

Вам необходимо реализовать собственное решение для кеширования на стороне сервера и для кеширования на стороне клиента использовать ClientCacheAttribute или OutputCache. Вот причина, по которой вам потребуется ваше собственное решение для кеша на стороне сервера.

  • ClientCacheAttribute устанавливает политику кэширования в Response.Cache, который является типом HttpCachePolicyBase
  • и встроенный OutputCache также задает политику кэша Response.Cache

Здесь я хочу подчеркнуть, что we don't have collection of HttpCachePolicyBase but we only have one object of HttpCachePolicyBase so we can't set multiple cache policy for given response.

Даже если мы можем установить Http Cacheability в HttpCacheability.ServerAndPrivate, но снова вы будете запускать в другой проблеме с продолжительностью кэша (то есть для клиента 10 минут и 24 часа в сутки)

Я бы предложил использовать OutputCache для кэширования на стороне клиента и реализовать собственный механизм кеширования для кеширования на стороне сервера.

Ответ 2

В профиле OutputCache установите его в ServerAndClient. Если вы это сделаете, ваш фильтр действий должен должным образом отменить выход.

Ответ 3

В .Net 4.5 возможно использование разных политик кэширования клиентов и серверов без написания пользовательских поставщиков кэшей:

// Set the cache response expiration to 3600 seconds (use your own value here).
HttpContext.Current.Response.Cache.SetExpires(DateTime.UtcNow.AddSeconds(3600));

// Set both server and browser caching.
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);

// Prevent browser default max-age=0 header on first request
// from invalidating the server cache on each request.
HttpContext.Current.Response.Cache.SetValidUntilExpires(true);

// Set an HTTP ETag header on the page using a random GUID.
HttpContext.Current.Response.Cache.SetETag(System.Guid.NewGuid()
                                           .ToString().Replace("-", ""));

// Set last modified time.
HttpContext.Current.Response.Cache.SetLastModified(DateTime.UtcNow);

// Now here is the critical piece that forces revalidation on each request!:
HttpContext.Current.Response.Cache.AppendCacheExtension(
    "must-revalidate, proxy-revalidate, max-age=0");

В результате каждый раз, когда страница восстанавливается в кеше сервера, он получает новый ETag. Это приводит к тому, что запрос If-None-Match возвращает всю страницу обратно в браузер. Если кешированная копия браузера совпадает с тем, что сгенерировано в кеше сервера (тот же ETag), браузер возвращает 304 Not Modified.

Обратите внимание, что ни один из заголовков кеша, которые я добавил в AppendCacheExtension, не конфликтует с заголовками, испускаемыми встроенным кэшированием. Всякий раз, когда вы пытаетесь изменить заголовки кеширования, испускаемые самим кэшированием .Net,.NET всегда будет заменять то, что вы пытаетесь сделать. Хитрость заключается в том, чтобы добавить новые не конфликтующие заголовки, а не пытаться изменить то, что .Net уже испускает. Самое главное, вы должны добавить заголовок max-age. Вы не должны использовать .SetMaxAge(), так как это также устанавливает максимальный возраст кешированной копии страницы на сервере.

Это потребовало немало усилий, чтобы выяснить, но оно работает, по крайней мере, в .Net 4.5.