Intergiciel de mise en cache de sortie dans ASP.NET Core

Par Tom Dykstra

Note

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 10 de cet article.

Warning

Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la stratégie de support .NET et .NET Core. Pour la version actuelle, consultez la version .NET 10 de cet article.

Cet article explique comment configurer l’intergiciel de mise en cache de sortie dans une application ASP.NET Core. Pour une présentation de la mise en cache de sortie, consultez Mise en cache de sortie.

Le middleware de mise en cache de sortie peut être utilisé dans tous les types d’applications ASP.NET Core : api Minimal, API Web avec des contrôleurs, MVC et Razor Pages. Les exemples de code sont fournis pour les API minimales et les API basées sur le contrôleur. Les exemples d’API basées sur le contrôleur montrent comment utiliser des attributs pour configurer la mise en cache. Ces attributs peuvent également être utilisés dans les applications MVC et Razor Pages.

Les exemples de code font référence à une classe Gravatar qui génère une image et fournit une date et une heure de génération. La classe est définie et utilisée uniquement dans l’exemple d’application. Son but est de permettre de voir facilement quand la sortie mise en cache est utilisée. Pour plus d’informations, consultez Comment télécharger un exemple et desdirectives de préprocesseur.

Ajouter l’intergiciel à l’application

Ajoutez le middleware de mise en cache de sortie à la collection de services en appelant la méthode AddOutputCache. Par exemple:

builder.Services.AddOutputCache();

Ajoutez l’intergiciel au pipeline de traitement des demandes en appelant la UseOutputCache méthode. Par exemple:

var app = builder.Build();

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

L’appel des AddOutputCacheméthodes et UseOutputCache ne démarre pas le comportement de mise en cache, il rend la mise en cache disponible. Pour que l'application mette en cache les réponses, la mise en cache doit être configurée comme décrit dans les sections suivantes.

Note

  • Dans les applications qui utilisent le middleware CORS (Cross-Origin Requests), la UseOutputCache méthode doit être appelée après la UseCors méthode.
  • Dans les applications avec des pages Razor et celles avec des contrôleurs, la méthode UseOutputCache doit être appelée après la méthode UseRouting.

Configurer un point de terminaison ou une page

Pour les applications API minimales, configurez un point de terminaison pour effectuer la mise en cache en appelant la méthode CacheOutput ou en appliquant l’attribut [OutputCache], comme indiqué dans les exemples suivants :

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

Pour les applications avec des contrôleurs, appliquez l’attribut [OutputCache] à la méthode d’action, comme indiqué dans le code suivant :

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

Pour les applications Razor Pages, appliquez l’attribut à la classe de page Razor.

Configurer plusieurs points de terminaison ou pages

Créez des stratégies lors de l’appel de la AddOutputCache méthode pour spécifier la configuration de mise en cache qui s’applique à plusieurs points de terminaison. Une stratégie peut être sélectionnée pour des points de terminaison spécifiques, tandis qu’une stratégie de base fournit une configuration de mise en cache par défaut pour une collection de points de terminaison.

Le code mis en surbrillance suivant configure la mise en cache pour tous les points de terminaison de l’application, avec une durée d’expiration de 10 secondes. Si aucune heure d’expiration n’est spécifiée, elle est définie par défaut sur une minute.

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

Le code suivant, mis en évidence, crée deux politiques, chacune avec une heure d'expiration différente. Les points de terminaison sélectionnés peuvent utiliser le délai d’expiration de 20 secondes, tandis que les autres peuvent utiliser le délai d’expiration de 30 secondes.

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

Vous pouvez sélectionner une stratégie pour un point de terminaison lors de l’appel de la CacheOutput méthode ou à l’aide de l’attribut [OutputCache] .

Dans une application API minimale, le code suivant configure un point de terminaison avec une expiration de 20 secondes et un avec une expiration de 30 secondes :

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

Pour les applications avec des contrôleurs, appliquez l’attribut [OutputCache] à la méthode d’action pour sélectionner une stratégie :

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

Pour les applications Razor Pages, appliquez l’attribut à la classe de page Razor.

Utiliser la stratégie de mise en cache de sortie par défaut

Par défaut, la mise en cache de sortie suit les règles suivantes :

  • Seules les réponses HTTP 200 sont mises en cache.
  • Seules les requêtes HTTP GET ou HEAD sont mises en cache.
  • Les réponses qui utilisent des cookies ne sont pas mises en cache.
  • Les réponses aux demandes authentifiées ne sont pas mises en cache.

Le code suivant applique toutes les règles de mise en cache par défaut à tous les points de terminaison d’une application :

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

Modifier la politique par défaut

Le code suivant montre comment remplacer les règles de stratégie par défaut. Les lignes mises en surbrillance dans le code de stratégie personnalisé suivant permettent la mise en cache pour les méthodes HTTP POST et les réponses 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;
    }
}

Pour utiliser cette stratégie personnalisée, créez une stratégie nommée :

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

Et, sélectionnez la politique nommée pour un point de terminaison. Le code suivant sélectionne la stratégie personnalisée d’un point de terminaison dans une application API minimale :

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

Le code suivant fait de même pour une action de contrôleur :

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

Utiliser une alternative de dérogation de politique par défaut

Vous pouvez également utiliser l’injection de dépendances (DI) pour initialiser une instance avec les modifications suivantes apportées à la classe de stratégie personnalisée :

  • Utilisez un constructeur public au lieu d’un constructeur privé.
  • Supprimez la propriété Instance dans la classe de stratégie personnalisée.

Par exemple:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

Le reste de la classe est le même qu’indiqué précédemment. Ajoutez la stratégie personnalisée comme indiqué dans l’exemple suivant :

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

Le code précédent utilise DI pour créer l’instance de la classe de politique personnalisée. Tous les arguments publics dans le constructeur sont résolus.

Lorsque vous utilisez une stratégie personnalisée comme stratégie de base, n’appelez pas la OutputCache() méthode (sans argument) ni utilisez l’attribut [OutputCache] sur un point de terminaison auquel la stratégie de base doit s’appliquer. L’appel de la OutputCache() méthode ou l’utilisation de l’attribut ajoute la politique par défaut au point de terminaison.

Indiquer la clé cache

Par défaut, chaque partie de l’URL est incluse comme clé d’entrée de cache, c’est-à-dire le schéma, l’hôte, le port, le chemin d’accès et la chaîne de requête. Toutefois, vous pouvez contrôler explicitement la clé de cache. Par exemple, supposons que vous ayez un point de terminaison qui retourne une réponse unique seulement pour chaque valeur unique de la chaîne de requête culture. La variation d’autres parties de l’URL, telles que d’autres chaînes de requête, ne doit pas entraîner d’entrées de cache différentes. Vous pouvez spécifier ces règles dans une stratégie, comme indiqué dans le code en surbrillance suivant :

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

Vous pouvez ensuite sélectionner la stratégie VaryByQuery pour un point de terminaison. Dans une application API minimale, le code suivant sélectionne la VaryByQuery stratégie d’un point de terminaison qui retourne une réponse unique uniquement pour chaque valeur unique de la culture chaîne de requête :

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

Le code suivant fait de même pour une action de contrôleur :

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

Voici quelques-unes des options permettant de contrôler la clé de cache :

  • La SetVaryByQuery méthode spécifie un ou plusieurs noms de chaînes de requête à ajouter à la clé de cache.

  • La SetVaryByHeader méthode spécifie un ou plusieurs en-têtes HTTP à ajouter à la clé de cache.

  • La VaryByValue méthode fournit une valeur à ajouter à la clé de cache. L’exemple suivant utilise une valeur qui indique si l’heure actuelle du serveur en secondes est impaire ou égale. Une nouvelle réponse est générée uniquement lorsque le nombre de secondes passe d’une valeur impaire à une valeur égale ou inversement.

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

Utilisez la OutputCacheOptions.UseCaseSensitivePaths propriété pour spécifier que la partie chemin d’accès de la clé respecte la casse. Le principe par défaut est insensible à la majuscule.

Pour plus d’options, consultez la classe OutputCachePolicyBuilder.

Activer la revalidation du cache

La revalidation du cache signifie que le serveur peut retourner un code d’état HTTP non modifié 304 au lieu du corps de réponse complet. Ce code statut informe le client que la réponse à la requête est inchangée par rapport à ce que le client a reçu précédemment.

Le code suivant illustre l’utilisation d’un en-tête ETag pour activer la revalidation du cache. Si le client envoie un en-tête If-None-Match avec la valeur d’une ETag réponse antérieure et que l’entrée du cache est fraîche, le serveur retourne le code 304 Non modifié au lieu de la réponse complète.

Le code suivant définit la valeur ETag dans une politique dans une application API minimale.

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

}).CacheOutput();

Le code suivant fait de même pour une API basée sur un contrôleur :

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

Une autre façon d’effectuer la revalidation du cache consiste à vérifier la date de création de l’entrée de cache par rapport à la date demandée par le client. Lorsque l’en-tête If-Modified-Since de demande est fourni, la mise en cache de sortie retourne le code 304 si l’entrée mise en cache est antérieure et n’a pas expiré.

La revalidation du cache est automatique en réponse à ces en-têtes envoyés à partir du client. Aucune configuration spéciale n’est requise sur le serveur pour activer ce comportement, à part l’activation de la mise en cache de sortie.

Utiliser des balises pour supprimer les entrées de cache

Vous pouvez utiliser des balises pour identifier un groupe de points de terminaison et supprimer toutes les entrées de cache pour le groupe. Par exemple, le code API minimal suivant crée une paire de points de terminaison dont les URL commencent par le texte blog et applique la tag-blog balise :

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

Le code suivant montre comment attribuer des balises à un point de terminaison dans une API basée sur un contrôleur :

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

Une autre façon d’attribuer des balises pour les points de terminaison dont les itinéraires commencent par blog consiste à définir une stratégie de base qui s’applique à tous les points de terminaison avec cet itinéraire. Le code suivant illustre cette approche :

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

Une autre alternative pour les applications API minimales consiste à appeler la méthode MapGroup :

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

Dans les exemples d’attribution de balise précédents, les deux points de terminaison sont identifiés par la balise tag-blog. Vous pouvez ensuite supprimer les entrées de cache pour ces points de terminaison avec une seule instruction qui fait référence à cette balise :

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

Avec ce code, une requête HTTP POST envoyée à l’URL https://localhost:<port>/purge/tag-blog supprime les entrées du cache pour ces points de terminaison.

Vous souhaiterez peut-être pouvoir effacer toutes les entrées de cache pour tous les points de terminaison. Vous pouvez créer une stratégie de base pour tous les points de terminaison, comme illustré dans le code suivant :

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

Cette stratégie de base vous permet d’utiliser la balise pour supprimer tout dans le tag-all cache.

Désactiver le verrouillage des ressources

Par défaut, le verrouillage des ressources est activé pour atténuer le risque de ruée de cache et de foudre de fouets. Pour plus d’informations, consultez Cache de sortie.

Pour désactiver le verrouillage des ressources, appelez la méthode SetLocking(false) lors de la création d’une stratégie, comme illustré dans l’exemple suivant :

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’exemple suivant sélectionne la stratégie sans verrouillage d’un point de terminaison dans une application Minimal API :

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

Dans une API basée sur un contrôleur, utilisez l’attribut pour sélectionner la stratégie :

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

Configurer les limites

Les propriétés suivantes de la OutputCacheOptions classe vous permettent de configurer des limites qui s’appliquent à tous les points de terminaison :

  • La SizeLimit propriété définit la taille maximale du stockage du cache. Lorsque la limite est atteinte, aucune nouvelle réponse n’est mise en cache jusqu’à ce que les entrées plus anciennes soient supprimées. La valeur par défaut est de 100 Mo.
  • La MaximumBodySize propriété définit la taille maximale du corps de la réponse. Si le corps de la réponse dépasse la limite, il n’est pas mis en cache. La valeur par défaut est de 64 Mo.
  • La DefaultExpirationTimeSpan propriété définit la durée maximale qu’une réponse est mise en cache, lorsqu’une heure n’est pas spécifiée dans une stratégie. La valeur par défaut est de 60 secondes.

Explorer les options de stockage du cache

L’interface IOutputCacheStore est utilisée pour le stockage. Par défaut, il est utilisé avec la MemoryCache classe. Les réponses mises en cache sont stockées en cours, de sorte que chaque serveur dispose d’un cache distinct qui est perdu chaque fois que le processus du serveur redémarre.

Alternative : Cache Redis

Une alternative consiste à utiliser le cache Redis. Le cache Redis assure la cohérence entre les nœuds du serveur grâce à un cache partagé qui survit aux processus individuels du serveur. Pour utiliser Redis pour la mise en cache des sorties :

  • Installez le package NuGet Microsoft.AspNetCore.OutputCaching.StackExchangeRedis.

  • Appelez la méthode builder.Services.AddStackExchangeRedisOutputCache (et non la méthode AddStackExchangeRedisCache) et fournissez un chaîne de connexion qui pointe vers un serveur Redis.

    Par exemple:

    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)));
    });
    
    • La propriété options.Configuration est une chaîne de connexion à un serveur Redis local ou à une offre hébergée telle que Azure Cache pour Redis. Par exemple, <instance_name>.redis.cache.windows.net:6380,password=,pw,ssl=True,abortConnect=False pour Azure Cache pour Redis.

    • (Facultatif) Options . La propriété InstanceName spécifie une partition logique pour le cache.

    Les options de configuration sont identiques aux options de mise en cache distribuée basée sur Redis.

L’interface IDistributedCache n’est pas recommandée pour une utilisation avec la mise en cache de sortie. Cette interface ne fournit pas de fonctionnalités atomiques requises pour l’étiquetage.

L’approche recommandée consiste à utiliser la prise en charge intégrée de Redis ou à créer une implémentation personnalisée IOutputCacheStore à l’aide de dépendances directes sur le mécanisme de stockage sous-jacent.

Cet article explique comment configurer l’intergiciel de mise en cache de sortie dans une application ASP.NET Core. Pour une présentation de la mise en cache de sortie, consultez Mise en cache de sortie.

L’intergiciel de mise en cache de sortie peut être utilisé dans tous les types d’applications ASP.NET Core : API minimale, API web avec contrôleurs, MVC et Razor Pages. L’exemple d’application est une API minimale, mais chaque fonctionnalité de mise en cache qu’elle illustre est également prise en charge dans les autres types d’application.

Ajouter l’intergiciel à l’application

Ajoutez l’intergiciel de mise en cache de sortie à la collection de services en appelant AddOutputCache.

Ajoutez l’intergiciel au pipeline de traitement des demandes en appelant UseOutputCache.

Note

  • Dans les applications qui utilisent l’intergiciel CORS, UseOutputCache doit être appelé après UseCors.
  • Dans les applications Razor Pages et les applications avec contrôleurs, UseOutputCache doit être appelé après UseRouting.
  • Appeler AddOutputCache et UseOutputCache ne déclenche pas le comportement de mise en cache, mais rend la mise en cache disponible. La mise en cache des données de réponse doit être configurée comme indiqué dans les sections suivantes.

Configurer un point de terminaison ou une page

Pour les applications API minimales, configurez un point de terminaison pour effectuer la mise en cache en appelant CacheOutputou en appliquant l’attribut [OutputCache] , comme indiqué dans les exemples suivants :

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

Pour les applications avec des contrôleurs, appliquez l’attribut [OutputCache] pour la méthode d’action. Pour les applications Razor Pages, appliquez l’attribut à la classe de page Razor.

Configurer plusieurs points de terminaison ou pages

Créez des stratégies lors de l’appel de AddOutputCache pour spécifier la configuration de mise en cache qui s’applique à plusieurs points de terminaison. Une stratégie peut être sélectionnée pour des points de terminaison spécifiques, tandis qu’une stratégie de base fournit une configuration de mise en cache par défaut pour une collection de points de terminaison.

Le code suivant mis en évidence configure la mise en cache pour tous les points de terminaison de l’application, avec une durée d’expiration de 10 secondes. Si aucune heure d’expiration n’est spécifiée, elle est définie par défaut sur une minute.

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

Le code suivant en mise en surbrillance crée deux politiques, chacune spécifiant une heure d'expiration différente. Les points de terminaison sélectionnés peuvent utiliser l’expiration de 20 secondes, tandis que d’autres peuvent utiliser l’expiration de 30 secondes.

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

Vous pouvez sélectionner une stratégie pour un point de terminaison lors de l’appel de la méthode CacheOutput ou de l’attribut [OutputCache] :

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

Pour les applications avec des contrôleurs, appliquez l’attribut [OutputCache] pour la méthode d’action. Pour les applications Razor Pages, appliquez l’attribut à la classe de page Razor.

Stratégie de mise en cache de sortie par défaut

Par défaut, la mise en cache de sortie suit les règles suivantes :

  • Seules les réponses HTTP 200 sont mises en cache.
  • Seules les requêtes HTTP GET ou HEAD sont mises en cache.
  • Les réponses qui utilisent des cookies ne sont pas mises en cache.
  • Les réponses aux demandes authentifiées ne sont pas mises en cache.

Le code suivant applique toutes les règles de mise en cache par défaut à tous les points de terminaison d’une application :

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

Remplacer la politique par défaut

Le code suivant montre comment substituer les règles par défaut. Les lignes mises en surbrillance dans le code de stratégie personnalisé suivant permettent la mise en cache pour les méthodes HTTP POST et les réponses 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;
    }
}

Pour utiliser cette stratégie personnalisée, créez une stratégie nommée :

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

Et sélectionnez la politique nommée pour un point de terminaison :

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

Dérogation de la politique par défaut alternative

Vous pouvez également utiliser l’injection de dépendances (DI) pour initialiser une instance, avec les modifications suivantes apportées à la classe de stratégie personnalisée :

  • Un constructeur public au lieu d’un constructeur privé.
  • Supprimez la propriété Instance dans la classe de stratégie personnalisée.

Par exemple:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

Le reste de la classe est le même qu’indiqué précédemment. Ajoutez la stratégie personnalisée comme indiqué dans l’exemple suivant :

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

Le code précédent utilise DI pour créer l’instance de la classe de politique personnalisée. Tous les arguments publics dans le constructeur sont résolus.

Lorsque vous utilisez une stratégie personnalisée comme stratégie de base, n’appelez pas OutputCache() (sans argument) sur un point de terminaison auquel la stratégie de base doit s’appliquer. Appeler OutputCache() ajoute la stratégie par défaut au point de terminaison.

Indiquer la clé cache

Par défaut, chaque partie de l’URL est incluse comme clé d’entrée de cache, c’est-à-dire le schéma, l’hôte, le port, le chemin d’accès et la chaîne de requête. Toutefois, vous pouvez contrôler explicitement la clé de cache. Par exemple, supposons que vous ayez un point de terminaison qui retourne une réponse unique seulement pour chaque valeur unique de la chaîne de requête culture. La variation d’autres parties de l’URL, telles que d’autres chaînes de requête, ne doit pas entraîner d’entrées de cache différentes. Vous pouvez spécifier ces règles dans une stratégie, comme indiqué dans le code en surbrillance suivant :

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

Vous pouvez ensuite sélectionner la stratégie VaryByQuery pour un point de terminaison :

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

Voici quelques-unes des options permettant de contrôler la clé de cache :

  • SetVaryByQuery – Spécifiez un ou plusieurs noms de chaîne de requête à ajouter à la clé de cache.

  • SetVaryByHeader – Spécifiez un ou plusieurs en-têtes HTTP à ajouter à la clé de cache.

  • VaryByValue – Spécifiez une valeur à ajouter à la clé de cache. L’exemple suivant utilise une valeur qui indique si l’heure actuelle du serveur en secondes est impaire ou égale. Une nouvelle réponse n’est générée que lorsque le nombre de secondes passe de pair à pair ou pair.

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

Utilisez OutputCacheOptions.UseCaseSensitivePaths pour spécifier que la partie chemin de la clé est sensible à la majuscule. Le principe par défaut est insensible à la majuscule.

Pour plus d’options, consultez la classe OutputCachePolicyBuilder.

Revalidation du cache

La revalidation du cache signifie que le serveur peut retourner un code d’état HTTP 304 Not Modified au lieu d’un corps de réponse entier. Ce code statut informe le client que la réponse à la requête est inchangée par rapport à ce que le client a reçu précédemment.

Le code suivant illustre l’utilisation d’un en-tête Etag pour activer la revalidation du cache. Si le client envoie un en-tête If-None-Match avec la valeur etag d’une réponse antérieure et que l’entrée du cache est nouvelle, le serveur retourne 304 Non modifié au lieu de la réponse complète :

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

}).CacheOutput();

Une autre façon d’effectuer la revalidation du cache consiste à vérifier la date de création de l’entrée de cache par rapport à la date demandée par le client. Lorsque l’en-tête de demande If-Modified-Since est fourni, la mise en cache de sortie retourne 304 si l’entrée mise en cache est antérieure et n’a pas expiré.

La revalidation du cache est automatique en réponse à ces en-têtes envoyés à partir du client. Aucune configuration spéciale n’est requise sur le serveur pour activer ce comportement, à part l’activation de la mise en cache de sortie.

Utiliser des balises pour supprimer les entrées de cache

Vous pouvez utiliser des balises pour identifier un groupe de points de terminaison et supprimer toutes les entrées de cache pour le groupe. Par exemple, le code suivant crée une paire de points d'accès dont les URLs commencent par « blog » et les balises sont « 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"));

Une autre façon d’affecter des balises pour la même paire de points de terminaison consiste à définir une stratégie de base qui s’applique aux points de terminaison qui commencent par 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));
});

Une autre solution consiste à appeler MapGroup :

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

Dans les exemples d’attribution de balise précédents, les deux points de terminaison sont identifiés par la balise tag-blog. Vous pouvez ensuite supprimer les entrées de cache pour ces points de terminaison avec une seule instruction qui fait référence à cette balise :

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

Avec ce code, une requête HTTP POST envoyée à https://localhost:<port>/purge/tag-blog supprime les entrées de cache pour ces points de terminaison.

Vous souhaiterez peut-être pouvoir effacer toutes les entrées de cache pour tous les points de terminaison. Pour ce faire, créez une stratégie de base pour tous les points de terminaison comme le fait le code suivant :

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

Cette stratégie de base vous permet d’utiliser la balise « tag-all » pour tout supprimer dans le cache.

Désactiver le verrouillage des ressources

Par défaut, le verrouillage des ressources est activé pour atténuer le risque de ruée de cache et de foudre de fouets. Pour plus d’informations, consultez Cache de sortie.

Pour désactiver le verrouillage des ressources, appelez SetLocking(false) lors de la création d’une stratégie, comme illustré dans l’exemple suivant :

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’exemple suivant sélectionne la stratégie de non-verrouillage pour un point de terminaison :

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

Limits

Les propriétés suivantes de OutputCacheOptions vous permettent de configurer des limites qui s’appliquent à tous les points de terminaison :

  • SizeLimit – Taille maximale du stockage de cache. Lorsque cette limite est atteinte, aucune nouvelle réponse n’est mise en cache tant que les entrées plus anciennes n’ont pas été supprimées. La valeur par défaut est 100 Mo.
  • MaximumBodySize – Si le corps de la réponse dépasse cette limite, elle ne sera pas mise en cache. La valeur par défaut est 64 Mo.
  • DefaultExpirationTimeSpan – Durée d’expiration qui s’applique quand elle n’est pas spécifiée par une stratégie. La valeur par défaut est 60 secondes.

Stockage du cache

IOutputCacheStore est utilisé pour le stockage. Par défaut, il est utilisé avec MemoryCache. Nous vous déconseillons d’utiliser IDistributedCache avec la mise en cache de sortie. IDistributedCache n’a pas de caractéristiques atomiques, qui sont requises pour le balisage. Nous vous recommandons de créer des implémentations IOutputCacheStore personnalisées à l’aide de dépendances directes sur le mécanisme de stockage sous-jacent, tel que Redis. Vous pouvez aussi utiliser la prise en charge intégrée pour le cache Redis dans .NET 8.

Voir aussi