しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core で静的コンテンツのキャッシュを適切に行う

ASP.NET Core で静的コンテンツを配信する時に、デフォルトだと挙動が厄介なので設定を変更した方が効率的という話です。地味にはまったのでメモとして残します。

TL;DR

  • Static Files Middleware で Cache-Control を付ける
  • 更新されるファイルには asp-append-version = true を指定する

デフォルトの挙動

ASP.NET Core プロジェクトを作成した直後の状態では、静的コンテンツに対して Cache-Control や Expires を Static Files Middleware は付けてくれません。

f:id:shiba-yan:20171128203754p:plain

しかし、ETag や Last-Modified は付いているので 304 を返す処理は正しく動作します。

f:id:shiba-yan:20171128203803p:plain

Cache-Control が付いていない場合の処理は、大体のブラウザーでは徐々にコンテンツへの検証時間を延ばすという、サーバー側で制御できない処理が実行されるみたいです。

Stack Overflow に参考になる情報が載ってました。これではキャッシュが残ってしまって困ります。

ファイルが更新された直後は割と頻繁に検証リクエストが飛んでくるので、表示時間もかかるという状態でした。デフォルトがこの挙動なのは地味にはまりそうです。

Cache-Control を付ける

StaticFiles のデフォルトではキャッシュの有効期限に関係するヘッダーが出力されないので、Cache-Control を出力する処理を追加して対応します。公式ドキュメントにも書いてある方法です。

IIS の staticContent に比べると手間が増えていまいちな感じがしますが、今のところ OnPrepareResponse で付けるしか方法がないみたいです。ドキュメントの通りに Append を使うとビルドエラーになるので注意。

一応 HttpContext とファイル情報が取れるようになってるので、ファイルの種類別やディレクトリを条件にして TTL を変えることも出来なくはなさそうです。雑多なコードになりそうですが。

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseStaticFiles(new StaticFileOptions
    {
        OnPrepareResponse = ctx => ctx.Context.Response.Headers.Add("Cache-Control", "public, max-age=600")
    });
}

とりあえずこれで実行すると、ちゃんと Cache-Control が付くようになります。

f:id:shiba-yan:20171128205439p:plain

max-age = 600 にしたので 10 分間はリクエストを投げることなくキャッシュが使われます。

asp-append-version = true を指定する

Cache-Control では出来るだけ長い時間を指定したいので、更新されることが分かっているファイルには asp-append-version を設定して、クエリ文字列としてファイルのハッシュを付けます。

ファイルの内容が変わったときには当然ハッシュが変わるので、キャッシュが無効になります。

<script src="~/js/site.js" asp-append-version="true"></script>

実行してみると、ちゃんとハッシュがクエリ文字列として付いています。

f:id:shiba-yan:20171128205337p:plain

これで TTL を長くしても、更新されたタイミングでちゃんとファイルを取り直してくれるので安心です。

ハッシュ計算のオーバーヘッドは存在しますが、ハッシュの計算は初回だけで後はキャッシュ + ファイルの変更通知を利用しているので最小限で済むようになっています。