Промежуточное ПО для кэширования ответов в ASP.NET Core

Джон Луо и Рик Андерсон

Статья объясняет, как настроить промежуточное ПО кэширования ответов в приложении ASP.NET Core. Промежуточное ПО определяет, когда ответы могут быть кэшированы, сохраняет их и предоставляет из кэша. Общие сведения о кэшировании HTTP и [ResponseCache] атрибуте см. в разделе "Кэширование ответов".

Промежуточное программное обеспечение для кэширования ответов включает кэширование ответов сервера на основе заголовков HTTP Cache-Control.

  • Поведение кэширования реализует стандартную семантику кэширования HTTP.

  • Кэширование основано на заголовках кэша HTTP, аналогичных методу, используемому прокси-серверами.

  • Эта форма кэширования полезна для общедоступных запросов GET или HEAD API от клиентов, в которых выполнены условия кэширования .

  • Для приложений пользовательского интерфейса, таких как Razor Pages, кэширование ответов обычно не является полезным. Браузеры обычно задают заголовки запросов, которые предотвращают кэширование.

    Кэширование вывода (доступно в .NET 7 и более поздних версиях) является лучшим подходом для приложений пользовательского интерфейса. В этом сценарии конфигурация определяет, что кэшировать независимо от заголовков HTTP.

Чтобы проверить кэширование ответов, используйте Fiddler или другое средство, которое может явно задать заголовки запросов. Для тестирования кэширования предпочтительнее явно задать заголовки. Дополнительные сведения см. в разделе "Устранение неполадок промежуточного слоя кэширования ответов>".

Configuration

Добавьте службы промежуточного слоя для кэширования ответа Program.cs в коллекцию служб AddResponseCaching и настройте приложение для использования промежуточного слоя методом расширения UseResponseCaching. UseResponseCaching добавляет ПО промежуточного слоя в конвейер обработки запросов:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCaching();

var app = builder.Build();

app.UseHttpsRedirection();

// UseCors must be called before UseResponseCaching
//app.UseCors();

app.UseResponseCaching();

Warning

UseCors необходимо вызывать до UseResponseCaching использования CORS-мидлвэр.

Пример приложения добавляет заголовки для управления кэшированием при последующих запросах:

  • Cache-Control: кэширует кэшируемые ответы до 10 секунд.
  • Vary: Настраивает посредническое программное обеспечение для обслуживания кэшированного ответа только в том случае, если заголовок Accept-Encoding последующих запросов соответствует заголовку исходного запроса.
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCaching();

var app = builder.Build();

app.UseHttpsRedirection();

// UseCors must be called before UseResponseCaching
//app.UseCors();

app.UseResponseCaching();

app.Use(async (context, next) =>
{
    context.Response.GetTypedHeaders().CacheControl =
        new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
        {
            Public = true,
            MaxAge = TimeSpan.FromSeconds(10)
        };
    context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
        new string[] { "Accept-Encoding" };

    await next();
});

app.MapGet("/", () => DateTime.Now.Millisecond);

app.Run();

Предыдущие заголовки не записываются в ответ и переопределяются при выполнении контроллера, действия или Razor страницы.

Промежуточное ПО для кэширования ответов кэширует только те ответы сервера, которые имеют код состояния 200 (ОК). Любые другие ответы, включая страницы ошибок, игнорируются ПО промежуточного слоя.

Warning

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

Предыдущий код обычно не возвращает кэшированное значение в браузер. Используйте Fiddler или другое средство, которое может явно задать заголовки запросов и предпочтительнее для тестирования кэширования. Для получения дополнительной информации см. раздел Устранение неполадок в этой статье.

Options

Параметры кэширования ответов показаны в следующей таблице.

Option Description
MaximumBodySize Самый большой кэшируемый размер текста ответа в байтах. Значение по умолчанию — 64 * 1024 * 1024 64 МБ.
SizeLimit Ограничение размера промежуточного ПО для кэша ответов в байтах. Значение по умолчанию — 100 * 1024 * 1024 100 МБ.
UseCaseSensitivePaths Определяет, кэшируются ли ответы на пути с учетом регистра. Значение по умолчанию — false.

Следующий пример настраивает ПО промежуточного слоя следующим образом:

  • Кэшировать ответы с размером тела, не превышающим 1 024 байта.
  • Сохраните ответы по путям с учетом регистра. Например, /page1 и /Page1 хранятся отдельно.
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCaching(options =>
{
    options.MaximumBodySize = 1024;
    options.UseCaseSensitivePaths = true;
});

var app = builder.Build();

app.UseHttpsRedirection();

// UseCors must be called before UseResponseCaching
//app.UseCors();

app.UseResponseCaching();

app.Use(async (context, next) =>
{
    context.Response.GetTypedHeaders().CacheControl =
        new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
        {
            Public = true,
            MaxAge = TimeSpan.FromSeconds(10)
        };
    context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
        new string[] { "Accept-Encoding" };

    await next(context);
});

app.MapGet("/", () => DateTime.Now.Millisecond);

app.Run();

VaryByQueryKeys

При использовании MVC, контроллеров веб-API или моделей страниц Razor атрибут [ResponseCache] указывает параметры, необходимые для задания соответствующих заголовков для кэширования ответов. Единственным параметром [ResponseCache] атрибута, который строго требуется промежуточным слоем, является VaryByQueryKeys, что не соответствует фактическому заголовку HTTP. Дополнительные сведения см. в статье Кэширование ответов в ASP.NET Core.

Когда атрибут [ResponseCache] не используется, кэширование ответов может варьироваться с помощью VaryByQueryKeys. Использовать ResponseCachingFeature напрямую из HttpContext.Features:

var responseCachingFeature = context.HttpContext.Features.Get<IResponseCachingFeature>();

if (responseCachingFeature != null)
{
    responseCachingFeature.VaryByQueryKeys = new[] { "MyKey" };
}

Использование одного значения, равного *, в VaryByQueryKeys, изменяет кэш в зависимости от всех параметров запроса.

Заголовки HTTP, используемые промежуточным слоем кэширования ответов

В следующей таблице содержатся сведения о заголовках HTTP, влияющих на кэширование ответов.

Header Details
Authorization Ответ не кэширован, если заголовок существует.
Cache-Control ПО промежуточного слоя учитывает только ответы кэширования, помеченные директивой кэша public . Управление кэшированием со следующими параметрами:
  • max-age
  • max-stale†
  • min-fresh
  • must-revalidate
  • no-cache
  • no-store
  • only-if-cached
  • private
  • public
  • s-maxage
  • proxy-revalidate‡
†Если не задано ограничение для max-stale, посредническое ПО не выполняет никаких действий.
proxy-revalidate имеет тот же эффект, что must-revalidateи .

Дополнительные сведения см. в статье RFC 9111: Директивы запроса.
Pragma Заголовок Pragma: no-cache в запросе создает тот же эффект, что Cache-Control: no-cache. Этот заголовок переопределяется соответствующими директивами в заголовке Cache-Control, если он присутствует. Рассматривается для обратной совместимости с HTTP/1.0.
Set-Cookie Ответ не кэширован, если заголовок существует. Любое ПО промежуточного слоя в конвейере обработки запросов, устанавливающее один или несколько файлов cookie, препятствует работе промежуточного ПО кэширования ответа (например, поставщик на основе cookieTempData).
Vary Заголовок Vary используется для изменения поведения кэшированного ответа в зависимости от другого заголовка. Например, чтобы закэшировать ответы в зависимости от кодировки, используйте заголовок Vary: Accept-Encoding, который отдельно кэширует ответы для запросов с заголовками Accept-Encoding: gzip и Accept-Encoding: text/plain. Ответ со значением заголовка * никогда не сохраняется.
Expires Ответ, который считается устаревшим по этому заголовку, не сохраняется или извлекается, если не переопределяется другими Cache-Control заголовками.
If-None-Match Полный ответ выдаётся из кэша, если значение не *, и ETag ответа не соответствует ни одному из предоставленных значений. В противном случае возвращается ответ 304 (не модифицировано).
If-Modified-Since If-None-Match Если заголовок отсутствует, полный ответ обслуживается из кэша, если дата кэшированного ответа является более новой, чем указанное значение. В противном случае отдается ответ 304 — не изменен.
Date При обслуживании из кэша заголовок Date устанавливается посредником, если он не был указан в исходном ответе.
Content-Length При обслуживании из кэша посредником устанавливается заголовок Content-Length, если его не было в исходном ответе.
Age Заголовок, Age отправленный в исходном ответе, игнорируется. ПО промежуточного слоя вычисляет новое значение при обслуживании кэшированного ответа.

Кэширование учитывает директивы Cache-Control для запросов

ПО промежуточного слоя учитывает правила RFC 9111: кэширование HTTP (раздел 5.2. Cache-Control). Правила требуют от кэша уважать допустимые Cache-Control заголовки, отправленные клиентом. В соответствии со спецификацией клиент может выполнять запросы со значением заголовка no-cache и заставлять сервер создавать новый ответ для каждого запроса. В настоящее время разработчики не могут контролировать это поведение кэширования при использовании промежуточного ПО, так как оно соответствует официальной спецификации кэширования.

Чтобы получить больше контроля над поведением кэширования, изучите другие функции кэширования в ASP.NET Core. См. следующие разделы:

Troubleshooting

По промежуточному ПО кэширования ответа используется IMemoryCache, которое имеет ограниченную емкость. При превышении емкости кэш памяти сжимается (TriggerOvercapacityCompaction).

Note

Ссылки в документации на исходный код .NET обычно загружают ветку репозитория по умолчанию, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для конкретного релиза, используйте раскрывающийся список Переключение ветвей или тегов. Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

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

При тестировании и устранении неполадок при кэшировании браузер обычно задает заголовки запросов, которые препятствуют кэшированию. Браузер может задать заголовок Cache-Control на no-cache или max-age=0 при обновлении страницы. Fiddler и другие инструменты могут настраивать заголовки запросов и предпочтительны для тестирования кэширования.

Условия кэширования

  • Запрос должен привести к ответу сервера с кодом состояния 200 (ОК).
  • Метод запроса должен быть GET или HEAD.
  • Промежуточное ПО для кэширования ответов должно быть размещено перед промежуточным ПО, которое требует кэширования. Дополнительные сведения см. в статье ПО промежуточного слоя ASP.NET Core.
  • Заголовок Authorization не должен присутствовать.
  • Cache-Control Параметры заголовка должны быть допустимыми, а ответ должен быть помечен public и не помечен private.
  • Заголовок Pragma: no-cache не должен присутствовать, если заголовок Cache-Control отсутствует, поскольку заголовок Cache-Control переопределяет заголовок Pragma, если он присутствует.
  • Заголовок Set-Cookie не должен присутствовать.
  • Vary Параметры заголовка должны быть допустимыми и не равными *.
  • Значение Content-Length заголовка (если задано) должно соответствовать размеру текста ответа.
  • IHttpSendFileFeature не используется.
  • Ответ не должен быть устаревшим Expires , как указано в заголовке и max-ages-maxage директивах кэша.
  • Буферизация ответов должна быть успешной. Размер ответа должен быть меньше настроенного или по умолчанию SizeLimit. Размер текста ответа должен быть меньше настроенного или по умолчанию MaximumBodySize.
  • Ответ должен быть кэшируемым в соответствии с RFC 9111: кэширование HTTP. Например, директива no-store не должна существовать в полях заголовка запроса или ответа. Дополнительные сведения см. в разделе RFC 9111: кэширование HTTP (раздел 3. Хранение ответов в кэшах ).

Note

Система антифальсификации для создания безопасных маркеров, предотвращающих атаки межсайтовой подделки запросов (CSRF), задает заголовки Cache-Control и Pragma, так чтобы ответы no-cache не кэшировались. Сведения об отключении маркеров защиты от подделки для элементов HTML-форм см. в статье Предотвращение атак межсайтовых запросов forgery (XSRF/CSRF) в ASP.NET Core.

Дополнительные ресурсы

В этой статье объясняется, как настроить промежуточное программное обеспечение для кэширования ответов в приложении ASP.NET Core. Промежуточное ПО определяет, когда ответы могут быть кэшированы, сохраняет их и предоставляет из кэша. Общие сведения о кэшировании HTTP и [ResponseCache] атрибуте см. в разделе "Кэширование ответов".

Просмотреть или скачать образец кода (описание загрузки)

Configuration

Промежуточное ПО кэширования ответов неявно предоставляется для приложений ASP.NET Core через общий фреймворк.

Добавьте ПО промежуточного слоя кэширования ответов Startup.ConfigureServices в коллекцию служб:

public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCaching();
    services.AddRazorPages();
}

Настройте приложение для использования промежуточного ПО с UseResponseCaching методом расширения, которое добавляет промежуточное ПО в конвейер обработки запросов в Startup.Configure:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
    }

    app.UseStaticFiles();
    app.UseRouting();
    // UseCors must be called before UseResponseCaching
    // app.UseCors("myAllowSpecificOrigins");

    app.UseResponseCaching();

    app.Use(async (context, next) =>
    {
        context.Response.GetTypedHeaders().CacheControl = 
            new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
            {
                Public = true,
                MaxAge = TimeSpan.FromSeconds(10)
            };
        context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] = 
            new string[] { "Accept-Encoding" };

        await next();
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Warning

UseCors необходимо вызывать до UseResponseCaching использования CORS-мидлвэр.

Пример приложения добавляет заголовки для управления кэшированием при последующих запросах:

  • Cache-Control: кэширует кэшируемые ответы до 10 секунд.
  • Vary: Настраивает посредническое программное обеспечение для обслуживания кэшированного ответа только в том случае, если заголовок Accept-Encoding последующих запросов соответствует заголовку исходного запроса.
// using Microsoft.AspNetCore.Http;

app.Use(async (context, next) =>
{
    context.Response.GetTypedHeaders().CacheControl = 
        new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
        {
            Public = true,
            MaxAge = TimeSpan.FromSeconds(10)
        };
    context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] = 
        new string[] { "Accept-Encoding" };

    await next();
});

Предыдущие заголовки не записываются в ответ и переопределяются при выполнении контроллера, действия или Razor страницы.

Промежуточное ПО для кэширования ответов кэширует только те ответы сервера, которые имеют код состояния 200 (ОК). Любые другие ответы, включая страницы ошибок, игнорируются ПО промежуточного слоя.

Warning

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

Options

Параметры кэширования ответов показаны в следующей таблице.

Option Description
MaximumBodySize Самый большой кэшируемый размер текста ответа в байтах. Значение по умолчанию — 64 * 1024 * 1024 64 МБ.
SizeLimit Ограничение размера промежуточного ПО для кэша ответов в байтах. Значение по умолчанию — 100 * 1024 * 1024 100 МБ.
UseCaseSensitivePaths Определяет, кэшируются ли ответы на пути с учетом регистра. Значение по умолчанию — false.

Следующий пример настраивает ПО промежуточного слоя следующим образом:

  • Кэшировать ответы с размером тела, не превышающим 1 024 байта.
  • Сохраните ответы по путям с учетом регистра. Например, /page1 и /Page1 хранятся отдельно.
services.AddResponseCaching(options =>
{
    options.MaximumBodySize = 1024;
    options.UseCaseSensitivePaths = true;
});

VaryByQueryKeys

При использовании контроллеров MVC или Razor моделей страниц веб-API атрибут задает параметры, [ResponseCache] необходимые для задания соответствующих заголовков для кэширования ответов. Единственным параметром [ResponseCache] атрибута, который строго требуется промежуточным слоем, является VaryByQueryKeys, что не соответствует фактическому заголовку HTTP. Дополнительные сведения см. в статье Кэширование ответов в ASP.NET Core.

Когда атрибут [ResponseCache] не используется, кэширование ответов может варьироваться с помощью VaryByQueryKeys. Использовать ResponseCachingFeature напрямую из HttpContext.Features:

var responseCachingFeature = context.HttpContext.Features.Get<IResponseCachingFeature>();

if (responseCachingFeature != null)
{
    responseCachingFeature.VaryByQueryKeys = new[] { "MyKey" };
}

Использование одного значения, равного *, в VaryByQueryKeys, изменяет кэш в зависимости от всех параметров запроса.

Заголовки HTTP, используемые промежуточным слоем кэширования ответов

В следующей таблице содержатся сведения о заголовках HTTP, влияющих на кэширование ответов.

Header Details
Authorization Ответ не кэширован, если заголовок существует.
Cache-Control ПО промежуточного слоя учитывает только ответы кэширования, помеченные директивой кэша public . Управление кэшированием со следующими параметрами:
  • max-age
  • max-stale†
  • min-fresh
  • must-revalidate
  • no-cache
  • no-store
  • only-if-cached
  • private
  • public
  • s-maxage
  • proxy-revalidate‡
†Если не задано ограничение для max-stale, посредническое ПО не выполняет никаких действий.
proxy-revalidate имеет тот же эффект, что must-revalidateи .

Дополнительные сведения см. в статье RFC 9111: Директивы запроса.
Pragma Заголовок Pragma: no-cache в запросе создает тот же эффект, что Cache-Control: no-cache. Этот заголовок переопределяется соответствующими директивами в заголовке Cache-Control, если он присутствует. Рассматривается для обратной совместимости с HTTP/1.0.
Set-Cookie Ответ не кэширован, если заголовок существует. Любое ПО промежуточного слоя в конвейере обработки запросов, устанавливающее один или несколько файлов cookie, препятствует работе промежуточного ПО кэширования ответа (например, поставщик на основе cookieTempData).
Vary Заголовок Vary используется для изменения поведения кэшированного ответа в зависимости от другого заголовка. Например, чтобы закэшировать ответы в зависимости от кодировки, используйте заголовок Vary: Accept-Encoding, который отдельно кэширует ответы для запросов с заголовками Accept-Encoding: gzip и Accept-Encoding: text/plain. Ответ со значением заголовка * никогда не сохраняется.
Expires Ответ, который считается устаревшим по этому заголовку, не сохраняется или извлекается, если не переопределяется другими Cache-Control заголовками.
If-None-Match Полный ответ выдаётся из кэша, если значение не *, и ETag ответа не соответствует ни одному из предоставленных значений. В противном случае возвращается ответ 304 (не модифицировано).
If-Modified-Since If-None-Match Если заголовок отсутствует, полный ответ обслуживается из кэша, если дата кэшированного ответа является более новой, чем указанное значение. В противном случае отдается ответ 304 — не изменен.
Date При обслуживании из кэша посредником устанавливается заголовок Date, если его не было в исходном ответе.
Content-Length При обслуживании из кэша посредником устанавливается заголовок Content-Length, если его не было в исходном ответе.
Age Заголовок, Age отправленный в исходном ответе, игнорируется. ПО промежуточного слоя вычисляет новое значение при обслуживании кэшированного ответа.

Кэширование учитывает директивы Cache-Control для запросов

ПО промежуточного слоя учитывает правила RFC 9111: кэширование HTTP (раздел 5.2. Cache-Control). Правила требуют от кэша уважать допустимые Cache-Control заголовки, отправленные клиентом. В соответствии со спецификацией клиент может выполнять запросы со значением заголовка no-cache и заставлять сервер создавать новый ответ для каждого запроса. В настоящее время разработчики не могут контролировать это поведение кэширования при использовании промежуточного ПО, так как оно соответствует официальной спецификации кэширования.

Чтобы получить больше контроля над поведением кэширования, изучите другие функции кэширования в ASP.NET Core. См. следующие разделы:

Troubleshooting

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

При тестировании и устранении неполадок с кэшированием браузер может задать заголовки запросов, влияющие на кэширование нежелательными способами. Браузер может задать заголовок Cache-Control на no-cache или max-age=0 при обновлении страницы. Следующие инструменты могут явно задать заголовки HTTP-запросов и предпочтительнее для тестирования кэширования:

Условия кэширования

  • Запрос должен привести к ответу сервера с кодом состояния 200 (ОК).
  • Метод запроса должен быть GET или HEAD.
  • Промежуточное программное обеспечение для кэширования ответов должно быть размещено перед промежуточным программным обеспечением, если необходимо кэширование. Дополнительные сведения см. в статье ПО промежуточного слоя ASP.NET Core.
  • Заголовок Authorization не должен присутствовать.
  • Cache-Control Параметры заголовка должны быть допустимыми, а ответ должен быть помечен public и не помечен private.
  • Заголовок Pragma: no-cache не должен присутствовать, если заголовок Cache-Control отсутствует, поскольку заголовок Cache-Control переопределяет заголовок Pragma, если он присутствует.
  • Заголовок Set-Cookie не должен присутствовать.
  • Vary Параметры заголовка должны быть допустимыми и не равными *.
  • Значение Content-Length заголовка (если задано) должно соответствовать размеру текста ответа.
  • IHttpSendFileFeature не используется.
  • Ответ не должен быть устаревшим Expires , как указано в заголовке и max-ages-maxage директивах кэша.
  • Буферизация ответов должна быть успешной. Размер ответа должен быть меньше настроенного или по умолчанию SizeLimit. Размер текста ответа должен быть меньше настроенного или по умолчанию MaximumBodySize.
  • Ответ должен быть кэшируемым в соответствии с RFC 9111: кэширование HTTP. Например, директива no-store не должна существовать в полях заголовка запроса или ответа. Дополнительные сведения см. в разделе RFC 9111: кэширование HTTP (раздел 3. Хранение ответов в кэшах ).

Note

Система антифальсификации для создания безопасных маркеров, предотвращающих атаки межсайтовой подделки запросов (CSRF), задает заголовки Cache-Control и Pragma, так чтобы ответы no-cache не кэшировались. Сведения об отключении маркеров защиты от подделки для элементов HTML-форм см. в статье Предотвращение атак межсайтовых запросов forgery (XSRF/CSRF) в ASP.NET Core.

Дополнительные ресурсы