ASP.NET MVC 3 の新機能、グローバルフィルタは地味だけどイケてる - しばやん雑記 で OutputCache の挙動が MVC 3 RC 2 で改善されて、VaryByParam は不要だよ!と言いましたが、実際は VaryByParam = * 相当でかなり残念な感じでした。
しかし、モデルバインダなどでアクションの引数情報は必ず取得しているはずですし、何故こんな挙動になっているのか不思議だったので OutputCache 属性を継承して OutputCacheEx(仮)属性を作成してみました。
// アクションメソッドの引数を自動的にキャッシュキーにする OutputCache 属性 public class OutputCacheExAttribute : OutputCacheAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { // ActionParameters に Key-Value でアクションメソッドの引数が格納されている VaryByParam = string.Join(";", filterContext.ActionParameters.Keys); base.OnActionExecuting(filterContext); } }
え、これだけ??という感が満載ですね。
実際にキャッシュを行っているのは OnResultExecuted メソッド内なので、OnActionExecuting で VaryByParam を設定してみました。ActionExecutingContext.ActionParameters にはアクションメソッドのパラメータが引数名-値で格納されているので、キーだけ取得して結合しただけです。
わざわざパラメータ名を結合して VaryByParam に渡しているのが見るからに非効率ですが、キャッシュの設定を保持するプロパティは internal になっているので仕方ないですね。
では、本当に動作しているのか確認してみましょう。検証用に簡単なコントローラとビューを用意しました。
コントローラ
public class ProductController : Controller { // // GET: /Product/Details/5 // 60 秒間、id のみをキーとしてキャッシュする [OutputCacheEx(Duration = 60)] public ActionResult Details(int id) { ViewBag.Id = id; return View(); } }
ビュー
@{ ViewBag.Title = "Details"; } <h2>Details</h2> <p> Product ID : @ViewBag.Id <br /> Current DateTime : @DateTime.Now.ToString() </p>
もう説明が要らないぐらい簡単なコントローラとビューです。キャッシュされた時間を調べるために、ビューに現在時刻を表示するようにしています。
それでは検証してみましょう、まずは普通に id 付きでアクセスしてみます。
/Product/Details/2
id の値が違うと現在時刻の値が異なっているので、キャッシュキーとして id が使われていることがわかりますね。それではアクションの引数には存在していないパラメータを付けてアクセスしてみます。
/Product/Details/1?p=1
クエリパラメータとして p = 1 を追加しました。MVC 3 RC 2 の OutputCache では p の値もキャッシュキーとして使用されてしまうので現在時刻が変化していましたが、今回作成した OutputCacheEx を使うと id = 1 の時と現在時刻が変わっていないことがわかります。
これで前回のエントリで思っていた通りの挙動が実現できました!
何故 MVC チームが OutputCache の挙動を VaryByParam = * にしようと思ったのかはよく分かりませんが、今回は部分キャッシュ周りでの検証はしていないのでその辺りに深い理由があるのかもしれません。でも、個人的にはこっちの挙動の方が素直だと思うのですが、ケースバイケースですかね…?
さて、フィードバックでもしましょうか…。