ASP.NET MVC и IE кеширование - манипулирование заголовками ответов неэффективно - PullRequest
24 голосов
/ 10 февраля 2012

Фон

Я пытаюсь помочь коллеге отладить проблему, которая не была проблемой в течение последних 6 месяцев. После самого последнего развертывания приложения ASP.NET MVC 2 ответы FileResult, которые вынуждают пользователя открыть файл PDF для сохранения или сохранения, на клиентском компьютере имеют проблемы с тем, чтобы программа чтения PDF открывала их.

Более ранние версии IE (особенно 6) - это единственные затронутые браузеры. Firefox и Chrome и более новые версии IE (> 8) ведут себя как положено. Учитывая это, в следующем разделе определяются действия, необходимые для воссоздания проблемы.

Поведение

  1. Пользователь щелкает ссылку, указывающую на метод действия (обычная гиперссылка с атрибутом href).
  2. Метод действия генерирует PDF, представленный в виде потока байтов. Метод всегда воссоздает PDF.
  3. В методе действия заголовки устанавливаются для указания браузерам, как кэшировать ответ. Это:

    response.AddHeader("Cache-Control", "public, must-revalidate, post-check=0, pre-check=0");
    response.AddHeader("Pragma", "no-cache");
    response.AddHeader("Expires", "0");
    

    Для тех, кто не знает точно, что делают заголовки :

    а. Cache-Control: public

    Указывает, что ответ МОЖЕТ быть кэширован любым кешем, даже если он обычно не кешируется или кешируется только в не кешируемом кеше.

    б. Cache-Control: must-revalidate

    Когда директива must-revalidate присутствует в ответе, полученном кешем, этот кеш НЕ ДОЛЖЕН использовать запись после того, как она устарела, чтобы ответить на последующий запрос без предварительной валидации с сервером происхождения

    с. Cache-Control: предварительная проверка (введено в IE5)

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

    * * Д тысячи сорок-девять. Cache-Control: пост-проверка (введено в IE5)

    Определяет интервал в секундах, после которого объект должен быть проверен на свежесть, прежде чем показывать пользователю ресурс.

    е. Pragma: no-cache (для обеспечения обратной совместимости с HTTP / 1.0)

    Когда в сообщении с запросом присутствует директива no-cache, приложение ДОЛЖНО переслать запрос на исходный сервер, даже если у него есть кэшированная копия того, что запрашивается

    е. 1067 * Истекает *

    В поле заголовка объекта Expires указывается дата / время, после которого ответ считается устаревшим.

  4. Возвращаем файл из действия

    return File(file, "mime/type", fileName);
    
  5. Пользователю предоставляется диалоговое окно Open / Save

  6. Нажатие «Сохранить» работает должным образом, но нажатие «Открыть» запускает программу чтения PDF, но временный файл, сохраненный в IE, уже был удален к тому времени, когда программа чтения пытается открыть файл, поэтому он жалуется, что файл отсутствует (и это так).

Здесь есть полдюжины других приложений, которые используют те же заголовки для принудительного использования Excel, CSV, PDF, Word и тонны другого контента у пользователей, и никогда не было проблем.

Вопрос

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

Заголовки ответа устанавливаются в методе действия перед возвратом FileResult. Я попросил моего коллегу попытаться создать новый класс, который наследуется от FileResult, и вместо этого переопределить метод ExecuteResult, чтобы он изменял заголовки, а затем вместо этого делал base.ExecuteResult() - никакого статуса для этого.

У меня есть подозрение, что "Expires" заголовок "0" является виновником. Согласно этой статье W3C , установка его в «0» подразумевает «уже истек». Я хочу, чтобы срок его действия истек, я просто не хочу, чтобы IE удалил его из файловой системы до того, как приложение, обработавшее его, получит возможность открыть его.

Как всегда, спасибо!

Редактировать: Решение

После дальнейшего тестирования (используя Fiddler для проверки заголовков) мы увидели, что заголовки ответов, которые, как мы думали, были установлены, не были интерпретированы браузером. Я сам не был знаком с кодом, но не знал о существующей проблеме: заголовки оказывались вне метода действия.

Тем не менее, я собираюсь оставить этот вопрос открытым. Все еще выдающимся является следующее: кажется, есть некоторое расхождение между заголовком Expires, имеющим значение 0 против -1. Если кто-то может претендовать на различия по конструкции, что касается IE , я все равно хотел бы услышать об этом. Что касается решения, то приведенные выше заголовки работают, как и предполагалось, со значением Expires, установленным на -1 во всех браузерах.

Обновление 1

Пост Как управлять кэшированием веб-страниц во всех браузерах? подробно описывает, что кэширование может быть предотвращено во всех браузерах с помощью установки Expires = 0. Я до сих пор не продал это 0 против -1 аргумент ...

Ответы [ 3 ]

16 голосов
/ 14 февраля 2012

Я думаю, вы должны просто использовать

HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0));

или

HttpContext.Current.Response.Headers.Set ("Cache-Control", "private, max-age=0");

для установки max-age=0, что не означает ничего, кроме повторной проверки кэша (см. здесь ). Если в заголовке будет дополнительно установлено значение ETag с некоторой пользовательской контрольной суммой хэша из данных, ETag из предыдущего запроса будет отправлен на сервер. Сервер может либо вернуть данные, либо, если данные точно такие же, как и раньше, он может вернуть пустое тело и HttpStatusCode.NotModified в качестве кода состояния. В случае, если веб-браузер будет получать данные из кэша локального браузера.

Я рекомендую вам использовать Cache-Control: private, что вызывает две важные вещи: 1) отключить кэширование данных на прокси-сервере, который иногда имеет очень агрессивные настройки кэширования 2) это разрешит кэширование данных, но не разрешит совместное использование кеша с другими пользователями. Это может решить проблемы конфиденциальности, потому что данные, которые вы возвращаете одному пользователю, могут быть недоступны для чтения другим пользователям. Кстати, код HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0)) по умолчанию устанавливает Cache-Control: private, max-age=0 в заголовке HTTP. Если вы хотите использовать Cache-Control: public, вы можете использовать SetCacheability (HttpCacheability.Public); для перезаписи поведения или использовать Headers.Set вместо Cache.SetMaxAge.

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

ОБНОВЛЕНО : Я решил написать больше информации, чтобы прояснить свою позицию. Соответствует информации из Википедии, даже такие старые веб-браузеры, как Mosaic 2.7, Netscape 2.0 и Internet Explorer 3.0 поддерживают март 1996 года, предварительный стандарт HTTP / 1.1, описанный в RFC 2068. Так что я полагаю (но не проверьте это), что старые веб-браузеры поддерживают HTTP-заголовок max-age=0. В любом случае Netscape 2.06 и Internet Explorer 4.0 окончательно поддерживают HTTP 1.1.

Итак, сначала спросите себя: какие стандарты HTML вы используете? Вы все еще используете HTML 2.0 вместо более поздней версии HTML 3.2, опубликованной в январе 1997 года? Я предполагаю, что вы используете как минимум HTML 4.0, опубликованный в декабре 1997 года. Поэтому, если вы создаете свое приложение хотя бы в HTML 4.0, ваш сайт может быть ориентирован на веб-клиентов, которые поддерживают HTTP 1.1, и игнорировать (не поддерживать) веб-клиенты, которые не поддерживает HTTP 1.1.

Теперь о других заголовках "Cache-Control" как "private, max-age = 0". Включение заголовков на мой взгляд является чистой паранойей . Поскольку у меня возникла некоторая проблема с кэшированием, я также попытался включить другие заголовки, но позже, внимательно прочитав раздел 14.9 RFC2616, я использую только «Cache-Control: private, max-age = 0».

Единственный заголовок "Cache-Control", который может быть дополнительно обсужден, это "must-revalidate", описанный в разделе 14.9.4, на который я ссылался ранее. Вот цитата:

Директива must-revalidate необходима для поддержки надежного работа для определенных функций протокола. При любых обстоятельствах Кеш HTTP / 1.1 ДОЛЖЕН подчиняться директиве must-revalidate; особенно, если кеш не может достичь исходного сервера по какой-либо причине, он ДОЛЖЕН создать ответ 504 (время ожидания шлюза).

Серверы ДОЛЖНЫ отправить директиву must-revalidate, если и только если невозможность повторной проверки запроса объекта может привести к неправильная операция, такая как молча сделка. Получатели НЕ ДОЛЖНЫ предпринимать автоматические действия, которые нарушает эту директиву, и НЕ ДОЛЖЕН автоматически предоставлять непроверенная копия объекта, если повторная проверка не удалась.

Хотя это не рекомендуется, пользовательские агенты работают в условиях серьезного подключения ограничения МОГУТ нарушать эту директиву, но, если это так, ДОЛЖНЫ явно предупредить пользователя о том, что был предоставлен неподтвержденный ответ. Предупреждение ДОЛЖНО быть предоставлено при каждом недействительном доступе и ДОЛЖНО требуется явное подтверждение пользователя.

Иногда, если у меня возникают проблемы с подключением к Интернету, я вижу пустую страницу с сообщением «Время ожидания шлюза». Это происходит от использования директивы must-revalidate. Я не думаю, что сообщение «Gateway Timeout» действительно поможет пользователю.

Таким образом, люди, которые предпочитают начинать процедуру саморазрушения, если он слышит сигнал «Занят» по вызову своему боссу, должны дополнительно использовать директиву must-revalidate в заголовке «Cache-Control». Другим людям я рекомендую просто использовать «Cache-Control: private, max-age = 0» и ничего более.

2 голосов
/ 14 мая 2013

Я знаю, что уже поздно, но эта ссылка может быть огромной помощью для всех, кто интересуется этой темой: http://dotnet.dzone.com/articles/output-caching-aspnet-mvc

0 голосов
/ 13 февраля 2012

Для IE я помню, что нужно установить Expires: -1. Как предотвратить кэширование в Internet Explorer , кажется, подтверждает это следующим фрагментом кода.

<% Response.CacheControl = "no-cache" %>
<% Response.AddHeader "Pragma", "no-cache" %>
<% Response.Expires = -1 %>

Оглядываясь назад, я нашел это. Кроме того, я смутно помню, что если вы установите Cache-Control: private, это может работать неправильно с SSL.

Response.AddHeader("Cache-Control", "no-cache");
Response.AddHeader("Expires", "-1");

Кроме того, Итак, вы не хотите кэшировать, да? упоминает -1, но вместо этого использует методы на Response.Cache:

// Stop Caching in IE
Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
// Stop Caching in Firefox
Response.Cache.SetNoStore();

Однако Проблема кэширования ASP-страницы (IE8) говорит, что этот код не работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...