前言
對於緩存我們都已經很熟悉了,緩存分為很多種,瀏覽器緩存、試圖緩存、服務器緩存、數據庫緩存等等一些,那今天我們先介紹一下視圖緩存和MemoryCache內存緩存的概念和用法:
視圖緩存
在老的版本的MVC里面,有一種可以緩存視圖的特性(OutputCache),可以保持同一個參數的請求,在N段時間內,直接從mvc的緩存中讀取,不去走視圖的邏輯。
//老版本的.NET 做法 [OutputCache(Duration =20)]//設置過期時間為20秒 public ActionResult ExampleCacheAction() { var time=DateTime.Now.ToString("yyyy年MM月dd日 HH時mm分ss秒"); ViewBag.time= time; return View(); }
在Asp.Net core 2.1中,官方文檔上稱:響應緩存可減少客戶端或代理對 web 服務器的請求數。 響應緩存還可減少量工作的 web 服務器執行程序生成響應。 響應緩存由標頭,指定你希望客戶端、 代理和緩存響應的中間件如何控制。
在Asp.Net Core 2.1 中,沒有了OutputCache,換成了ResponseCache,ResponseCache必須帶一個參數:Duration 單位為秒,最少設置一秒鍾
//.NET Core2.1做法 [ResponseCache(Duration = 5)] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH時mm分ss秒"); return View(); }
然后再瀏覽器請求這個視圖
在瀏覽器的響應頭的Cache-Control 中出現max-age=5, Http協議對此的解釋是
客戶端將不會接受其保留時間大於指定的秒數的響應。 示例:
max-age=60
(60 秒),max-age=2592000
(1 個月)
如果在瀏覽器中禁用緩存,那么ResponseCache不會有任何效果
Vary過濾
[ResponseCache(VaryByHeader = "User-Agent", Duration = 5)] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH時mm分ss秒"); return View(); }
關於vary在Http響應頭的作用就是:告訴緩存服務器或者CDN,我還是同一個瀏覽器的請求,你給我緩存就行了,如果你換個瀏覽器去請求,那么vary的值肯定為空,那么緩存服務器就會認為你是一個新的請求,就會去讀取最新的數據給瀏覽器
參考資料:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
禁用緩存(NoStore 和 Location.None)
在Http中 :no-store,請求和響應的信息都不應該被存儲在對方的磁盤系統中
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH時mm分ss秒"); return View(); }
ResponseCacheLocation.None是在Cache-Control設置一個no-cache屬性,讓瀏覽器不緩存當前這個URL
緩存配置(CacheProfiles)
在一個正常的項目中,肯定有很多個控制器,但是不可能每個控制器的緩存策略都一樣,這時候,我們就需要一個緩存的配置來靈活應對這個問題
在mvc的服務注入的時候,我們可以在option里面注入進我們的緩存策略
services.AddMvc(option=> { option.CacheProfiles.Add("test1", new CacheProfile() { Duration = 5 }); option.CacheProfiles.Add("test2", new CacheProfile() { Location = ResponseCacheLocation.None, NoStore = true }); });
然后我們在使用的時候,直接使用配置策略的名稱就好了
[ResponseCache(CacheProfileName = "test1")] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH時mm分ss秒"); return View(); }
這樣我們就能和之前在特性后邊配置一樣了,這是視圖緩存,下面我們就來看看MemoryCache 是個什么東東
MemoryCache
如果回到老版本的.NET,說到內存緩存大家可能立馬想到了HttpRuntime.Cache,它位於System.Web命名空間下,但是在ASP.NET Core中System.Web已經不復存在。今兒個就簡單的聊聊如何在ASP.NET Core中使用內存緩存
有幾個問題我們需要先進行了解:
1.什么時候需要用到緩存?
一般將經常訪問但是又不是經常改變的數據放進緩存是再好不過了,這樣可以明顯提高應用程序的性能。
2.緩存的好處?
建議http://www.baidu.com
使用
不同於 ASP.NET Web 窗體和 ASP.NET MVC,ASP.NET Core 沒有內置的 Cache 對象,可以拿來在控制器里面直接使用。 這里,內存緩存時通過依賴注入來啟用的,因此第一步就是在 Startup 類中注冊內存緩存的服務。如此,就得打開 Startup 類然后定位到 ConfigureServices() 方法,像下面這樣修改 ConfigureServices() 方法:
①首先需要在ConfigureServices中注冊緩存服務
services.AddMemoryCache()
為了向你的應用程序加入內存緩存能力,你需要在服務集合上調用 AddMemoryCache() 方法。采用這種辦法就可以讓一個內存緩存(它是一個 IMemoryCache 對象)的默認實現可以被注入到控制器中去。
②在下面的代碼中從Home控制器的構造函中獲取IMemoryCache實例(內存緩存使用依賴注入來注入緩存對象)
private readonly IMemoryCache _memoryCache; public HomeController(IMemoryCache memoryCache) { _memoryCache = memoryCache; }
如你所見,上述代碼聲明了一個 ImemoryCache 的私有變量。該變量會被構造器中被賦值。構造器會通過 DI(依賴注入)接收到緩存參數,然后被存儲在本地變量總,提供后續使用。
③關於緩存的使用常用的就是Set Get Remove,一般有以下幾種做法可以參考:
⑴可以使用 Set() 方法來在緩存中存東西
等你有了這個 IMemoryCache 對象,就可以讀取或者向它寫入數據了。向緩存寫入數據項是相當直接的
public IActionResult Index() { _memoryCache.Set<string>("timestamp", DateTime.Now.ToString()); return View(); }
上述代碼在 Index() 這個 action 中設置了一個緩存項。這是通過使用 IMemoryCache 的 Set<T>() 來完成的。Set() 方法的第一個參數是鍵名,用來標識該數據項。第二個參數是鍵的取值。在此例中,我們存儲一個字符串的鍵和一個字符串的值,而你也可以存儲其它類型 (原生以及自定義的類型) 的鍵值對。
⑵可以使用 Get 方法來從緩存中獲取到一個數據項
等你向緩存中添加好了數據,也許會想要在應用程序的其它地方去獲取到該數據,可以用 Get() 來做到。如下代碼會告訴你如何來做這件事情。
public IActionResult Show() { string timestamp = _memoryCache.Get<string>("timestamp"); return View("Show",timestamp); }
上述代碼從 HomeController 的另外一個action(Show)那里獲取到了一個緩存的數據項。Get() 方法會指定數據項的類型以及它的鍵名。如果該數據項存在的話,就會被返回並且被賦值給 timestamp 這個字符串變量。然后這個 timestamp 的值就會被傳遞給 Show 視圖。
Show 視圖只是簡單地輸出了 timestamp 的值,如下所示:
<h1>TimeStamp : @Model</h1> <h2>@Html.ActionLink("Go back", "Index", "Home")</h2>
如果你觀察前面的示例,會發現每次你導航至 /Home/Index 的時候, 都會有一個新的 timestamp 被賦值給了緩存項。這是因為我們並沒有對此進行檢查,規定只有在數據項不存在的時候才賦值。許多時候你都會想要這樣做的。這里有兩種辦法可以在 Index() 這個 action 里面來做這樣的檢查。我們把兩種辦法都在下面列了出來
可以使用 TryGet() 來檢查緩存中是否存在特定的鍵值
//first way if (string.IsNullOrEmpty (_memoryCache.Get<string>("timestamp"))) { _memoryCache.Set<string>("timestamp", DateTime.Now.ToString()); } //second way if (!_memoryCache.TryGetValue<string> ("timestamp", out string timestamp)) { _memoryCache.Set<string>("timestamp", DateTime.Now.ToString()); }
第一種辦法使用了你早先用過的同一個 Get() 方法,這一次它被拿來跟 if 塊一起用。如果 Get() 不能在緩存中找到指定的數據項,IsNullOrEmpty() 就會返回 true。而只有這時候 Set() 才會被調用,一次來添加數據項。
第二種辦法更加優雅一點。它使用 TryGet() 方法來獲取一個數據項。TryGet() 方法會返回一個布爾值來指明數據項有沒有被找到。實際的數據項可以使用一個輸出參數拉取出來。如果 TryGet() 返回false,Set() 就會被用來添加數據。
如果不存在的話,可以使用 GetOrCreate() 來添加一項
有時你需要從緩存中檢索現有項。如果該項目不存在,則希望添加該項。這兩個任務 - 如果它存在獲取值,否則創建之 - 可以使用 GetOrCreate() 方法來實現。修改后的 Show() 方法展示了如何實現的
public IActionResult Show() { string timestamp = cache.GetOrCreate<string> ("timestamp", entry => { return DateTime.Now.ToString(); }); return View("Show",timestamp); }
Show() 動作現在使用 GetOrCreate() 方法。 GetOrCreate() 方法將檢查時間戳的鍵值是否存在。如果是,現有值將被賦值給局部變量。否則,將根據第二個參數中指定的邏輯創建一個新條目並將其添加到緩存中。
在緩存的數據項上面設置絕對和滾動的過期時間
一個緩存項只要被添加到緩存就會一直存儲,除非它被明確地使用 Remove() 從緩存中移除。你也可以在一個緩存項上面設置一個絕對和滾動的過期時間。一個絕對的過期設置意味着該緩存項會在嚴格指定的日期和時間點被移除,而滾動過期設置則意味着它在給定的一段時間量處於空閑狀態(也就是沒人去訪問)之后被移除。
為了能在一個緩存項上面設置這兩種過期策略,你要用到 MemoryCacheEntryOptions 對象。如下代碼向你展示了如何去使用。
MemoryCacheEntryOptions options = new MemoryCacheEntryOptions(); options.AbsoluteExpiration = DateTime.Now.AddMinutes(1); options.SlidingExpiration = TimeSpan.FromMinutes(1); _memoryCache.Set<string>("timestamp", DateTime.Now.ToString(), options);
上述代碼來自於修改過的 Index() action,它創建了一個 MemoryCacheEntryOptions 的對象,然后將它的 AbsoluteExpiration 屬性設置為從此刻到一分鍾之后的一個 DateTime 值,它還將 SlidingExpiration 屬性設置為一分鍾。這些值都指定了該緩存項會在一分鍾之后從緩存移除,不管其是否會被訪問。此外,如果該緩存項如初持續空閑了有一分鍾,它也會被從緩存中移除。
等你將 AbsoluteExpiration 和 SlidingExpiration 的值設置后, Set() 方法就可以被用來將一個數據項添加到緩存。這一次 MemoryCacheEntryOptions 對象會被作為第三個參數傳遞給 Set() 方法。
當緩存項會被移除時,可以連接回調
有時你會想要在緩存項從緩存中被移除時收到通知。可能會有多種原因需要從緩存中移除數據項。例如,因為明確地執行了 Remove() 方法而移除了一個緩存項, 也有可能是因為它的 AbsoluteExpiration 和 SlidingExpiration 值已經到期而被移除,諸如此類的原因。
為了能知道項目是何時從緩存移除的,你需要編寫一個緩存函數。如下代碼向你展示了如何去做這件事情
MemoryCacheEntryOptions options = new MemoryCacheEntryOptions(); options.AbsoluteExpiration =DateTime.Now.AddMinutes(1);options.SlidingExpiration = TimeSpan.FromMinutes(1); options.RegisterPostEvictionCallback(MyCallback, this); _memoryCache.Set<string>("timestamp", DateTime.Now.ToString(), options); private static void MyCallback(object key, object value,EvictionReason reason, object state) { var message = $"Cache entry was removed : {reason}"; ((HomeController)state). _memoryCache.Set("callbackMessage", message); }
請仔細觀察這段代碼。 MyCallback() 是 HomeController 類里面的一個私有靜態函數,它有四個參數。前面兩個參數表示剛剛刪除的緩存項的鍵和值,第三個參數表示的是該數據項被刪除的原因。EvictionReason 是一個枚舉類型,它維護者各種可能的刪除原因,如過期,刪除以及替換。
在回調函數的內部,我們會基於刪除的原因構造一個字符串消息。我們想要將此消息設置成另外一個緩存項。這樣做的話就需要訪問 HomeController 的緩存對象,此時狀態參數就可以排上用場了。使用狀態對象,你可以對 HomeController 的緩存對象進行控制,並使用 Set() 增加一個 callbackMessage 緩存項。
你可以通過 Show() 這個 action 來訪問到 callbackMessage,如下所示:
public IActionResult Show() { string timestamp = cache.Get<string>("timestamp"); ViewData["callbackMessage"] = _memoryCache.Get<string>("callbackMessage"); return View("Show",timestamp); }
最后就可以在 Show 視圖中顯示出來了:
<h1>TimeStamp : @Model</h1> <h3>@ViewData["callbackMessage"]</h3> <h2>@Html.ActionLink("Go back", "Index", "Home")</h2>
一些建議,像上面提到的設置緩存數據項的過期時間那塊,如果一個項目中所有的緩存過期時間是一致的,我們可以有更簡單的做法,而不是每個地方都去寫一堆這個:
MemoryCacheEntryOptions options = new MemoryCacheEntryOptions(); options.AbsoluteExpiration = DateTime.Now.AddMinutes(1); options.SlidingExpiration = TimeSpan.FromMinutes(1);
可以在頭部定義一個共有的
readonly MemoryCacheEntryOptions _options = Cache.GetMemoryCacheEntryOptions();
Cache是自定義的一個緩存類,其中GetMemoryCacheEntryOptions就是獲取當前緩存設置項的,代碼如下:
public static MemoryCacheEntryOptions GetMemoryCacheEntryOptions() { var options = new MemoryCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(double.Parse($"{SiteConfig.GetSite("ExpiredTime")}")), SlidingExpiration = TimeSpan.FromMinutes(double.Parse($"{SiteConfig.GetSite("ExpiredTime")}")) }; return options; }
這里面我們將過期時間放到配置文件中,這里讀取配置文件可以參考前面的博客:https://www.cnblogs.com/zhangxiaoyong/p/9411036.html
這樣,我們的寫法就可以簡寫很多,當然,剛才提到了是使用的 IMemoryCache 的 Set<T>() 來完成的,所以,這里我們不僅可以傳String,還可以按需傳遞,比如:
var homeCache = _memoryCache.Get<Task<HomeInfo>>("HomeCache");//獲取HomeCache if (homeCache == null)//判斷是否存在 { homeCache = _dadaServices.GetHomeData();//調用API獲取數據 _memoryCache.Set<Task<HomeInfo>>("HomeCache", homeCache, _options);//存放HomeCache,傳入data數據和設置的數據項 } return View("~/Views/Home/Index.cshtml", homeCache.Result);//返回Cache
到這里,你已經大概知道了MemoryCache 的簡單使用方式,剩下的就自行研究吧~