【ASP】キャッシュを有効にしつつ、cssやjsファイルの変更を確実に反映させる(Cache Busting)

# はじめに
お仕事で、レガシーASPによる既存アプリケーションの改修作業があり、お客様の方でユーザーテストを実施してもらったのですが、キャッシュが効いてしまい表示が正しく変更されなかったわけです。
ブラウザのキャッシュを削除してもらって正しく表示されたのですが、やはり対応策を求められました。

改修作業自体は何年も前から何度か行っているので、同じようなことは発生していたと思われるのですが、目についてしまったということでしょうか。

# 対応策
## Cache Busting
ネットで検索すると、クエリー文字列を付加することでキャッシュを使われずに対応できることが分かりました。英語では「Cache Busting」と呼ばれるようです。

```html
<script src="./jquery.js?v=xxxxx"></script>
```

何故、クエリー付加させたらキャッシュクリアになるのかは下記サイトを見てください。
参照：[CSSファイルやJavaScriptファイルを読み込むときの末尾にあるクエリー文字列は何のためにあるか](http://www.koikikukan.com/archives/2011/08/29-015555.php)

## クエリー文字列に付加させる情報
付加するクエリー文字列をどうするのか？
候補として下記4点を列挙しました。

* ファイル更新日時 (例：src="./jquery.js?date=20160821170230")
* バージョン(例：src="./jquery.js?ver=3410")
* 現在日時
* ランダム数・ハッシュ値

### ファイル更新日時
同僚と相談した結果、ファイル更新日時が良いのではないかということで、下記サイト(PHP版)を参考にASP版を作成しました。
参照：[キャッシュを有効にしつつ、cssやjsファイルの変更を確実に反映させる](http://doop-web.com/blog/archives/1182)

```vb
Function filedate(filename)
    Dim fso,file,dtm

    Set fso = CreateObject("Scripting.FileSystemObject")
    Set file = fso.GetFile(Server.MapPath(filename))
    dtm = file.DateLastModified
    Set fso = Nothing
    Set file = Nothing

    'yyyy/mm/dd hh:mm:ss を yyyymmddhhmmss に変換
    filedate = filename & "?date=" & Replace(Replace(Replace(dtm, "/", ""), ":", ""), " ", "")

End Function
```

使用例　filedate関数にスクリプトファイルをインライン展開で設定する

```
<script type="text/javascript" src="<%=filedate("../script/test.js")%>" charset="utf-8"></script>
```

せっかく作ったわけですが最終的には、ファイル更新日時は却下しました。これは既存アプリケーションの作りがあまり良くなく、同じファイルの読み出しが複数点在しており、その度にファイルオブジェクトを使用するのが気になったからです。

ちなみにASP.NET版も考えたので記載しておきます。

```vb:MyUtil.vb
public shared function GetFileDate(ByVal path As string)
	return path & "?date=" & System.IO.File.GetLastWriteTime(HttpContext.Current.Server.MapPath(path)).ToString("yyyyMMddHHmmss")
end function
```

```
<%@ Import Namespace="MyUtil" %>
<LINK rel="stylesheet" type="text/css" href="<%=GetFileDate("stylesheet.css")%>" title="Style">
```

### バージョン
結局、必ず読み込む共通ファイルにバージョン番号をセットするGetVersion関数を作成し、インライン展開で設定するようにしました。
関数にしたのは、開発時にはバージョン番号の代わりに現在日時を返すようにすれば毎回変更されるように出来るからです。

```
<script type="text/javascript" src="../script/test.js?Ver="<%=GetVersion()%>" charset="utf-8"></script>
```

### 現在日時
単なる日付であれば、バージョンと仕組み的には一緒なんですが、現在日時(Now()を使う)となると読み込む度に毎回クエリー文字列が変わるわけです。
そうすると、変更されていないファイルも毎回読む込むことになるのであまりよろしくないですね。
レガシーASPは日時書式が無いので面倒でやらないけど、ASP.NETは下記サイトにて方法が提示されています。
参照：[[ASP.NET][C#][JavaScript] 特定のファイルをブラウザにキャッシュさせなくしたい - ちょまど帳](https://chomado.com/programming/c-sharp/asp-netc-%E7%89%B9%E5%AE%9A%E3%81%AE%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AB%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%81%95%E3%81%9B/)

### ランダム数・ハッシュ値
これも作り方によっては現在日時と同様に読み込む度に毎回クエリー文字列が変わってしまいます。

ASP.NET MVC4以降では、最適化として「ResolveBundleUrl」が用意されており、毎回違うハッシュ値ではなく、対象のファイルが更新されるとハッシュ値も更新される仕組みになっています。
参照：[JavaScript / CSS ファイルの自動縮小・結合処理: ASP.NET MVC 4 新機能シリーズ](https://blogs.msdn.microsoft.com/chack/2012/03/27/javascript-css-asp-net-mvc-4-12474/)

```
<script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/bundles/jsHello")" type="text/javascript"></script>
```

出力結果

```
<script src="/bundles/jsHello?v=BZGaXsRK3rFdbpY0k8ak1Ls0nJDWwbTUS4hURH1dUIo1" type="text/javascript"></script>
```

# 最後に
キャッシュはいい機能ですが開発する側は面倒だったりする。お客様はそんなこと分からないですから、出来るだけトラブルが起こらないように対策はしておかないとね。

仕事ではレガシーASPが未だ多いので、ASP.NETの「ResolveBundleUrl」は今回調べて初めて知りました。いつか使うときがくるでしょう。

あと、海外の情報を探すにしても、キーワードが分からないと探しにくいですよね、今回「Cache Busting」というキーワードを見つけれたのは良かったです。

# 参照
* [CSSファイルやJavaScriptファイルを読み込むときの末尾にあるクエリー文字列は何のためにあるか](http://www.koikikukan.com/archives/2011/08/29-015555.php)
* [キャッシュを有効にしつつ、cssやjsファイルの変更を確実に反映させる](http://doop-web.com/blog/archives/1182)
* [【ASP.NET MVC】 ASP.NET MVCで、ブラウザに、JavaScript/CSS/画像をキャッシュさせないためには...](http://blogs.yahoo.co.jp/dk521123/35852515.html)
* [JavaScript / CSS ファイルの自動縮小・結合処理: ASP.NET MVC 4 新機能シリーズ](https://blogs.msdn.microsoft.com/chack/2012/03/27/javascript-css-asp-net-mvc-4-12474/)
* [Microsoft.Web.Optimization で JavaScript と CSS の最適化を行う](http://blog.shibayan.jp/entry/20111103/1320331131)