Middleware di memorizzazione nella cache dell'output in ASP.NET Core

Tom Dykstra

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Questo articolo illustra come configurare il middleware di memorizzazione nella cache di output in un'app ASP.NET Core. Per un'introduzione alla memorizzazione nella cache dell'output, vedere Memorizzazione nella cache dell'output.

Il middleware di memorizzazione nella cache di output può essere usato in tutti i tipi di app core di ASP.NET: API minima, API Web con controller, MVC e Razor Pagine. Vengono forniti esempi di codice per API minime e API basate su controller. Gli esempi di API basate su controller illustrano come usare gli attributi per configurare la memorizzazione nella cache. Questi attributi possono essere usati anche nelle app MVC e Razor Pages.

Gli esempi di codice fanno riferimento a una classe Gravatar che genera un'immagine e fornisce una data e un'ora "generate in corrispondenza di". La classe è definita e usata solo nell'app di esempio. Lo scopo è quello di semplificare la verifica quando viene usato l'output memorizzato nella cache. Per altre informazioni, vedere Come scaricare un esempio e direttive del preprocessore nel codice di esempio.

Aggiungere il middleware all'app

Aggiungere il middleware di memorizzazione nella cache di output alla raccolta di servizi chiamando AddOutputCache.

Aggiungere il middleware alla pipeline di elaborazione delle richieste chiamando UseOutputCache.

Ad esempio:

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

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

La chiamata AddOutputCachee UseOutputCache non avvia il comportamento di memorizzazione nella cache, rende disponibile la memorizzazione nella cache. Per rendere le risposte della cache dell'app, la memorizzazione nella cache deve essere configurata come illustrato nelle sezioni seguenti.

Nota

  • Nelle app che usano il middleware CORS, UseOutputCache è necessario chiamare dopo UseCors.
  • Nelle Razor app e nelle app Pages con controller deve UseOutputCache essere chiamato dopo UseRouting.

Configurare un endpoint o una pagina

Per le app per le API minime, configurare un endpoint per eseguire la memorizzazione nella cache chiamando CacheOutputo applicando l'attributo [OutputCache] , come illustrato negli esempi seguenti:

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

Per le app con controller, applicare l'attributo [OutputCache] al metodo di azione, come illustrato di seguito:

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

Per Razor le app Pages, applicare l'attributo alla Razor classe page.

Configurare più endpoint o pagine

Creare criteri quando si chiama AddOutputCache per specificare la configurazione di memorizzazione nella cache applicabile a più endpoint. È possibile selezionare un criterio per endpoint specifici, mentre un criterio di base fornisce la configurazione di memorizzazione nella cache predefinita per una raccolta di endpoint.

Il codice evidenziato seguente configura la memorizzazione nella cache per tutti gli endpoint dell'app, con scadenza di 10 secondi. Se non viene specificata un'ora di scadenza, l'impostazione predefinita è un 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)));
});

Il codice evidenziato seguente crea due criteri, ognuno che specifica un'ora di scadenza diversa. Gli endpoint selezionati possono usare la scadenza di 20 secondi e altri possono usare la scadenza di 30 secondi.

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

È possibile selezionare un criterio per un endpoint quando si chiama il CacheOutput metodo o si usa l'attributo [OutputCache] .

In un'app per le API minima, il codice seguente configura un endpoint con scadenza di 20 secondi e uno con scadenza di 30 secondi:

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

Per le app con controller, applicare l'attributo [OutputCache] al metodo di azione per selezionare un criterio:

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

Per Razor le app Pages, applicare l'attributo alla Razor classe page.

Criterio di memorizzazione nella cache dell'output predefinito

Per impostazione predefinita, la memorizzazione nella cache dell'output segue queste regole:

  • Vengono memorizzate nella cache solo le risposte HTTP 200.
  • Vengono memorizzate nella cache solo le richieste HTTP GET o HEAD.
  • Le risposte che impostano i cookie non vengono memorizzate nella cache.
  • Le risposte alle richieste autenticate non vengono memorizzate nella cache.

Il codice seguente applica tutte le regole di memorizzazione nella cache predefinite a tutti gli endpoint di un'app:

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

Eseguire l'override dei criteri predefiniti

Il codice seguente illustra come eseguire l'override delle regole predefinite. Le righe evidenziate nel codice dei criteri personalizzati seguente abilitano la memorizzazione nella cache per i metodi HTTP POST e le risposte 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;
    }
}

Per usare questo criterio personalizzato, creare un criterio denominato:

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

Selezionare i criteri denominati per un endpoint. Il codice seguente seleziona i criteri personalizzati per un endpoint in un'app per le API minima:

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

Il codice seguente esegue la stessa operazione per un'azione del controller:

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

Override alternativo dei criteri predefiniti

In alternativa, usare l'inserimento delle dipendenze (DI) per inizializzare un'istanza, con le modifiche seguenti alla classe di criteri personalizzata:

  • Un costruttore pubblico anziché un costruttore privato.
  • Eliminare la Instance proprietà nella classe di criteri personalizzata.

Ad esempio:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

Il resto della classe è uguale a quello illustrato in precedenza. Aggiungere i criteri personalizzati come illustrato nell'esempio seguente:

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

Il codice precedente usa l'inserimento delle dipendenze per creare l'istanza della classe di criteri personalizzata. Tutti gli argomenti pubblici nel costruttore vengono risolti.

Quando si usa un criterio personalizzato come criterio di base, non chiamare OutputCache() (senza argomenti) o usare l'attributo [OutputCache] in qualsiasi endpoint a cui applicare i criteri di base. La chiamata OutputCache() o l'uso dell'attributo aggiunge i criteri predefiniti all'endpoint.

Specificare la chiave della cache

Per impostazione predefinita, ogni parte dell'URL viene inclusa come chiave di una voce della cache, ovvero lo schema, l'host, la porta, il percorso e la stringa di query. Tuttavia, potrebbe essere necessario controllare in modo esplicito la chiave della cache. Si supponga, ad esempio, di avere un endpoint che restituisce una risposta univoca solo per ogni valore univoco della culture stringa di query. La variazione in altre parti dell'URL, ad esempio altre stringhe di query, non dovrebbe comportare voci di cache diverse. È possibile specificare tali regole in un criterio, come illustrato nel codice evidenziato seguente:

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

È quindi possibile selezionare i VaryByQuery criteri per un endpoint. In un'app per le API minima, il codice seguente seleziona i VaryByQuery criteri per un endpoint che restituisce una risposta univoca solo per ogni valore univoco della culture stringa di query:

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

Il codice seguente esegue la stessa operazione per un'azione del controller:

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

Ecco alcune delle opzioni per controllare la chiave della cache:

  • SetVaryByQuery - Specificare uno o più nomi di stringa di query da aggiungere alla chiave della cache.

  • SetVaryByHeader - Specificare una o più intestazioni HTTP da aggiungere alla chiave della cache.

  • VaryByValue- Specificare un valore da aggiungere alla chiave della cache. Nell'esempio seguente viene utilizzato un valore che indica se il tempo del server corrente in secondi è dispari o pari. Viene generata una nuova risposta solo quando il numero di secondi passa da dispari a pari o pari a dispari.

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

Usare OutputCacheOptions.UseCaseSensitivePaths per specificare che la parte del percorso della chiave fa distinzione tra maiuscole e minuscole. Il valore predefinito non fa distinzione tra maiuscole e minuscole.

Per altre opzioni, vedere la OutputCachePolicyBuilder classe .

Riconvalida della cache

La riconvalida della cache indica che il server può restituire un 304 Not Modified codice di stato HTTP anziché il corpo completo della risposta. Questo codice di stato informa il client che la risposta alla richiesta è invariata rispetto a quella ricevuta in precedenza dal client.

Il codice seguente illustra l'uso di un'intestazione Etag per abilitare la riconvalida della cache. Se il client invia un'intestazione If-None-Match con il valore etag di una risposta precedente e la voce della cache è aggiornata, il server restituisce 304 Non modificato anziché la risposta completa. Ecco come impostare il valore etag in un criterio, in un'app per le API minima:

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

}).CacheOutput();

Ecco come impostare il valore etag in un'API basata su controller:

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

Un altro modo per eseguire la riconvalida della cache consiste nel controllare la data di creazione della voce della cache rispetto alla data richiesta dal client. Quando viene specificata l'intestazione If-Modified-Since della richiesta, la memorizzazione nella cache dell'output restituisce 304 se la voce memorizzata nella cache è precedente e non è scaduta.

La riconvalida della cache è automatica in risposta a queste intestazioni inviate dal client. Non è necessaria alcuna configurazione speciale nel server per abilitare questo comportamento, oltre ad abilitare la memorizzazione nella cache dell'output.

Usare i tag per rimuovere le voci della cache

È possibile usare i tag per identificare un gruppo di endpoint e rimuovere tutte le voci della cache per il gruppo. Ad esempio, il codice API minimo seguente crea una coppia di endpoint i cui URL iniziano con "blog" e li contrassegna "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"));

Il codice seguente illustra come assegnare tag a un endpoint in un'API basata su controller:

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

Un modo alternativo per assegnare tag per gli endpoint con route che iniziano con blog consiste nel definire un criterio di base applicabile a tutti gli endpoint con tale route. Il codice seguente illustra come eseguire questa operazione:

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

Un'altra alternativa per le app per le API minime consiste nel chiamare MapGroup:

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

Negli esempi di assegnazione di tag precedenti, entrambi gli endpoint vengono identificati dal tag-blog tag . È quindi possibile rimuovere le voci della cache per tali endpoint con una singola istruzione che fa riferimento a tale tag:

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

Con questo codice, una richiesta HTTP POST inviata per https://localhost:<port>/purge/tag-blog rimuovere le voci della cache per questi endpoint.

È possibile rimuovere tutte le voci della cache per tutti gli endpoint. A tale scopo, creare un criterio di base per tutti gli endpoint come il codice seguente:

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

Questo criterio di base consente di usare il tag "tag-all" per rimuovere tutti gli elementi nella cache.

Disabilitare il blocco delle risorse

Per impostazione predefinita, il blocco delle risorse è abilitato per attenuare il rischio di cache stampede e fulmine. Per altre informazioni, vedere Memorizzazione nella cache dell'output.

Per disabilitare il blocco delle risorse, chiamare SetLocking(false) durante la creazione di un criterio, come illustrato nell'esempio seguente:

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

L'esempio seguente seleziona i criteri senza blocco per un endpoint in un'app per le API minima:

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

In un'API basata su controller usare l'attributo per selezionare i criteri:

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

Limiti

Le proprietà seguenti di consentono di OutputCacheOptions configurare i limiti applicabili a tutti gli endpoint:

  • SizeLimit - Dimensioni massime dell'archiviazione della cache. Quando viene raggiunto questo limite, non vengono memorizzate nuove risposte nella cache fino a quando non vengono rimosse le voci meno recenti. Il valore predefinito è 100 MB.
  • MaximumBodySize - Se il corpo della risposta supera questo limite, non viene memorizzato nella cache. Il valore predefinito è 64 MB.
  • DefaultExpirationTimeSpan - Durata dell'ora di scadenza che si applica quando non viene specificato da un criterio. Il valore predefinito è 60 secondi.

Archiviazione cache

IOutputCacheStore viene usato per l'archiviazione. Per impostazione predefinita, viene usata con MemoryCache. Le risposte memorizzate nella cache vengono archiviate in-process, quindi ogni server dispone di una cache separata che viene persa ogni volta che viene riavviato il processo del server.

Cache Redis

Un'alternativa consiste nell'usare la cache Redis . La cache Redis garantisce coerenza tra i nodi del server tramite una cache condivisa che sopravvive ai singoli processi del server. Per usare Redis per la memorizzazione nella cache di output:

  • Installare il pacchetto NuGet Microsoft.AspNetCore.OutputCaching.StackExchangeRedis .

  • Chiamare builder.Services.AddStackExchangeRedisOutputCache (non AddStackExchangeRedisCache) e specificare un stringa di connessione che punta a un server Redis.

    Ad esempio:

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

    Le opzioni di configurazione sono identiche alle opzioni di memorizzazione nella cache distribuita basate su Redis.

Non è consigliabile IDistributedCache usare con la memorizzazione nella cache dell'output. IDistributedCache non dispone di funzionalità atomiche, necessarie per l'assegnazione di tag. È consigliabile usare il supporto predefinito per Redis o creare implementazioni personalizzate IOutputCacheStore usando dipendenze dirette sul meccanismo di archiviazione sottostante.

Vedi anche

Questo articolo illustra come configurare il middleware di memorizzazione nella cache di output in un'app ASP.NET Core. Per un'introduzione alla memorizzazione nella cache dell'output, vedere Memorizzazione nella cache dell'output.

Il middleware di memorizzazione nella cache di output può essere usato in tutti i tipi di app core di ASP.NET: API minima, API Web con controller, MVC e Razor Pagine. L'app di esempio è un'API minima, ma ogni funzionalità di memorizzazione nella cache illustrata è supportata anche negli altri tipi di app.

Aggiungere il middleware all'app

Aggiungere il middleware di memorizzazione nella cache di output alla raccolta di servizi chiamando AddOutputCache.

Aggiungere il middleware alla pipeline di elaborazione delle richieste chiamando UseOutputCache.

Nota

  • Nelle app che usano il middleware CORS, UseOutputCache è necessario chiamare dopo UseCors.
  • Nelle Razor app e nelle app Pages con controller deve UseOutputCache essere chiamato dopo UseRouting.
  • La chiamata AddOutputCachee UseOutputCache non avvia il comportamento di memorizzazione nella cache, rende disponibile la memorizzazione nella cache. La memorizzazione nella cache dei dati di risposta deve essere configurata come illustrato nelle sezioni seguenti.

Configurare un endpoint o una pagina

Per le app per le API minime, configurare un endpoint per eseguire la memorizzazione nella cache chiamando CacheOutputo applicando l'attributo [OutputCache] , come illustrato negli esempi seguenti:

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

Per le app con controller, applicare l'attributo [OutputCache] al metodo di azione. Per Razor le app Pages, applicare l'attributo alla Razor classe page.

Configurare più endpoint o pagine

Creare criteri quando si chiama AddOutputCache per specificare la configurazione di memorizzazione nella cache applicabile a più endpoint. È possibile selezionare un criterio per endpoint specifici, mentre un criterio di base fornisce la configurazione di memorizzazione nella cache predefinita per una raccolta di endpoint.

Il codice evidenziato seguente configura la memorizzazione nella cache per tutti gli endpoint dell'app, con scadenza di 10 secondi. Se non viene specificata un'ora di scadenza, l'impostazione predefinita è un 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)));
});

Il codice evidenziato seguente crea due criteri, ognuno che specifica un'ora di scadenza diversa. Gli endpoint selezionati possono usare la scadenza di 20 secondi e altri possono usare la scadenza di 30 secondi.

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

È possibile selezionare un criterio per un endpoint quando si chiama il CacheOutput metodo o si usa l'attributo [OutputCache] :

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

Per le app con controller, applicare l'attributo [OutputCache] al metodo di azione. Per Razor le app Pages, applicare l'attributo alla Razor classe page.

Criterio di memorizzazione nella cache dell'output predefinito

Per impostazione predefinita, la memorizzazione nella cache dell'output segue queste regole:

  • Vengono memorizzate nella cache solo le risposte HTTP 200.
  • Vengono memorizzate nella cache solo le richieste HTTP GET o HEAD.
  • Le risposte che impostano i cookie non vengono memorizzate nella cache.
  • Le risposte alle richieste autenticate non vengono memorizzate nella cache.

Il codice seguente applica tutte le regole di memorizzazione nella cache predefinite a tutti gli endpoint di un'app:

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

Eseguire l'override dei criteri predefiniti

Il codice seguente illustra come eseguire l'override delle regole predefinite. Le righe evidenziate nel codice dei criteri personalizzati seguente abilitano la memorizzazione nella cache per i metodi HTTP POST e le risposte 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;
    }
}

Per usare questo criterio personalizzato, creare un criterio denominato:

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

Selezionare i criteri denominati per un endpoint:

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

Override alternativo dei criteri predefiniti

In alternativa, usare l'inserimento delle dipendenze (DI) per inizializzare un'istanza, con le modifiche seguenti alla classe di criteri personalizzata:

  • Un costruttore pubblico anziché un costruttore privato.
  • Eliminare la Instance proprietà nella classe di criteri personalizzata.

Ad esempio:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

Il resto della classe è uguale a quello illustrato in precedenza. Aggiungere i criteri personalizzati come illustrato nell'esempio seguente:

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

Il codice precedente usa l'inserimento delle dipendenze per creare l'istanza della classe di criteri personalizzata. Tutti gli argomenti pubblici nel costruttore vengono risolti.

Quando si usa un criterio personalizzato come criterio di base, non chiamare OutputCache() (senza argomenti) in alcun endpoint a cui applicare i criteri di base. La chiamata OutputCache() aggiunge i criteri predefiniti all'endpoint.

Specificare la chiave della cache

Per impostazione predefinita, ogni parte dell'URL viene inclusa come chiave di una voce della cache, ovvero lo schema, l'host, la porta, il percorso e la stringa di query. Tuttavia, potrebbe essere necessario controllare in modo esplicito la chiave della cache. Si supponga, ad esempio, di avere un endpoint che restituisce una risposta univoca solo per ogni valore univoco della culture stringa di query. La variazione in altre parti dell'URL, ad esempio altre stringhe di query, non dovrebbe comportare voci di cache diverse. È possibile specificare tali regole in un criterio, come illustrato nel codice evidenziato seguente:

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

È quindi possibile selezionare i VaryByQuery criteri per un endpoint:

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

Ecco alcune delle opzioni per controllare la chiave della cache:

  • SetVaryByQuery - Specificare uno o più nomi di stringa di query da aggiungere alla chiave della cache.

  • SetVaryByHeader - Specificare una o più intestazioni HTTP da aggiungere alla chiave della cache.

  • VaryByValue- Specificare un valore da aggiungere alla chiave della cache. Nell'esempio seguente viene utilizzato un valore che indica se il tempo del server corrente in secondi è dispari o pari. Viene generata una nuova risposta solo quando il numero di secondi passa da dispari a pari o pari a dispari.

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

Usare OutputCacheOptions.UseCaseSensitivePaths per specificare che la parte del percorso della chiave fa distinzione tra maiuscole e minuscole. Il valore predefinito non fa distinzione tra maiuscole e minuscole.

Per altre opzioni, vedere la OutputCachePolicyBuilder classe .

Riconvalida della cache

La riconvalida della cache indica che il server può restituire un 304 Not Modified codice di stato HTTP anziché il corpo completo della risposta. Questo codice di stato informa il client che la risposta alla richiesta è invariata rispetto a quella ricevuta in precedenza dal client.

Il codice seguente illustra l'uso di un'intestazione Etag per abilitare la riconvalida della cache. Se il client invia un'intestazione If-None-Match con il valore etag di una risposta precedente e la voce della cache è aggiornata, il server restituisce 304 Non modificato anziché la risposta completa:

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

}).CacheOutput();

Un altro modo per eseguire la riconvalida della cache consiste nel controllare la data di creazione della voce della cache rispetto alla data richiesta dal client. Quando viene specificata l'intestazione If-Modified-Since della richiesta, la memorizzazione nella cache dell'output restituisce 304 se la voce memorizzata nella cache è precedente e non è scaduta.

La riconvalida della cache è automatica in risposta a queste intestazioni inviate dal client. Non è necessaria alcuna configurazione speciale nel server per abilitare questo comportamento, oltre ad abilitare la memorizzazione nella cache dell'output.

Usare i tag per rimuovere le voci della cache

È possibile usare i tag per identificare un gruppo di endpoint e rimuovere tutte le voci della cache per il gruppo. Ad esempio, il codice seguente crea una coppia di endpoint i cui URL iniziano con "blog" e li contrassegna "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"));

Un modo alternativo per assegnare tag per la stessa coppia di endpoint consiste nel definire un criterio di base applicabile agli endpoint che iniziano con 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));
});

Un'altra alternativa consiste nel chiamare MapGroup:

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

Negli esempi di assegnazione di tag precedenti, entrambi gli endpoint vengono identificati dal tag-blog tag . È quindi possibile rimuovere le voci della cache per tali endpoint con una singola istruzione che fa riferimento a tale tag:

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

Con questo codice, una richiesta HTTP POST inviata a https://localhost:<port>/purge/tag-blog rimuoverà le voci della cache per questi endpoint.

È possibile rimuovere tutte le voci della cache per tutti gli endpoint. A tale scopo, creare un criterio di base per tutti gli endpoint come il codice seguente:

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

Questo criterio di base consente di usare il tag "tag-all" per rimuovere tutti gli elementi nella cache.

Disabilitare il blocco delle risorse

Per impostazione predefinita, il blocco delle risorse è abilitato per attenuare il rischio di cache stampede e fulmine. Per altre informazioni, vedere Memorizzazione nella cache dell'output.

Per disabilitare il blocco delle risorse, chiamare SetLocking(false) durante la creazione di un criterio, come illustrato nell'esempio seguente:

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

Nell'esempio seguente vengono selezionati i criteri senza blocco per un endpoint:

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

Limiti

Le proprietà seguenti di consentono di OutputCacheOptions configurare i limiti applicabili a tutti gli endpoint:

  • SizeLimit - Dimensioni massime dell'archiviazione della cache. Quando viene raggiunto questo limite, non verranno memorizzate nuove risposte nella cache fino a quando non vengono rimosse le voci meno recenti. Il valore predefinito è 100 MB.
  • MaximumBodySize - Se il corpo della risposta supera questo limite, non verrà memorizzato nella cache. Il valore predefinito è 64 MB.
  • DefaultExpirationTimeSpan - Durata dell'ora di scadenza che si applica quando non viene specificato da un criterio. Il valore predefinito è 60 secondi.

Archiviazione cache

IOutputCacheStore viene usato per l'archiviazione. Per impostazione predefinita, viene usata con MemoryCache. Non è consigliabile IDistributedCache usare con la memorizzazione nella cache dell'output. IDistributedCache non dispone di funzionalità atomiche, necessarie per l'assegnazione di tag. È consigliabile creare implementazioni personalizzate IOutputCacheStore usando dipendenze dirette sul meccanismo di archiviazione sottostante, ad esempio Redis. In alternativa, usare il supporto predefinito per la cache Redis in .NET 8..

Vedi anche