Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Автор: Том Дикстра (Tom Dykstra)
Note
Это не последняя версия этой статьи. В текущей версии см. версию .NET 10 этой статьи.
Warning
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущей версии см. версию .NET 10 этой статьи.
В этой статье объясняется, как настроить промежуточное программное обеспечение для кэширования выходных данных в приложении ASP.NET Core. Общие сведения о кэшировании выходных данных см. в разделе "Кэширование выходных данных".
Промежуточное ПО для кэширования можно использовать во всех типах приложений ASP.NET Core: Minimal APIs, Web API с контроллерами, MVC и Razor. Примеры кода предоставляются для минимальных API и API на основе контроллера. Примеры API на основе контроллера показывают, как использовать атрибуты для настройки кэширования. Эти атрибуты также можно использовать в приложениях MVC и Razor Pages.
Примеры кода ссылаются на класс Gravatar, который создает изображение и предоставляет дату и время "создания". Класс определен и используется только в примере приложения. Его цель заключается в том, чтобы упростить просмотр времени использования кэшированных выходных данных. Дополнительные сведения см. в разделе "Как скачать примери директивы препроцессора".
Добавьте промежуточное ПО в приложение
Добавьте промежуточное ПО для кэширования выхода в коллекцию служб с помощью метода AddOutputCache. Рассмотрим пример.
builder.Services.AddOutputCache();
Добавьте middleware в конвейер обработки запросов, вызвав метод UseOutputCache. Рассмотрим пример.
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseOutputCache();
app.UseAuthorization();
Вызов методов AddOutputCache и UseOutputCache не запускает кэширование, это делает его доступным. Чтобы приложение кэшировало ответы, кэширование необходимо настроить, как описано в следующих разделах.
Note
- В приложениях, использующих посредник CORS,
UseOutputCacheметод должен вызываться после UseCors метода. - В приложениях 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);
});
Выберите именованную политику для конечной точки. Следующий код выбирает пользовательскую политику для конечной точки в приложении Minimal 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 классе.
Включение повторной проверки кэша
Изменение кэша означает, что сервер может вернуть код состояния HTTP 304 Not Modified HTTP вместо полного текста ответа. Этот код состояния сообщает клиенту, что ответ на запрос не изменяется от того, что клиент ранее получил.
В следующем коде показано использование заголовка ETag для включения повторной сортировки кэша. Если клиент отправляет заголовок If-None-Match со значением для ETag предыдущего ответа, а запись кэша является свежей, сервер возвращает код 304 Not Modified вместо полного ответа.
Следующий код устанавливает значение ETag в политике минимального приложения API:
app.MapGet("/etag", async (context) =>
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(context);
}).CacheOutput();
Следующий код выполняет то же самое для 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-адреса которых начинаются с текста blog и применяют 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 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));
});
Эта базовая политика позволяет использовать 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=,pw,ssl=True,abortConnect=Falseдля Кэш Azure для Redis.(Необязательно) Свойство options.InstanceName задает логическую секцию для кэша.
Параметры конфигурации идентичны параметрам распределенного кэширования на основе Redis.
Не рекомендуется: IDistributedCache
Интерфейс IDistributedCache не рекомендуется использовать при кэшировании выходных данных. Этот интерфейс не предоставляет атомарные функции, необходимые для тегов.
Рекомендуемый подход — использовать встроенную поддержку Redis или создать пользовательскую IOutputCacheStore реализацию, используя прямые зависимости на базовый механизм хранения.
Связанный контент
В этой статье объясняется, как настроить промежуточное программное обеспечение для кэширования выходных данных в приложении ASP.NET Core. Общие сведения о кэшировании выходных данных см. в разделе "Кэширование выходных данных".
Выходное ПО промежуточного слоя кэширования можно использовать во всех типах приложений ASP.NET Core: Минимальный API, веб-API с контроллерами, MVC и Razor Pages. Пример приложения — это минимальный API, но каждая функция кэширования, которая иллюстрируется, также поддерживается в других типах приложений.
Добавьте промежуточное ПО в приложение
Добавьте выходное ПО промежуточного слоя кэширования в коллекцию служб путем вызова AddOutputCache.
Добавьте ПО промежуточного слоя в конвейер обработки запросов путем вызова UseOutputCache.
Note
- В приложениях, использующих ПО промежуточного слоя CORS,
UseOutputCacheнеобходимо вызвать после UseCors. - В приложениях 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-адреса которых начинаются с "blog", и помечает их тегом "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");
Limits
Следующие свойства OutputCacheOptions позволяют настроить ограничения, которые применяются ко всем конечным точкам:
- SizeLimit — максимальный размер хранилища кэша. Когда это ограничение достигнуто, новые ответы не будут кэшированы до тех пор, пока старые записи не будут вытесны. Значение по умолчанию — 100 МБ.
- MaximumBodySize — Если текст ответа превышает это ограничение, он не будет кэширован. Значение по умолчанию — 64 МБ.
- DefaultExpirationTimeSpan — срок действия, который применяется, если не указано в настройках. Значение по умолчанию — 60 секунд.
Хранилище кэша
IOutputCacheStore используется для хранения. По умолчанию он используется с MemoryCache. Мы не рекомендуем IDistributedCache использовать для кэширования выходных данных.
IDistributedCache не имеет атомарных функций, необходимых для тегов. Рекомендуется создавать пользовательские IOutputCacheStore реализации с помощью прямых зависимостей в базовом механизме хранения, например Redis. Или используйте встроенную поддержку кэша Redis в .NET 8..
См. также
ASP.NET Core