ASP.NET OutputCache 快取行為深入觀察
2 |
ASP.NET WebForm 及 ASP.NET MVC 支援 OutputCache 機制,針對不會頻繁變化的 aspx 或 MVC Action,可將輸出結果快取在伺服器端重複使用,同時還能控制瀏覽器的快取行為,藉此提高系統負載量。而 OutputCache 所提供的 Location、NoStore 等參數,將如何影響 HTTP Cache-Control Header 及伺服器快取行為?即是本篇文章要探討的重點,我們將透過簡單的實驗深入觀察 ASP.NET 輸出快取行為。
我們先建一個 ASP.NET MVC 專案,在 Home/Index Action 傳回執行當下分、秒數,測試時間隔兩秒呼叫兩次,比對傳回數字即可判斷回應為即時產生或來自伺服器快取:
using System.Web.Mvc;
namespace OutputCacheTest.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return Content($"{DateTime.Now:mmss}");
}
}
}
接著是測試程式,PowerShell 產生包含隨機參數的 URL,相隔一秒存取同一 URL,觀察傳回內容及 Cache 相關 Header:
$url = "http://localhost/OutputCacheTest/?rnd=$([System.DateTime]::Now.ToString("mmssfff"))"
function RunTest {
$resp = (Invoke-WebRequest -Uri $url)
Write-Host " > $($resp.Content)" -ForegroundColor Yellow
$resp.Headers.Keys | Where-Object { !$_.StartsWith('X-') } | ForEach-Object {
Write-Host " # $($_): $($resp.Headers[$_])" -ForegroundColor Gray
}
}
Write-Host "URL = $url" -ForegroundColor White
Write-Host "First Request" -ForegroundColor Cyan
RunTest
Write-Host
Start-Sleep -Seconds 1
Write-Host "Second Request" -ForegroundColor Cyan
RunTest
在預設情況下,ASP.NET 預設的快取政策為 Cache-Control: private,允許瀏覽器快取其內容,但伺服器端沒有快取,二次取得分秒數不同:
接著我們來觀察 OutputCache 的效果。
ASP.NET MVC 提供 OutputCacheAttribute 可指定快取行為 ( 若要用在 ASP.NET 則是寫成 <%@ OutputCache Duration="60" VaryByParam="None"%>
參考 )。OutpuCacheAttribute 有以下屬性可控制快取行為:
- Duration 快取保留時間(以秒為單位)
- Location 快取儲存位置 參考
Any - 可任意快取,包含瀏覽器、Proxy 及 ASP.NET 所在伺服器
Client - 只可快取在瀏覽器用戶端
Downstream - 可被快存在瀏覽器及 Proxy,但 ASP.NET 端不做快取
None - 停用 OutputCache
Server - 只在 ASP.NET 端快取,禁止瀏覽器及 Proxy 快取
ServerAndClient - 只會快取在 ASP.NET 伺服器及瀏覽器,不包含 Proxy - NoStore 不允許被快取
- ProfileName 由 config 設定決定快取行為
- VaryByContentEncoding、VaryByHeader、VaryByParam、VaryByCustom
依內容編碼、Header、Form & Query 參數或客製化條件產生多組快取內容。
【延伸閱讀】
* Output Caching in MVC
* ASP.NET MVC 針對電腦與手機提供不同 OutputCache
* 克服 VaryByParam 不支援 JSON 形式參數問題 by 軟體廚房
將 Duration 等設定寫死在 [OutputCache(Duration = 300... )] 的缺點是同一政策常會套用在多個 Action,搞出一大堆噁心的複製貼上;二則快取策略常需要滾動式修正,若調整需要改程式碼重新編譯上版,有點自找麻煩。因此,實務上較常見的做法是寫成 [OutputCache(CacheProfile = "CacheProfileName")] 指定採用的 OutputCacheProfile,而 OutputCacheProfile 則在 web.config system.web/caching 定義,要調整時改 config 即可:
<system.web>
<caching>
<outputCache enableOutputCache="true" />
<outputCacheSettings>
<outputCacheProfiles>
<add name="StdCachePolicy" duration="300"></add>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>
先看最基本的五分鐘快取設定 duration="300",其餘參數省略,當成對照組。
如上圖,快取控制 Header 為 Cache-Control: public, max-age=300,第二次為 max-age=298,而 Last-Modified 是 MVC Action 實際執行時間,Expires 則為過期時間,等於 Last-Modified 加 5 分鐘,兩次的 Last-Modified 與 Expires 值相同。(關於 Last-Modified、max-age 的觀察可參考 IIS HTML 檔 Cache 行為觀察)
實驗一 duration="300" location="Any"
兩次 Cache-Control 分別為 public, max-age=300 及 public, max-age=298,傳回內容相同,表示沿用伺服器端快取內容
實驗二 duration="300" location="Client"
兩次 Cache-Control 都是 private, max-age=300,兩次傳回內容不同,未使用伺服器端快取內容
實驗三 duration="300" location="Downstream"
兩次 Cache-Control 均為 public, max-age=300,兩次傳回內容不同,未使用伺服器端快取內容
實驗四 duration="300" location="None"
兩次 Cache-Control 均為 no-cache,並多了 Pragma: no-cache, Expires: -1,兩次傳回內容不同,未使用伺服器端快取內容
實驗五 duration="300" location="Server"
兩次均為 Cache-Control: no-cache, Pragma: no-cache, Expires: -1,兩次傳回內容相同,表示沿用伺服器端快取內容
實驗六 duration="300" location="ServerAndClient"
兩次 Cache-Control 分別為 private, max-age=300 及 private, max-age=298,兩次傳回內容相同,表示沿用伺服器端快取內容
實驗七 duration="0" noStore="true"
兩次均為 Cache-Control: public, no-store, max-age=0,兩次傳回內容不同,未使用伺服器端快取內容
將觀察結果歸納如以下:
參數 | Cache-Control | 其他 Header | 伺服器端 快取 | 瀏覽器 快取 | Proxy 快取 |
---|---|---|---|---|---|
未指定 | public | Y | Y | Y | |
location="Any" | public | Y | Y | Y | |
location="ServerAndClient" | private | Y | Y | N | |
location="Downstream" | public | N | Y | Y | |
location="Client" | private | N | Y | N | |
location="None" | no-cache | Pragma: no-cache, Expires: -1 | N | N | N |
location="Server" | no-cache | Pragma: no-cache, Expires: -1 | Y | N | N |
noStore ="true" | public, no-store, max-age=0 | N | N | N |
透過 location,我們可以控制要不要啟用伺服器端快取,Cache-Control 要設 public、private 還是 no-cache,如要 no-store 則是使用 noStore="true" 控制。
另外,設定 <outputCache enableOutputCache="false"></outputCache>
會停用 OutputCache 機制,形同未宣告 [OutputCache()] 時的結果。(如第一張圖,Cache-Control: private)
最後補充 VaryByParam 參數,實測發現雖然 web.config 可以設定 varyByParam,但不會發揮作用,寫 <add name="StdCachePolicy" duration="300" varByParam="None"></add>
,?rnd= 參數不同時無法沿用伺服器端的 Cache,但若將 Home/Index Action 改成 '''[OutputCache(CacheProfile = "StdCachePolicy", VaryByParam = "None")]''' 之後,如下圖所示,兩次 ?rnd= 參數不同但仍讀到同一份 OutputCache 內容。
了解上述規則,我們就能更精準控制 ASP.NET 快取行為囉~~
A series of experiments to demostrate the behavior of ASP.NET MVC OutputCache.
Comments
# by William
請教一下, 不允許快取就是設成 NoStore=true, 那還有必要再設duration="0" 嗎? 因為NoStore=true看起來已經很明確了,還是說是為了避免在NoStore=true的情況下, duration被設成非0而造成困惑, 才特別再去設duration="0",是這樣嗎?
# by Jeffrey
to William, duration="0" 應該算多餘的。會這麼寫沿自 ASPX 古老時代https://blog.darkthread.net/blog/tips-uncachable-asp-net-page/ Response.Cache.SetCacheability(HttpCacheability.NoCache) 概念,把 Header的Cache-Control, Pragma, Expires 一次設足以因應瀏覽器行為差異。 現今瀏覽器規格已不若當年混亂,應已無此需要,但老人習慣難改,呵。