OutputCache отправляет неверный заголовок Vary, когда вызов попадает в кеш

У меня есть метод действий, который я хочу кэшировать:

[OutputCache(Duration=60*5, Location=OutputCacheLocation.Any, VaryByCustom="index")]
public ActionResult Index()
{
    return View();
}

При таком подходе:

public override string GetVaryByCustomString(HttpContext context, string custom)
{
    context.Response.Cache.SetOmitVaryStar(true);
    context.Response.Cache.VaryByHeaders["Cookie"] = true;

    if (User.Identity.IsAuthenticated)
    {
        Debug.Print("Authenticated");
        context.Response.Cache.SetNoServerCaching();
        context.Response.Cache.SetCacheability(HttpCacheability.Private);
        return null;
    }
    else
    {
        Debug.Print("Non authenticated");
        return custom;
    }
}

Идея заключалась в сохранении кешированной версии страницы для пользователей, не прошедших аутентификацию, но избегающей кэширования для аутентифицированных.

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

HTTP/1.1 200 OK
Cache-Control: public, max-age=300
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 10:53:36 GMT
Last-Modified: Thu, 09 Feb 2012 10:48:36 GMT
Vary: Cookie
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 10:48:37 GMT
Content-Length: 441

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

HTTP/1.1 200 OK
Cache-Control: public, max-age=297
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 10:53:36 GMT
Last-Modified: Thu, 09 Feb 2012 10:48:36 GMT
Vary: *
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 10:48:39 GMT
Content-Length: 441

Итак, насколько я знаю, браузеры не будут кэшировать запрос, даже если он является общедоступным, так как Vary:* означает, что запрос был сгенерирован с параметрами, которые не указаны в URL-адресе или в заголовках HTTP. Есть ли способ исправить это?

С уважением.

UPDATE:

Аналогичным образом, когда я отправляю два идентичных аутентифицированных запроса, первый вызов получает модификатор private, но не заголовок Vary:

HTTP/1.1 200 OK
Cache-Control: private, max-age=300
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 12:43:14 GMT
Last-Modified: Thu, 09 Feb 2012 12:38:14 GMT
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 12:38:14 GMT
Content-Length: 443

Но второй получает тот же ответ, что и не аутентифицированный запрос:

HTTP/1.1 200 OK
Cache-Control: public, max-age=298
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 12:44:32 GMT
Last-Modified: Thu, 09 Feb 2012 12:39:32 GMT
Vary: *
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 12:39:33 GMT
Content-Length: 443

Я загрузил проект показывающий проблему, поэтому, возможно, вы хотите попробовать.

Имейте в виду, что существует IHttpModule, который устанавливает запрос как аутентифицированный или нет, в зависимости от того, имеет ли запрос файл cookie или нет, это не подход "реальной жизни", он просто предназначен для тестирования.

Проект содержит только веб-страницу со ссылкой на себя, ссылку, в которую вы входите, и другую ссылку, в которой вы выходите:

  • LogIn: снова перенаправляет файл cookie в перенаправление HTTP 302 на домашнюю страницу.
  • LogOut: снова отправляет файл cookie с истекшим сроком в HTTP 302 на главную страницу.

ожидаемое/идеальное поведение:

  • Индекс доступа пользователей и получить страницу с сервера. Дата показа страницы "A" .
  • Индекс доступа пользователей снова, и браузер показывает кешированную версию. На странице отображается дата "A" .
  • Очистить кеш браузера.
  • Индекс доступа пользователей снова, и браузер отображает версию кеширования сервера. Дата показа страницы "A" .
  • Пользователь нажимает логин, а broswer получает новую страницу, отображающую дату "B" .
  • Пользователь нажимает кнопку выхода из системы, и браузер получает страницу кэширования сервера. На странице появится дата "A" .

Но это поведение до сих пор:

  • Индекс доступа пользователей и получить страницу с сервера. Дата показа страницы "A" .
  • Индекс доступа пользователей снова, и браузер показывает кешированную версию. На странице отображается дата "A" .
  • Очистить кеш браузера.
  • Индекс доступа пользователей снова, и браузер отображает версию кеширования сервера. Дата показа страницы "A" .
  • Пользователь нажимает логин, а broswer получает новую страницу, отображающую дату "B" .
  • Пользователь нажимает кнопку выхода из системы, а браузер должен получать серверную кешированную страницу, но не. Страница show date "B" снова из кеша браузера. Это связано с отсутствием заголовка Vary в аутентифицированном ответе.

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

Приветствия.

ОБНОВЛЕНИЕ 2:

Я намерен использовать семантику кеша HTTP для:

  • Разрешить браузерам и прокси кэшировать "общедоступную" версию страницы.
  • Разрешить браузерам кэшировать "аутентифицированную" версию страницы для своего пользователя.

Если я изменил объявление OutputCache, чтобы сделать кеширование только на сервере и предотвратить кэширование ниже по течению и клиенту:

[OutputCache(Duration=60*5, Location=OutputCacheLocation.Server, VaryByCustom="index")]

он ведет себя так, как ожидалось, но кеш-сервер и клиентский кеш предотвращаются, и это не то, что я хочу.

Ответ 1

Я не думаю, что атрибут [OutputCache] - это то, что вы хотите, метод VaryByCustom в основном говорит, что я хочу кэшировать разные версии на основе этих параметров, на самом деле у него нет опции для Do not Cache, а большая часть кода в атрибуте построена на кешировании на основе сервера.

При этом документация на MSDN для пользовательского кэширования, по-видимому, указывает, что вам нужно вернуть строку, которая будет меняться в зависимости от аутентификации состояние:

public override string GetVaryByCustomString(HttpContext context, string custom)
{
    if(custom == "user") return "User:" + context.Request.User.Identity.Name;

    return base.GetVaryByCustomString(context, custom);
}

И затем используйте пользовательский литерал в VaryByCustom:

[OutputCache(Duration=60*5, Location=OutputCacheLocation.Any, VaryByCustom="user")]
public ActionResult Index()
{
    return View();
}

Таким образом, в основном это приведет к созданию кеша для анонимности (при условии, что анонимная идентификация - пустая строка или что-то еще), и каждый пользователь на сервере, и Vary: *, отправленный клиенту, я считаю. Очевидно, что это не идеальное то, что вы ищете.

Если вы действительно хотите кэшировать не прошедшую проверку версию с использованием кеширования HTTP, я бы рекомендовал не использовать OutputCacheAttribute и использовать что-то более обычное.

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

public class HttpCacheUnauthenticatedAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if(!filterContext.HttpContext.Request.IsAuthenticated) {
            //TODO: set unauthenticated caching values and vary here
        }
    }
}

И затем пометьте свой метод действий с ним:

[HttpCacheUnauthenticated]
public ActionResult Index()
{
    return View();
}

Ответ 3

Я использую собственный поставщик кеша, и в этом случае для этого есть простое решение. В BeginRequest, на основе состояния проверки подлинности пользователя, мы устанавливаем контекстную информацию, чтобы не запускать кеш:

HttpContext.Current.Items["NoCache"] = "1";

И затем в нашем методе GetVaryBy мы возвращаем значение null, если эта информация задана:

public override string GetVaryByCustomString(HttpContext context, string custom)
{
    if (HttpContext.Current.Items["NoCache"] != null)
        return null;

    // remaining code here
}

И затем в методах кеша мы можем проверить то же самое. Например:

public override object Add(string key, object entry, DateTime utcExpiry)
{
    if (HttpContext.Current.Items["NoCache"] != null)
        return null;

    // remaining code here
}