在 ASP.NET 所有功能中,緩存對應用程式效能的潛在影響最大。善用緩存可讓您大膽使用高耗能控件(如 GridView、DataGrid)而不必擔心效能瓶頸。本文系統梳理三層緩存策略:頁面輸出緩存、片段/用戶控件緩存、資料層 Cache 物件緩存,並給出生產級的存取模式與配置建議。
最簡單的緩存形式:將完整 HTML 輸出留在記憶體,後續請求直接服務,直到到期。
<%@ OutputCache Duration="60" VaryByParam="*" %>
| 屬性 | 必要 | 說明 |
|---|---|---|
| Duration | 是 | 緩存秒數,正整數 |
| VaryByParam | 是 | 依查詢字串/表單參數建立不同緩存條目;* 為每組參數建獨立條目,none 不變動 |
| Location | 否 | Any|Client|Downstream|Server|ServerAndClient|None,預設 Any |
| VaryByHeader | 否 | 依 HTTP 標頭變動(如 User-Agent、Accept-Language) |
| VaryByCustom | 否 | 自定義變動邏輯,需在 Global.asax 覆寫 GetVaryByCustomString;內建 "browser" 可依瀏覽器名稱/主版本分條目 |
<!-- 產品目錄:依 categoryID 與 page 分頁緩存 1 小時 -->
<%@ OutputCache Duration="3600" VaryByParam="categoryID;page" %>
<!-- 同一 URL 需同時服務桌面版與行動版 -->
<%@ OutputCache Duration="60" VaryByParam="none" VaryByCustom="browser" %>
完整頁面緩存常因「個人化區塊」(如使用者名稱、購物車數量)而不可行。解法:將共用區塊(導覽列、頁尾、側邊欄)封裝為 .ascx 用戶控件 並獨立緩存。
@ OutputCache,但放在 .ascx 檔案頂端。<%-- 模式 A:依查詢字串變動,每頁獨立 --%>
<%@ OutputCache Duration="60" VaryByParam="*" %>
<%-- 模式 B:依控件值變動,每頁獨立 --%>
<%@ OutputCache Duration="60" VaryByParam="none" VaryByControl="CategoryDropDownList" %>
<%-- 模式 C:依瀏覽器變動,跨頁共用 --%>
<%@ OutputCache Duration="60" VaryByParam="none" VaryByCustom="browser" Shared="true" %>
System.Web.Caching.Cache 可存放任何可序列化物件,並支援豐富的相依性與到期策略。
// 簡單存放(無相依,記憶體壓力下自動淘汰)
Cache["key"] = "value";
// 檔案相依:檔案變更即失效
Cache.Insert("xmlData", xmlDoc, new CacheDependency(Server.MapPath("users.xml")));
// 鍵值相依:父鍵失效則子鍵連帶失效
Cache.Insert("child", childObj, new CacheDependency(null, new[] { "parentKey" }));
// 絕對到期:1 分鐘後必失效
Cache.Insert("timeKey", obj, null, DateTime.Now.AddMinutes(1), Cache.NoSlidingExpiration);
// 滑動到期:1 分鐘未被存取則失效(適合熱門資料)
Cache.Insert("hotKey", obj, null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(1));
var callback = new CacheItemRemovedCallback(OnRemove);
Cache.Insert("key", obj, null,
Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(10),
CacheItemPriority.Default, callback);
public static void OnRemove(string key, object value, CacheItemRemovedReason reason)
{
// 記錄淘汰原因,判斷是否需擴充記憶體或調整策略
Log($"Cache '{key}' removed: {reason}");
}
> ⚠️ 注意:回呼在項目已從緩存移除後觸發,無法做到「背景預熱」。若需無縫熱更新,建議採用「雙緩存鍵輪換」或外部分散式緩存(Redis)。
public DataTable GetCustomers(bool bypassCache = false)
{
const string cacheKey = "CustomersDataTable";
var cached = Cache[cacheKey] as DataTable;
if (bypassCache || cached == null)
{
cached = FetchCustomersFromDb(); // 實際資料存取
var duration = GetCacheSecondsFromConfig(cacheKey); // 從 config 讀取
Cache.Insert(cacheKey, cached, null,
DateTime.Now.AddSeconds(duration), Cache.NoSlidingExpiration);
}
return cached;
}
關鍵點:
as DataTable 只讀取一次,避免雙重檢查間隙失效拋 NullReferenceException。bypassCache=true,即時看到最新資料。web.config 或 appsettings.json,方便調優與 A/B 測試。| 層級 | 技術 | 適用資料 | TTL 建議 |
|---|---|---|---|
| 瀏覽器 | HTTP Cache-Control / ETag | 靜態資源、不變動頁面 | 長(天/月) |
| CDN/反向代理 | Vary 標頭、Edge 緩存 | 公開頁面、產品圖片 | 中(分/時) |
| Web 伺服器 | OutputCache / UserControl Cache | 共用片段、導覽、頁尾 | 短~中(秒/分) |
| 應用層 | Cache 物件 / MemoryCache / IDistributedCache | 資料實體、查詢結果、計算結果 | 視業務而定 |
| 資料庫 | 查詢計畫緩存、物化視圖 | 熱門查詢、彙總報表 | 由 DB 管理 |
PagedDataSource 或僅緩存 DTO 陣列。lock / SemaphoreSlim / 分散式鎖 單飛重建。CacheKeys 靜態類別。緩存是「用 20% 功夫換 80% 效能提升」的高杠桿手段。建議採取 由外而內、漸進式 策略:
「盡早緩存,經常緩存;內存現在很便宜,聰明地用它換取穩定的高效能。」