MemoryCache 在 ASP.NET MVC 上的應用
TLDR
OutputCacheAttribute是 MVC 內建的ActionFilter,預設使用MemoryCache實作 Server 端快取。- 若需針對不同使用者權限進行快取隔離,可透過
VaryByCustom搭配Global.asax的GetVaryByCustomString實作。 NoStore與Location.None行為不同:NoStore僅影響瀏覽器快取,Location.None則同時停用 Server 端快取。- 若需確保資料庫異動時快取即時更新,可使用
SqlChangeMonitor結合SqlDependency監聽 SQL Server 變更。 - 使用
SqlDependency前,必須確保資料庫已啟用Service Broker功能。
使用 ActionFilter 快取 Action 內容
OutputCacheAttribute 用於標註 Action Method 以啟用快取機制。若未特別設定,預設使用 MemoryCache 儲存。
關鍵屬性說明
- Duration: 快取持續時間(秒)。
- Location: 指定快取儲存位置(如
Server,Client,Any等)。 - VaryByParam: 根據參數(如 QueryString 或 POST 參數)區分快取版本,設定為
*可針對所有參數組合建立快取。 - CacheProfile: 引用
Web.config中定義的快取方案,便於統一管理。
TIP
NoStore 與 Location.None 的差異:
NoStore: 將 Header 的Cache-Control設為no-store,僅告知瀏覽器不要快取,不影響 Web Server 的快取行為。Location.None: 將 Header 的Cache-Control設為no-cache,且 Web Server 不會儲存任何快取。
實作:針對不同使用者進行快取隔離
什麼情況下會遇到這個問題:當應用程式需要根據使用者權限或身分顯示不同內容,但預設的 OutputCache 會導致不同使用者讀取到相同的快取結果時。
Web.config 設定:
xml
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="Default" duration="30" varyByParam="*" varyByCustom="Cookie" noStore="true" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>Global.asax.cs 實作: 透過 Override GetVaryByCustomString 產生唯一的快取 Key。
csharp
public override string GetVaryByCustomString(HttpContext context, string custom) {
const string OutputCacheKey = "OutputCacheId";
if (custom.Equals("Cookie", StringComparison.OrdinalIgnoreCase)) {
if (Request.Cookies[OutputCacheKey] == null) {
string cacheId = Guid.NewGuid().ToString();
Response.Cookies.Add(new HttpCookie(OutputCacheKey) {
Value = cacheId,
HttpOnly = true,
Expires = DateTime.Now.AddHours(1)
});
return cacheId;
}
return Request.Cookies[OutputCacheKey].Value;
}
return base.GetVaryByCustomString(context, custom);
}更新資料庫時清除快取資料
什麼情況下會遇到這個問題:當快取的資料來源為資料庫,且需確保資料庫內容異動時,快取能自動失效或更新,而非等待過期。
使用 SqlChangeMonitor 監聽資料庫
MemoryCache 支援 ChangeMonitor,其中 SqlChangeMonitor 可透過 SqlDependency 監聽 SQL Server 的異動通知。
Global.asax.cs 設定:
csharp
protected void Application_Start() {
SqlDependency.Start(WebConfigurationManager.ConnectionStrings["MyDB"].ConnectionString);
}
protected void Application_End() {
SqlDependency.Stop(WebConfigurationManager.ConnectionStrings["MyDB"].ConnectionString);
}Controller 實作範例:
csharp
private void CreateCache() {
string connectionStr = WebConfigurationManager.ConnectionStrings["MyDB"].ConnectionString;
CacheItemPolicy policy = new CacheItemPolicy();
using (SqlConnection conn = new SqlConnection(connectionStr))
using (SqlCommand cmd = new SqlCommand("SELECT Key1 FROM dbo.Config", conn)) {
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += SqlDependencyOnChange;
conn.Open();
string key1 = cmd.ExecuteScalar().ToString();
SqlChangeMonitor monitor = new SqlChangeMonitor(dependency);
policy.ChangeMonitors.Add(monitor);
MemoryCache.Default.Set(CacheKey, key1, policy);
}
}WARNING
- 必須在資料庫中啟用
Service Broker功能。 - SQL 語法必須指定具體欄位,且資料表名稱需包含 Schema(如
dbo.TableName)。 SqlDependency設定後,必須執行一次SqlCommand監聽才會生效。
啟用 Service Broker
若資料庫未啟用,請執行:
sql
ALTER DATABASE {資料庫名稱} SET ENABLE_BROKER;若遇到 GUID 不符錯誤,請使用強制重置指令:
sql
ALTER DATABASE {資料庫名稱} SET NEW_BROKER WITH ROLLBACK IMMEDIATE;異動歷程
- 2022-11-14 初版文件建立。
