Middleware pro ukládání výstupu do mezipaměti v ASP.NET Core

Od Tom Dykstra

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Tento článek vysvětluje, jak nakonfigurovat middleware pro ukládání výstupu do mezipaměti v aplikaci ASP.NET Core. Úvod do ukládání výstupu do mezipaměti najdete v tématu Ukládání výstupu do mezipaměti.

Middleware pro ukládání výstupu do mezipaměti se dá použít ve všech typech aplikací ASP.NET Core: minimální rozhraní API, webové rozhraní API s kontrolery, MVC a Razor Pages. Ukázková aplikace je minimální rozhraní API, ale každá funkce ukládání do mezipaměti, kterou ilustruje, je podporována také v ostatních typech aplikací.

Přidání middlewaru do aplikace

Přidejte do kolekce služeb middleware ukládání výstupu do mezipaměti voláním AddOutputCache.

Přidejte middleware do kanálu zpracování požadavků voláním UseOutputCache.

Poznámka:

  • V aplikacích, které používají middleware CORS, UseOutputCache musí být volána po UseCors.
  • V Razor aplikacích Pages a aplikacích s řadiči UseOutputCache musí být volána po UseRouting.
  • Volání AddOutputCachea UseOutputCache nespustí chování při ukládání do mezipaměti, zpřístupňuje ukládání do mezipaměti. Ukládání do mezipaměti musí být nakonfigurovaná data odpovědí, jak je znázorněno v následujících částech.

Konfigurace jednoho koncového bodu nebo stránky

V případě minimálních aplikací API nakonfigurujte koncový bod pro ukládání do mezipaměti voláním CacheOutputnebo použitím atributu [OutputCache] , jak je znázorněno v následujících příkladech:

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

U aplikací s kontrolery použijte [OutputCache] atribut na metodu akce. U Razor aplikací Pages použijte atribut na Razor třídu stránky.

Konfigurace několika koncových bodů nebo stránek

Vytvořte zásady při volání AddOutputCache pro určení konfigurace ukládání do mezipaměti, která se vztahuje na více koncových bodů. Pro konkrétní koncové body je možné vybrat zásadu, zatímco základní zásada poskytuje výchozí konfiguraci ukládání do mezipaměti pro kolekci koncových bodů.

Následující zvýrazněný kód konfiguruje ukládání do mezipaměti pro všechny koncové body aplikace s dobou vypršení platnosti 10 sekund. Pokud není zadaný čas vypršení platnosti, výchozí hodnota je jedna minuta.

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

Následující zvýrazněný kód vytvoří dvě zásady, z nichž každý určuje jinou dobu vypršení platnosti. Vybrané koncové body můžou používat 20sekundové vypršení platnosti a ostatní můžou používat 30sekundové vypršení platnosti.

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

Při volání CacheOutput metody nebo pomocí atributu [OutputCache] můžete vybrat zásadu pro koncový bod:

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

U aplikací s kontrolery použijte [OutputCache] atribut na metodu akce. U Razor aplikací Pages použijte atribut na Razor třídu stránky.

Výchozí zásady ukládání výstupu do mezipaměti

Ve výchozím nastavení se ukládání výstupu do mezipaměti řídí těmito pravidly:

  • Ukládají se do mezipaměti pouze odpovědi HTTP 200.
  • Ukládají se do mezipaměti pouze požadavky HTTP GET nebo HEAD.
  • Odpovědi, které jsou nastavené cookie, nejsou uložené v mezipaměti.
  • Odpovědi na ověřené požadavky nejsou uložené v mezipaměti.

Následující kód použije všechna výchozí pravidla ukládání do mezipaměti na všechny koncové body aplikace:

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

Přepsání výchozích zásad

Následující kód ukazuje, jak přepsat výchozí pravidla. Zvýrazněné řádky v následujícím kódu vlastních zásad umožňují ukládání do mezipaměti pro metody HTTP POST a odpovědi 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;
    }
}

Pokud chcete použít tuto vlastní zásadu, vytvořte pojmenovanou zásadu:

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

A vyberte pojmenovanou zásadu pro koncový bod:

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

Alternativní výchozí přepsání zásad

Alternativně můžete inicializovat instanci pomocí inicializace injektáže závislostí (DI) s následujícími změnami třídy vlastních zásad:

  • Veřejný konstruktor místo soukromého konstruktoru.
  • Eliminujte Instance vlastnost ve třídě vlastních zásad.

Příklad:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

Zbývající část třídy je stejná jako v předchozím příkladu. Přidejte vlastní zásadu, jak je znázorněno v následujícím příkladu:

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

Předchozí kód používá DI k vytvoření instance vlastní třídy zásad. Všechny veřejné argumenty v konstruktoru jsou vyřešeny.

Při použití vlastní zásady jako základní zásady nezavolejte OutputCache() (bez argumentů) u žádného koncového bodu, na který by se základní zásada měla použít. Volání OutputCache() přidá do koncového bodu výchozí zásadu.

Zadání klíče mezipaměti

Ve výchozím nastavení je každá část adresy URL zahrnuta jako klíč položky mezipaměti, tj. schéma, hostitel, port, cesta a řetězec dotazu. Můžete ale chtít klíč mezipaměti explicitně řídit. Předpokládejme například, že máte koncový bod, který vrací jedinečnou odpověď pouze pro každou jedinečnou hodnotu culture řetězce dotazu. Varianta v jiných částech adresy URL, jako jsou jiné řetězce dotazů, by neměla vést k různým položkám mezipaměti. Taková pravidla můžete zadat v zásadách, jak je znázorněno v následujícím zvýrazněném kódu:

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

Pak můžete vybrat zásadu VaryByQuery pro koncový bod:

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

Tady jsou některé možnosti řízení klíče mezipaměti:

  • SetVaryByQuery – Zadejte jeden nebo více názvů řetězců dotazu, které chcete přidat do klíče mezipaměti.

  • SetVaryByHeader – Zadejte jedno nebo více hlaviček HTTP, které se mají přidat do klíče mezipaměti.

  • VaryByValue– Zadejte hodnotu, kterou chcete přidat do klíče mezipaměti. Následující příklad používá hodnotu, která označuje, jestli je aktuální čas serveru v sekundách lichý nebo sudý. Nová odpověď se vygeneruje jenom v případě, že počet sekund pochází z lichých na sudé nebo dokonce liché.

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

Slouží OutputCacheOptions.UseCaseSensitivePaths k určení, že v části klíče se rozlišují malá a velká písmena. Výchozí hodnota nerozlišuje malá a velká písmena.

Další možnosti najdete v předmětu OutputCachePolicyBuilder .

Obnovení platnosti mezipaměti

Obnovení mezipaměti znamená, že server může místo celého textu odpovědi vrátit stavový 304 Not Modified kód HTTP. Tento stavový kód informuje klienta, že odpověď na požadavek se nezmění od toho, co klient dříve přijal.

Následující kód znázorňuje použití hlavičky k povolení opětovného Etag ověření mezipaměti. Pokud klient odešle hlavičku If-None-Match s hodnotou etag dřívější odpovědi a položka mezipaměti je čerstvá, server místo úplné odpovědi vrátí hodnotu 304 Není změněno :

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

}).CacheOutput();

Dalším způsobem, jak provést opětovné ověření mezipaměti, je zkontrolovat datum vytvoření položky mezipaměti v porovnání s datem požadovaným klientem. Po zadání hlavičky If-Modified-Since požadavku vrátí ukládání výstupu do mezipaměti hodnotu 304, pokud je položka uložená v mezipaměti starší a nevyprší platnost.

Opětovná aktualizace mezipaměti je automatická v reakci na tyto hlavičky odeslané z klienta. Na serveru není nutná žádná zvláštní konfigurace, aby bylo možné toto chování povolit, a to kromě povolení ukládání výstupu do mezipaměti.

Vyřazení položek mezipaměti pomocí značek

Značky můžete použít k identifikaci skupiny koncových bodů a vyřazení všech položek mezipaměti pro skupinu. Například následující kód vytvoří dvojici koncových bodů, jejichž adresy URL začínají na blogu, a označí je jako 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")); ;

Alternativním způsobem přiřazení značek pro stejnou dvojici koncových bodů je definovat základní zásadu, která se vztahuje na koncové body začínající na 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));
});

Další alternativou je volání MapGroup:

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

V předchozích příkladech přiřazení značek jsou značkou identifikovány tag-blog oba koncové body. Položky mezipaměti pro tyto koncové body pak můžete vyřadit jediným příkazem, který odkazuje na tuto značku:

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

S tímto kódem se odešle požadavek HTTP POST, který vyřadí https://localhost:<port>/purge/tag-blog položky mezipaměti pro tyto koncové body.

Možná budete chtít vyřadit všechny položky mezipaměti pro všechny koncové body. Uděláte to tak, že vytvoříte základní zásadu pro všechny koncové body, jak to dělá následující kód:

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

Tato základní zásada umožňuje použít značku "tag-all" k vyřazení všeho v mezipaměti.

Zakázání uzamčení prostředků

Ve výchozím nastavení je uzamčení prostředků povolené, aby se zmírnit riziko kolku mezipaměti a hřmění herdy. Další informace najdete v tématu Ukládání do mezipaměti výstupu.

Pokud chcete zakázat zamykání prostředků, zavolejte Při vytváření zásad metodu SetLocking(false), jak je znázorněno v následujícím příkladu:

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

Následující příklad vybere zásadu bez uzamčení pro koncový bod:

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

Omezení

Následující vlastnosti OutputCacheOptions umožňují konfigurovat limity, které platí pro všechny koncové body:

  • SizeLimit – Maximální velikost úložiště mezipaměti. Po dosažení tohoto limitu nebudou žádné nové odpovědi uloženy do mezipaměti, dokud nebudou vyřazeny starší položky. Výchozí hodnota je 100 MB.
  • MaximumBodySize – Pokud tělo odpovědi tento limit překročí, nebude uložena do mezipaměti. Výchozí hodnota je 64 MB.
  • DefaultExpirationTimeSpan – Doba trvání vypršení platnosti, která platí, pokud není určena zásadou. Výchozí hodnota je 60 sekund.

Ukládání do mezipaměti

IOutputCacheStore se používá pro úložiště. Ve výchozím nastavení se používá s MemoryCache. Odpovědi uložené v mezipaměti se ukládají v procesu, takže každý server má samostatnou mezipaměť, která se ztratí při každém restartování procesu serveru.

Redis Cache

Alternativou je použití mezipaměti Redis . Redis Cache poskytuje konzistenci mezi uzly serveru prostřednictvím sdílené mezipaměti, která prožívá jednotlivé procesy serveru. Použití Redis pro ukládání výstupu do mezipaměti:

  • Nainstalujte Microsoft.AspNetCore.Output Ukládání do mezipaměti. Balíček NuGet StackExchangeRedis

  • Zavolejte builder.Services.AddStackExchangeRedisOutputCache (neAddStackExchangeRedisCache) a zadejte připojovací řetězec, který odkazuje na server Redis.

    Příklad:

    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– připojovací řetězec k místnímu serveru Redis nebo hostované nabídce, jako je Azure Cache for Redis. Například <instance_name>.redis.cache.windows.net:6380,password=<password>,ssl=True,abortConnect=False pro Azure Cache for Redis.
    • options.InstanceName – Volitelné, určuje logický oddíl mezipaměti.

    Možnosti konfigurace jsou identické s možnostmi distribuované mezipaměti založené na Redis.

IDistributedCache Nedoporučujeme používat s ukládáním výstupu do mezipaměti. IDistributedCache nemá atomické funkce, které jsou potřeba pro označování. Doporučujeme používat integrovanou podporu pro Redis nebo vytvářet vlastní IOutputCacheStore implementace pomocí přímých závislostí na základním mechanismu úložiště.

Viz také

Tento článek vysvětluje, jak nakonfigurovat middleware pro ukládání výstupu do mezipaměti v aplikaci ASP.NET Core. Úvod do ukládání výstupu do mezipaměti najdete v tématu Ukládání výstupu do mezipaměti.

Middleware pro ukládání výstupu do mezipaměti se dá použít ve všech typech aplikací ASP.NET Core: minimální rozhraní API, webové rozhraní API s kontrolery, MVC a Razor Pages. Ukázková aplikace je minimální rozhraní API, ale každá funkce ukládání do mezipaměti, kterou ilustruje, je podporována také v ostatních typech aplikací.

Přidání middlewaru do aplikace

Přidejte do kolekce služeb middleware ukládání výstupu do mezipaměti voláním AddOutputCache.

Přidejte middleware do kanálu zpracování požadavků voláním UseOutputCache.

Poznámka:

  • V aplikacích, které používají middleware CORS, UseOutputCache musí být volána po UseCors.
  • V Razor aplikacích Pages a aplikacích s řadiči UseOutputCache musí být volána po UseRouting.
  • Volání AddOutputCachea UseOutputCache nespustí chování při ukládání do mezipaměti, zpřístupňuje ukládání do mezipaměti. Ukládání do mezipaměti musí být nakonfigurovaná data odpovědí, jak je znázorněno v následujících částech.

Konfigurace jednoho koncového bodu nebo stránky

V případě minimálních aplikací API nakonfigurujte koncový bod pro ukládání do mezipaměti voláním CacheOutputnebo použitím atributu [OutputCache] , jak je znázorněno v následujících příkladech:

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

U aplikací s kontrolery použijte [OutputCache] atribut na metodu akce. U Razor aplikací Pages použijte atribut na Razor třídu stránky.

Konfigurace několika koncových bodů nebo stránek

Vytvořte zásady při volání AddOutputCache pro určení konfigurace ukládání do mezipaměti, která se vztahuje na více koncových bodů. Pro konkrétní koncové body je možné vybrat zásadu, zatímco základní zásada poskytuje výchozí konfiguraci ukládání do mezipaměti pro kolekci koncových bodů.

Následující zvýrazněný kód konfiguruje ukládání do mezipaměti pro všechny koncové body aplikace s dobou vypršení platnosti 10 sekund. Pokud není zadaný čas vypršení platnosti, výchozí hodnota je jedna minuta.

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

Následující zvýrazněný kód vytvoří dvě zásady, z nichž každý určuje jinou dobu vypršení platnosti. Vybrané koncové body můžou používat 20sekundové vypršení platnosti a ostatní můžou používat 30sekundové vypršení platnosti.

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

Při volání CacheOutput metody nebo pomocí atributu [OutputCache] můžete vybrat zásadu pro koncový bod:

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

U aplikací s kontrolery použijte [OutputCache] atribut na metodu akce. U Razor aplikací Pages použijte atribut na Razor třídu stránky.

Výchozí zásady ukládání výstupu do mezipaměti

Ve výchozím nastavení se ukládání výstupu do mezipaměti řídí těmito pravidly:

  • Ukládají se do mezipaměti pouze odpovědi HTTP 200.
  • Ukládají se do mezipaměti pouze požadavky HTTP GET nebo HEAD.
  • Odpovědi, které jsou nastavené cookie, nejsou uložené v mezipaměti.
  • Odpovědi na ověřené požadavky nejsou uložené v mezipaměti.

Následující kód použije všechna výchozí pravidla ukládání do mezipaměti na všechny koncové body aplikace:

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

Přepsání výchozích zásad

Následující kód ukazuje, jak přepsat výchozí pravidla. Zvýrazněné řádky v následujícím kódu vlastních zásad umožňují ukládání do mezipaměti pro metody HTTP POST a odpovědi 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;
    }
}

Pokud chcete použít tuto vlastní zásadu, vytvořte pojmenovanou zásadu:

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

A vyberte pojmenovanou zásadu pro koncový bod:

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

Alternativní výchozí přepsání zásad

Alternativně můžete inicializovat instanci pomocí inicializace injektáže závislostí (DI) s následujícími změnami třídy vlastních zásad:

  • Veřejný konstruktor místo soukromého konstruktoru.
  • Eliminujte Instance vlastnost ve třídě vlastních zásad.

Příklad:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

Zbývající část třídy je stejná jako v předchozím příkladu. Přidejte vlastní zásadu, jak je znázorněno v následujícím příkladu:

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

Předchozí kód používá DI k vytvoření instance vlastní třídy zásad. Všechny veřejné argumenty v konstruktoru jsou vyřešeny.

Při použití vlastní zásady jako základní zásady nezavolejte OutputCache() (bez argumentů) u žádného koncového bodu, na který by se základní zásada měla použít. Volání OutputCache() přidá do koncového bodu výchozí zásadu.

Zadání klíče mezipaměti

Ve výchozím nastavení je každá část adresy URL zahrnuta jako klíč položky mezipaměti, tj. schéma, hostitel, port, cesta a řetězec dotazu. Můžete ale chtít klíč mezipaměti explicitně řídit. Předpokládejme například, že máte koncový bod, který vrací jedinečnou odpověď pouze pro každou jedinečnou hodnotu culture řetězce dotazu. Varianta v jiných částech adresy URL, jako jsou jiné řetězce dotazů, by neměla vést k různým položkám mezipaměti. Taková pravidla můžete zadat v zásadách, jak je znázorněno v následujícím zvýrazněném kódu:

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

Pak můžete vybrat zásadu VaryByQuery pro koncový bod:

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

Tady jsou některé možnosti řízení klíče mezipaměti:

  • SetVaryByQuery – Zadejte jeden nebo více názvů řetězců dotazu, které chcete přidat do klíče mezipaměti.

  • SetVaryByHeader – Zadejte jedno nebo více hlaviček HTTP, které se mají přidat do klíče mezipaměti.

  • VaryByValue– Zadejte hodnotu, kterou chcete přidat do klíče mezipaměti. Následující příklad používá hodnotu, která označuje, jestli je aktuální čas serveru v sekundách lichý nebo sudý. Nová odpověď se vygeneruje jenom v případě, že počet sekund pochází z lichých na sudé nebo dokonce liché.

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

Slouží OutputCacheOptions.UseCaseSensitivePaths k určení, že v části klíče se rozlišují malá a velká písmena. Výchozí hodnota nerozlišuje malá a velká písmena.

Další možnosti najdete v předmětu OutputCachePolicyBuilder .

Obnovení platnosti mezipaměti

Obnovení mezipaměti znamená, že server může místo celého textu odpovědi vrátit stavový 304 Not Modified kód HTTP. Tento stavový kód informuje klienta, že odpověď na požadavek se nezmění od toho, co klient dříve přijal.

Následující kód znázorňuje použití hlavičky k povolení opětovného Etag ověření mezipaměti. Pokud klient odešle hlavičku If-None-Match s hodnotou etag dřívější odpovědi a položka mezipaměti je čerstvá, server místo úplné odpovědi vrátí hodnotu 304 Není změněno :

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

}).CacheOutput();

Dalším způsobem, jak provést opětovné ověření mezipaměti, je zkontrolovat datum vytvoření položky mezipaměti v porovnání s datem požadovaným klientem. Po zadání hlavičky If-Modified-Since požadavku vrátí ukládání výstupu do mezipaměti hodnotu 304, pokud je položka uložená v mezipaměti starší a nevyprší platnost.

Opětovná aktualizace mezipaměti je automatická v reakci na tyto hlavičky odeslané z klienta. Na serveru není nutná žádná zvláštní konfigurace, aby bylo možné toto chování povolit, a to kromě povolení ukládání výstupu do mezipaměti.

Vyřazení položek mezipaměti pomocí značek

Značky můžete použít k identifikaci skupiny koncových bodů a vyřazení všech položek mezipaměti pro skupinu. Například následující kód vytvoří dvojici koncových bodů, jejichž adresy URL začínají na blogu, a označí je jako 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")); ;

Alternativním způsobem přiřazení značek pro stejnou dvojici koncových bodů je definovat základní zásadu, která se vztahuje na koncové body začínající na 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));
});

Další alternativou je volání MapGroup:

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

V předchozích příkladech přiřazení značek jsou značkou identifikovány tag-blog oba koncové body. Položky mezipaměti pro tyto koncové body pak můžete vyřadit jediným příkazem, který odkazuje na tuto značku:

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

V tomto kódu požadavek HTTP POST odeslaný na https://localhost:<port>/purge/tag-blog tento koncový bod vyřadí položky mezipaměti.

Možná budete chtít vyřadit všechny položky mezipaměti pro všechny koncové body. Uděláte to tak, že vytvoříte základní zásadu pro všechny koncové body, jak to dělá následující kód:

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

Tato základní zásada umožňuje použít značku "tag-all" k vyřazení všeho v mezipaměti.

Zakázání uzamčení prostředků

Ve výchozím nastavení je uzamčení prostředků povolené, aby se zmírnit riziko kolku mezipaměti a hřmění herdy. Další informace najdete v tématu Ukládání do mezipaměti výstupu.

Pokud chcete zakázat zamykání prostředků, zavolejte Při vytváření zásad metodu SetLocking(false), jak je znázorněno v následujícím příkladu:

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

Následující příklad vybere zásadu bez uzamčení pro koncový bod:

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

Omezení

Následující vlastnosti OutputCacheOptions umožňují konfigurovat limity, které platí pro všechny koncové body:

  • SizeLimit – Maximální velikost úložiště mezipaměti. Po dosažení tohoto limitu nebudou žádné nové odpovědi uloženy do mezipaměti, dokud nebudou vyřazeny starší položky. Výchozí hodnota je 100 MB.
  • MaximumBodySize – Pokud tělo odpovědi tento limit překročí, nebude uložena do mezipaměti. Výchozí hodnota je 64 MB.
  • DefaultExpirationTimeSpan – Doba trvání vypršení platnosti, která platí, pokud není určena zásadou. Výchozí hodnota je 60 sekund.

Ukládání do mezipaměti

IOutputCacheStore se používá pro úložiště. Ve výchozím nastavení se používá s MemoryCache. IDistributedCache Nedoporučujeme používat s ukládáním výstupu do mezipaměti. IDistributedCache nemá atomické funkce, které jsou potřeba pro označování. Doporučujeme vytvořit vlastní IOutputCacheStore implementace pomocí přímých závislostí na základním mechanismu úložiště, jako je Redis. Nebo použijte integrovanou podporu mezipaměti Redis v .NET 8..

Viz také