2013年1月16日 星期三

JavaScript與CSS Cache

我們在開發css或javscript,常常遇到一個問題;那就是瀏覽器的快取(Cache),快取本身的好處是不需要重新自伺服器取得檔案,也就是說以伺服器的角度來說,只有新的訪客發出Request時,才需要送一份新的網頁所需的資源(包含圖檔、音樂、影像等…),而拜訪過的訪客,瀏覽器會自動將這些資源檔存在快取中。如此一來,便可以減少伺服器的頻寬的消耗。

不過這樣的機制也帶來的代價,如果你的檔案更新時,在瀏覽器的快取仍未更新,將導致一些異常(比如CSS的圖片未更新或是Javascript的邏輯錯誤)。

我們可以用一些簡單的方法來避免這樣的情況。如:
1.更改javascript 或css的檔名(就像jQuery將版本號加入他的檔名一樣)
2.在import外部檔案時,加入一些像是QueryString的參數。

用一個簡單的範例重現這個問題:
首先,我們寫一個簡單的javascript檔,命名為「js_cached.js」
js_cached.js
function fun() {
    return "A";
}
再來,建立一支網頁來import這隻js檔。
default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="default.aspx.cs" Inherits="cache_default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="js_cached.js" type="text/javascript"></script>
    <script type="text/javascript">
        var cacheFile = fun();
    </script>
    <script type="text/javascript" src='<%=Tool.GetFileRefUrl("js_cached.js")%>' ></script>
    <script type="text/javascript">
        var lastFile = fun();
    </script>
</head>
<body>
    <form>
    <div>
    
        The cache file is :<script type="text/javascript">
                               document.writeln(cacheFile);
                        </script>
        <br />
        The last file is :<script type="text/javascript">
                              document.writeln(lastFile);
                        </script>    
    </div>
    </form>
</body>
</html>
上面這個網頁,會透過兩種方式載入「js_cached.js」,一個是普通的import,
另外一個則是動態的產生,並帶入參數。
送到client(也就是瀏覽器啦)的網頁html大概會長這樣。

    <script src="js_cached.js" type="text/javascript"></script>
    <script type="text/javascript">
        var cacheFile = fun();
    </script>
    <script type="text/javascript" src="js_cached.js?v=634933730146375000"></script>
    <script type="text/javascript">
        var lastFile = fun();
    </script>

結果畫面如下:




接下來 ,我們要讓cache這件事發生,但在這之前先修改我們的js檔
function fun() {
    return "B";
}
讓快取發生最簡單的方式為(測試用瀏覽器為chrome)
1.保留原本頁面
2.開啟新的分頁
3.鍵入原本頁面的URL
我們就會看到以下的畫面





如此一來,就能看到未帶參數的變數cacheFile被cache住,仍然為A,
而有帶參數的lastFile 值為B,正是server端的最新版本。

這是一個有效的防止js檔被cache住的方法,同樣的也可以在css或其它檔案中應用。
要注意的是,cache本身的機制有其用意在,在應用此方法時,應因時因地制易才對,避免途增伺服器的負擔。

最後提供本例的Tool.GetFileRefUrl Function
public class Tool
{
    public static string GetFileRefUrl(string url)
    {
        var ticks = GetLastWriteTime(url);
        var result = string.Format("{0}?v={1}", url, ticks);
        return result;
    }

    private static long GetLastWriteTime(string fileName)
    {
        var lastWriteTime = File.GetLastWriteTime(HttpContext.Current.Server.MapPath(fileName));
        return lastWriteTime.Ticks;
    }
}




參考文章:


2012年1月13日 星期五

XML序列化與反序列化

這次有機會處理一組頗大型的xml檔,資料很簡單確很大。在同事的指導下,學習了用序列化去處理這類的問題,並籍此學習序列化。
檔案的格式如下:
Candys.xml
<?xmlversion="1.0"?>
<Box>
     <Candy>
         <Taste>Chocolate</Taste>
     </Candy>
     <Candy>
         <Taste>Strawberry</Taste>
     </Candy>
     <Candy>
         <Taste>Milk</Taste>
     </Candy>
</Box>

當然資料量遠大於此範例;但是這個範例已經足夠讓我們作為序列化的參考範本了。
依上述的xml檔,我們寫了兩個類別如下:
    Box 類別
[Serializable]
    public class Box
    {
        private Candy[] candys = null;
        public Candy[] Candys
        {
            get { return candys; }
            set { candys = value; }
        }
    }
   Candy類別
    [Serializable]
    public class Candy
    {
        private String taste = string.Empty;
        public String Taste
        {
            get { return taste; }
            set { taste = value; }
        }
    }


接下來我們看主程式:
            {
                Stopwatch sw = new Stopwatch();
                FileStream f = new FileStream("./Candys.xml", FileMode.Open, FileAccess.Read);
                StreamReader sr = new StreamReader(f);
                sw.Start();
                string XmlStr = sr.ReadToEnd();
                //同事提供的反序列化範例
                XmlSerializer xs = new XmlSerializer(typeof(Box));
                MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(XmlStr));
                XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
                Box box = xs.Deserialize(memoryStream) as Box;
                //
                Console.WriteLine(box.Candys.Length);
                sw.Stop();
                Console.WriteLine("Test : {0}ms",
                    sw.ElapsedMilliseconds);
            }
            {
                Console.Read();
            }

很可惜,這樣的程式是不能夠執行的,必須對我們的xml檔作調整才有辦法執行。修正xml檔如下:
<?xmlversion="1.0"?>
<Box>
     <Candys>
          <Candy>
              <Taste>Chocolate</Taste>
          </Candy>
          <Candy>
              <Taste>Strawberry</Taste>
          </Candy>
          <Candy>
              <Taste>Milk</Taste>
          </Candy>
     </Candys>
</Box>

差別的地方在<Box> tag裡面再加入一組<Candys> tag雖然這個折中的方法可以解決當時的問題,但是實際上我的xml檔有近千個,每個檔案大小約2x M。所以後來還是研究了一下XML序列化與反序列化的規則。要怎麼設計出合適的類別來處理你的xml檔,而不是去修改xml檔(當然前提你的xml檔要真得符合xml的規則)。

這裡先賣個關子,我們來看看要如何修改類別,讓本來的xml檔就可以順利的反序化成物件。


Box類別
    [Serializable]
    public class Box
    {
        private Candy[] candys = null;
        ///
        [System.Xml.Serialization.XmlElementAttribute("Candy", Form =System.Xml.Schema.XmlSchemaForm.Unqualified)] 
        public Candy[] Candys
        {
            get { return candys; }
            set { candys = value; }
        }
    }
Candy類別
    [Serializable]
    public class Candy
    {
        private String taste = string.Empty;
        public String Taste
        {
            get { return taste; }
            set { taste = value; }
        }
    }
由結果可以得知,加入
[System.Xml.Serialization.XmlElementAttribute("Candy", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
這行後,就不再需要為xml檔額外的加入<Candys>這組tag了。
不過小弟並不是很了解它其中的意義與邏輯,還希望有高手能夠指點一番。

但是其實xml檔案可以很簡單,也可以很複雜,要辛辛苦苦手工打造出可以序列化的類別並不是那麼容易。參考這篇文章,http://stackoverflow.com/questions/364253/how-to-deserialize-xml-document
微軟提供非常好用的工具xsd.exe,作法也非常簡單,只要擁有相對應的xml檔與三個步驟,讓人可以輕鬆產出可序列化的類別。


1.      以vs2010為例,執行Visual Studio 的命令提示字元,它會在 Visual Studio Tools底下。開啟後,輸入以下指令,xsd.exe C:\Candys.xml ,會產生Candys.xsd檔。
2.      取得Candys.xsd後,我們執行xsd.exe C:\Candys.xsd /Classes,這個指令會產生我們所需要的類別檔。
3.      在專案中載入此類別檔,完成。(當然我已經將之前寫的Box和Candy類別自專案中移除)。
產生的類別檔如下,而且保證可以序列化/反序列化。
//------------------------------------------------------------------------------
// 
//     這段程式碼是由工具產生的。
//     執行階段版本:4.0.30319.239
//
//     對這個檔案所做的變更可能會造成錯誤的行為,而且如果重新產生程式碼,
//     變更將會遺失。
// 
//------------------------------------------------------------------------------

using System.Xml.Serialization;

// 
// 此原始程式碼由 xsd 版本=4.0.30319.1 自動產生。
// 


/// 
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
public partial class Box {
    
    private BoxCandy[] itemsField;
    
    /// 
    [System.Xml.Serialization.XmlElementAttribute("Candy", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public BoxCandy[] Items {
        get {
            return this.itemsField;
        }
        set {
            this.itemsField = value;
        }
    }
}

/// 
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
public partial class BoxCandy {
    
    private string tasteField;
    
    /// 
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string Taste {
        get {
            return this.tasteField;
        }
        set {
            this.tasteField = value;
        }
    }
}