在項目使用OutputCacheAttribute是遇到了問題,當我想在配置文件web.config中配置OutputCache的VaryByParam時竟然不起作用,下面是相關代碼:
文件FaceController.cs
[OutputCache(CacheProfile = "faceProfile")]
public ActionResult Index()
{
return View();
}
文件index.cshtml
<h2>@DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")</h2>
web.config的cache配置
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="faceProfile" duration ="180" varyByParam="none" enabled="true"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
<system.web>
下面是我的測試:
請求 /face/index 頁面,輸出: 2014-08-02 18:40:56.184
再次請求 /face/index 頁面,輸出: 2014-08-02 18:40:56.184 (不變)
因為我指定了varyByParam="none"
,所以我添加參數或改變參數,輸出的時間應該不變才對,可是:
請求 /face/index/?p=19999 頁面,輸出: 2014-08-02 18:42:29.720 (變了)
請求 /face/index/?p=10000 頁面,輸出: 2014-08-02 18:43:30.981 (變了)
請求 /face/index/?abcd 頁面,輸出: 2014-08-02 18:44:00.440 (變了)
請求結果隨着參數的變化而變化,所以它應該是為每個參數都緩存了一個版本相當於設置了varyByParam="*"
從測試結果可以看出配置文件中設置的duration ="180"
有起到作用,而varyByParam="none"
沒有起到作用.
然后我試着把varyByParam="none"直接寫到代碼里:
[OutputCache( Duration=180,VaryByParam="none")]
public ActionResult Index()
{
return View();
}
這次結果它正確進行緩存頁面,沒有為每個參數緩存一個版本,這使我很困惑,然后我看了一下OutputCacheAttribute
的源碼,這里我貼出的是部分關鍵源碼:
public class OutputCacheAttribute : ActionFilterAttribute, IExceptionFilter
{
private OutputCacheParameters _cacheSettings = new OutputCacheParameters { VaryByParam = "*" };
//_cacheSettings中的每個屬性都以 CacheProfile 屬性的方式再包裝了一遍
//其余類似的屬性我就不貼出來了
public string CacheProfile
{
get{return _cacheSettings.CacheProfile ?? String.Empty; }
set{_cacheSettings.CacheProfile = value;}
}
//省去了無關代碼
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
//省去了無關代碼
using (OutputCachedPage page = new OutputCachedPage(_cacheSettings))
{
page.ProcessRequest(HttpContext.Current);
}
}
private sealed class OutputCachedPage : Page
{
private OutputCacheParameters _cacheSettings;
public OutputCachedPage(OutputCacheParameters cacheSettings)
{
// Tracing requires Page IDs to be unique.
ID = Guid.NewGuid().ToString();
_cacheSettings = cacheSettings;
}
protected override void FrameworkInitialize()
{
// when you put the <%@ OutputCache %> directive on a page, the generated code calls InitOutputCache() from here
base.FrameworkInitialize();
InitOutputCache(_cacheSettings);
}
}
}
由上面的代碼可以看出_cacheSettings根本就沒從配置文件里讀取,而是直接由 OutputCache( Duration=180,VaryByParam="none") 這樣設置進去的,然后直接傳人Page.InitOutputCache(cacheSettings)中,可見它直接使用asp.net以前的方式OutputCacheModule
來實現緩存的,InitOutputCache中應該有去加載配置里面的設置, 然后看看cacheSettings的相應字段是否已經有合適的值了,如果沒有則使用配置里邊的值。而我們在使用OutputCache時,如果沒有傳人VaryByParam值,則它默認的值就為"*"(從cacheSettings初始化時看出來的) 。故代碼:
[OutputCache(CacheProfile = "faceProfile")]
public ActionResult Index()
{
return View();
}
相當於
[OutputCache(CacheProfile = "faceProfile",VaryByParam = "*")]
public ActionResult Index()
{
return View();
}
配置里的VaryByParam的值是不會覆蓋代碼里面的VaryByParam的值的,所以才會出現我們測試時,對每個參數都進行緩存的情況。
解決辦法:
- 把VaryByParam 就直接寫入代碼, 其他的參數在配置中來完成如:OutputCache(CacheProfile = "faceProfile", VaryByParam = "none")
- 自己實現一個OutputCacheAttritube
下面就是我把OutputCacheAttritube源碼做一些修改后滿足自己需求的MySimpleCacheAttribute,不過它不適用於ChildAction,但我可以在配置文件中控制VaryByParam參數,改造后的代碼如下:
namespace Mvc.Cache
{
using System.Web.Mvc;
using System.Web.UI;
using System;
using System.Web;
public class MySimpleCacheAttribute : ActionFilterAttribute
{
private OutputCacheParameters _cacheSettings = new OutputCacheParameters();
public MySimpleCacheAttribute()
{
}
public string CacheProfile
{
get
{
return _cacheSettings.CacheProfile ?? String.Empty;
}
set
{
_cacheSettings.CacheProfile = value;
}
}
internal OutputCacheParameters CacheSettings
{
get
{
return _cacheSettings;
}
}
public int Duration
{
get
{
return _cacheSettings.Duration;
}
set
{
_cacheSettings.Duration = value;
}
}
public OutputCacheLocation Location
{
get
{
return _cacheSettings.Location;
}
set
{
_cacheSettings.Location = value;
}
}
public bool NoStore
{
get
{
return _cacheSettings.NoStore;
}
set
{
_cacheSettings.NoStore = value;
}
}
public string SqlDependency
{
get
{
return _cacheSettings.SqlDependency ?? String.Empty;
}
set
{
_cacheSettings.SqlDependency = value;
}
}
public string VaryByContentEncoding
{
get
{
return _cacheSettings.VaryByContentEncoding ?? String.Empty;
}
set
{
_cacheSettings.VaryByContentEncoding = value;
}
}
public string VaryByCustom
{
get
{
return _cacheSettings.VaryByCustom ?? String.Empty;
}
set
{
_cacheSettings.VaryByCustom = value;
}
}
public string VaryByHeader
{
get
{
return _cacheSettings.VaryByHeader ?? String.Empty;
}
set
{
_cacheSettings.VaryByHeader = value;
}
}
public string VaryByParam
{
get
{
return _cacheSettings.VaryByParam ?? String.Empty;
}
set
{
_cacheSettings.VaryByParam = value;
}
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.IsChildAction)
{
using (OutputCachedPage page = new OutputCachedPage(_cacheSettings))
{
page.ProcessRequest(HttpContext.Current);
}
}
}
private sealed class OutputCachedPage : Page
{
private OutputCacheParameters _cacheSettings;
public OutputCachedPage(OutputCacheParameters cacheSettings)
{
// Tracing requires Page IDs to be unique.
ID = Guid.NewGuid().ToString();
_cacheSettings = cacheSettings;
}
protected override void FrameworkInitialize()
{
base.FrameworkInitialize();
InitOutputCache(_cacheSettings);
}
}
}
}
使用:
[MySimpleCache(CacheProfile = "faceProfile")]
public ActionResult Index()
{
return View();
}