1. 程式人生 > >適合ASP.NET MVC的檢視片斷快取方式(上):起步

適合ASP.NET MVC的檢視片斷快取方式(上):起步

說到網站效能優化,沒有什麼比“快取”更重要了。即便是某些朋友口中念念不忘的“靜態頁”,說到底也只是快取了整張頁面內容而已。但是,顯然這樣大粒度的快取策略,在如今“牽一髮而動全身”的Web 2.0站點中幾乎是無法使用的。試想,在Twitter中的某個名人被數十萬人訂閱,那麼他發一條訊息,難道此時網站要去修改數十萬使用者的靜態頁面?因此,我們需要粒度更小的快取。而比“整頁快取”粒度小一號的快取,便是所謂“檢視片斷快取”了。

檢視片斷快取非常重要,因為它快取的也是頁面內容,這表示它比更低級別的快取更有效率,也比靜態頁等整頁內容快取的適用面要大得多。在ASP.NET WebForm模型中提供了控制元件級別的快取,我們可以為控制元件標記輸出快取策略,這樣控制元件便不會每次都完整執行一遍。當然這個策略還不夠靈活,因為它快取的最小單元是“控制元件”,而不是頁面中任意的部分。因此我在一年多前提出了一個

CachePanel,由它包裝的頁面內容都可以被快取,無論其內部是控制元件還是普通輸出的內容。在實際生產過程中,CachePanel起到了非常重要的作用,許多場景下只要在頁面中包裹一個<ext:CachePanel runat="server" />,效能立即就有了質的飛躍。

只可惜,在如今ASP.NET MVC的時代無法直接使用CachePanel這樣的伺服器端控制元件。因為CachePanel需要伺服器端程式碼的配合,而ASP.NET MVC中的頁面只是“檢視模板”,除了呈現之外就不應該有其他職責。因此,我們必須提出一種脫離於後端程式碼的“標記”方式,將檢視中的內容片斷進行隨意地快取。在

RailsDjango中都有類似的特性,但ASP.NET MVC甚至在2.0的Road Map中還沒有包含這一功能,於是我們只能自己動手豐衣足食。不過有了ASP.NET WebForm作為強大的檢視引擎,加這樣的功能簡直是舉手之勞:

public static class CacheExtensions
{
    public static string Cache(
        this HtmlHelper htmlHelper,
        string cacheKey,
        CacheDependency cacheDependencies,
        DateTime 
absoluteExpiration, TimeSpan slidingExpiration, Func<object> func) { var cache = htmlHelper.ViewContext.HttpContext.Cache; var content = cache.Get(cacheKey) as string; if (content == null) { content = func().ToString(); cache.Insert(cacheKey, content, cacheDependencies, absoluteExpiration, slidingExpiration); } return content; } }

我們為HtmlHelper增加了一個Cache擴充套件方法,接受一些快取引數(快取鍵,絕對過期時間,偏移過期時間),以及一個生成快取內容的Func<object>委託。Cache方法的邏輯非常簡單:首先根據快取鍵來獲取內容,如果存在則直接返回,否則即呼叫委託物件獲得新內容,並將其放入快取。這樣在快取命中的情況下,委託的開銷便可以節省下來了。

例如,我們可以使用這樣的程式碼進行測試:

Before Rendering:
<%= DateTime.Now %>

<br />

Rendering:
<%= Html.Cache("Now", null, DateTime.Now.AddSeconds(60), Cache.NoSlidingExpiration,
    () => { System.Threading.Thread.Sleep(5000); return DateTime.Now; }) %>

<br />

After Rendering:
<%= DateTime.Now %>

在實際情況中,我們是不會在程式碼中呼叫Thread.Sleep方法的,不過這裡我們需要模擬一段開銷,因此通過暫停當前執行緒來實現時間消耗。於是我們第一次開啟頁面:

Before Rendering: 2009/9/17 16:52:37 
Rendering: 2009/9/17 16:52:42 
After Rendering: 2009/9/17 16:52:42

從結果中可以看出,Before Rendering和After Rendering相差了5秒鐘,這就是Thread.Sleep(5000)的效果。但是如果您在60秒以內再次重新整理頁面,便可以看到快取的效果:

Before Rendering: 2009/9/17 16:52:55 
Rendering: 2009/9/17 16:52:42 
After Rendering: 2009/9/17 16:52:55

可以看出,Rendering階段顯示的還是剛才的時間,而Before Rendering和After Rendering是即時更新的。此外,由於Cache方法將Thread.Sleep(5000)的開銷節省了下來,因此Before Rendering和After Rendering兩個階段打印出的時間完全相同。

怎麼樣,簡單吧。不過您應該會感到疑惑,這不是我們想要的結果啊,我們想快取的是頁面上的一個片斷,但是現在必須將被快取的內容作為一個完整的字串輸出,那麼我們又該如何實現呢?難道我們要這麼寫嗎?

<%= Html.Cache(..., () => "<span style=\"color:red;\">" + Model.Title + "</span>") %>

當然不可能這樣。如果只是這樣的話,那麼這個Cache的可用性毫無疑問會非常低。因此,我們還需要尋找更好的解決方案——關於這點,我們下次再聊。而目前的Cache方法,最方便的輸出大端頁面內容的做法則是將內容放在一個Partial View中,然後使用Html.Partial方法輸出內容:

<%= Html.Cache(..., () => Html.Partial("MyPartialViewToCache")) %>

請注意,我們這裡使用的是擴充套件後的Partial方法,而不是自帶的RenderPartial。之前我們談過WebForm頁面的輸出方式,而RenderPartial方法是直接向Response.Output輸出頁面內容,因此我們無法將其捕捉為一個字串。不過,之前文章中的Partial方法是“山寨”版本,而符合“標準”的Partial方法實現已經包含在MvcPatch專案中。如果您感興趣的話,可以獲取它的原始碼並編譯。我這段時間在一部分一部分地將以前專案中較為通用的擴充套件及修改提取至MvcPatch中,希望可以使MvcPatch成為一個可複用的強大元件。

相關文章