在 ASP.NET 1.x 中,我們可以使用 CacheDependency 來實現緩存依賴策略,但由於這個類是 sealed 的,我們無法繼承這個類來實現我們自己的策略。但是到了 ASP.Net 2.0,我們已經可以從這個類派生出自己的緩存依賴類了。
假定我們要設計一個頁面,需要從博客園首頁獲取最新的貼子信息。為了提高性能,我們希望頁面數據僅當博客園首頁有更新時才重新生成,否則的話就直接從緩存中獲取。如何實現?
一、設計 BlogCacheDependency 類
先分析一下,首先,毫無疑問的,這個類應當從 CacheDependency 派生出來,然後它才能在 Cache 的 Insert 方法中使用,或者被用在 AggregateDependency 類中。
其次,從博客園提供的 RSS 以及頁面設計的角度考慮,可以在緩存中放置 RSS 數據,顯示的時候使用一個樣式轉換。而在檢查依賴性的時候,我們只需要簡單地比較一下當前的 RSS 與網站的 RSS 是否相同就可以了。
一個比較重要的問題是:我們何時去檢查比較 RSS 數據?在每次請求的時候嗎?顯然不行,這樣一來跟不使用緩存幾乎沒什麼區別,甚至實際上加重了無謂的負擔。考慮在沒有請求的時候進行檢查呢?我們可以使用一個 Timer 來控制,讓它定期去檢查一個是否有更新,如果有更新則通知依賴發生了改變。
我們知道 CacheDependency 類有一個 HasChanged 屬性,但是當 BlogCacheDependency 檢查到依賴改變時如何告訴它的基類呢?這就是在 ASP.Net 2.0 中 CacheDependency 類中新增的 NotifyDependencyChanged 方法的使命了。
此外為了便於重用,BlogCacheDependency 類須得有一個 feed 數據,用來保存我們要獲取的 RSS 數據的 URL。還要有一個時間間隔,便於在使用的時候調整刷新速度。
好,看看實際的實現代碼:
1public class BlogCacheDependency : CacheDependency
2{
3 private Timer _tickTimer;
4 private int _timeInterval;
5 private XPathNavigator _rss;
6 private string _feed;
7
8 public XPathNavigator RSS
9 {
10 get
11 {
12 return _rss;
13 }
14 }
15
16 public BlogCacheDependency(string feed, int timeInterval)
17 {
18 _feed = feed;
19 _timeInterval = timeInterval;
20 _rss = GetRSS();
21 _tickTimer = new Timer(new TimerCallback(CheckDependencyCallback),
22 this, _timeInterval * 1000, _timeInterval * 1000);
23 }
24
25 private XPathNavigator GetRSS()
26 {
27 XPathDocument rssDoc = new XPathDocument(_feed);
28 return rssDoc.CreateNavigator();
29 }
30
31 public void CheckDependencyCallback(object sender)
32 {
33 BlogCacheDependency bcd = sender as BlogCacheDependency;
34 XPathNavigator newRSS = GetRSS();
35 if (newRSS.OuterXml != _rss.OuterXML)
36 {
37 bcd.NotifyDependencyChanged(bcd, EventArgs.Empty);
38 }
39 }
40
41 protected override void DependencyDispose()
42 {
43 _tickTimer = null;
44 base.DependencyDispose();
45 }
46}
47
48
這裡,BlogCacheDependency 的構造函數中使用 _tickTimer 實現了一個定時檢查更新的機制,它根據設定的時間間隔去調用 CheckDependencyCallback 方法。
而 CheckDependencyCallback 方法則將兩個 RSS 信息進行比較,如果不同,則調用 NotifyDependencyChanged 方法通知基類,相應的緩存依賴已經發生了變化,緩存中的數據應當被清除。
二、頁面設計
下面是頁面代碼(有刪節),其中顯示了 BlogCacheDependency 的使用方法:
1<script runat="server">
2 protected void Page_Load(object sender, EventArgs e)
3 {
4 string feed = "http://www.cnblogs.com/RSS.ASPx";
5 if (Cache[feed] == null)
6 {
7 BlogCacheDependency bcd = new BlogCacheDependency(feed, 600);
8 Cache.Insert(feed, bcd.RSS, bcd);
9 Label1.Text = "當前數據為剛剛獲取,並已更新入緩存!";
10 }
11 else
12 {
13 Label1.Text = "當前數據系從緩存中取得!";
14 }
15 RssXml.XPathNavigator = Cache[feed] as System.XML.XPath.XPathNavigator;
16 RssXML.TransformSource = "translate.xsl";
17 }
18</script>
19
20<body>
21 <form id="form1" runat="server"> ; 22 博客園最新貼子:
23 <br />
24 <ASP:Xml ID="RssXML" runat="server" />
25 <br />
26 <ASP:Label ID="Label1" runat="server" ForeColor="red" />
27 </form>
28</body>
29
本例中設定的訪問博客園首頁最新貼子列表,時間間隔為600秒,即每10分鐘檢查一次更新情況。
幾個值得注意的地方:
1、注意使用的 RssXml.XPathNavigator 屬性,有人可能奇怪為什麼不用 RssXML.Document 呢?實際上 Document 屬性在 .Net 2.0 中已廢除,推薦用來替代的是 XPathNavigator 屬性,從前面的 BlogCacheDependency 類中可以看到,它是來源於 XPathDocument.CreateNavigator() 所創建的,從 MSDN 我們可以知道,XPathDocument 類提供一種只讀的快速緩存,顯然就這個例子而言確實更加適合。
2、考慮一下,BlogCacheDependency 類中的 DependencyDispose 方法作何用?它與 Dispose 方法有何區別?讓我們想一想,如果說某一次檢查更新時,已經發現依賴變化了,但是卻一直沒有再次發送請求,那麼這時會不會始終連續不斷按間隔地執行 CheckDependencyCallback 方法呢?如果真的如此的話,那豈不是完全多余,因為只要查到一次有變化就不必再查了嘛。而如果我們進行跟蹤或是記錄日志的話可以發現,實際上只要查到依賴變化以後就不會再次 Check 了。奧妙在哪裡?想一想就能知道 NotifyDependencyChanged 方法大有玄機,而且之所以會有 DependencyDispose 方法的原因其實也就在這裡。其中的設計思想,值得我們細細品味吧。
三、頁面使用到的 translate.xsl
不再多說,貼出主要代碼:
1<xsl:template match="channel">
2 <div>
3 <xsl:for-each select="item">
4 <a>
5 <xsl:attribute name="href">
6 <xsl:value-of select="link"/>
7 </xsl:attribute>
8 <xsl:value-of select="title"/>
9 </a>
10 <br />
11 </xsl:for-each>
12 </div>
13</xsl:template>
四、執行情況
這是最初執行的截圖:
當博客園首頁沒有出現新貼子的時候,我們刷新頁面,總是可以得到如下的頁面:
而一旦有了新貼子,那麼刷新的時候出現的是上一張圖。
五、你想再高級一點嗎?
如果你和我一樣懶或是比我更懶,那麼你可以考慮再用 Javascript 寫一個自動刷新頁面的小功能,再把頁面美工一下,或是包裝成一個可復用的組件用在你的網站上,又或者只是想在本機裡做一個“我最關注的內容集”之類的東西?嗯,想來效果會比較不錯的哦。