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

Автор: Том Дикстра (Tom Dykstra)

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

В этой статье объясняется, как настроить по промежуточному слоям кэширования выходных данных в приложении 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.AddBasePolicy(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. Кэшированные ответы хранятся в процессе, поэтому каждый сервер имеет отдельный кэш, который теряется при перезапуске процесса сервера.

Кэш 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 не имеет атомарных функций, необходимых для тегов. Рекомендуется использовать встроенную поддержку 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.AddBasePolicy(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..

См. также