[Asp.Net MVC]如何清除Asp.Net MVC Output Cache

為了增加網站的承載量,我們通常會在謹慎的在關鍵處加上Cache來避免高成本的開銷,(例如需要高CPU運算、大量的IO操作,但卻並不是隨時都需要動態獲得最新值之處),除了在程式碼中使用Cache之外,我們也常常會使用Asp.Net提供的Output Cache,它可以幫助我們將整個網頁包含Html做鏡像的Cache,來快速地回應使用者的Request,在使用Cache很重要的一點是所有的Cache都必須要能夠有效及時地進行清除或更新,才不會造成系統使用上的困擾。

前言

為了增加網站的承載量,我們通常會在謹慎的在關鍵處加上Cache來避免高成本的開銷,

(例如需要高CPU運算、大量的IO操作,但卻並不是隨時都需要動態獲得最新值之處),

除了在程式碼中使用Cache之外,我們也常常會使用Asp.Net提供的Output Cache,

它可以幫助我們將整個網頁包含Html做鏡像的Cache,來快速地回應使用者的Request,

在使用Cache很重要的一點是所有的Cache都必須要能夠有效及時地進行清除或更新,

才不會造成系統使用上的困擾。

實際演練

首先我們來很簡單設定一個頁面呈現目前的時間,並且加上OutputCache,

並建立一個方法用來清除OutputCache。

Step1. 先建立Asp.Net MVC的Routing規則

Global.asax


routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Step2. 建立一個簡單的Action, 有一個參數id, 並且會顯示目前的時間

HomeController.cs


public class HomeController : Controller
{
    public ActionResult Index(int id)
    {
        return View(id);
    }
}

Index.cshtml


@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
@model int

<h2>Index</h2>

ID: @Model <br />

DateTime: @DateTime.Now.ToString()

Step3. 執行網址/Home/Index/1, 並重複更新網頁, 每次網頁上的時間都會不同

Step4. 加上Output Cache Attribute, 並設定根據id的不同, 分別做不同的Cache, Cache時間為60分鐘

HomeController.cs


[OutputCache(Duration=3600, VaryByParam="id")]
public ActionResult Index(int id)
{
    return View(id);
} 

Step5. 執行網址/Home/Index/1, 並重複更新網頁, 可以看到Cache有生效

Step6. 建立一個Action, 用來清除Output Cache, 已確保網站隨時能更新

HomeController.cs


public ActionResult RemoveCache(int id)
{
    //// Outputcache root
    var url = Url.Action("Index", "Home", new { id = id });

    //// Clean output cache by root
    HttpResponse.RemoveOutputCacheItem(url);

    return Content(string.Format("Clear Output Cache by Url {0} Success!", url));
}

Step7. 執行一次之後, 再執行/Home/RemoveCache/1, 再次執行網址, 可以看到OutputCache成功的被清除

特殊情境

通過上面的例子, 我們了解了如何清除OutputCache,

但我在線上網站實際操作的時候, 卻發現沒有這麼簡單, 讓我們來看看下個例子

我們在實際環境中, 不可能每個網站都是提供一個參數id, 也符合預設的Routing

例如我們在撰寫部落格的時候, 常常會使用名字加文章序號來當作參數

Step1. 建立一個Action來當作部落格, 提供兩個參數author, postname

並建立一個用來清除BlogCache的Action

HomeController.cs


[OutputCache(Duration = 3600, VaryByParam = "author;postname")]
public ActionResult Blog(string author, string postname)
{
    this.ViewBag.Author = author;
    this.ViewBag.PostName = postname;

    return View();
}  

public ActionResult RemoveBlogCache(string author, string postname)
{
    //// Outputcache root
    var url = Url.Action("Blog", "Home", new { author = author, postname = postname });

    //// Clean output cache by root
    HttpResponse.RemoveOutputCacheItem(url);

    return Content(string.Format("Clear Output Cache by Url {0} Success!", url));
}

Blog.cshtml


@{
    ViewBag.Title = "Blog";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Blog</h2>

Author: @ViewBag.Author 

PostName: @ViewBag.PostName 

DateTime: @DateTime.Now.ToString()

Step2. 執行網址/Home/Blog?author=kirk&postname=test, 再執行/Home/RemoveBlogCache?author=kirk&postname=test,

卻發現OutputCache並沒有被清除

Step3. 原來事情並沒有那麼單純, 清除OutputCache要直接指定OutputCache的Root才行,

並且它會清除所有關聯的Cache,包括VaryByParam的參數

而Root的祕密就在Global.asax的Routing設定中可以發現...

透過網址/Home/Blog?author=kirk&postname=test, 我們可以發現這個網址其實是符合Default這個Routing的,

但因為我們沒有id這個參數, 所以可想而知我們的Root網址應該為/Home/Blog,

我們重新修改清除OutputCache的Action如下


public ActionResult RemoveBlogCache()
{
    //// Outputcache root
    var url = Url.Action("Blog", "Home");

    //// Clean output cache by root
    HttpResponse.RemoveOutputCacheItem(url);

    return Content(string.Format("Clear Output Cache by Url {0} Success!", url));
}

Step4. 執行網址/Home/Blog?author=kirk&postname=test, 再執行/Home/RemoveBlogCache,

再次執行文章網址, 可以發現OutputCache確實的被清除了

聰明的你, 想必也在此時發現事情並不單純,

由於我們在清除OutputCache是直接針對/Home/Blog做清除,

所以不單單只是這篇文章, 而是系統中所有作者和所有文章的OutputCache都被清除了,

這跟我們原本想像的一點都不一樣, 我並不想一次清除所有文章的Cache, 只想要可以清除單篇文章的Cache就好

修改做法

重新在Global.asax中為我們的Action指定一組專屬的Routing,

來為每篇文章網址產生不同的Cache Root

Global.asax


routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
    name: "Blog",
    url: "{controller}/{action}/{author}/{postname}",
    defaults: new { controller = "Home", action = "Blog" }
);

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

重新改寫清除OutputCache的Action

HomeController.cs


public ActionResult RemoveBlogCache(string author, string postname)
{
    //// Outputcache root
    var url = Url.Action("Blog", "Home", new { author = author, postname = postname });

    //// Clean output cache by root
    HttpResponse.RemoveOutputCacheItem(url);

    return Content(string.Format("Clear Output Cache by Url {0} Success!", url));
}

分別執行網址, 並清除OutputCache在重新執行

/Home/Blog/Kirk/Test1

/Home/Blog/Jason/Test2

/Home/RemoveBlogCache/Kirk/Test1

/Home/Blog/Kirk/Test1

/Home/Blog/Jason/Test2

可以看到能夠針對單篇文章進行OutputCache的清除了,

妥善的指定Routing的網址對OutputCache來說也是相當重要的!

感想

當初在對於清除OutputCache的機制不夠熟悉時,

一直無法理解為什麼OutputCache的清除有時可以成功, 有時卻又總是失敗,

花時間測試過後才恍然大悟, 原來跟Routing的設定有相關聯性,

要清除該網址的OutputCache還需要找到對應的Routing Root是哪一個才能對症下藥,

理解之後也就不用擔心會有無法清除OutputCache的問題囉!