Oprogramowanie pośredniczące buforowania danych wyjściowych w programie ASP.NET Core

Autor: Tom Dykstra

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

W tym artykule wyjaśniono, jak skonfigurować oprogramowanie pośredniczące buforowania danych wyjściowych w aplikacji ASP.NET Core. Aby zapoznać się z wprowadzeniem do buforowania danych wyjściowych, zobacz Buforowanie danych wyjściowych.

Oprogramowanie pośredniczące buforowania danych wyjściowych może być używane we wszystkich typach aplikacji ASP.NET Core: minimalny interfejs API, internetowy interfejs API z kontrolerami, MVC i Razor stronami. Przykładowa aplikacja jest minimalnym interfejsem API, ale każda funkcja buforowania, która ilustruje, jest również obsługiwana w innych typach aplikacji.

Dodawanie oprogramowania pośredniczącego do aplikacji

Dodaj oprogramowanie pośredniczące buforowania danych wyjściowych do kolekcji usług, wywołując metodę AddOutputCache.

Dodaj oprogramowanie pośredniczące do potoku przetwarzania żądań, wywołując metodę UseOutputCache.

Uwaga

  • W aplikacjach korzystających z oprogramowaniaUseOutputCache pośredniczącego CORS należy wywołać metodę po UseCors.
  • W Razor aplikacjach Stron i aplikacjach z kontrolerami UseOutputCache należy wywołać metodę po UseRouting.
  • Wywoływanie AddOutputCachei UseOutputCache nie rozpoczyna buforowania, dzięki czemu buforowanie jest dostępne. Buforowanie dane odpowiedzi muszą być skonfigurowane, jak pokazano w poniższych sekcjach.

Konfigurowanie jednego punktu końcowego lub strony

W przypadku minimalnych aplikacji interfejsu API skonfiguruj punkt końcowy do buforowania, wywołując CacheOutputmetodę , lub [OutputCache] stosując atrybut, jak pokazano w poniższych przykładach:

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

W przypadku aplikacji z kontrolerami zastosuj [OutputCache] atrybut do metody akcji. W przypadku Razor aplikacji Pages zastosuj atrybut do Razor klasy strony.

Konfigurowanie wielu punktów końcowych lub stron

Utwórz zasady podczas wywoływania AddOutputCache w celu określenia konfiguracji buforowania, która ma zastosowanie do wielu punktów końcowych. Zasady można wybrać dla określonych punktów końcowych, podczas gdy zasady podstawowe zapewniają domyślną konfigurację buforowania dla kolekcji punktów końcowych.

Poniższy wyróżniony kod konfiguruje buforowanie dla wszystkich punktów końcowych aplikacji z czasem wygaśnięcia 10 sekund. Jeśli nie określono czasu wygaśnięcia, wartość domyślna to 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)));
});

Poniższy wyróżniony kod tworzy dwie zasady, z których każda określa inny czas wygaśnięcia. Wybrane punkty końcowe mogą używać 20-sekundowego wygaśnięcia, a inne mogą korzystać z 30-sekundowego wygaśnięcia.

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

Zasady dla punktu końcowego można wybrać podczas wywoływania CacheOutput metody lub przy użyciu atrybutu [OutputCache] :

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

W przypadku aplikacji z kontrolerami zastosuj [OutputCache] atrybut do metody akcji. W przypadku Razor aplikacji Pages zastosuj atrybut do Razor klasy strony.

Domyślne zasady buforowania danych wyjściowych

Domyślnie buforowanie danych wyjściowych jest zgodne z następującymi regułami:

  • Buforowane są tylko odpowiedzi HTTP 200.
  • Buforowane są tylko żądania HTTP GET lub HEAD.
  • Odpowiedzi, które zestaw cookienie są buforowane.
  • Odpowiedzi na uwierzytelnione żądania nie są buforowane.

Poniższy kod stosuje wszystkie domyślne reguły buforowania do wszystkich punktów końcowych aplikacji:

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

Zastępowanie zasad domyślnych

Poniższy kod pokazuje, jak zastąpić reguły domyślne. Wyróżnione wiersze w poniższym niestandardowym kodzie zasad umożliwiają buforowanie metod HTTP POST i odpowiedzi 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;
    }
}

Aby użyć tych zasad niestandardowych, utwórz nazwane zasady:

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

Wybierz nazwane zasady dla punktu końcowego:

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

Zastępowanie alternatywnych zasad domyślnych

Alternatywnie użyj iniekcji zależności (DI), aby zainicjować wystąpienie, z następującymi zmianami w niestandardowej klasie zasad:

  • Publiczny konstruktor zamiast prywatnego konstruktora.
  • Wyeliminuj Instance właściwość w niestandardowej klasie zasad.

Na przykład:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

Pozostała część klasy jest taka sama, jak pokazano wcześniej. Dodaj zasady niestandardowe, jak pokazano w poniższym przykładzie:

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

Powyższy kod używa di do utworzenia wystąpienia niestandardowej klasy zasad. Wszystkie publiczne argumenty w konstruktorze są rozpoznawane.

W przypadku używania zasad niestandardowych jako zasad podstawowych nie należy wywoływać OutputCache() (bez argumentów) w żadnym punkcie końcowym, do którego mają być stosowane zasady podstawowe. Wywołanie OutputCache() powoduje dodanie domyślnych zasad do punktu końcowego.

Określanie klucza pamięci podręcznej

Domyślnie każda część adresu URL jest dołączana jako klucz do wpisu pamięci podręcznej, czyli schematu, hosta, portu, ścieżki i ciągu zapytania. Można jednak jawnie kontrolować klucz pamięci podręcznej. Załóżmy na przykład, że masz punkt końcowy, który zwraca unikatową odpowiedź tylko dla każdej unikatowej culture wartości ciągu zapytania. Odmiana w innych częściach adresu URL, takich jak inne ciągi zapytania, nie powinna powodować różnych wpisów pamięci podręcznej. Takie reguły można określić w zasadach, jak pokazano w poniższym wyróżnionym kodzie:

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

Następnie możesz wybrać VaryByQuery zasady dla punktu końcowego:

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

Poniżej przedstawiono niektóre opcje kontrolowania klucza pamięci podręcznej:

  • SetVaryByQuery — Określ co najmniej jedną nazwę ciągu zapytania, która ma zostać dodana do klucza pamięci podręcznej.

  • SetVaryByHeader — Określ co najmniej jeden nagłówek HTTP, który ma zostać dodany do klucza pamięci podręcznej.

  • VaryByValue— Określ wartość, która ma zostać dodana do klucza pamięci podręcznej. W poniższym przykładzie użyto wartości, która wskazuje, czy bieżący czas serwera w sekundach jest dziwny, czy nawet. Nowa odpowiedź jest generowana tylko wtedy, gdy liczba sekund przechodzi od nieparzystego do parzystego, a nawet do dziwnego.

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

Użyj OutputCacheOptions.UseCaseSensitivePaths polecenia , aby określić, że część ścieżki klucza uwzględnia wielkość liter. Wartość domyślna to bez uwzględniania wielkości liter.

Aby uzyskać więcej opcji, zobacz klasę OutputCachePolicyBuilder .

Ponowne dodawanie pamięci podręcznej

Zmiana pamięci podręcznej oznacza, że serwer może zwrócić 304 Not Modified kod stanu HTTP zamiast pełnej treści odpowiedzi. Ten kod stanu informuje klienta, że odpowiedź na żądanie nie zmienia się od tego, co klient otrzymał wcześniej.

Poniższy kod ilustruje użycie nagłówka Etag w celu włączenia ponownego stosowania pamięci podręcznej. Jeśli klient wysyła If-None-Match nagłówek z wartością etag wcześniejszej odpowiedzi, a wpis pamięci podręcznej jest świeży, serwer zwraca wartość 304 Not Modified zamiast pełnej odpowiedzi:

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

}).CacheOutput();

Innym sposobem na ponowne zaktualizowanie pamięci podręcznej jest sprawdzenie daty utworzenia wpisu pamięci podręcznej w porównaniu z datą żądaną przez klienta. Po podaniu nagłówka If-Modified-Since żądania buforowanie danych wyjściowych zwraca wartość 304, jeśli buforowany wpis jest starszy i nie wygasł.

Ponowne aktualizowanie pamięci podręcznej jest automatyczne w odpowiedzi na te nagłówki wysyłane z klienta. Nie jest wymagana żadna specjalna konfiguracja na serwerze, aby włączyć to zachowanie, oprócz włączania buforowania danych wyjściowych.

Eksmitowanie wpisów pamięci podręcznej za pomocą tagów

Za pomocą tagów można zidentyfikować grupę punktów końcowych i wykluczyć wszystkie wpisy pamięci podręcznej dla grupy. Na przykład poniższy kod tworzy parę punktów końcowych, których adresy URL zaczynają się od "bloga" i tagują 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")); ;

Alternatywnym sposobem przypisywania tagów dla tej samej pary punktów końcowych jest zdefiniowanie podstawowych zasad, które mają zastosowanie do punktów końcowych rozpoczynających się od 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));
});

Inną alternatywą jest wywołanie metody MapGroup:

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

W poprzednich przykładach przypisania tagów oba punkty końcowe są identyfikowane przez tag-blog tag . Następnie można wykluczyć wpisy pamięci podręcznej dla tych punktów końcowych za pomocą pojedynczej instrukcji, która odwołuje się do tego tagu:

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

Za pomocą tego kodu żądanie HTTP POST wysłane do https://localhost:<port>/purge/tag-blog eksmituje wpisy pamięci podręcznej dla tych punktów końcowych.

Możesz chcieć wykluczyć wszystkie wpisy pamięci podręcznej dla wszystkich punktów końcowych. W tym celu należy utworzyć podstawowe zasady dla wszystkich punktów końcowych, tak jak w poniższym kodzie:

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

Te podstawowe zasady umożliwiają eksmitowanie wszystkich elementów w pamięci podręcznej za pomocą tagu "tag-all".

Wyłączanie blokowania zasobów

Domyślnie blokada zasobów jest włączona w celu ograniczenia ryzyka stemplowania pamięci podręcznej i grzmotu stada. Aby uzyskać więcej informacji, zobacz Dane wyjściowe Buforowanie.

Aby wyłączyć blokowanie zasobów, wywołaj metodę SetLocking(false) podczas tworzenia zasad, jak pokazano w poniższym przykładzie:

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

Poniższy przykład wybiera zasady braku blokowania dla punktu końcowego:

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

Limity

Następujące właściwości OutputCacheOptions umożliwiają skonfigurowanie limitów, które mają zastosowanie do wszystkich punktów końcowych:

  • SizeLimit — Maksymalny rozmiar magazynu pamięci podręcznej. Po osiągnięciu tego limitu żadne nowe odpowiedzi nie są buforowane do momentu eksmitowania starszych wpisów. Wartość domyślna to 100 MB.
  • MaximumBodySize — Jeśli treść odpowiedzi przekroczy ten limit, nie jest buforowana. Wartość domyślna to 64 MB.
  • DefaultExpirationTimeSpan - Czas wygaśnięcia, który ma zastosowanie, gdy nie zostanie określony przez zasady. Wartość domyślna to 60 sekund.

Magazyn pamięci podręcznej

IOutputCacheStore jest używany do magazynowania. Domyślnie jest używany z MemoryCache. Buforowane odpowiedzi są przechowywane w procesie, dlatego każdy serwer ma oddzielną pamięć podręczną, która zostanie utracona przy każdym ponownym uruchomieniu procesu serwera.

Redis Cache

Alternatywą jest użycie pamięci podręcznej Redis Cache. Pamięć podręczna Redis Cache zapewnia spójność między węzłami serwera za pośrednictwem udostępnionej pamięci podręcznej, która przeżywa poszczególne procesy serwera. Aby użyć usługi Redis do buforowania danych wyjściowych:

  • Zainstaluj plik Microsoft.AspNetCore.Output Buforowanie. Pakiet NuGet StackExchangeRedis.

  • Wywołaj builder.Services.AddStackExchangeRedisOutputCache metodę (nie AddStackExchangeRedisCache), a następnie podaj parametry połączenia wskazującą serwer Redis.

    Na przykład:

    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— parametry połączenia do lokalnego serwera Redis lub do oferty hostowanej, takiej jak Azure Cache for Redis. Na przykład <instance_name>.redis.cache.windows.net:6380,password=<password>,ssl=True,abortConnect=False w przypadku usługi Azure Cache for Redis.
    • options.InstanceName — Opcjonalnie określa partycję logiczną pamięci podręcznej.

    Opcje konfiguracji są identyczne z opcjami buforowania rozproszonego opartego na usłudze Redis.

Nie zalecamy IDistributedCache używania z buforowaniem danych wyjściowych. IDistributedCache nie ma funkcji niepodzielnych, które są wymagane do tagowania. Zalecamy korzystanie z wbudowanej obsługi usługi Redis lub tworzenia niestandardowych IOutputCacheStore implementacji przy użyciu bezpośrednich zależności od podstawowego mechanizmu magazynu.

Zobacz też

W tym artykule wyjaśniono, jak skonfigurować oprogramowanie pośredniczące buforowania danych wyjściowych w aplikacji ASP.NET Core. Aby zapoznać się z wprowadzeniem do buforowania danych wyjściowych, zobacz Buforowanie danych wyjściowych.

Oprogramowanie pośredniczące buforowania danych wyjściowych może być używane we wszystkich typach aplikacji ASP.NET Core: minimalny interfejs API, internetowy interfejs API z kontrolerami, MVC i Razor stronami. Przykładowa aplikacja jest minimalnym interfejsem API, ale każda funkcja buforowania, która ilustruje, jest również obsługiwana w innych typach aplikacji.

Dodawanie oprogramowania pośredniczącego do aplikacji

Dodaj oprogramowanie pośredniczące buforowania danych wyjściowych do kolekcji usług, wywołując metodę AddOutputCache.

Dodaj oprogramowanie pośredniczące do potoku przetwarzania żądań, wywołując metodę UseOutputCache.

Uwaga

  • W aplikacjach korzystających z oprogramowaniaUseOutputCache pośredniczącego CORS należy wywołać metodę po UseCors.
  • W Razor aplikacjach Stron i aplikacjach z kontrolerami UseOutputCache należy wywołać metodę po UseRouting.
  • Wywoływanie AddOutputCachei UseOutputCache nie rozpoczyna buforowania, dzięki czemu buforowanie jest dostępne. Buforowanie dane odpowiedzi muszą być skonfigurowane, jak pokazano w poniższych sekcjach.

Konfigurowanie jednego punktu końcowego lub strony

W przypadku minimalnych aplikacji interfejsu API skonfiguruj punkt końcowy do buforowania, wywołując CacheOutputmetodę , lub [OutputCache] stosując atrybut, jak pokazano w poniższych przykładach:

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

W przypadku aplikacji z kontrolerami zastosuj [OutputCache] atrybut do metody akcji. W przypadku Razor aplikacji Pages zastosuj atrybut do Razor klasy strony.

Konfigurowanie wielu punktów końcowych lub stron

Utwórz zasady podczas wywoływania AddOutputCache w celu określenia konfiguracji buforowania, która ma zastosowanie do wielu punktów końcowych. Zasady można wybrać dla określonych punktów końcowych, podczas gdy zasady podstawowe zapewniają domyślną konfigurację buforowania dla kolekcji punktów końcowych.

Poniższy wyróżniony kod konfiguruje buforowanie dla wszystkich punktów końcowych aplikacji z czasem wygaśnięcia 10 sekund. Jeśli nie określono czasu wygaśnięcia, wartość domyślna to 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)));
});

Poniższy wyróżniony kod tworzy dwie zasady, z których każda określa inny czas wygaśnięcia. Wybrane punkty końcowe mogą używać 20 sekundowego wygaśnięcia, a inne mogą korzystać z 30-sekundowego wygaśnięcia.

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

Zasady dla punktu końcowego można wybrać podczas wywoływania CacheOutput metody lub przy użyciu atrybutu [OutputCache] :

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

W przypadku aplikacji z kontrolerami zastosuj [OutputCache] atrybut do metody akcji. W przypadku Razor aplikacji Pages zastosuj atrybut do Razor klasy strony.

Domyślne zasady buforowania danych wyjściowych

Domyślnie buforowanie danych wyjściowych jest zgodne z następującymi regułami:

  • Buforowane są tylko odpowiedzi HTTP 200.
  • Buforowane są tylko żądania HTTP GET lub HEAD.
  • Odpowiedzi, które zestaw cookienie są buforowane.
  • Odpowiedzi na uwierzytelnione żądania nie są buforowane.

Poniższy kod stosuje wszystkie domyślne reguły buforowania do wszystkich punktów końcowych aplikacji:

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

Zastępowanie zasad domyślnych

Poniższy kod pokazuje, jak zastąpić reguły domyślne. Wyróżnione wiersze w poniższym niestandardowym kodzie zasad umożliwiają buforowanie metod HTTP POST i odpowiedzi 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;
    }
}

Aby użyć tych zasad niestandardowych, utwórz nazwane zasady:

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

Wybierz nazwane zasady dla punktu końcowego:

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

Zastępowanie alternatywnych zasad domyślnych

Alternatywnie użyj iniekcji zależności (DI), aby zainicjować wystąpienie, z następującymi zmianami w niestandardowej klasie zasad:

  • Publiczny konstruktor zamiast prywatnego konstruktora.
  • Wyeliminuj Instance właściwość w niestandardowej klasie zasad.

Na przykład:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

Pozostała część klasy jest taka sama, jak pokazano wcześniej. Dodaj zasady niestandardowe, jak pokazano w poniższym przykładzie:

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

Powyższy kod używa di do utworzenia wystąpienia niestandardowej klasy zasad. Wszystkie publiczne argumenty w konstruktorze są rozpoznawane.

W przypadku używania zasad niestandardowych jako zasad podstawowych nie należy wywoływać OutputCache() (bez argumentów) w żadnym punkcie końcowym, do którego mają być stosowane zasady podstawowe. Wywołanie OutputCache() powoduje dodanie domyślnych zasad do punktu końcowego.

Określanie klucza pamięci podręcznej

Domyślnie każda część adresu URL jest dołączana jako klucz do wpisu pamięci podręcznej, czyli schematu, hosta, portu, ścieżki i ciągu zapytania. Można jednak jawnie kontrolować klucz pamięci podręcznej. Załóżmy na przykład, że masz punkt końcowy, który zwraca unikatową odpowiedź tylko dla każdej unikatowej culture wartości ciągu zapytania. Odmiana w innych częściach adresu URL, takich jak inne ciągi zapytania, nie powinna powodować różnych wpisów pamięci podręcznej. Takie reguły można określić w zasadach, jak pokazano w poniższym wyróżnionym kodzie:

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

Następnie możesz wybrać VaryByQuery zasady dla punktu końcowego:

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

Poniżej przedstawiono niektóre opcje kontrolowania klucza pamięci podręcznej:

  • SetVaryByQuery — Określ co najmniej jedną nazwę ciągu zapytania, która ma zostać dodana do klucza pamięci podręcznej.

  • SetVaryByHeader — Określ co najmniej jeden nagłówek HTTP, który ma zostać dodany do klucza pamięci podręcznej.

  • VaryByValue— Określ wartość, która ma zostać dodana do klucza pamięci podręcznej. W poniższym przykładzie użyto wartości, która wskazuje, czy bieżący czas serwera w sekundach jest dziwny, czy nawet. Nowa odpowiedź jest generowana tylko wtedy, gdy liczba sekund przechodzi od nieparzystego do parzystego, a nawet do dziwnego.

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

Użyj OutputCacheOptions.UseCaseSensitivePaths polecenia , aby określić, że część ścieżki klucza uwzględnia wielkość liter. Wartość domyślna to bez uwzględniania wielkości liter.

Aby uzyskać więcej opcji, zobacz klasę OutputCachePolicyBuilder .

Ponowne dodawanie pamięci podręcznej

Zmiana pamięci podręcznej oznacza, że serwer może zwrócić 304 Not Modified kod stanu HTTP zamiast pełnej treści odpowiedzi. Ten kod stanu informuje klienta, że odpowiedź na żądanie nie zmienia się od tego, co klient otrzymał wcześniej.

Poniższy kod ilustruje użycie nagłówka Etag w celu włączenia ponownego stosowania pamięci podręcznej. Jeśli klient wysyła If-None-Match nagłówek z wartością etag wcześniejszej odpowiedzi, a wpis pamięci podręcznej jest świeży, serwer zwraca wartość 304 Not Modified zamiast pełnej odpowiedzi:

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

}).CacheOutput();

Innym sposobem na ponowne zaktualizowanie pamięci podręcznej jest sprawdzenie daty utworzenia wpisu pamięci podręcznej w porównaniu z datą żądaną przez klienta. Po podaniu nagłówka If-Modified-Since żądania buforowanie danych wyjściowych zwraca wartość 304, jeśli buforowany wpis jest starszy i nie wygasł.

Ponowne aktualizowanie pamięci podręcznej jest automatyczne w odpowiedzi na te nagłówki wysyłane z klienta. Nie jest wymagana żadna specjalna konfiguracja na serwerze, aby włączyć to zachowanie, oprócz włączania buforowania danych wyjściowych.

Eksmitowanie wpisów pamięci podręcznej za pomocą tagów

Za pomocą tagów można zidentyfikować grupę punktów końcowych i wykluczyć wszystkie wpisy pamięci podręcznej dla grupy. Na przykład poniższy kod tworzy parę punktów końcowych, których adresy URL zaczynają się od "bloga" i tagują 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")); ;

Alternatywnym sposobem przypisywania tagów dla tej samej pary punktów końcowych jest zdefiniowanie podstawowych zasad, które mają zastosowanie do punktów końcowych rozpoczynających się od 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));
});

Inną alternatywą jest wywołanie metody MapGroup:

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

W poprzednich przykładach przypisania tagów oba punkty końcowe są identyfikowane przez tag-blog tag . Następnie można wykluczyć wpisy pamięci podręcznej dla tych punktów końcowych za pomocą pojedynczej instrukcji, która odwołuje się do tego tagu:

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

Za pomocą tego kodu żądanie HTTP POST wysłane do programu https://localhost:<port>/purge/tag-blog spowoduje wykluczenie wpisów pamięci podręcznej dla tych punktów końcowych.

Możesz chcieć wykluczyć wszystkie wpisy pamięci podręcznej dla wszystkich punktów końcowych. W tym celu należy utworzyć podstawowe zasady dla wszystkich punktów końcowych, tak jak w poniższym kodzie:

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

Te podstawowe zasady umożliwiają eksmitowanie wszystkich elementów w pamięci podręcznej za pomocą tagu "tag-all".

Wyłączanie blokowania zasobów

Domyślnie blokada zasobów jest włączona w celu ograniczenia ryzyka stemplowania pamięci podręcznej i grzmotu stada. Aby uzyskać więcej informacji, zobacz Dane wyjściowe Buforowanie.

Aby wyłączyć blokowanie zasobów, wywołaj metodę SetLocking(false) podczas tworzenia zasad, jak pokazano w poniższym przykładzie:

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

Poniższy przykład wybiera zasady braku blokowania dla punktu końcowego:

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

Limity

Następujące właściwości OutputCacheOptions umożliwiają skonfigurowanie limitów, które mają zastosowanie do wszystkich punktów końcowych:

  • SizeLimit — Maksymalny rozmiar magazynu pamięci podręcznej. Po osiągnięciu tego limitu żadne nowe odpowiedzi nie będą buforowane do czasu eksmitowania starszych wpisów. Wartość domyślna to 100 MB.
  • MaximumBodySize — Jeśli treść odpowiedzi przekroczy ten limit, nie zostanie ona buforowana. Wartość domyślna to 64 MB.
  • DefaultExpirationTimeSpan - Czas wygaśnięcia, który ma zastosowanie, gdy nie zostanie określony przez zasady. Wartość domyślna to 60 sekund.

Magazyn pamięci podręcznej

IOutputCacheStore jest używany do magazynowania. Domyślnie jest używany z MemoryCache. Nie zalecamy IDistributedCache używania z buforowaniem danych wyjściowych. IDistributedCache nie ma funkcji niepodzielnych, które są wymagane do tagowania. Zalecamy tworzenie niestandardowych IOutputCacheStore implementacji przy użyciu bezpośrednich zależności od podstawowego mechanizmu magazynu, takiego jak Redis. Możesz też użyć wbudowanej obsługi pamięci podręcznej Redis Cache na platformie .NET 8..

Zobacz też