ASP.NET позволяет кэшировать ответ приложения, и для этого применяет сервис IOutputCacheStore и middleware OutputCacheMiddleware. Рассмотрим, как их использовать в приложении.
Для кэширования ответа приложения нам надо прежде всего сконфигурировать приложения. Для этого следует выполнить два шага. Прежде всего, добавить в коллекцию сервисов приложения все необходимые сервисы с помощью метода AddOutputCache()
var builder = WebApplication.CreateBuilder(args); builder.Services.AddOutputCache();
Во-вторых, надо добить в конвейер приложения middleware OutputCacheMiddleware
с помощью метода UseOutputCache():
var app = builder.Build(); app.UseOutputCache();
После этого можно применять кэширование. Рассмотрим простейший пример:
var users = new List<string> { "Tom", "Bob", "Sam" }; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOutputCache(); // добавляем сервисы var app = builder.Build(); app.UseOutputCache(); // добавляем OutputCacheMiddleware app.MapGet("/", async () => { await Task.Delay(5000); // имитация долгой обработки return users; }).CacheOutput(); // применяем кэширование к результату обработки метода app.MapGet("/") app.Run();
Здесь при обращении по адресу "/" приложение будет отдавать список users.
app.MapGet("/", async () => { await Task.Delay(5000); // имитация долгой обработки return users; }).CacheOutput();
Для упрощения задачи здесь данные определены локально в виде списка, но в реальности это могут быть данные,
которые приходят из базы данных, с другого сервера с помощью дополнительного сетевого запроса и т.д. А для искусственной имитации долгой обработки запроса я добавил задержку в 5 секунд.
И чтобы при всех последующих обращениях приложение не обрабатывало повторно запрос, применяем кэширование. Для этого к методу app.MapGet
по цепочке добавляем вызов метода CacheOutput().
Чтобы применить кэширование к результату определенного метода, можно использовать два способа:
Вызов метода CacheOutput(), которые определен как метод расширения для типа IEndpointConventionBuilder
. То есть, как в примере выше, мы можем его
вызвать по цепочке после метода Map/MapGet/MapPost/MapPut/MapDelete/MapPatch и т.д.:
app.MapGet("/", () => users).CacheOutput(); // применяем кэширование
Применение атрибута [CacheOutput] к обработчику конечной точки:
using Microsoft.AspNetCore.OutputCaching; // для атрибута [OutputCache] ..................... app.MapGet("/", [OutputCache]() => users);
Одна из перегруженных версий метода AddOutputCache
принимает делегат Action<OutputCacheOptions>, параметр которого - объект OutputCacheOptions с помощью свойств позволяет настроить предельные значения кэширования:
SizeLimit: максимальный размер кэш-хранилища. При достижении этого предела новые ответы не будут кэшироваться до тех пор, пока старые записи не будут удалены. Значение по умолчанию — 100 МБ
MaximumBodySize: максимальный размер отдельного объекта, помещаемого в кэш. Если ответ превышает этот предел, он не будет кэшироваться. Значение по умолчанию — 64 МБ
DefaultExpirationTimeSpan: срок действия, который применяется, если он не указан политикой. Значение по умолчанию — 60 секунд
Пример применения:
using Microsoft.AspNetCore.OutputCaching; var users = new List<string> { "Tom", "Bob", "Sam" }; var builder = WebApplication.CreateBuilder(args); // переустанавливаем ограничения кэширования builder.Services.AddOutputCache(o=> { o.MaximumBodySize = 4 * 1024 * 1024; // 4 Мб o.SizeLimit = 64 * 1024 * 1024; // 64 Мб o.DefaultExpirationTimeSpan = TimeSpan.FromMinutes(2); // 2 минуты }); var app = builder.Build(); app.UseOutputCache(); app.MapGet("/", () => users).CacheOutput(); app.Run();
Метод CacheOutput()
имеет ряд перегрузок, которые позволяют настроить кэширование. Рассмотрим одну из них:
CacheOutput (Action<Microsoft.AspNetCore.OutputCaching.OutputCachePolicyBuilder> policy)
В данную перегрузку в метод передается делегат, который с помощью методов параметра OutputCachePolicyBuilder настраивает опции кэширования. В частности, мы можем использовать следующие методы OutputCachePolicyBuilder:
Expire (TimeSpan expiration)
: устанавливает время кэширования в виде объекта TimeSpan. После истечения этого времени кэш сбрасывается
SetVaryByHeader (string[] headerNames)
: для каждого набора заголовков, переданных в метод, устанавливает свою версию кэша
SetVaryByHost (bool enabled)
: если в метод передается значение true
, то для каждого хоста устанавливается своя версия кэша.
SetVaryByQuery (string[] queryKeys)
: устанавливает свою версию кэша для набора параметров строки запроса, которые передаются в метод
SetVaryByRouteValue (string[] routeValueNames)
: : устанавливает свою версию кэша для набора параметров маршрута, которые передаются в метод
Так, в первом примере из статьи кэш сохранялся на протяжении всего приложения, что не очень хорошо, поскольку данные могут измениться. Изменим код приложения, установив время кэширования:
var users = new List<string> { "Tom", "Bob", "Sam" }; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOutputCache(); // добавляем сервисы var app = builder.Build(); app.UseOutputCache(); // добавляем OutputCacheMiddleware // добавляем в список строку, которая передается через параметр маршрута name app.MapGet("/add/{name}", (string name) => { users.Add(name); return $"{name} has been added"; }); app.MapGet("/", () => users) .CacheOutput(t => t.Expire(TimeSpan.FromMinutes(2))); // время кэширования 2 минуты app.Run();
Для тестирования также определена еще одна конечная точка, которая обрабатывает запрос по путь "/add/{name}" - через параметр name передается строка, которая добавляется в список users:
app.MapGet("/add/{name}", (string name) => { users.Add(name); return $"{name} has been added"; });
Также устанавливаем для кэша время действия в 2 минуты
app.MapGet("/", () => users) .CacheOutput(t => t.Expire(TimeSpan.FromMinutes(2)));
Запустим приложение и сначала обратимся по пути "/", чтобы получить и закэшировать список:
И затем, пока идет время кэширования, обратимся по пути "add/Alice", добавив строку "Alice" в список:
Если время кэширования НЕ истекло, то при повторном обращении по пути "/" мы получим ранее закешированный список без добавленного элемента.
После завершения времени кэширования, которое в примере выше равно 2 минутам, мы получим список с уже добавленным элементом, и этот обновленный список также будет закэшировна на 2 минуты
Подобным образом можно устанавливать и другие параметры кэширования. Например, разграничим кэш для разных значений параметра "username" в строке запроса:
app.MapGet("/", () => users) .CacheOutput(t => t.SetVaryByQuery("username"));
Аналогичную настройку кэширования можно выполнить с помощью свойств атрибута [OutputCache]:
int Duration
: устанавливает время кэширования в секундах
string[]? VaryByHeaderNames
: устанавливает набор заголовоков, для которых создается кэш
string[]? VaryByQueryKeys
: устанавливает набор параметров строки запроса, для которых создается кэш
string[]? VaryByRouteValueNames
: устанавливает набор параметров маршрута, для которых создается кэш
Например, установки кэширования на 2 минуты:
app.MapGet("/", [OutputCache(Duration = 120)] () => users);
Метод Tag() типа OutputCachePolicyBuilder позволяет задать для закешированного результата определенную метку:
app.MapGet("/", () => users) .CacheOutput(t=> { t.Expire(TimeSpan.FromMinutes(2)); // время кэширования - 2 минуты t.Tag("users"); // метка "users" });
С помощью метки мы сможем удалить кэш в произвольный момент времени:
using Microsoft.AspNetCore.OutputCaching; var users = new List<string> { "Tom", "Bob", "Sam" }; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOutputCache(); var app = builder.Build(); app.UseOutputCache(); app.MapGet("/add/{name}", (string name) => { users.Add(name); return $"{name} has been added"; }); app.MapGet("/", () => users) .CacheOutput(t=> { t.Expire(TimeSpan.FromMinutes(2)); t.Tag("users"); }); // сбрасываем кэш app.MapGet("/reset", async (IOutputCacheStore cache) => { // удаляем кэшированный объект с меткой "users" await cache.EvictByTagAsync("users", new CancellationToken()); return "Cache Reset"; }); app.Run();
Для удаления из кэша объекта с меткой "users" получаем из коллекции сервисов сервис IOutputCacheStore и у него вызываем метод EvictByTagAsync(). Этот метод в качестве первого параметра принимает метку объекта в кэше, а в качестве второго токен отмены CancellationToken:
app.MapGet("/reset", async (IOutputCacheStore cache) => { // удаляем кэшированный объект с меткой "users" await cache.EvictByTagAsync("users", new CancellationToken()); return "Cache Reset"; });
Если в приложении множество конечных точек, для которых надо применять одни и те же параметры кэширования, то лучше подобные параметры выделить в отдельную политику.
При этом мы можем настроить одну базовую политику, которая будет действовать глобально и
задать отдельные именованные политики, которые будут применяться при необходимости. Для этого в метод AddOutputCache() передается делегат
Action<OutputCacheOptions>
. Объект OutputCacheOptions имеет два метода: AddBasePolicy()
(настраивает базовую политику) и
AddPolicy
(настраивает именованную политику). Все эти методы в качестве параметра принимают делегат с параметром OutputCachePolicyBuilder, методы которого выше уже были рассмотрены.
По умолчанию в приложении уже определена базовая политика кэширования, которая предусматривает, что
Кэшируются только ответы со статусным кодом 200
Кэшируются только ответы на запросы типа GET и HEAD
Если в ответе устанавливаются куки, то ответ не кэшируется
Ответы на аутентифицированные запросы не кэшируются
Настроим базовую политику, например, установим время кэширования в 1 минуту:
var users = new List<string> { "Tom", "Bob", "Sam" }; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOutputCache(options => { options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(1))); }); var app = builder.Build(); app.UseOutputCache(); app.MapGet("/", () => users).CacheOutput(); app.Run();
Наряду с базовой политикой можно определить и именнованные политики:
var users = new List<string> { "Tom", "Bob", "Sam" }; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOutputCache(options => { options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(1))); options.AddPolicy("Expire2", builder => builder.Expire(TimeSpan.FromMinutes(2))); options.AddPolicy("Expire3", builder =>builder.Expire(TimeSpan.FromMinutes(3))); }); var app = builder.Build(); app.UseOutputCache(); app.MapGet("/", () => users).CacheOutput("Expire2"); // применяем политику "Expire2" app.Run();
Здесь определены две именнованные политики - "Expire2" и "Expire3", которые устанавливают время действия кэша в 2 и 3 секунду соответственно. Для применения именованной политики можно использовать специальную перегрузку метода CacheOutput(), которая принимает имя политики. В этом случае именованная политика переопределяет действие базовой.
Также можно установить нужную политику при использовании атрибута OutputCache. Для этого у атрибута есть свойство PolicyName, которое принимает имя политики:
app.MapGet("/", [OutputCache(PolicyName ="Expire2")] () => users);