Asp.net使用快取 (二)

Asp.net使用快取 (一)

向大家簡單介紹

  1. 快取是什麼
  2. 為何要使用快取
  3. 使用簡單HttpRuntime.Cache使用快取機制

這篇是分享把快取程式碼變得更有彈性


第二篇大綱

  1. 提出介面,提高可替換性
  2. 使用泛型改寫快取 讀取方式
  3. 使用擴充方法改寫快取

提出介面,提高可替換性

情境:

目前有個專案使用 HttpRuntime.Cache 物件

在記憶體快取中除了使用 Asp.Net 中HttpRuntime.Cache類別外還有很多解決方案.例如使用Memcache,Redis

如果我們原本使用HttpRuntime.Cache類別但之後要轉成其他快取方式怎麼辦?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class HomeController : Controller
{
System.Web.Caching.Cache cacheContainer = HttpRuntime.Cache;

public ActionResult Index()
{

string cacheData = cacheContainer.Get("data") as string;

if (cacheData==null)
{
cacheContainer.Insert("test1", DateTime.Now.ToShortDateString());
}

return View(cacheData);
}
}

雖然使用不同快取方式,但記得我上篇的重點快取會有兩個動作,讀和寫,所以最基本就會有讀和寫這兩個動作

OOP有個很重要的觀念 多個類有重複動作考慮提出父類別

為了方便了解我把HttpRuntime.Cache封裝成一個類別

1
2
3
4
5
6
7
8
9
10
public class NetCache {
System.Web.Caching.Cache cacheContainer = HttpRuntime.Cache;
public object GetCacheObject(string key) {
return cacheContainer.Get(key);
}

public void SetCache(string key,object obj) {
cacheContainer.Insert(key, obj);
}
}

這邊有另一個Memcache快取Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MemeryCache {
private ObjectCache _cache = MemoryCache.Default;
public object GetCacheObject(string key)
{
return _cache[cacheKey];
}

public void SetCache(string key, object obj)
{
var policy = new CacheItemPolicy();
policy.RemovedCallback = OnFileContentsCacheRemove;
// 設定快取時間2分鐘
policy.AbsoluteExpiration = DateTimeOffset.Now.Minute(2);
_cache.Set(cacheKey, fileContents, policy);
}
}

先不關注這兩個物件裡面細節,我們可以發現他們都有 GetCacheObject 方法和SetCache 方法

這時我們就可以適時提出介面(interface),當作這兩個類別的合約

1
2
3
4
5
6
public interface ICache {

void Set(string key,object obj);

object Get(string key);
}

之後將他們兩個類別實現 ICache 介面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MemeryCache : ICache
{
private ObjectCache _cache = MemoryCache.Default;
public object Get(string key)
{
return _cache[cacheKey];
}

public void Set(string key, object obj)
{
var policy = new CacheItemPolicy();
policy.RemovedCallback = OnFileContentsCacheRemove;
// 設定快取時間2分鐘
policy.AbsoluteExpiration = DateTimeOffset.Now.Minute(2);
_cache.Set(cacheKey, fileContents, policy);
}
}

public class NetCache : ICache
{
System.Web.Caching.Cache cacheContainer = HttpRuntime.Cache;
public object Get(string key) {
return cacheContainer.Get(key);
}

public void Set(string key,object obj) {
cacheContainer.Insert(key, obj);
}
}

提出介面有甚麼好處?

我們可以把前面程式碼改成IOC依賴注入的方式,不要在程式碼寫死使用HttpRuntime.Cache,由IOC容器幫我們把物件注入程式碼中.

Note:我使用建構子注入法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class HomeController : Controller
{
//不用寫死使用 HttpRuntime.Cache
//System.Web.Caching.Cache cacheContainer = HttpRuntime.Cache;
ICache cacheContainer;
public HomeController(ICache Container){
cacheContainer = Container;
}

public ActionResult Index()
{

string cacheData = cacheContainer.Get("data") as string;

if (cacheData==null)
{
cacheContainer.Insert("test1", DateTime.Now.ToShortDateString());
}

return View(cacheData);
}
}

ICache 變成快取程式碼的潤滑劑.可讓程式變得更有彈性


使用泛型改寫快取 讀取方式

我在StackOverFlow解答的方式就是第二種

其中最主要的技巧就是把Get方法返回的Object改成使用泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 public T GetOrSetCache<T>
(string key,T obj, int cacheTime) where T:class,new()
{
System.Web.Caching.Cache cacheContainer = HttpRuntime.Cache;
T cacheObj = cacheContainer.Get(key) as T;

if (cacheObj == null)
{
cacheContainer.Insert(key,
obj,
null,
DateTime.Now.AddMinutes(cacheTime),
System.Web.Caching.Cache.NoSlidingExpiration);
cacheObj = obj;
}

return cacheObj;
}

讓我們在使用時可以變成

1
2
3
var data = DateTime.Now.ToShortDateString();
int numberOfMinutes = 3;
data = GetOrSetCache("name1",data,numberOfMinutes );

我們只需要呼叫GetOrSetCache方法,這個方法把GetCacheSetCache封裝起來了


使用擴充方法改寫快取

.Net有提供一個很方便的機制 擴充方法,這個機制幫我們解決一個很重要的問題.
我們可以擴充已經封裝但沒有原始碼的類別,

在這段程式碼中,使用Func<TObj> 可以使用lambda 表達式,讓程式碼更簡潔有力!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static TObj GetOrSetCache<TObj>(this Func<TObj> selector, string key, int cacheTime)    where TObj : class
{
Cache cacheContainer = HttpRuntime.Cache;
//get cache Object
var obj = cacheContainer.Get(key) as TObj;

//if there isn't cache object add this object to cache
if (obj == null)
{
obj = selector();
cacheContainer.Insert(key, obj);
}

return obj;
}

我們使用時如下

變更簡潔動作更漂亮

1
2
int numberOfMinutes = 3;
data = GetOrSetCache(()=> DateTime.Now.ToShortDateString(),"name1",data,numberOfMinutes );

同場加映:

擴展方法和介面搭配使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

public class WebDefaultCache : ICache
{
Cache cacheContainer = HttpRuntime.Cache;
public object Get(string key)
{
return cacheContainer.Get(key);
}

public void Set(string key, object obj)
{
cacheContainer.Insert(key, obj);
}
}
public interface ICache{
void Set(string key, object obj);

object Get(string key);
}

public static class InfrastructureExtension
{
public static TObj GetOrSetCache<TObj>(this Func<TObj> selector, string key) where TObj : class {
return GetOrSetCache(selector, key,10);
}

public static TObj GetOrSetCache<TObj>(this Func<TObj> selector, string key, int cacheTime) where TObj : class
{
return GetOrSetCache(selector, key, cacheTime, new WebDefaultCache());
}

public static TObj GetOrSetCache<TObj>(this Func<TObj> selector, string key, int cacheTime, ICache cacheContainer) where TObj : class
{
//get cache Object
var obj = cacheContainer.Get(key) as TObj;

//if there isn't cache object add this object to cache
if (obj == null)
{
obj = selector();
cacheContainer.Set(key, obj);
}

return obj;
}
}

雖然在使用上和第三種一樣
但我們多了使用方法重載多傳一個參數ICache介面 可以讓我們在寫程式時決定要使用哪種cache方式,不用改快去那邊程式碼.

同場加映程式碼我放在我自己常用的ExtenionTool專案中

此文作者:Daniel Shih(石頭)
此文地址https://isdaniel.github.io/cache-asp-net-2/
版權聲明:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!


如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^