Middleware para cache de saída no ASP.NET Core

Por Tom Dykstra

Note

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.

Warning

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 10 deste artigo.

Este artigo explica como configurar o middleware de cache de saída em um aplicativo ASP.NET Core. Para obter uma introdução ao cache de saída, consulte Cache de saída.

O middleware de cache de saída pode ser usado em todos os tipos de aplicações ASP.NET Core: APIs mínimas, Web API com controladores, MVC e Razor Pages. Exemplos de código são fornecidos para APIs Mínimas e APIs baseadas em controladores. Os exemplos de API baseados em controlador mostram como usar atributos para configurar o cache. Esses atributos também podem ser usados em aplicativos MVC e Razor Pages.

Os exemplos de código referem-se a uma classe Gravatar que gera uma imagem e fornece uma data e hora "gerada em". A classe é definida e usada somente no aplicativo de exemplo. Seu objetivo é tornar mais fácil ver quando a saída em cache está sendo usada. Para mais informações, veja Como descarregar um exemplo e diretivas do Pré-processador.

Adicionar o middleware ao aplicativo

Adicione o middleware de cache de saída à coleção de serviços chamando o AddOutputCache método. Por exemplo:

builder.Services.AddOutputCache();

Adicione o middleware ao pipeline de processamento de pedidos chamando o método UseOutputCache. Por exemplo:

var app = builder.Build();

// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseOutputCache();
app.UseAuthorization();

Chamar os métodos AddOutputCache e UseOutputCache não inicia o comportamento de cache, mas torna o cache disponível. Para fazer as respostas de cache da aplicação, a cache deve ser configurada conforme descrito nas secções seguintes.

Note

  • Em aplicações que utilizam middleware Cross-Origin Requests (CORS), o UseOutputCache método deve ser chamado após o UseCors método.
  • Nas aplicações Razor Pages e nas aplicações com controladores, o método UseOutputCache deve ser chamado depois do método UseRouting.

Configurar um ponto de extremidade ou página

Para aplicações de API Mínima, configure um endpoint para fazer cache chamando o método CacheOutput , ou aplicando o atributo [OutputCache], como mostrado nos exemplos seguintes:

app.MapGet("/cached", Gravatar.WriteGravatar).CacheOutput();
app.MapGet("/attribute", [OutputCache] (context) => 
    Gravatar.WriteGravatar(context));

Para aplicações com comandos, aplique o [OutputCache] atributo ao método de ação conforme mostrado no seguinte código:

[ApiController]
[Route("/[controller]")]
[OutputCache]
public class CachedController : ControllerBase
{
    public async Task GetAsync()
    {
        await Gravatar.WriteGravatar(HttpContext);
    }
}

Para aplicações Razor Pages, aplique o atributo à classe Razor Page.

Configurar vários pontos de extremidade ou páginas

Crie políticas ao chamar o AddOutputCache método para especificar a configuração de cache que se aplica a múltiplos endpoints. Uma política pode ser selecionada para pontos de extremidade específicos, enquanto uma política base fornece configuração de cache padrão para uma coleção de pontos de extremidade.

O código destacado a seguir configura a cache para todos os endpoints da aplicação, com um tempo de expiração de 10 segundos. Se um tempo de expiração não for especificado, o padrão será de um minuto.

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

O código realçado a seguir cria duas políticas, cada uma especificando um tempo de expiração diferente. Os pontos de extremidade selecionados podem usar uma expiração de 20 segundos, enquanto outros podem usar uma expiração de 30 segundos.

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

Pode selecionar uma política para um endpoint ao chamar o CacheOutput método ou ao usar o [OutputCache] atributo.

Numa aplicação Minimal API, o seguinte código configura um endpoint com uma expiração de 20 segundos e outro com expiração de 30 segundos:

app.MapGet("/20", Gravatar.WriteGravatar).CacheOutput("Expire20");
app.MapGet("/30", [OutputCache(PolicyName = "Expire30")] (context) => 
    Gravatar.WriteGravatar(context));

Para aplicativos com controladores, aplique o [OutputCache] atributo ao método action para selecionar uma política:

[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "Expire20")]
public class Expire20Controller : ControllerBase
{
    public async Task GetAsync()
    {
        await Gravatar.WriteGravatar(HttpContext);
    }
}

Para aplicações Razor Pages, aplique o atributo à classe de página Razor.

Trabalhar com a política padrão de cache de saída

Por padrão, o cache de saída segue estas regras:

  • Apenas as respostas HTTP 200 são armazenadas em cache.
  • Somente solicitações HTTP GET ou HEAD são armazenadas em cache.
  • As respostas que definem cookies não são armazenadas em cache.
  • As respostas a solicitações autenticadas não são armazenadas em cache.

O código a seguir aplica todas as regras de cache padrão a todos os pontos de extremidade de um aplicativo:

builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(builder => builder.Cache());
});

Substituir a política padrão

O código seguinte mostra como sobrepor as regras de política padrão. As linhas realçadas no seguinte código de política personalizado permitem o armazenamento em cache para métodos HTTP POST e respostas 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;
    }
}

Para usar essa política personalizada, crie uma política nomeada:

builder.Services.AddOutputCache(options =>
{
    options.AddPolicy("CachePost", MyCustomPolicy.Instance);
});

E, selecione a política especificada para um endpoint. O código seguinte seleciona a política personalizada para um endpoint numa aplicação Minimal API:

app.MapPost("/cachedpost", Gravatar.WriteGravatar)
    .CacheOutput("CachePost");

O código a seguir faz o mesmo para uma ação do controlador:

[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "CachePost")]
public class PostController : ControllerBase
{
    public async Task GetAsync()
    {
        await Gravatar.WriteGravatar(HttpContext);
    }
}

Usar uma substituição de política padrão alternativa

Em alternativa, utilize a Injeção de Dependências (DI) para inicializar uma instância com as seguintes alterações à classe de política personalizada:

  • Use um construtor público em vez de um construtor privado.
  • Elimine a Instance propriedade na classe de política personalizada.

Por exemplo:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

O restante da classe é igual ao previamente apresentado. Adicione a política personalizada conforme mostrado no exemplo a seguir:

builder.Services.AddOutputCache(options =>
{
    options.AddPolicy("CachePost", builder => 
        builder.AddPolicy<MyCustomPolicy2>(), true);
});

O código anterior usa DI para criar a instância da classe de política personalizada. Quaisquer argumentos públicos do construtor são resolvidos.

Ao usar uma política personalizada como política base, não chame o OutputCache() método (sem argumentos) nem use o [OutputCache] atributo em qualquer endpoint ao qual a política base deva aplicar-se. Chamar o método OutputCache() ou usar o atributo adiciona a política padrão ao endpoint.

Especificar a chave de cache

Por padrão, cada parte da URL é incluída como a chave para uma entrada de cache, ou seja, o esquema, o host, a porta, o caminho e a cadeia de caracteres de consulta. No entanto, convém controlar explicitamente a chave de cache. Por exemplo, suponha que se tenha um endpoint que retorna uma resposta única para cada valor distinto da culture string de consulta. A variação em outras partes da URL, como outras cadeias de caracteres de consulta, não deve resultar em entradas de cache diferentes. Você pode especificar essas regras em uma política, conforme mostrado no seguinte código realçado:

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

Em seguida, pode selecionar a VaryByQuery política para um endpoint. Numa aplicação de API Minimal, o seguinte código seleciona a VaryByQuery política para um endpoint que retorna uma resposta única apenas para cada valor único da culture cadeia de consulta:

app.MapGet("/query", Gravatar.WriteGravatar).CacheOutput("Query");

O código a seguir faz o mesmo para uma ação do controlador:

[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "Query")]
public class QueryController : ControllerBase
{
    public async Task GetAsync()
    {
        await Gravatar.WriteGravatar(HttpContext);
    }
}

Aqui estão algumas das opções para controlar a chave de cache:

  • O SetVaryByQuery método especifica um ou mais nomes de cadeias de consulta para adicionar à chave de cache.

  • O SetVaryByHeader método especifica um ou mais cabeçalhos HTTP para adicionar à chave de cache.

  • O VaryByValue método fornece um valor a adicionar à chave de cache. O exemplo a seguir usa um valor que indica se o tempo atual do servidor em segundos é ímpar ou par. Uma nova resposta só é gerada quando o número de segundos muda de valor ímpar para par, ou vice-versa.

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

A propriedade OutputCacheOptions.UseCaseSensitivePaths especifica que a parte do caminho da chave é case-sensitive. O padrão é insensível a maiúsculas e minúsculas.

Para obter mais opções, consulte a OutputCachePolicyBuilder classe.

Ativar a revalidação da cache

A revalidação de cache significa que o servidor pode devolver um código de estado HTTP 304 Não Modificado em vez do corpo completo da resposta. Esse código de status informa ao cliente que a resposta à solicitação não foi alterada em relação ao que o cliente recebeu anteriormente.

O código seguinte ilustra a utilização de um cabeçalho ETag para permitir a revalidação do cache. Se o cliente enviar um cabeçalho If-None-Match com o valor para o ETag de uma resposta anterior, e a entrada de cache for nova, o servidor devolve o código 304 Não Modificado em vez da resposta completa.

O seguinte código define o ETag valor numa política numa aplicação de API Mínima:

app.MapGet("/etag", async (context) =>
{
    var etag = $"\"{Guid.NewGuid():n}\"";
    context.Response.Headers.ETag = etag;
    await Gravatar.WriteGravatar(context);

}).CacheOutput();

O seguinte código faz o mesmo para uma API baseada em controlador:

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

Outra maneira de fazer a revalidação de cache é verificar a data de criação da entrada de cache em comparação com a data solicitada pelo cliente. Quando o cabeçalho If-Modified-Since do pedido é fornecido, a cache de saída devolve o código 304 se a entrada em cache for mais antiga e não estiver expirada.

A revalidação de cache é automática em resposta a esses cabeçalhos enviados do cliente. Nenhuma configuração especial é necessária no servidor para habilitar esse comportamento, além de habilitar o cache de saída.

Usar tags para expulsar entradas na cache

Você pode usar tags para identificar um grupo de pontos de extremidade e remover todas as entradas de cache para o grupo. Por exemplo, o seguinte código Minimal API cria um par de endpoints cujos URLs começam com o texto blog e aplica a tag-blog etiqueta:

app.MapGet("/blog", Gravatar.WriteGravatar)
    .CacheOutput(builder => builder.Tag("tag-blog"));
app.MapGet("/blog/post/{id}", Gravatar.WriteGravatar)
    .CacheOutput(builder => builder.Tag("tag-blog"));

O código a seguir mostra como atribuir tags a um ponto de extremidade em uma API baseada em controlador:

[ApiController]
[Route("/[controller]")]
[OutputCache(Tags = new[] { "tag-blog", "tag-all" })]
public class TagEndpointController : ControllerBase
{
    public async Task GetAsync()
    {
        await Gravatar.WriteGravatar(HttpContext);
    }
}

Uma maneira alternativa de atribuir tags para pontos de extremidade com rotas que começam com blog é definir uma política base que se aplique a todos os pontos de extremidade com essa rota. O código seguinte demonstra esta abordagem:

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

Outra alternativa para aplicações Minimal API é chamar o método MapGroup :

var blog = app.MapGroup("blog")
    .CacheOutput(builder => builder.Tag("tag-blog"));
blog.MapGet("/", Gravatar.WriteGravatar);
blog.MapGet("/post/{id}", Gravatar.WriteGravatar);

Nos exemplos de atribuição de tag anteriores, ambos os pontos de extremidade são identificados pela tag-blog tag. Em seguida, és capaz de remover as entradas de cache para esses pontos de extremidade com uma única instrução que faz referência a essa etiqueta.

app.MapPost("/purge/{tag}", async (IOutputCacheStore cache, string tag) =>
{
    await cache.EvictByTagAsync(tag, default);
});

Com este código, um pedido HTTP POST enviado para a https://localhost:<port>/purge/tag-blog URL expulsa entradas de cache para estes endpoints.

Talvez tu queiras uma maneira de eliminar todas as entradas de cache para todos os endpoints. Pode criar uma política base para todos os endpoints, conforme demonstrado no seguinte código:

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

Esta política base permite-te usar a tag-all etiqueta para despejar tudo o que está na cache.

Desativar bloqueio de recursos

Por padrão, o bloqueio de recursos é habilitado para reduzir o risco de debandada de cache e rebanho trovejante. Para obter mais informações, consulte Cache de saída.

Para desativar o bloqueio de recursos, chame o método SetLocking(false) ao criar uma política, como mostrado no seguinte exemplo:

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

O exemplo seguinte seleciona a política de não bloqueio para um endpoint numa aplicação Minimal API:

app.MapGet("/nolock", Gravatar.WriteGravatar)
    .CacheOutput("NoLock");

Em uma API baseada em controlador, use o atributo para selecionar a política:

[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "NoLock")]
public class NoLockController : ControllerBase
{
    public async Task GetAsync()
    {
        await Gravatar.WriteGravatar(HttpContext);
    }
}

Configurar limites

As seguintes propriedades da OutputCacheOptions classe permitem-lhe configurar limites que se aplicam a todos os endpoints:

  • A SizeLimit propriedade define o tamanho máximo para o armazenamento em cache. Quando o limite é atingido, nenhuma nova resposta é armazenada em cache até que as entradas mais antigas sejam expulsas. O valor padrão é 100 MB.
  • A MaximumBodySize propriedade define o tamanho máximo para o corpo de resposta. Se o corpo de resposta ultrapassar o limite, não será armazenado em cache. O valor padrão é 64 MB.
  • A DefaultExpirationTimeSpan propriedade define a duração máxima em que uma resposta é armazenada em cache, quando uma hora não está especificada numa política. O valor padrão é 60 segundos.

Explore opções de armazenamento de cache

A IOutputCacheStore interface é usada para armazenamento. Por padrão, é usado com a MemoryCache classe. As respostas em cache são armazenadas em processo, pelo que cada servidor tem uma cache separada que se perde sempre que o processo do servidor reinicia.

Alternativa: cache Redis

Uma alternativa é usar o cache Redis . O cache Redis fornece consistência entre os nós do servidor por meio de um cache compartilhado que sobrevive aos processos individuais do servidor. Para usar o Redis para cache de saída:

  • Instale o pacote NuGet Microsoft.AspNetCore.OutputCaching.StackExchangeRedis .

  • Chame o método builder.Services.AddStackExchangeRedisOutputCache (não o método AddStackExchangeRedisCache) e forneça um cadeia de ligação que aponte para um servidor Rederis.

    Por exemplo:

    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)));
    });
    
    • A propriedade options.Configuration é uma cadeia de conexão para um servidor Redis local ou para uma oferta hospedada, como o Cache do Azure para Redis. Por exemplo, <instance_name>.redis.cache.windows.net:6380,password=,pw,ssl=True,abortConnect=False para Cache do Azure para Redis.

    • (Opcional) A propriedade opções.InstanceName especifica uma partição lógica para a cache.

    As opções de configuração são idênticas às opções de cache distribuído baseadas em Redis.

A IDistributedCache interface não é recomendada para uso com cache de saída. Esta interface não fornece características atómicas, que são necessárias para a marcação.

A abordagem recomendada é usar o suporte incorporado para o Redis ou criar uma implementação personalizada IOutputCacheStore usando dependências diretas do mecanismo de armazenamento subjacente.

Este artigo explica como configurar o middleware de cache de saída em um aplicativo ASP.NET Core. Para obter uma introdução ao cache de saída, consulte Cache de saída.

O middleware de cache de saída pode ser usado em todos os tipos de aplicativos ASP.NET Core: API mínima, API Web com controladores, MVC e Razor Pages. O aplicativo de exemplo é uma API mínima, mas todos os recursos de cache que ele ilustra também são suportados nos outros tipos de aplicativo.

Adicionar o middleware ao aplicativo

Adicione o middleware de cache de saída à coleção de serviços chamando AddOutputCache.

Adicione o middleware ao pipeline de processamento de solicitações chamando UseOutputCache.

Note

  • Em aplicativos que usam middleware CORS, UseOutputCache deve ser chamado após UseCors.
  • Em Razor aplicativos do Pages e aplicativos com controladores, UseOutputCache deve ser chamado após UseRouting.
  • Chamando as funções AddOutputCache e UseOutputCache não inicia o comportamento de cache, mas disponibiliza o cache. O armazenamento em cache dos dados de resposta deve ser configurado conforme mostrado nas seções a seguir.

Configurar um ponto de extremidade ou página

Para aplicações Minimal API, configure um endpoint para fazer cache chamando CacheOutput, ou aplicando o [OutputCache] atributo, como mostrado nos seguintes exemplos:

app.MapGet("/cached", Gravatar.WriteGravatar).CacheOutput();
app.MapGet("/attribute", [OutputCache] (context) => 
    Gravatar.WriteGravatar(context));

Para aplicativos com controladores, aplique o atributo [OutputCache] ao método action. Para aplicações Razor Pages, aplique o atributo à classe de página Razor.

Configurar vários pontos de extremidade ou páginas

Crie políticas ao invocar AddOutputCache para especificar a configuração de cache que se aplica a vários endpoints. Uma política pode ser selecionada para pontos de extremidade específicos, enquanto uma política base fornece configuração de cache padrão para uma coleção de pontos de extremidade.

O código destacado a seguir configura o cache para todos os pontos de extremidade do aplicativo, com tempo de expiração de 10 segundos. Se um tempo de expiração não for especificado, o padrão será de um minuto.

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

O código realçado a seguir cria duas políticas, cada uma especificando um tempo de expiração diferente. Os endpoints selecionados podem usar a expiração de 20 segundos, e outros podem usar a expiração de 30 segundos.

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

Você pode selecionar uma política para um ponto de extremidade ao chamar o CacheOutput método ou usar o [OutputCache] atributo:

app.MapGet("/20", Gravatar.WriteGravatar).CacheOutput("Expire20");
app.MapGet("/30", [OutputCache(PolicyName = "Expire30")] (context) => 
    Gravatar.WriteGravatar(context));

Para aplicativos com controladores, aplique o atributo [OutputCache] ao método action. Para aplicações Razor Pages, aplique o atributo à classe de página Razor.

Política de cache de saída padrão

Por padrão, o cache de saída segue estas regras:

  • Apenas as respostas HTTP 200 são armazenadas em cache.
  • Somente solicitações HTTP GET ou HEAD são armazenadas em cache.
  • As respostas que definem cookies não são armazenadas em cache.
  • As respostas a solicitações autenticadas não são armazenadas em cache.

O código a seguir aplica todas as regras de cache padrão a todos os pontos de extremidade de um aplicativo:

builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(builder => builder.Cache());
});

Substituir a política padrão

O código a seguir mostra como substituir as regras padrão. As linhas realçadas no seguinte código de política personalizado permitem o armazenamento em cache para métodos HTTP POST e respostas 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;
    }
}

Para usar essa política personalizada, crie uma política nomeada:

builder.Services.AddOutputCache(options =>
{
    options.AddPolicy("CachePost", MyCustomPolicy.Instance);
});

E selecione a política nomeada para o endpoint:

app.MapPost("/cachedpost", Gravatar.WriteGravatar)
    .CacheOutput("CachePost");

Substituição de regra padrão alternativa

Como alternativa, use a injeção de dependência (DI) para inicializar uma instância, com as seguintes alterações na classe de política personalizada:

  • Um construtor público em vez de um construtor privado.
  • Elimine a Instance propriedade na classe de política personalizada.

Por exemplo:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

O restante da classe é igual ao previamente apresentado. Adicione a política personalizada conforme mostrado no exemplo a seguir:

builder.Services.AddOutputCache(options =>
{
    options.AddPolicy("CachePost", builder => 
        builder.AddPolicy<MyCustomPolicy2>(), true);
});

O código anterior usa DI para criar a instância da classe de política personalizada. Quaisquer argumentos públicos do construtor são resolvidos.

Ao usar uma política personalizada como política base, não chame OutputCache() (sem argumentos) em nenhum ponto de extremidade ao qual a política base deverá ser aplicada. A chamada OutputCache() adiciona a política padrão ao endereço de extremidade.

Especificar a chave de cache

Por padrão, cada parte da URL é incluída como a chave para uma entrada de cache, ou seja, o esquema, o host, a porta, o caminho e a cadeia de caracteres de consulta. No entanto, convém controlar explicitamente a chave de cache. Por exemplo, suponha que se tenha um endpoint que retorna uma resposta única para cada valor distinto da culture string de consulta. A variação em outras partes da URL, como outras cadeias de caracteres de consulta, não deve resultar em entradas de cache diferentes. Você pode especificar essas regras em uma política, conforme mostrado no seguinte código realçado:

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

Em seguida, você pode selecionar a VaryByQuery política para um ponto de extremidade:

app.MapGet("/query", Gravatar.WriteGravatar).CacheOutput("Query");

Aqui estão algumas das opções para controlar a chave de cache:

  • SetVaryByQuery - Especifique um ou mais nomes de cadeia de caracteres de consulta para adicionar à chave de cache.

  • SetVaryByHeader - Especifique um ou mais cabeçalhos HTTP para adicionar à chave de cache.

  • VaryByValue- Especifique um valor para adicionar à chave de cache. O exemplo a seguir usa um valor que indica se o tempo atual do servidor em segundos é ímpar ou par. Uma nova resposta é gerada somente quando o valor do número de segundos passa de ímpar para par ou de par para ímpar.

    app.MapGet("/varybyvalue", Gravatar.WriteGravatar)
        .CacheOutput(c => c.VaryByValue((context) => 
            new KeyValuePair<string, string>(
                "time", (DateTime.Now.Second % 2)
                    .ToString(CultureInfo.InvariantCulture))));
    

Use OutputCacheOptions.UseCaseSensitivePaths para especificar que a parte do caminho da chave diferencia maiúsculas de minúsculas. O padrão é insensível a maiúsculas e minúsculas.

Para obter mais opções, consulte a OutputCachePolicyBuilder classe.

Revalidação de cache

A revalidação de cache significa que o servidor pode retornar um 304 Not Modified código de status HTTP em vez do corpo de resposta completo. Esse código de status informa ao cliente que a resposta à solicitação não foi alterada em relação ao que o cliente recebeu anteriormente.

O código a seguir ilustra o uso de um Etag cabeçalho para habilitar a revalidação de cache. Se o cliente enviar um If-None-Match cabeçalho com o valor etag de uma resposta anterior e a entrada de cache for recente, o servidor retornará 304 Não modificado em vez da resposta completa:

app.MapGet("/etag", async (context) =>
{
    var etag = $"\"{Guid.NewGuid():n}\"";
    context.Response.Headers.ETag = etag;
    await Gravatar.WriteGravatar(context);

}).CacheOutput();

Outra maneira de fazer a revalidação de cache é verificar a data de criação da entrada de cache em comparação com a data solicitada pelo cliente. Quando o cabeçalho If-Modified-Since da solicitação é fornecido, o cache de saída retorna 304 se a entrada armazenada em cache for mais antiga e não tiver expirado.

A revalidação de cache é automática em resposta a esses cabeçalhos enviados do cliente. Nenhuma configuração especial é necessária no servidor para habilitar esse comportamento, além de habilitar o cache de saída.

Usar tags para expulsar entradas na cache

Você pode usar tags para identificar um grupo de pontos de extremidade e remover todas as entradas de cache para o grupo. Por exemplo, o código a seguir cria um par de endpoints cujas URLs começam com "blog" e etiqueta-os como "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"));

Uma maneira alternativa de atribuir tags para o mesmo par de pontos de extremidade é definir uma política base que se aplique a pontos de extremidade que comecem com 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));
});

Outra alternativa é chamar MapGroup:

var blog = app.MapGroup("blog")
    .CacheOutput(builder => builder.Tag("tag-blog"));
blog.MapGet("/", Gravatar.WriteGravatar);
blog.MapGet("/post/{id}", Gravatar.WriteGravatar);

Nos exemplos de atribuição de tag anteriores, ambos os pontos de extremidade são identificados pela tag-blog tag. Em seguida, és capaz de remover as entradas de cache para esses pontos de extremidade com uma única instrução que faz referência a essa etiqueta.

app.MapPost("/purge/{tag}", async (IOutputCacheStore cache, string tag) =>
{
    await cache.EvictByTagAsync(tag, default);
});

Com esse código, uma solicitação HTTP POST enviada para https://localhost:<port>/purge/tag-blog removerá entradas de cache para esses pontos de extremidade.

Talvez tu queiras uma maneira de eliminar todas as entradas de cache para todos os endpoints. Para fazer isso, crie uma política base para todos os pontos de extremidade, como faz o código a seguir:

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

Essa política básica permite que você use a tag "tag-all" para remover tudo em cache.

Desativar bloqueio de recursos

Por padrão, o bloqueio de recursos é habilitado para reduzir o risco de debandada de cache e rebanho trovejante. Para obter mais informações, consulte Cache de saída.

Para desabilitar o bloqueio de recursos, chame SetLocking(false) ao criar uma política, conforme mostrado no exemplo a seguir:

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

O exemplo a seguir seleciona a política de não bloqueio para um endpoint:

app.MapGet("/nolock", Gravatar.WriteGravatar)
    .CacheOutput("NoLock");

Limits

As seguintes propriedades de OutputCacheOptions permitem configurar limites que se aplicam a todos os endpoints.

  • SizeLimit - Tamanho máximo do armazenamento em cache. Quando esse limite for atingido, nenhuma nova resposta será armazenada em cache até que as entradas mais antigas sejam removidas. O valor padrão é 100 MB.
  • MaximumBodySize - Se o corpo da resposta exceder esse limite, ele não será armazenado em cache. O valor padrão é 64 MB.
  • DefaultExpirationTimeSpan - A duração do tempo de expiração que se aplica quando não especificado por uma política. O valor padrão é 60 segundos.

Armazenamento em cache

IOutputCacheStore é utilizado para armazenamento. Por padrão, ele é usado com MemoryCache. Não recomendamos IDistributedCache para uso com cache de saída. IDistributedCache não tem características atómicas, que são necessárias para a marcação. Recomendamos que você crie implementações personalizadas IOutputCacheStore usando dependências diretas no mecanismo de armazenamento subjacente, como Redis. Ou use o suporte interno para cache Redis no .NET 8..

Consulte também