Partilhar via


Middleware de 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 9 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 aplicativos ASP.NET Core: API mínima, API Web 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 obter mais informações, consulte Como baixar um exemplo e diretivas de pré-processador no código de exemplo.

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.

Por exemplo:

builder.Services.AddOutputCache();
var app = builder.Build();

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

Chamando AddOutputCachee UseOutputCache não inicia o comportamento de cache, ele torna o cache disponível. Para fazer as respostas de cache do aplicativo, o cache deve ser configurado conforme mostrado nas seções a seguir.

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.

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 [OutputCache] atributo ao método action, conforme mostrado aqui:

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

Para Razor aplicativos Pages, aplique o atributo à Razor classe page.

Configurar vários pontos de extremidade ou páginas

Crie políticas ao chamar AddOutputCache para especificar a configuração de cache que se aplica a vários pontos de extremidade. 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 pontos de extremidade 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.

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 Razor aplicativos Pages, aplique o atributo à Razor classe page.

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 um ponto de extremidade. 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);
    }
}

Substituição de política 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 é o mesmo mostrado anteriormente. 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 no construtor são resolvidos.

Ao usar uma política personalizada como uma política base, não chame OutputCache() (sem argumentos) ou use o [OutputCache] atributo em qualquer ponto de extremidade ao qual a política base deva ser aplicada. Chamar OutputCache() ou usar o atributo adiciona a política padrão ao ponto 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 você tenha um ponto de extremidade que retorna uma resposta exclusiva somente para cada valor exclusivo da culture cadeia de caracteres 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. 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:

  • 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 apenas quando o número de segundos passa de ímpar para par ou par para ímpar.

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

Use OutputCacheOptions.UseCaseSensitivePaths para especificar que a parte do caminho da chave diferencia maiúsculas de minúsculas. O padrão não diferencia maiúsculas de 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. Veja como definir o valor etag em uma política, em um aplicativo de API mínima:

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

}).CacheOutput();

E aqui está como definir o valor etag em 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 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 remover entradas de 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 cujas URLs começam por "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"));

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 a seguir mostra como fazer isso:

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 com API mínima é 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, você pode remover as entradas de cache para esses pontos de extremidade com uma única instrução que faz referência a essa marca:

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 você queira uma maneira de remover todas as entradas de cache para todos os pontos de extremidade. 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 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);
    }
}

Limits

As seguintes propriedades de permitem configurar limites que se aplicam a todos os pontos de OutputCacheOptions extremidade:

  • SizeLimit - Tamanho máximo do armazenamento em cache. Quando esse limite é atingido, nenhuma nova resposta é 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 MemoryCacheo . As respostas armazenadas em cache são armazenadas durante o processo, portanto, cada servidor tem um cache separado que é perdido sempre que o processo do servidor é reiniciado.

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 builder.Services.AddStackExchangeRedisOutputCache (não AddStackExchangeRedisCache) e forneça uma cadeia de conexão que aponte para um servidor Redis.

    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)));
    });
    
    • 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 o cache do Azure para Redis.
    • options.InstanceName - Opcional, especifica uma partição lógica para o cache.

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

Não recomendamos IDistributedCache o 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ê use o suporte interno para Redis ou crie implementações personalizadas IOutputCacheStore usando dependências diretas no mecanismo de armazenamento subjacente.

Consulte também

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 AddOutputCachee UseOutputCache não inicia o comportamento de cache, ele torna o cache disponível. 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 Razor aplicativos Pages, aplique o atributo à Razor classe page.

Configurar vários pontos de extremidade ou páginas

Crie políticas ao chamar AddOutputCache para especificar a configuração de cache que se aplica a vários pontos de extremidade. 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 pontos de extremidade 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 Razor aplicativos Pages, aplique o atributo à Razor classe page.

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 um ponto de extremidade:

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

Substituição de política 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 é o mesmo mostrado anteriormente. 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 no construtor são resolvidos.

Ao usar uma política personalizada como política base, não chame OutputCache() (sem argumentos) nenhum ponto de extremidade ao qual a política base deva ser aplicada. A chamada OutputCache() adiciona a política padrão ao ponto 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 você tenha um ponto de extremidade que retorna uma resposta exclusiva somente para cada valor exclusivo da culture cadeia de caracteres 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 apenas quando o número de segundos passa de ímpar para par ou 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 não diferencia maiúsculas de 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 remover entradas de 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 pontos de extremidade cujas URLs começam com "blog" e os marca 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, você pode remover as entradas de cache para esses pontos de extremidade com uma única instrução que faz referência a essa marca:

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 você queira uma maneira de remover todas as entradas de cache para todos os pontos de extremidade. 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 ponto de extremidade:

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

Limits

As seguintes propriedades de permitem configurar limites que se aplicam a todos os pontos de OutputCacheOptions extremidade:

  • 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 MemoryCacheo . Não recomendamos IDistributedCache o 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