Выходное ПО промежуточного слоя кэширования в ASP.NET Core
Автор: Том Дикстра (Tom Dykstra)
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 8 этой статьи.
В этой статье объясняется, как настроить по промежуточному слоям кэширования выходных данных в приложении ASP.NET Core. Общие сведения о кэшировании выходных данных см. в разделе "Кэширование выходных данных".
Выходное ПО промежуточного слоя кэширования можно использовать во всех типах приложений ASP.NET Core: Минимальный API, веб-API с контроллерами, MVC и Razor Pages. Примеры кода предоставляются для минимальных API и API на основе контроллера. Примеры API на основе контроллера показывают, как использовать атрибуты для настройки кэширования. Эти атрибуты также можно использовать в приложениях MVC и Razor Pages.
Примеры кода ссылаются на класс Gravatar, который создает изображение и предоставляет "созданную в" дату и время. Класс определен и используется только в примере приложения. Его цель заключается в том, чтобы упростить просмотр времени использования кэшированных выходных данных. Дополнительные сведения см. в статье "Скачивание примеров и директив препроцессора" в примере кода.
Добавление ПО промежуточного слоя в приложение
Добавьте выходное ПО промежуточного слоя кэширования в коллекцию служб путем вызова AddOutputCache.
Добавьте ПО промежуточного слоя в конвейер обработки запросов путем вызова UseOutputCache.
Например:
builder.Services.AddOutputCache();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseOutputCache();
app.UseAuthorization();
Вызов AddOutputCache
и UseOutputCache
не запускает кэширование, он делает кэширование доступным. Чтобы сделать ответы кэша приложений, кэширование необходимо настроить, как показано в следующих разделах.
Примечание.
- В приложениях, использующих ПО промежуточного слоя CORS,
UseOutputCache
необходимо вызвать после UseCors. - В Razor приложениях и приложениях Pages с контроллерами
UseOutputCache
необходимо вызывать послеUseRouting
.
Настройка одной конечной точки или страницы
Для минимальных приложений API настройте конечную точку для кэширования путем вызова CacheOutput
или применения атрибута [OutputCache]
, как показано в следующих примерах:
app.MapGet("/cached", Gravatar.WriteGravatar).CacheOutput();
app.MapGet("/attribute", [OutputCache] (context) =>
Gravatar.WriteGravatar(context));
Для приложений с контроллерами примените [OutputCache]
атрибут к методу действия, как показано ниже:
[ApiController]
[Route("/[controller]")]
[OutputCache]
public class CachedController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Для Razor приложений Pages примените атрибут к классу Razor страницы.
Настройка нескольких конечных точек или страниц
Создайте политики при вызове AddOutputCache
для указания конфигурации кэширования, которая применяется к нескольким конечным точкам. Для определенных конечных точек можно выбрать политику, а базовая политика предоставляет конфигурацию кэширования по умолчанию для коллекции конечных точек.
Следующий выделенный код настраивает кэширование для всех конечных точек приложения с сроком действия 10 секунд. Если срок действия не указан, значение по умолчанию равно одной минуте.
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder =>
builder.Expire(TimeSpan.FromSeconds(10)));
options.AddPolicy("Expire20", builder =>
builder.Expire(TimeSpan.FromSeconds(20)));
options.AddPolicy("Expire30", builder =>
builder.Expire(TimeSpan.FromSeconds(30)));
});
Следующий выделенный код создает две политики, каждый из которых задает другое время окончания срока действия. Выбранные конечные точки могут использовать 20-секундное истечение срока действия, а другие могут использовать 30-секундное истечение срока действия.
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder =>
builder.Expire(TimeSpan.FromSeconds(10)));
options.AddPolicy("Expire20", builder =>
builder.Expire(TimeSpan.FromSeconds(20)));
options.AddPolicy("Expire30", builder =>
builder.Expire(TimeSpan.FromSeconds(30)));
});
Политику для конечной точки можно выбрать при вызове CacheOutput
метода или с помощью атрибута [OutputCache]
.
В минимальном приложении API следующий код настраивает одну конечную точку с 20-секундным сроком действия и одной с 30-секундным сроком действия:
app.MapGet("/20", Gravatar.WriteGravatar).CacheOutput("Expire20");
app.MapGet("/30", [OutputCache(PolicyName = "Expire30")] (context) =>
Gravatar.WriteGravatar(context));
Для приложений с контроллерами примените [OutputCache]
атрибут к методу действия, чтобы выбрать политику:
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "Expire20")]
public class Expire20Controller : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Для Razor приложений Pages примените атрибут к классу Razor страницы.
Политика кэширования выходных данных по умолчанию
По умолчанию кэширование выходных данных следует следующим правилам:
- Кэшируются только ответы HTTP 200.
- Кэшируются только HTTP-запросы GET или HEAD.
- Ответы, которые задают файлы cookie, не кэшируются.
- Ответы на запросы, прошедшие проверку подлинности, не кэшируются.
Следующий код применяет все правила кэширования по умолчанию ко всем конечным точкам приложения:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Cache());
});
Переопределите политику по умолчанию
В следующем коде показано, как переопределить правила по умолчанию. Выделенные строки в следующем коде пользовательской политики позволяют кэширование для методов HTTP POST и ответов HTTP 301:
using Microsoft.AspNetCore.OutputCaching;
using Microsoft.Extensions.Primitives;
namespace OCMinimal;
public sealed class MyCustomPolicy : IOutputCachePolicy
{
public static readonly MyCustomPolicy Instance = new();
private MyCustomPolicy()
{
}
ValueTask IOutputCachePolicy.CacheRequestAsync(
OutputCacheContext context,
CancellationToken cancellationToken)
{
var attemptOutputCaching = AttemptOutputCaching(context);
context.EnableOutputCaching = true;
context.AllowCacheLookup = attemptOutputCaching;
context.AllowCacheStorage = attemptOutputCaching;
context.AllowLocking = true;
// Vary by any query by default
context.CacheVaryByRules.QueryKeys = "*";
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeFromCacheAsync
(OutputCacheContext context, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeResponseAsync
(OutputCacheContext context, CancellationToken cancellationToken)
{
var response = context.HttpContext.Response;
// Verify existence of cookie headers
if (!StringValues.IsNullOrEmpty(response.Headers.SetCookie))
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
// Check response code
if (response.StatusCode != StatusCodes.Status200OK &&
response.StatusCode != StatusCodes.Status301MovedPermanently)
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
return ValueTask.CompletedTask;
}
private static bool AttemptOutputCaching(OutputCacheContext context)
{
// Check if the current request fulfills the requirements
// to be cached
var request = context.HttpContext.Request;
// Verify the method
if (!HttpMethods.IsGet(request.Method) &&
!HttpMethods.IsHead(request.Method) &&
!HttpMethods.IsPost(request.Method))
{
return false;
}
// Verify existence of authorization headers
if (!StringValues.IsNullOrEmpty(request.Headers.Authorization) ||
request.HttpContext.User?.Identity?.IsAuthenticated == true)
{
return false;
}
return true;
}
}
Чтобы использовать эту настраиваемую политику, создайте именованную политику:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("CachePost", MyCustomPolicy.Instance);
});
Выберите именованную политику для конечной точки. Следующий код выбирает пользовательскую политику для конечной точки в минимальном приложении API:
app.MapPost("/cachedpost", Gravatar.WriteGravatar)
.CacheOutput("CachePost");
Следующий код выполняет то же самое для действия контроллера:
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "CachePost")]
public class PostController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Переопределение альтернативной политики по умолчанию
Кроме того, используйте внедрение зависимостей (DI) для инициализации экземпляра со следующими изменениями в пользовательском классе политики:
- Открытый конструктор вместо частного конструктора.
Instance
Удалите свойство в пользовательском классе политики.
Например:
public sealed class MyCustomPolicy2 : IOutputCachePolicy
{
public MyCustomPolicy2()
{
}
Оставшаяся часть класса аналогична показанной ранее. Добавьте настраиваемую политику, как показано в следующем примере:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("CachePost", builder =>
builder.AddPolicy<MyCustomPolicy2>(), true);
});
Предыдущий код использует DI для создания экземпляра пользовательского класса политики. Все открытые аргументы в конструкторе разрешаются.
При использовании настраиваемой политики в качестве базовой политики не вызывайте OutputCache()
(без аргументов) или используйте [OutputCache]
атрибут в любой конечной точке, к которым должна применяться базовая политика. Вызов OutputCache()
или использование атрибута добавляет политику по умолчанию в конечную точку.
Указание ключа кэша
По умолчанию каждая часть URL-адреса включается в качестве ключа для записи кэша, то есть схемы, узла, порта, пути и строки запроса. Однако может потребоваться явно контролировать ключ кэша. Например, предположим, что у вас есть конечная точка, которая возвращает уникальный ответ только для каждого уникального culture
значения строки запроса. Вариант в других частях URL-адреса, например других строк запроса, не должен привести к разным записям кэша. Такие правила можно указать в политике, как показано в следующем выделенном коде:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder
.With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog"))
.Tag("tag-blog"));
options.AddBasePolicy(builder => builder.Tag("tag-all"));
options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture"));
options.AddPolicy("NoCache", builder => builder.NoCache());
options.AddPolicy("NoLock", builder => builder.SetLocking(false));
});
Затем можно выбрать VaryByQuery
политику для конечной точки. В минимальном приложении API следующий код выбирает VaryByQuery
политику для конечной точки, которая возвращает уникальный ответ только для каждого уникального culture
значения строки запроса:
app.MapGet("/query", Gravatar.WriteGravatar).CacheOutput("Query");
Следующий код выполняет то же самое для действия контроллера:
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "Query")]
public class QueryController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Ниже приведены некоторые варианты управления ключом кэша:
SetVaryByQuery — укажите одно или несколько имен строк запроса, добавляемых в ключ кэша.
SetVaryByHeader — укажите один или несколько заголовков HTTP для добавления в ключ кэша.
VaryByValue— укажите значение для добавления в ключ кэша. В следующем примере используется значение, указывающее, является ли текущее время сервера нечетным или даже. Новый ответ создается только в том случае, если количество секунд переходит от нечетного до даже или даже нечетного.
builder.Services.AddOutputCache(options => { options.AddBasePolicy(builder => builder .With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog")) .Tag("tag-blog")); options.AddBasePolicy(builder => builder.Tag("tag-all")); options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture")); options.AddPolicy("NoCache", builder => builder.NoCache()); options.AddPolicy("NoLock", builder => builder.SetLocking(false)); options.AddPolicy("VaryByValue", builder => builder.VaryByValue((context) => new KeyValuePair<string, string>( "time", (DateTime.Now.Second % 2) .ToString(CultureInfo.InvariantCulture)))); });
Используется OutputCacheOptions.UseCaseSensitivePaths для указания того, что путь части ключа учитывает регистр. Значение по умолчанию не учитывает регистр.
Дополнительные варианты см. в OutputCachePolicyBuilder классе.
Повторная проверка кэша
Повторная проверка кэша означает, что сервер может возвращать 304 Not Modified
код состояния HTTP вместо полного текста ответа. Этот код состояния сообщает клиенту, что ответ на запрос не изменяется от того, что клиент ранее получил.
Следующий код иллюстрирует использование заголовка для включения повторной Etag
сортировки кэша. Если клиент отправляет If-None-Match
заголовок со значением etag предыдущего ответа, а запись кэша является свежей, сервер возвращает значение 304 Не изменено вместо полного ответа. Вот как задать значение etag в политике в минимальном приложении API:
app.MapGet("/etag", async (context) =>
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(context);
}).CacheOutput();
Вот как задать значение etag в API на основе контроллера:
[ApiController]
[Route("/[controller]")]
[OutputCache]
public class EtagController : ControllerBase
{
public async Task GetAsync()
{
var etag = $"\"{Guid.NewGuid():n}\"";
HttpContext.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(HttpContext);
}
}
Еще одним способом повторного выполнения кэша является проверка даты создания записи кэша по сравнению с датой, запрошенной клиентом. Если указан заголовок If-Modified-Since
запроса, кэширование выходных данных возвращает 304, если кэшированная запись устарела и не истекла.
Повторная проверка кэша автоматически выполняется в ответ на эти заголовки, отправляемые клиентом. Для включения этого поведения на сервере не требуется специальная конфигурация, а не включение кэширования выходных данных.
Использование тегов для вытеснения записей кэша
Теги можно использовать для идентификации группы конечных точек и вытеснения всех записей кэша для группы. Например, следующий минимальный код API создает пару конечных точек, URL-адреса которых начинаются с "блога", и тегами их "tag-blog":
app.MapGet("/blog", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog"));
app.MapGet("/blog/post/{id}", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog"));
В следующем коде показано, как назначать теги конечной точке в API на основе контроллера:
[ApiController]
[Route("/[controller]")]
[OutputCache(Tags = new[] { "tag-blog", "tag-all" })]
public class TagEndpointController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Альтернативным способом назначения тегов конечных точек с маршрутами, начинающимися blog
с, является определение базовой политики, которая применяется ко всем конечным точкам с этим маршрутом. В следующем коде показано, как это сделать:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder
.With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog"))
.Tag("tag-blog"));
options.AddBasePolicy(builder => builder.Tag("tag-all"));
options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture"));
options.AddPolicy("NoCache", builder => builder.NoCache());
options.AddPolicy("NoLock", builder => builder.SetLocking(false));
});
Еще одной альтернативой для минимальных приложений API является вызов MapGroup
:
var blog = app.MapGroup("blog")
.CacheOutput(builder => builder.Tag("tag-blog"));
blog.MapGet("/", Gravatar.WriteGravatar);
blog.MapGet("/post/{id}", Gravatar.WriteGravatar);
В предыдущих примерах назначения тегов обе конечные точки определяются тегом tag-blog
. Затем можно вытеснить записи кэша для этих конечных точек с помощью одной инструкции, которая ссылается на этот тег:
app.MapPost("/purge/{tag}", async (IOutputCacheStore cache, string tag) =>
{
await cache.EvictByTagAsync(tag, default);
});
В этом коде http-запрос POST отправляется для https://localhost:<port>/purge/tag-blog
вытеснения записей кэша для этих конечных точек.
Может потребоваться вытеснить все записи кэша для всех конечных точек. Для этого создайте базовую политику для всех конечных точек, как это делает следующий код:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder
.With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog"))
.Tag("tag-blog"));
options.AddBasePolicy(builder => builder.Tag("tag-all"));
options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture"));
options.AddPolicy("NoCache", builder => builder.NoCache());
options.AddPolicy("NoLock", builder => builder.SetLocking(false));
});
Эта базовая политика позволяет использовать тег "tag-all" для вытеснения всего в кэше.
Отключение блокировки ресурсов
По умолчанию блокировка ресурсов включена для снижения риска стека кэша и грома стада. Дополнительные сведения см. в разделе "Кэширование выходных данных".
Чтобы отключить блокировку ресурсов, вызовите SetLocking(false) при создании политики, как показано в следующем примере:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder
.With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog"))
.Tag("tag-blog"));
options.AddBasePolicy(builder => builder.Tag("tag-all"));
options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture"));
options.AddPolicy("NoCache", builder => builder.NoCache());
options.AddPolicy("NoLock", builder => builder.SetLocking(false));
});
В следующем примере выбирается политика без блокировки для конечной точки в минимальном приложении API:
app.MapGet("/nolock", Gravatar.WriteGravatar)
.CacheOutput("NoLock");
В API на основе контроллера используйте атрибут для выбора политики:
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "NoLock")]
public class NoLockController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Ограничения
Следующие свойства OutputCacheOptions позволяют настроить ограничения, которые применяются ко всем конечным точкам:
- SizeLimit — максимальный размер хранилища кэша. По достижении этого ограничения новые ответы кэшируются до тех пор, пока старые записи не вытеснили. Значение по умолчанию — 100 МБ.
- MaximumBodySize — Если текст отклика превышает это ограничение, он не кэширован. Значение по умолчанию — 64 МБ.
- DefaultExpirationTimeSpan — длительность срока действия, которая применяется, если политика не указана. Значение по умолчанию — 60 секунд.
Хранилище кэша
IOutputCacheStore используется для хранения. По умолчанию он используется с MemoryCache. Кэшированные ответы хранятся в процессе, поэтому каждый сервер имеет отдельный кэш, который теряется при перезапуске процесса сервера.
Кэш Redis
Альтернативой является использование кэша Redis . Кэш Redis обеспечивает согласованность между узлами сервера через общий кэш, который выходит за пределы отдельных процессов сервера. Чтобы использовать Redis для кэширования выходных данных:
Установите пакет NuGet Microsoft.AspNetCore.OutputCaching.StackExchangeRedis.
Вызов
builder.Services.AddStackExchangeRedisOutputCache
(неAddStackExchangeRedisCache
) и предоставление строка подключения, указывающего на сервер Redis.Например:
builder.Services.AddStackExchangeRedisOutputCache(options => { options.Configuration = builder.Configuration.GetConnectionString("MyRedisConStr"); options.InstanceName = "SampleInstance"; }); builder.Services.AddOutputCache(options => { options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromSeconds(10))); });
options.Configuration
— строка подключения на локальный сервер Redis или размещенное предложение, например Кэш Azure для Redis. Например,<instance_name>.redis.cache.windows.net:6380,password=<password>,ssl=True,abortConnect=False
для кэша Azure для Redis.options.InstanceName
— Необязательный, указывает логическую секцию для кэша.
Параметры конфигурации идентичны параметрам распределенного кэширования на основе Redis.
IDistributedCache
не рекомендуется
Мы не рекомендуем IDistributedCache использовать для кэширования выходных данных. IDistributedCache
не имеет атомарных функций, необходимых для тегов. Рекомендуется использовать встроенную поддержку Redis или создавать пользовательские IOutputCacheStore реализации с помощью прямых зависимостей в базовом механизме хранения.
См. также
В этой статье объясняется, как настроить по промежуточному слоям кэширования выходных данных в приложении ASP.NET Core. Общие сведения о кэшировании выходных данных см. в разделе "Кэширование выходных данных".
Выходное ПО промежуточного слоя кэширования можно использовать во всех типах приложений ASP.NET Core: Минимальный API, веб-API с контроллерами, MVC и Razor Pages. Пример приложения — это минимальный API, но каждая функция кэширования, которая иллюстрируется, также поддерживается в других типах приложений.
Добавление ПО промежуточного слоя в приложение
Добавьте выходное ПО промежуточного слоя кэширования в коллекцию служб путем вызова AddOutputCache.
Добавьте ПО промежуточного слоя в конвейер обработки запросов путем вызова UseOutputCache.
Примечание.
- В приложениях, использующих ПО промежуточного слоя CORS,
UseOutputCache
необходимо вызвать после UseCors. - В Razor приложениях и приложениях Pages с контроллерами
UseOutputCache
необходимо вызывать послеUseRouting
. - Вызов
AddOutputCache
иUseOutputCache
не запускает кэширование, он делает кэширование доступным. Кэширование данных ответа должно быть настроено, как показано в следующих разделах.
Настройка одной конечной точки или страницы
Для минимальных приложений API настройте конечную точку для кэширования путем вызова CacheOutput
или применения атрибута [OutputCache]
, как показано в следующих примерах:
app.MapGet("/cached", Gravatar.WriteGravatar).CacheOutput();
app.MapGet("/attribute", [OutputCache] (context) =>
Gravatar.WriteGravatar(context));
Для приложений с контроллерами примените [OutputCache]
атрибут к методу действия. Для Razor приложений Pages примените атрибут к классу Razor страницы.
Настройка нескольких конечных точек или страниц
Создайте политики при вызове AddOutputCache
для указания конфигурации кэширования, которая применяется к нескольким конечным точкам. Для определенных конечных точек можно выбрать политику, а базовая политика предоставляет конфигурацию кэширования по умолчанию для коллекции конечных точек.
Следующий выделенный код настраивает кэширование для всех конечных точек приложения с сроком действия 10 секунд. Если срок действия не указан, значение по умолчанию равно одной минуте.
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder =>
builder.Expire(TimeSpan.FromSeconds(10)));
options.AddPolicy("Expire20", builder =>
builder.Expire(TimeSpan.FromSeconds(20)));
options.AddPolicy("Expire30", builder =>
builder.Expire(TimeSpan.FromSeconds(30)));
});
Следующий выделенный код создает две политики, каждый из которых задает другое время окончания срока действия. Выбранные конечные точки могут использовать 20-секундный срок действия, а другие могут использовать 30 секунд.
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder =>
builder.Expire(TimeSpan.FromSeconds(10)));
options.AddPolicy("Expire20", builder =>
builder.Expire(TimeSpan.FromSeconds(20)));
options.AddPolicy("Expire30", builder =>
builder.Expire(TimeSpan.FromSeconds(30)));
});
Политику для конечной точки можно выбрать при вызове CacheOutput
метода или с помощью атрибута [OutputCache]
:
app.MapGet("/20", Gravatar.WriteGravatar).CacheOutput("Expire20");
app.MapGet("/30", [OutputCache(PolicyName = "Expire30")] (context) =>
Gravatar.WriteGravatar(context));
Для приложений с контроллерами примените [OutputCache]
атрибут к методу действия. Для Razor приложений Pages примените атрибут к классу Razor страницы.
Политика кэширования выходных данных по умолчанию
По умолчанию кэширование выходных данных следует следующим правилам:
- Кэшируются только ответы HTTP 200.
- Кэшируются только HTTP-запросы GET или HEAD.
- Ответы, которые задают файлы cookie, не кэшируются.
- Ответы на запросы, прошедшие проверку подлинности, не кэшируются.
Следующий код применяет все правила кэширования по умолчанию ко всем конечным точкам приложения:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Cache());
});
Переопределите политику по умолчанию
В следующем коде показано, как переопределить правила по умолчанию. Выделенные строки в следующем коде пользовательской политики позволяют кэширование для методов HTTP POST и ответов HTTP 301:
using Microsoft.AspNetCore.OutputCaching;
using Microsoft.Extensions.Primitives;
namespace OCMinimal;
public sealed class MyCustomPolicy : IOutputCachePolicy
{
public static readonly MyCustomPolicy Instance = new();
private MyCustomPolicy()
{
}
ValueTask IOutputCachePolicy.CacheRequestAsync(
OutputCacheContext context,
CancellationToken cancellationToken)
{
var attemptOutputCaching = AttemptOutputCaching(context);
context.EnableOutputCaching = true;
context.AllowCacheLookup = attemptOutputCaching;
context.AllowCacheStorage = attemptOutputCaching;
context.AllowLocking = true;
// Vary by any query by default
context.CacheVaryByRules.QueryKeys = "*";
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeFromCacheAsync
(OutputCacheContext context, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeResponseAsync
(OutputCacheContext context, CancellationToken cancellationToken)
{
var response = context.HttpContext.Response;
// Verify existence of cookie headers
if (!StringValues.IsNullOrEmpty(response.Headers.SetCookie))
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
// Check response code
if (response.StatusCode != StatusCodes.Status200OK &&
response.StatusCode != StatusCodes.Status301MovedPermanently)
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
return ValueTask.CompletedTask;
}
private static bool AttemptOutputCaching(OutputCacheContext context)
{
// Check if the current request fulfills the requirements
// to be cached
var request = context.HttpContext.Request;
// Verify the method
if (!HttpMethods.IsGet(request.Method) &&
!HttpMethods.IsHead(request.Method) &&
!HttpMethods.IsPost(request.Method))
{
return false;
}
// Verify existence of authorization headers
if (!StringValues.IsNullOrEmpty(request.Headers.Authorization) ||
request.HttpContext.User?.Identity?.IsAuthenticated == true)
{
return false;
}
return true;
}
}
Чтобы использовать эту настраиваемую политику, создайте именованную политику:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("CachePost", MyCustomPolicy.Instance);
});
И выберите именованную политику для конечной точки:
app.MapPost("/cachedpost", Gravatar.WriteGravatar)
.CacheOutput("CachePost");
Переопределение альтернативной политики по умолчанию
Кроме того, используйте внедрение зависимостей (DI) для инициализации экземпляра со следующими изменениями в пользовательском классе политики:
- Открытый конструктор вместо частного конструктора.
Instance
Удалите свойство в пользовательском классе политики.
Например:
public sealed class MyCustomPolicy2 : IOutputCachePolicy
{
public MyCustomPolicy2()
{
}
Оставшаяся часть класса аналогична показанной ранее. Добавьте настраиваемую политику, как показано в следующем примере:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("CachePost", builder =>
builder.AddPolicy<MyCustomPolicy2>(), true);
});
Предыдущий код использует DI для создания экземпляра пользовательского класса политики. Все открытые аргументы в конструкторе разрешаются.
При использовании настраиваемой политики в качестве базовой политики не вызывайте OutputCache()
(без аргументов) в любой конечной точке, к которым должна применяться базовая политика. Вызов OutputCache()
добавляет политику по умолчанию в конечную точку.
Указание ключа кэша
По умолчанию каждая часть URL-адреса включается в качестве ключа для записи кэша, то есть схемы, узла, порта, пути и строки запроса. Однако может потребоваться явно контролировать ключ кэша. Например, предположим, что у вас есть конечная точка, которая возвращает уникальный ответ только для каждого уникального culture
значения строки запроса. Вариант в других частях URL-адреса, например других строк запроса, не должен привести к разным записям кэша. Такие правила можно указать в политике, как показано в следующем выделенном коде:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder
.With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog"))
.Tag("tag-blog"));
options.AddBasePolicy(builder => builder.Tag("tag-all"));
options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture"));
options.AddPolicy("NoCache", builder => builder.NoCache());
options.AddPolicy("NoLock", builder => builder.SetLocking(false));
});
Затем можно выбрать VaryByQuery
политику для конечной точки:
app.MapGet("/query", Gravatar.WriteGravatar).CacheOutput("Query");
Ниже приведены некоторые варианты управления ключом кэша:
SetVaryByQuery — укажите одно или несколько имен строк запроса, добавляемых в ключ кэша.
SetVaryByHeader — укажите один или несколько заголовков HTTP для добавления в ключ кэша.
VaryByValue— укажите значение для добавления в ключ кэша. В следующем примере используется значение, указывающее, является ли текущее время сервера нечетным или даже. Новый ответ создается только в том случае, если количество секунд переходит от нечетного до даже или даже нечетного.
app.MapGet("/varybyvalue", Gravatar.WriteGravatar) .CacheOutput(c => c.VaryByValue((context) => new KeyValuePair<string, string>( "time", (DateTime.Now.Second % 2) .ToString(CultureInfo.InvariantCulture))));
Используется OutputCacheOptions.UseCaseSensitivePaths для указания того, что путь части ключа учитывает регистр. Значение по умолчанию не учитывает регистр.
Дополнительные варианты см. в OutputCachePolicyBuilder классе.
Повторная проверка кэша
Повторная проверка кэша означает, что сервер может возвращать 304 Not Modified
код состояния HTTP вместо полного текста ответа. Этот код состояния сообщает клиенту, что ответ на запрос не изменяется от того, что клиент ранее получил.
Следующий код иллюстрирует использование заголовка для включения повторной Etag
сортировки кэша. Если клиент отправляет If-None-Match
заголовок со значением etag предыдущего ответа, а запись кэша является свежей, сервер возвращает значение 304 Не изменено вместо полного ответа:
app.MapGet("/etag", async (context) =>
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(context);
}).CacheOutput();
Еще одним способом повторного выполнения кэша является проверка даты создания записи кэша по сравнению с датой, запрошенной клиентом. Если указан заголовок If-Modified-Since
запроса, кэширование выходных данных возвращает 304, если кэшированная запись устарела и не истекла.
Повторная проверка кэша автоматически выполняется в ответ на эти заголовки, отправляемые клиентом. Для включения этого поведения на сервере не требуется специальная конфигурация, а не включение кэширования выходных данных.
Использование тегов для вытеснения записей кэша
Теги можно использовать для идентификации группы конечных точек и вытеснения всех записей кэша для группы. Например, следующий код создает пару конечных точек, URL-адреса которых начинаются с "блога", и тегами их "tag-blog":
app.MapGet("/blog", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog"));
app.MapGet("/blog/post/{id}", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog"));
Альтернативным способом назначения тегов для одной пары конечных точек является определение базовой политики, которая применяется к конечным точкам, начинающимся с blog
:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder
.With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog"))
.Tag("tag-blog"));
options.AddBasePolicy(builder => builder.Tag("tag-all"));
options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture"));
options.AddPolicy("NoCache", builder => builder.NoCache());
options.AddPolicy("NoLock", builder => builder.SetLocking(false));
});
Другой альтернативой является вызов MapGroup
:
var blog = app.MapGroup("blog")
.CacheOutput(builder => builder.Tag("tag-blog"));
blog.MapGet("/", Gravatar.WriteGravatar);
blog.MapGet("/post/{id}", Gravatar.WriteGravatar);
В предыдущих примерах назначения тегов обе конечные точки определяются тегом tag-blog
. Затем можно вытеснить записи кэша для этих конечных точек с помощью одной инструкции, которая ссылается на этот тег:
app.MapPost("/purge/{tag}", async (IOutputCacheStore cache, string tag) =>
{
await cache.EvictByTagAsync(tag, default);
});
В этом коде http-запрос POST, отправленный для https://localhost:<port>/purge/tag-blog
вытеснения записей кэша для этих конечных точек.
Может потребоваться вытеснить все записи кэша для всех конечных точек. Для этого создайте базовую политику для всех конечных точек, как это делает следующий код:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder
.With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog"))
.Tag("tag-blog"));
options.AddBasePolicy(builder => builder.Tag("tag-all"));
options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture"));
options.AddPolicy("NoCache", builder => builder.NoCache());
options.AddPolicy("NoLock", builder => builder.SetLocking(false));
});
Эта базовая политика позволяет использовать тег "tag-all" для вытеснения всего в кэше.
Отключение блокировки ресурсов
По умолчанию блокировка ресурсов включена для снижения риска стека кэша и грома стада. Дополнительные сведения см. в разделе "Кэширование выходных данных".
Чтобы отключить блокировку ресурсов, вызовите SetLocking(false) при создании политики, как показано в следующем примере:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder
.With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog"))
.Tag("tag-blog"));
options.AddBasePolicy(builder => builder.Tag("tag-all"));
options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture"));
options.AddPolicy("NoCache", builder => builder.NoCache());
options.AddPolicy("NoLock", builder => builder.SetLocking(false));
});
В следующем примере выбирается политика без блокировки для конечной точки:
app.MapGet("/nolock", Gravatar.WriteGravatar)
.CacheOutput("NoLock");
Ограничения
Следующие свойства OutputCacheOptions позволяют настроить ограничения, которые применяются ко всем конечным точкам:
- SizeLimit — максимальный размер хранилища кэша. Когда это ограничение достигнуто, новые ответы не будут кэшированы до тех пор, пока старые записи не будут вытесны. Значение по умолчанию — 100 МБ.
- MaximumBodySize — Если текст ответа превышает это ограничение, он не будет кэширован. Значение по умолчанию — 64 МБ.
- DefaultExpirationTimeSpan — длительность срока действия, которая применяется, если политика не указана. Значение по умолчанию — 60 секунд.
Хранилище кэша
IOutputCacheStore используется для хранения. По умолчанию он используется с MemoryCache. Мы не рекомендуем IDistributedCache использовать для кэширования выходных данных. IDistributedCache
не имеет атомарных функций, необходимых для тегов. Рекомендуется создавать пользовательские IOutputCacheStore реализации с помощью прямых зависимостей в базовом механизме хранения, например Redis. Или используйте встроенную поддержку кэша Redis в .NET 8..
См. также
ASP.NET Core