在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 寫一個自動刷新頁面的小功能,再把頁面美工一下,或是包裝成一個可復用的組件用在你的網站上,又或只是想在本機裡做一個「我最關注的內容集」之類的東西?嗯,想來效果會比較不錯的哦。