4

為 js/css 加上自動版本參數防止 Cache 惹禍 - WebForm 版

 9 months ago
source link: https://blog.darkthread.net/blog/asp-append-version-for-webform/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

為 js/css 加上自動版本參數防止 Cache 惹禍

.js/.css 換版後,想防止網頁讀取 Cache 強迫改用新版,最無敵的做法是在網址加個 ?v=XXX 參數,每次換版一併更新,URL 參數不同讓原 Cache 失效,即可確保萬無一失。

但手工維護 ?v=... 參數意味著每次換版必須叫出所有引用該 .js/.css 的網頁,一一修改 URL ?v= 參數,算不上什麼高明的主意。有個巧妙解法是由系統讀取 .js/.css 內容產生雜湊當成 v 參數,如此只要檔案內容異動,v 參數便自動更新,永遠不必擔心換版後還讀到 Cache 這種鳥事。

要實現自動版本參數,ASP.NET Core 有內建 asp-append-version Tag,針對 ASP.NET MVC 我也寫過類似機制:

最近維護古蹟踩到相同問題,卻發現軍火庫沒有兵器可用,少了 WebForm 版的自動版本參數機制。老系統說不定還得再戰十年,還是花點時間解決吧!

於是我寫了一顆 WebControl 來解決問題,網站還是 ASP.NET 3.5 無 MemoryCache 可用,改用 System.Web.Cache。為提升效率,以檔案絕對路徑當 Key 快取內容雜湊值,避免重複運算。System.Web.Cache 老歸老,還是有 CacheDependency(string filename) 可針對檔案建立依賴,能自動偵測檔案修改觸發快取更新,十分方便。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Web;
using System.Web.Hosting;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace FrontEndPack
{
    public class IncludeResWithVersion : WebControl
    {
        public string Src
        {
            get { return (string)ViewState["src"]; }
            set { ViewState["src"] = value; }
        }

        public string GetFileHash(string src)
        {
            var cache = HttpContext.Current.Cache;
            var path = HttpContext.Current.Server.MapPath(src);
            if (!File.Exists(path)) return string.Empty;
            string cacheKey = "_res_hash__" + path;
            if (cache[cacheKey] != null) return cache[cacheKey] as string;
            using (SHA256 sha256 = SHA256.Create())
            {
                var hash = HttpServerUtility.UrlTokenEncode(
                    sha256.ComputeHash(File.ReadAllBytes(path)));
                cache.Add(cacheKey, hash, 
                    // 指定快取依賴,檔案異動時自動更新快取
                    new System.Web.Caching.CacheDependency(path),
                    DateTime.MaxValue, TimeSpan.FromMinutes(10),
                    System.Web.Caching.CacheItemPriority.Normal, null);
                return hash;
            }
        }

        protected override void Render(HtmlTextWriter writer)
        {
            var src = Src.Split('?').First();
            var hash = GetFileHash(src);
            var ext = Path.GetExtension(src);
            writer.Write("\n");
            switch (ext) {
                case ".js":
                    writer.Write(string.Format(@"<script src=""{0}?v={1}""></script>", src, hash));
                    break;
                case ".css":
                    writer.Write(string.Format(@"<link href=""{0}?v={1}"" rel=""stylesheet"" />", src, hash));
                    break;
                default:
                    throw new Exception("File type " + ext + " not supported.");
            }
            writer.Write("\n");
        }
    }
}

將 IncludeResWithVersion.cs 放進 App_Code 目錄,要使用的網頁先加上 <%@Register Namespace="FrontEndPack" TagPrefix="fep" %> 註冊,接著將原本的 <script src="test.js"></script> 改成 <fep:IncludeResWithVersion runat="server" Src="./test.js" /> 即可。

寫個簡單網頁驗證。用 <fep:IncludeResWithVersion runat="server" Src="./test.js" /> 載入 .js,寫一小段 JavaScript 顯示其產生的 URL 及 test.js 內容:

<%@Page Language="C#" %>
<%@Register Namespace="FrontEndPack" TagPrefix="fep" %>
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <fep:IncludeResWithVersion runat="server" Src="./test.js" />
</head>

<body style="width: 400px;">
    <span></span>
    <pre style="background: #ccc; padding: 6px;">
</pre>
    <script>
        let src = document.head.querySelector('script').src;
        document.querySelector('span').innerHTML = src;
        this.fetch(src)
            .then(response => response.text())
            .then(text => {
                document.querySelector('pre').innerHTML = text;
            });            
    </script>
</body>

</html>

將 test.js 第一行註解 Ver 1.0 改成 Ver 1.1,重新整理 ?v= 自動修改,成功!

Fig1_638360878982166590.png


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK