Кэширование ответа и OutputCache

Последнее обновление: 25.08.2023

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)));

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

Установка кэширования ответа и метод OutputCache в ASP.NET Core в C#

И затем, пока идет время кэширования, обратимся по пути "add/Alice", добавив строку "Alice" в список:

Установка времени кэширования ответа и метод OutputCache в ASP.NET Core в C#

Если время кэширования НЕ истекло, то при повторном обращении по пути "/" мы получим ранее закешированный список без добавленного элемента.

Установка кэширования ответа и метод OutputCache в ASP.NET Core в C#

После завершения времени кэширования, которое в примере выше равно 2 минутам, мы получим список с уже добавленным элементом, и этот обновленный список также будет закэшировна на 2 минуты

Установка времени кэширования и метод OutputCache в ASP.NET Core в C#

Подобным образом можно устанавливать и другие параметры кэширования. Например, разграничим кэш для разных значений параметра "username" в строке запроса:

app.MapGet("/", () => users)
    .CacheOutput(t => t.SetVaryByQuery("username"));

Настройка кэширования с помощью атрибута OutputCache

Аналогичную настройку кэширования можно выполнить с помощью свойств атрибута [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); 
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850