筆記目錄

Skip to content

MemoryCache 在 ASP.NET MVC 上的應用

TLDR

  • OutputCacheAttribute 是 MVC 內建的 ActionFilter,預設使用 MemoryCache 實作 Server 端快取。
  • 若需針對不同使用者權限進行快取隔離,可透過 VaryByCustom 搭配 Global.asaxGetVaryByCustomString 實作。
  • NoStoreLocation.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

NoStoreLocation.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 初版文件建立。