Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。
Warning
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。
この記事では、ASP.NET Core アプリで出力キャッシュ ミドルウェアを構成する方法について説明します。 出力キャッシュの概要については、「 出力キャッシュ」を参照してください。
出力キャッシュ ミドルウェアは、Minimal API のすべての種類の ASP.NET Core アプリで使用できます。 コントローラー付きWeb API、MVC、および Razor Pages。 最小限の API とコントローラー ベースの API のコード例を示します。 コントローラーベースの API の例では、属性を使用してキャッシュを構成する方法を示します。 これらの属性は、MVC および Razor Pages アプリでも使用できます。
コード例は、イメージを生成し、"生成日時" を提供する Gravatar クラス を参照しています。 クラスは定義され、 サンプル アプリでのみ使用されます。 その目的は、キャッシュされた出力がいつ使用されているかを簡単に確認できるようにすることです。 詳細については、「サンプルディレクティブとプリプロセッサディレクティブをダウンロードする方法」を参照してください。
ミドルウェアをアプリに追加する
AddOutputCache メソッドを呼び出して、出力キャッシュ ミドルウェアをサービス コレクションに追加します。 例えば次が挙げられます。
builder.Services.AddOutputCache();
UseOutputCache メソッドを呼び出して、ミドルウェアを要求処理パイプラインに追加します。 例えば次が挙げられます。
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseOutputCache();
app.UseAuthorization();
AddOutputCacheメソッドとUseOutputCacheメソッドを呼び出してもキャッシュ動作は開始されません。これにより、キャッシュが使用可能になります。 アプリのキャッシュ応答を行うには、次のセクションで説明するようにキャッシュを構成する必要があります。
Note
-
クロス オリジン要求 (CORS) ミドルウェアを使用するアプリでは、
UseOutputCacheメソッドの後に UseCors メソッドを呼び出す必要があります。 -
Razor Pages アプリとコントローラーを使用するアプリでは、
UseOutputCacheメソッドの後にUseRoutingメソッドを呼び出す必要があります。
1 つのエンドポイントまたはページを構成する
最小 API アプリの場合は、次の例に示すように、 CacheOutput メソッドを呼び出すか、[OutputCache] 属性を適用してキャッシュを実行するようにエンドポイントを構成します。
app.MapGet("/cached", Gravatar.WriteGravatar).CacheOutput();
app.MapGet("/attribute", [OutputCache] (context) =>
Gravatar.WriteGravatar(context));
コントローラーがあるアプリの場合は、次のコードに示すように、 [OutputCache] 属性をアクション メソッドに適用します。
[ApiController]
[Route("/[controller]")]
[OutputCache]
public class CachedController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
Razor Pages アプリの場合は、Razor ページ クラスにこの属性を適用します。
複数のエンドポイントまたはページを構成する
メソッドを呼び出すときにAddOutputCacheを作成して、複数のエンドポイントに適用されるキャッシュ構成を指定します。 ポリシーは特定のエンドポイントに対して選択できますが、基本ポリシーはエンドポイントのコレクションに対して既定のキャッシュ構成を指定します。
次の強調表示されたコードでは、有効期限が 10 秒の状態で、すべてのアプリのエンドポイントのキャッシュが構成されます。 有効期限を指定しない場合、既定値は 1 分です。
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)));
});
次の強調表示されたコードは 2 つのポリシーを作成し、それぞれが異なる有効期限を指定します。 選択されたエンドポイントは 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] 属性を使用して、エンドポイントのポリシーを選択できます。
最小 API アプリでは、次のコードは、20 秒の有効期限と 30 秒の有効期限を持つ 1 つのエンドポイントを構成します。
app.MapGet("/20", Gravatar.WriteGravatar).CacheOutput("Expire20");
app.MapGet("/30", [OutputCache(PolicyName = "Expire30")] (context) =>
Gravatar.WriteGravatar(context));
コントローラーを備えたアプリの場合、アクション メソッドに [OutputCache] 属性を適用してポリシーを選択します。
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "Expire20")]
public class Expire20Controller : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
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);
});
また、エンドポイントの名前付きポリシーを選択します。 次のコードは、Minimal API アプリのエンドポイントのカスタム ポリシーを選択します。
app.MapPost("/cachedpost", Gravatar.WriteGravatar)
.CacheOutput("CachePost");
次のコードは、コントローラーのアクションに対して同じことを行います。
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "CachePost")]
public class PostController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
代替の既定のポリシーオーバーライドを使用する
または、依存関係挿入 (DI) を使用して、カスタム ポリシー クラスに次の変更を加えてインスタンスを初期化します。
- プライベート コンストラクターの代わりにパブリック コンストラクターを使用します。
- カスタム ポリシー クラスの
Instanceプロパティを削除します。
例えば次が挙げられます。
public sealed class MyCustomPolicy2 : IOutputCachePolicy
{
public MyCustomPolicy2()
{
}
クラスの残りの部分は、前に示したのと同じです。 次の例に示すように、カスタム ポリシーを追加します。
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("CachePost", builder =>
builder.AddPolicy<MyCustomPolicy2>(), true);
});
前述のコードは、DI を使用してカスタム ポリシー クラスのインスタンスを作成します。 コンストラクター内のすべてのパブリック引数が解決されます。
基本ポリシーとしてカスタム ポリシーを使用する場合は、 OutputCache() メソッド (引数なし) を呼び出したり、基本ポリシーを適用する必要があるエンドポイントで [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 ポリシーを選択できます。 最小 API アプリでは、次のコードは、VaryByQueryクエリ文字列の一意の値ごとにのみ一意の応答を返すエンドポイントのculture ポリシーを選択します。
app.MapGet("/query", Gravatar.WriteGravatar).CacheOutput("Query");
次のコードは、コントローラーのアクションに対して同じことを行います。
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "Query")]
public class QueryController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
キャッシュ キーを制御するためのオプションの一部を次に示します。
SetVaryByQuery メソッドは、キャッシュ キーに追加する 1 つ以上のクエリ文字列名を指定します。
SetVaryByHeader メソッドは、キャッシュ キーに追加する 1 つ以上の HTTP ヘッダーを指定します。
VaryByValue メソッドは、キャッシュ キーに追加する値を提供します。 次の例では、現在の秒単位でのサーバー時刻が奇数か偶数かを示す値を使用します。 新しい応答は、秒数が奇数から偶数の値に変わった場合、またはその逆の場合にのみ生成されます。
builder.Services.AddOutputCache(options => { options.AddBasePolicy(builder => builder .With(c => c.HttpContext.Request.Path.StartsWithSegments("/blog")) .Tag("tag-blog")); options.AddBasePolicy(builder => builder.Tag("tag-all")); options.AddPolicy("Query", builder => builder.SetVaryByQuery("culture")); options.AddPolicy("NoCache", builder => builder.NoCache()); options.AddPolicy("NoLock", builder => builder.SetLocking(false)); options.AddPolicy("VaryByValue", builder => builder.VaryByValue((context) => new KeyValuePair<string, string>( "time", (DateTime.Now.Second % 2) .ToString(CultureInfo.InvariantCulture)))); });
OutputCacheOptions.UseCaseSensitivePaths プロパティを使用して、キーのパス部分で大文字と小文字が区別されることを指定します。 既定値では大文字と小文字が区別されません。
他のオプションについては、OutputCachePolicyBuilder クラスを確認してください。
キャッシュの再有効化を有効にする
キャッシュ再検証は、サーバーが完全な応答本文ではなく 、304 未変更 HTTP 状態コードを返すことができるという意味です。 この状態コードは、要求に対する応答が以前にクライアントが受信したものと変わらないことをクライアントに通知します。
次のコードは、 ETag ヘッダーを使用してキャッシュの再検証を有効にする方法を示しています。 クライアントが以前の応答からの値を持つ ETag ヘッダーを送信し、キャッシュ エントリが新しい場合、サーバーは完全な応答ではなく 304 Not Modified コードを返します。
次のコードは、最小 API アプリのポリシーの ETag 値を設定します。
app.MapGet("/etag", async (context) =>
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(context);
}).CacheOutput();
次のコードは、コントローラー ベースの API でも同じです。
[ApiController]
[Route("/[controller]")]
[OutputCache]
public class EtagController : ControllerBase
{
public async Task GetAsync()
{
var etag = $"\"{Guid.NewGuid():n}\"";
HttpContext.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(HttpContext);
}
}
キャッシュの再検証を行うもう 1 つの方法は、キャッシュ エントリの作成日をクライアントが要求した日付と比較してチェックすることです。 要求ヘッダー If-Modified-Since が指定されると、キャッシュされたエントリが古く、有効期限が切れていない場合、出力キャッシュは 304 コードを返します。
キャッシュの再検証は、クライアントから送信されたこれらのヘッダーに応答して自動的に行われます。 出力キャッシュを有効にする以外に、この動作を有効にするためにサーバーで特別な構成は必要ありません。
タグを使用してキャッシュ エントリを削除する
タグを使用してエンドポイントのグループを識別し、グループのすべてのキャッシュ エントリを削除できます。 たとえば、次の最小 API コードは、テキスト blog で始まり、 tag-blog タグを適用する URL を持つエンドポイントのペアを作成します。
app.MapGet("/blog", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog"));
app.MapGet("/blog/post/{id}", Gravatar.WriteGravatar)
.CacheOutput(builder => builder.Tag("tag-blog"));
次のコードは、コントローラーベースの API でエンドポイントにタグを割り当てる方法を示しています。
[ApiController]
[Route("/[controller]")]
[OutputCache(Tags = new[] { "tag-blog", "tag-all" })]
public class TagEndpointController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
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));
});
最小 API アプリのもう 1 つの方法は、 MapGroup メソッドを呼び出す方法です。
var blog = app.MapGroup("blog")
.CacheOutput(builder => builder.Tag("tag-blog"));
blog.MapGet("/", Gravatar.WriteGravatar);
blog.MapGet("/post/{id}", Gravatar.WriteGravatar);
前述のタグ割り当ての例では、両方のエンドポイントが tag-blog タグによって識別されます。 その後、次のようにそのタグを参照する 1 つのステートメントを使用して、これらのエンドポイントのキャッシュ エントリを削除できます。
app.MapPost("/purge/{tag}", async (IOutputCacheStore cache, string tag) =>
{
await cache.EvictByTagAsync(tag, default);
});
このコードでは、 https://localhost:<port>/purge/tag-blog URL に送信された 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));
});
この基本ポリシーを使用すると、 tag-all タグを使用してキャッシュ内のすべてを削除できます。
リソース ロックを無効にする
既定では、キャッシュ スタンピードやサンダリングハードのリスクを軽減するために、リソース ロックが有効になっています。 詳細については、「 出力キャッシュ」を参照してください。
リソース ロックを無効にするには、次の例に示すように、ポリシーの作成時に 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));
});
次の例では、最小 API アプリのエンドポイントのロックなしポリシーを選択します。
app.MapGet("/nolock", Gravatar.WriteGravatar)
.CacheOutput("NoLock");
コントローラーベースの API では、属性を使用してポリシーを選択します。
[ApiController]
[Route("/[controller]")]
[OutputCache(PolicyName = "NoLock")]
public class NoLockController : ControllerBase
{
public async Task GetAsync()
{
await Gravatar.WriteGravatar(HttpContext);
}
}
制限を設定する
OutputCacheOptions クラスの次のプロパティを使用すると、すべてのエンドポイントに適用される制限を構成できます。
- SizeLimit プロパティは、キャッシュ ストレージの最大サイズを設定します。 制限に達すると、古いエントリが削除されるまで、新しい応答はキャッシュされません。 既定値は 100 MB です。
- MaximumBodySize プロパティは、応答本文の最大サイズを設定します。 応答本文が制限を超えた場合、キャッシュされません。 既定値は 64 MB です。
- DefaultExpirationTimeSpan プロパティは、ポリシーで時刻が指定されていない場合に応答がキャッシュされる最大期間を設定します。 既定値は 60 秒です。
キャッシュ ストレージ のオプションを調べる
IOutputCacheStore インターフェイスはストレージに使用されます。 既定では、 MemoryCache クラスで使用されます。 キャッシュされた応答はインプロセスで格納されるため、各サーバーには、サーバー プロセスが再起動されるたびに失われる個別のキャッシュがあります。
代替手段: Redis Cache
別の方法として、 Redis Cache を使用します。 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=,pw,ssl=True,abortConnect=False。(省略可能) オプション。InstanceName プロパティは、キャッシュの論理パーティションを指定します。
構成オプションは、 Redis ベースの分散キャッシュ オプションと同じです。
非推奨: IDistributedCache
IDistributedCache インターフェイスは、出力キャッシュで使用することはお勧めしません。 このインターフェイスでは、タグ付けに必要なアトミック機能は提供されません。
推奨される方法は、Redis の組み込みサポートを使用するか、基になるストレージ メカニズムへの直接依存関係を使用してカスタム IOutputCacheStore 実装を作成することです。
関連するコンテンツ
この記事では、ASP.NET Core アプリで出力キャッシュ ミドルウェアを構成する方法について説明します。 出力キャッシュの概要については、「 出力キャッシュ」を参照してください。
出力キャッシュ ミドルウェアは、すべての種類の ASP.NET Core アプリ (Minimal API、コントローラーを使用した Web API、MVC、Razor Pages) で使用できます。 サンプル アプリは Minimal API ですが、示されているすべてのキャッシュ機能は、他のアプリの種類でもサポートされています。
ミドルウェアをアプリに追加する
AddOutputCache を呼び出すことで、出力キャッシュ ミドルウェアをサービス コレクションに追加します。
要求処理パイプラインにミドルウェアを追加するには、UseOutputCache を呼び出します。
Note
-
CORS ミドルウェアを使用するアプリでは、
UseOutputCache後にUseCorsを呼び出す必要があります。 -
Razor Pages アプリとコントローラーを使用したアプリでは、
UseOutputCacheはUseRoutingの後に呼び出す必要があります。 -
AddOutputCacheとUseOutputCacheを呼び出してもキャッシュ動作は開始されません。これはキャッシュを使用可能にします。 応答データのキャッシュは、次のセクションに示すように構成する必要があります。
1 つのエンドポイントまたはページを構成する
最小 API アプリの場合は、次の例に示すように、 CacheOutputを呼び出すか、 [OutputCache] 属性を適用してキャッシュを実行するようにエンドポイントを構成します。
app.MapGet("/cached", Gravatar.WriteGravatar).CacheOutput();
app.MapGet("/attribute", [OutputCache] (context) =>
Gravatar.WriteGravatar(context));
コントローラーを使用したアプリの場合は、[OutputCache] 属性をアクション メソッドに適用します。
Razor Pages アプリの場合は、Razor ページ クラスにこの属性を適用します。
複数のエンドポイントまたはページを構成する
を呼び出すときの "ポリシー" を作成して、複数のエンドポイントに適用されるキャッシュ構成を指定します。AddOutputCache ポリシーは特定のエンドポイントに対して選択できますが、基本ポリシーはエンドポイントのコレクションに対して既定のキャッシュ構成を指定します。
次の強調表示されたコードは、10 秒の有効期限で、アプリのエンドポイントのすべてのキャッシュを構成します。 有効期限を指定しない場合、既定値は 1 分です。
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)));
});
次の強調表示されたコードは 2 つのポリシーを作成し、それぞれが異なる有効期限を指定します。 選択されたエンドポイントは 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.AddPolicy("CachePost", 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 - キャッシュ キーに追加する 1 つ以上のクエリ文字列名を指定します。
SetVaryByHeader - キャッシュ キーに追加する 1 つ以上の 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 Not Modified を返します。
app.MapGet("/etag", async (context) =>
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await Gravatar.WriteGravatar(context);
}).CacheOutput();
キャッシュの再検証を行うもう 1 つの方法は、キャッシュ エントリの作成日をクライアントが要求した日付と比較してチェックすることです。 要求ヘッダー 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));
});
もう 1 つの代替方法は次のように MapGroup を呼び出すことです。
var blog = app.MapGroup("blog")
.CacheOutput(builder => builder.Tag("tag-blog"));
blog.MapGet("/", Gravatar.WriteGravatar);
blog.MapGet("/post/{id}", Gravatar.WriteGravatar);
前述のタグ割り当ての例では、両方のエンドポイントが tag-blog タグによって識別されます。 その後、次のようにそのタグを参照する 1 つのステートメントを使用して、これらのエンドポイントのキャッシュ エントリを削除できます。
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));
});
この基本ポリシーを使用すると、"tag-all" タグを使用してキャッシュ内のすべてを削除できます。
リソース ロックを無効にする
既定では、キャッシュ スタンピードやサンダリングハードのリスクを軽減するために、リソース ロックが有効になっています。 詳細については、「 出力キャッシュ」を参照してください。
リソース ロックを無効にするには、次の例に示すように、ポリシーの作成時に 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");
Limits
OutputCacheOptions の次のプロパティを使用すると、すべてのエンドポイントに適用される制限を構成できます。
- SizeLimit - キャッシュ ストレージの最大サイズ。 この制限に達すると、古いエントリが削除されるまで、新しい応答はキャッシュされません。 既定値は 100 MB です。
- MaximumBodySize - 応答本文はこの制限を超えた場合、キャッシュされません。 既定値は 64 MB です。
- DefaultExpirationTimeSpan - ポリシーで指定されていない場合に適用される有効期限の長さ。 既定値は 60 秒です。
キャッシュ ストレージ
IOutputCacheStore はストレージに対して使用されます。 既定では、これは MemoryCache と共に使用されます。
IDistributedCache を出力キャッシュで使用することはお勧めしません。
IDistributedCache にはタグ付けに必要なアトミック機能がありません。 Redis などの基になるストレージ メカニズム上の直接の依存関係を使用することで、カスタムの IOutputCacheStore の実装を作成することをお勧めします。 または、 .NET 8 の Redis Cache の組み込みサポートを使用します。
こちらも参照ください
ASP.NET Core