這天在追查為何 A 客戶的訂單會出貨到 B 客戶哪裡去? 於是我看到了下面這段程式碼:
在 Login 的 Action 上面被加了 OutputCacheAttribute,Duration 屬性被設成 3,其中還用到了 VaryByParam
屬性,我猜寫這段 Code 的工程師應該是想要為每個登入的帳號做 OutputCache,這樣設定沒有問題,問題出在 HTTP Request 的發送內容。
首先我們得了解 VaryByParam 的實際作用是什麼? 在官方文件的說明是這樣的:
在使用 OutputCache 的時候,我們可以指定 VaryByParam 屬性,來更準確地控制 OutputCache 的內容,它可以是以下三種值:
- *:QueryString 或 POST 的參數不同,就會建立不同版本的 Cache 內容。
- none:永遠不會建立不同版本的 Cache 內容。
- 以分號(;)隔開的參數清單:根據參數清單內的參數,建立不同版本的 Cache 內容。
ContentType 的差異
我用一個範例來說明,我有一個 Login Action,有兩個參數分別是 memberAccount
及 memberPassword
,我想要讓 Login Action 的回傳內容快取 10 分鐘,於是我就加上 OutputCacheAttribute,Duration 指定為 600,還附加 VaryByParam 指定為 memberAccount;memberPassword,防止快取的內容受到其他參數的干擾。
一般我們用 jQuery.ajax() POST HTTP Request 大都這樣寫,這沒毛病。
function login() {
$.ajax({
url: "/Test/Login",
type: "POST",
data: { memberAccount: $("#memberAccount").val(), memberPassword: $("#memberPassword").val() }
}).done(function (data, textStatus, jqXHR) {
console.log(data);
});
}
OutputCache 也完全作用,沒有問題。
另一位工程師說,他 Content 想改 JSON 格式,於是他就直接動手了。
function login() {
$.ajax({
url: "/Test/Login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ memberAccount: $("#memberAccount").val(), memberPassword: $("#memberPassword").val() })
}).done(function (data, textStatus, jqXHR) {
console.log(data);
});
}
這時候就會發現,無論 memberAccount 及 memberPassword 的值怎麼改,回應的內容停在第一次 Cache 的結果。
這邊就是我覺得 OutputCache 不盡善盡美的地方,我們在使用 VaryByParam 指定參數清單的時候,OutputCache 需要根據參數清單去取得參數的值拿來產生 Cache Key,那 OutputCache 會從哪裡去取得參數的值? 答案是 Request.QueryString
及 Request.Form
,可是我們如果把 ContentType 改用 application/json,Content 改用 JSON 格式的話,Request.QueryString 及 Request.Form 會是空集合,這當然就取不到參數值來產生 Cache Key 了,Cache Key 就會都是一樣的,Cache Key 既然一樣,那麼 Cache 的內容當然也一樣了。
其實我認為 OutputCache 是可以根據 ContentType 去解析 Content 取得參數值的,但是它沒有做,不過我們也不必失望,我們可以用 VaryByCustom
來解決我們的問題,我附加 VaryByCustom 並且將值指定為 JsonParam:memberAccount;memberPassword
。
接著到 Global.asax.cs
覆寫 GetVaryByCustomString()
方法
這樣我們發送 JSON 格式的 Content,OutputCache 照樣可以 Work。
最後,我認為像 Login 這類型做登入檢查、安全驗證的 Action,最好還是不要用 OutputCache。