ASP.NET Core 中的輸出快取中介軟體

作者:Tom Dykstra

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前版本,請參閱本文的 .NET 8 版本

本文說明如何在 ASP.NET Core 應用程式中設定輸出快取中介軟體。 如需輸出快取的簡介,請參閱輸出快取

輸出快取中介軟體可用於所有類型的 ASP.NET 核心應用程式:最小 API、具有控制器、MVC 和 Razor Pages 的 Web API。 範例應用程式是基本 API,但它所說明的每個快取功能在其他應用程式類型中也受到支援。

將中介軟體新增至應用程式

呼叫 AddOutputCache,將輸出快取中介軟體新增至服務集合。

呼叫 UseOutputCache,將中介軟體新增至要求處理管線。

注意

  • 在使用 CORS 中介軟體的應用程式中,必須在 UseCors 之後呼叫 UseOutputCache
  • 在具有控制器的 Razor Pages 應用程式和應用程式中,必須在 UseRouting 之後呼叫 UseOutputCache
  • 呼叫 AddOutputCacheUseOutputCache 不會啟動快取行為,而是讓快取可供使用。 必須設定快取回應資料,如下列各節所示。

設定一個端點或頁面

針對最小 API 應用程式,請設定端點透過呼叫 CacheOutput 來執行快取,或套用 [OutputCache] 屬性,如下列範例所示:

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

對於具有控制器的應用程式,請將 [OutputCache] 屬性套用至動作方法。 針對 Razor Pages 應用程式,將屬性套用至 Razor 頁面類別。

設定多個端點或頁面

呼叫 AddOutputCache 時建立原則,以指定套用至多個端點的快取組態。 您可以針對特定端點選取原則,而基底原則則提供端點集合的預設快取設定。

下列醒目提示的程式碼會設定所有應用程式端點的快取功能,到期時間為 10 秒。 如果未指定到期時間,則預設值為一分鐘。

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

下列醒目提示的程式碼會建立兩個原則,每個原則都會指定不同的到期時間。 選取的端點可以使用 20 秒的到期時間,而其他端點則可使用 30 秒的到期時間。

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

呼叫 CacheOutput 方法或使用 [OutputCache] 屬性時,您可以選取端點的原則:

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

對於具有控制器的應用程式,請將 [OutputCache] 屬性套用至動作方法。 針對 Razor Pages 應用程式,將屬性套用至 Razor 頁面類別。

預設輸出快取原則

根據預設,輸出快取會遵循下列規則:

  • 只會快取 HTTP 200 回應。
  • 只會快取 HTTP GET 或 HEAD 要求。
  • 設定 cookie 的回應不會被快取。
  • 已驗證要求的回應不會被快取。

下列程式碼會將所有預設快取規則套用至應用程式的所有端點:

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

覆寫預設原則

下列程式碼示範如何覆寫預設規則。 下列自訂原則程式碼中反白顯示的幾行會啟用 HTTP POST 方法和 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;
    }
}

若要使用此自訂原則,請建立具名原則:

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

然後選取端點的具名原則:

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

替代的預設原則覆寫

或者,使用相依性插入 (DI) 初始化實例,並對自訂原則類別進行下列變更:

  • 公共建構函式,而不是私有建構函式。
  • 排除自訂原則類別中的 Instance 屬性。

例如:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

類別的其餘部分與先前所示相同。 新增自訂原則,如下列範例所示:

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

上述程式碼會使用 DI 來建立自訂原則類別的實例。 建構函式中的任何公共引數都已解析。

使用自訂原則作為基底原則時,請勿在任何基底原則應套用的端點上呼叫 OutputCache() (不含引數)。 呼叫 OutputCache() 會將預設原則新增至端點。

指定快取索引鍵

根據預設,URL 的每個部分都會包含為快取專案的索引鍵,也就是配置、主機、連接埠、路徑和查詢字串。 不過,您可能想要明確控制快取索引鍵。 例如,假設您有一個端點,其只會針對 culture 查詢字串的每個唯一值傳回唯一回應。 URL 的其他部分變化,例如其他查詢字串,不應該產生不同的快取專案。 您可以在原則中指定這類規則,如下列醒目提示的程式碼所示:

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

然後,您可以選取端點的 VaryByQuery 原則:

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

以下是控制快取索引鍵的一些選項:

  • SetVaryByQuery - 指定要新增至快取索引鍵的一個或多個查詢字串名稱。

  • SetVaryByHeader - 指定要新增至快取索引鍵的一個或多個 HTTP 標頭。

  • VaryByValue - 指定要新增至快取索引鍵的值。 下列範例會使用值,指出目前的伺服器時間 (秒數) 是否為奇數或偶數。 只有在秒數從奇數到偶數,或從偶數到奇數時,才會產生新的回應。

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

使用 OutputCacheOptions.UseCaseSensitivePaths 來指定索引鍵的路徑部分會區分大小寫。 預設值是不區分大小寫。

如需其他選項,請參閱 OutputCachePolicyBuilder 類別。

快取重新驗證

快取重新驗證表示伺服器可以傳回 304 Not Modified HTTP 狀態碼,而不是完整的回應主體。 此狀態碼會通知用戶端,對要求的回應與先前收到的用戶端沒有變更。

下列程式碼說明如何使用 Etag 標頭來啟用快取重新驗證。 如果用戶端傳送具有先前回應之 etag 值的 If-None-Match 標頭,且快取專案是全新的,則伺服器會傳回 304 未修改,而不是完整回應:

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

}).CacheOutput();

執行快取重新驗證的另一種方式是檢查快取專案建立的日期,與用戶端所要求的日期相比較。 當提供要求標頭 If-Modified-Since 時,如果快取的專案較舊且未過期,輸出快取會傳回 304。

快取重新驗證是自動的,以回應從用戶端傳送的這些標頭。 除了啟用輸出快取之外,伺服器上不需要任何特殊設定才能啟用此行為。

使用標記收回快取專案

您可以使用標記來識別端點群組,並收回群組的所有快取專案。 例如,下列程式碼會建立一組端點,其 URL 開頭為「blog」,並將它們標記為「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")); ;

指派相同端點組標記的替代方式,是定義套用至開頭為 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));
});

另一個替代方法是呼叫 MapGroup

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

在上述標籤指派範例中,這兩個端點都是由 tag-blog 標記來識別。 然後,您可以使用參考該標記的單一陳述式來收回這些端點的快取專案:

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

使用此程式碼,傳送至 https://localhost:<port>/purge/tag-blog 的 HTTP POST 要求將會收回這些端點的快取專案。

您可能想要一種方式來收回所有端點的所有快取專案。 若要這樣做,請建立所有端點的基底原則,如下列程式碼所示:

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

此基底原則可讓您使用「全部標記」標籤來收回快取中的所有專案。

停用資源鎖定

根據預設,會啟用資源鎖定,以降低快取踩踏和驚群效應的風險。 如需詳細資訊,請參閱輸出快取

若要停用資源鎖定,請在建立原則時呼叫 SetLocking(false),如下列範例所示:

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

下列範例會選取端點的無鎖定原則:

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

限制

下列 OutputCacheOptions 屬性可讓您設定適用于所有端點的限制:

  • SizeLimit - 快取儲存體的大小上限。 達到此限制時,在收回較舊的專案之前,不會快取任何新的回應。 預設值為 100 MB。
  • MaximumBodySize - 如果回應本文超過此限制,則不會快取。 預設值為 64 MB。
  • DefaultExpirationTimeSpan - 原則未指定時套用的到期時間持續時間。 預設值為 60 秒。

快取儲存體

IOutputCacheStore 用於儲存體。 根據預設,它會與 MemoryCache 搭配使用。 快取的回應會儲存在程序中,因此每部伺服器都有個別的快取,每當伺服器程序重新啟動時就會遺失。

Redis 快取

替代方法是使用 Redis 快取。 Redis 快取透過存留期超過個別伺服器程序的共用快取,提供伺服器節點之間的一致性。 若要使用 Redis 進行輸出快取:

  • 安裝 Microsoft.AspNetCore.OutputCaching.StackExchangeRedis NuGet 套件。

  • 呼叫 builder.Services.AddStackExchangeRedisOutputCache (非 AddStackExchangeRedisCache),並且提供指向 Redis 伺服器的連接字串。

    例如:

    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 - 指向內部部署 Redis 伺服器或指向裝載供應項目 (例如 Azure Cache for Redis) 的連接字串。 例如,適用於 Azure Cache for Redis 的 <instance_name>.redis.cache.windows.net:6380,password=<password>,ssl=True,abortConnect=False
    • options.InstanceName - 選擇性,指定快取的邏輯資料分割。

    設定選項與 Redis 型分散式快取選項相同。

不建議 IDistributedCache 搭配輸出快取使用。 IDistributedCache 沒有標記所需的不可部分完成功能。 建議您使用 Redis 的內建支援,或藉由在基礎儲存機制上使用直接相依性,建立自訂 IOutputCacheStore 實作。

另請參閱

本文說明如何在 ASP.NET Core 應用程式中設定輸出快取中介軟體。 如需輸出快取的簡介,請參閱輸出快取

輸出快取中介軟體可用於所有類型的 ASP.NET 核心應用程式:最小 API、具有控制器、MVC 和 Razor Pages 的 Web API。 範例應用程式是基本 API,但它所說明的每個快取功能在其他應用程式類型中也受到支援。

將中介軟體新增至應用程式

呼叫 AddOutputCache,將輸出快取中介軟體新增至服務集合。

呼叫 UseOutputCache,將中介軟體新增至要求處理管線。

注意

  • 在使用 CORS 中介軟體的應用程式中,必須在 UseCors 之後呼叫 UseOutputCache
  • 在具有控制器的 Razor Pages 應用程式和應用程式中,必須在 UseRouting 之後呼叫 UseOutputCache
  • 呼叫 AddOutputCacheUseOutputCache 不會啟動快取行為,而是讓快取可供使用。 必須設定快取回應資料,如下列各節所示。

設定一個端點或頁面

針對最小 API 應用程式,請設定端點透過呼叫 CacheOutput 來執行快取,或套用 [OutputCache] 屬性,如下列範例所示:

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

對於具有控制器的應用程式,請將 [OutputCache] 屬性套用至動作方法。 針對 Razor Pages 應用程式,將屬性套用至 Razor 頁面類別。

設定多個端點或頁面

呼叫 AddOutputCache 時建立原則,以指定套用至多個端點的快取組態。 您可以針對特定端點選取原則,而基底原則則提供端點集合的預設快取設定。

下列醒目提示的程式碼會設定所有應用程式端點的快取功能,到期時間為 10 秒。 如果未指定到期時間,則預設值為一分鐘。

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

下列醒目提示的程式碼會建立兩個原則,每個原則都會指定不同的到期時間。 選取的端點可以使用 20 秒的到期時間,而其他端點則可使用 30 秒的到期時間。

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

呼叫 CacheOutput 方法或使用 [OutputCache] 屬性時,您可以選取端點的原則:

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

對於具有控制器的應用程式,請將 [OutputCache] 屬性套用至動作方法。 針對 Razor Pages 應用程式,將屬性套用至 Razor 頁面類別。

預設輸出快取原則

根據預設,輸出快取會遵循下列規則:

  • 只會快取 HTTP 200 回應。
  • 只會快取 HTTP GET 或 HEAD 要求。
  • 設定 cookie 的回應不會被快取。
  • 已驗證要求的回應不會被快取。

下列程式碼會將所有預設快取規則套用至應用程式的所有端點:

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

覆寫預設原則

下列程式碼示範如何覆寫預設規則。 下列自訂原則程式碼中反白顯示的幾行會啟用 HTTP POST 方法和 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;
    }
}

若要使用此自訂原則,請建立具名原則:

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

然後選取端點的具名原則:

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

替代的預設原則覆寫

或者,使用相依性插入 (DI) 初始化實例,並對自訂原則類別進行下列變更:

  • 公共建構函式,而不是私有建構函式。
  • 排除自訂原則類別中的 Instance 屬性。

例如:

public sealed class MyCustomPolicy2 : IOutputCachePolicy
{

    public MyCustomPolicy2()
    {
    }

類別的其餘部分與先前所示相同。 新增自訂原則,如下列範例所示:

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

上述程式碼會使用 DI 來建立自訂原則類別的實例。 建構函式中的任何公共引數都已解析。

使用自訂原則作為基底原則時,請勿在任何基底原則應套用的端點上呼叫 OutputCache() (不含引數)。 呼叫 OutputCache() 會將預設原則新增至端點。

指定快取索引鍵

根據預設,URL 的每個部分都會包含為快取專案的索引鍵,也就是配置、主機、連接埠、路徑和查詢字串。 不過,您可能想要明確控制快取索引鍵。 例如,假設您有一個端點,其只會針對 culture 查詢字串的每個唯一值傳回唯一回應。 URL 的其他部分變化,例如其他查詢字串,不應該產生不同的快取專案。 您可以在原則中指定這類規則,如下列醒目提示的程式碼所示:

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

然後,您可以選取端點的 VaryByQuery 原則:

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

以下是控制快取索引鍵的一些選項:

  • SetVaryByQuery - 指定要新增至快取索引鍵的一個或多個查詢字串名稱。

  • SetVaryByHeader - 指定要新增至快取索引鍵的一個或多個 HTTP 標頭。

  • VaryByValue - 指定要新增至快取索引鍵的值。 下列範例會使用值,指出目前的伺服器時間 (秒數) 是否為奇數或偶數。 只有在秒數從奇數到偶數,或從偶數到奇數時,才會產生新的回應。

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

使用 OutputCacheOptions.UseCaseSensitivePaths 來指定索引鍵的路徑部分會區分大小寫。 預設值是不區分大小寫。

如需其他選項,請參閱 OutputCachePolicyBuilder 類別。

快取重新驗證

快取重新驗證表示伺服器可以傳回 304 Not Modified HTTP 狀態碼,而不是完整的回應主體。 此狀態碼會通知用戶端,對要求的回應與先前收到的用戶端沒有變更。

下列程式碼說明如何使用 Etag 標頭來啟用快取重新驗證。 如果用戶端傳送具有先前回應之 etag 值的 If-None-Match 標頭,且快取專案是全新的,則伺服器會傳回 304 未修改,而不是完整回應:

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

}).CacheOutput();

執行快取重新驗證的另一種方式是檢查快取專案建立的日期,與用戶端所要求的日期相比較。 當提供要求標頭 If-Modified-Since 時,如果快取的專案較舊且未過期,輸出快取會傳回 304。

快取重新驗證是自動的,以回應從用戶端傳送的這些標頭。 除了啟用輸出快取之外,伺服器上不需要任何特殊設定才能啟用此行為。

使用標記收回快取專案

您可以使用標記來識別端點群組,並收回群組的所有快取專案。 例如,下列程式碼會建立一組端點,其 URL 開頭為「blog」,並將它們標記為「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")); ;

指派相同端點組標記的替代方式,是定義套用至開頭為 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));
});

另一個替代方法是呼叫 MapGroup

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

在上述標籤指派範例中,這兩個端點都是由 tag-blog 標記來識別。 然後,您可以使用參考該標記的單一陳述式來收回這些端點的快取專案:

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

使用此程式碼,傳送至 https://localhost:<port>/purge/tag-blog 的 HTTP POST 要求將會收回這些端點的快取專案。

您可能想要一種方式來收回所有端點的所有快取專案。 若要這樣做,請建立所有端點的基底原則,如下列程式碼所示:

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

此基底原則可讓您使用「全部標記」標籤來收回快取中的所有專案。

停用資源鎖定

根據預設,會啟用資源鎖定,以降低快取踩踏和驚群效應的風險。 如需詳細資訊,請參閱輸出快取

若要停用資源鎖定,請在建立原則時呼叫 SetLocking(false),如下列範例所示:

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

下列範例會選取端點的無鎖定原則:

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

限制

下列 OutputCacheOptions 屬性可讓您設定適用于所有端點的限制:

  • SizeLimit - 快取儲存體的大小上限。 達到此限制時,在收回較舊的專案之前,不會快取任何新的回應。 預設值為 100 MB。
  • MaximumBodySize - 如果回應本文超過此限制,則不會快取。 預設值為 64 MB。
  • DefaultExpirationTimeSpan - 原則未指定時套用的到期時間持續時間。 預設值為 60 秒。

快取儲存體

IOutputCacheStore 用於儲存體。 根據預設,它會與 MemoryCache 搭配使用。 不建議 IDistributedCache 搭配輸出快取使用。 IDistributedCache 沒有標記所需的不可部分完成功能。 建議您使用直接相依性來建立自訂 IOutputCacheStore 實作,例如 Redis。 或使用 .NET 8 中 Redis 快取的內建支援

另請參閱