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 設定決定快取行為
  • VaryByContentEncodingVaryByHeaderVaryByParamVaryByCustom
    依內容編碼、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
快取
未指定publicYYY
location="Any"publicYYY
location="ServerAndClient"privateYYN
location="Downstream"publicNYY
location="Client"privateNYN
location="None"no-cachePragma: no-cache,
Expires: -1
NNN
location="Server"no-cachePragma: no-cache,
Expires: -1
YNN
noStore ="true"public, no-store, max-age=0NNN

透過 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 一次設足以因應瀏覽器行為差異。 現今瀏覽器規格已不若當年混亂,應已無此需要,但老人習慣難改,呵。

Post a comment