FineUIPro v3.5.0釋出了,減少 90% 的上行資料量,15行程式碼全搞定!

三生石上(FineUI控制元件)發表於2017-02-26

一切為客戶著想

一切的一切還得從和一位臺灣客戶的溝通說起:

客戶提到將ViewState儲存在伺服器端以減少上行資料量,從而加快頁面的回發速度。

 

但是在FineUI中,控制元件狀態都儲存在FState中,並且為了減少下行資料量,FState的資料不僅用來儲存狀態,而且用於JavaScript的資料來源。

所以FState必須寫入HTTP響應,才能被JavaScript使用。我在之前的一篇文章中曾詳細闡述:http://www.cnblogs.com/sanshi/archive/2013/01/08/2850459.html

 

但這的確是個問題,FState雖然能夠減少下行資料量,但是頁面回發時上行資料量依然很大,特別是頁面中有多個下拉選單和表格時,上行的資料量可達到500K或者更多,對網速受限的環境的確是個挑戰。

 

FineUI(專業版)v3.5.0將提供一種簡單的方法,將FState儲存在伺服器端,從而大幅減少頁面回發時的上行資料量。

 

注:FState仍然需要返回到頁面上(以便JavaScript使用,比如作為表格和下拉選單的資料來源),只不過不需要回發到伺服器而已。

 

減少 90% 的上行資料

作為對比,我們以下面的表格頁面為例:http://fineui.com/demo_pro/iframe/grid_iframe.aspx

 

首先選中一項,然後點選工具欄上的[刪除選中行]按鈕,此時會觸發按鈕點選事件,我們分別看下v3.3.0和v3.5.0中上傳資料量。

FineUI(專業版)v3.3.0:

 

FineUI(專業版)v3.5.0:

 

FineUI(專業版)v3.3.0上行資料量:32616bytes =  31.9K

FineUI(專業版)v3.5.0上行資料量:2137bytes =  2.1K

 

從上面資料來看,FineUI(專業版)v3.5.0上行資料量減少了 90% 左右。在一個包含多個表格和下拉選單的頁面,這個效果會更加明顯。

 

自定義編碼實現

雖然FineUI(專業版)v3.5.0提供了將FState儲存到伺服器端的方法,但不是預設就支援的,需要自己寫點程式碼。因為儲存到伺服器有多種方法,可以儲存到伺服器檔案中,HttpRuntime.Cache,SqlServer或者NoSQL資料庫(比如Memcached,Redis)。

 

官網示例的PageBase.cs中提供了兩種儲存方式:檔案、HttpRuntime.Cache,你可以直接複製程式碼到自己的專案中去。

現在來看下儲存到伺服器快取中(HttpRuntime.Cache)的方法:

1. 啟用PageManager的EnableFStatePersistence,並定義代理函式LoadFStateFromPersistenceMedium和SaveFStateToPersistenceMedium。

protected override void OnInit(EventArgs e)
{
    var pm = PageManager.Instance;
    if (pm != null)
    {
        // FState儲存到伺服器快取
        pm.EnableFStatePersistence = true;
        pm.LoadFStateFromPersistenceMedium = LoadFStateFromPersistenceMedium_Cache;
        pm.SaveFStateToPersistenceMedium = SaveFStateToPersistenceMedium_Cache;
        
    }
}

頁面級別的支援,方便我們控制哪些頁面啟用,哪些頁面可以不啟用。

 

2. 獲取和儲存FState的代理函式

private static readonly string FSTATE_CACHE_KEY = "__FSTATE_KEY";

private JObject LoadFStateFromPersistenceMedium_Cache()
{
    string cacheKey = Page.Request.Form[FSTATE_CACHE_KEY];

    var fstate = HttpRuntime.Cache[cacheKey] as JObject;

    if (fstate != null)
    {
        // 快取使用過一次後,5s後過期(用不到了)
        HttpRuntime.Cache.Insert(cacheKey, fstate, null,
           DateTime.Now.AddSeconds(5),
           System.Web.Caching.Cache.NoSlidingExpiration,
           System.Web.Caching.CacheItemPriority.Default, null);
    }
    
    return fstate;
}

private void SaveFStateToPersistenceMedium_Cache(JObject fstate)
{
   // 1. 生成快取Key = 使用者會話ID+當前時間
string cacheKey = String.Format("{0}_{1}", HttpContext.Current.Session.SessionID, DateTime.Now.Ticks.ToString());
   // 2. 將頁面的FState儲存到快取中,指定過期時間為當前使用者會話過期時間(Session.Timeout)
// 指定時間後過期(Session.Timeout) HttpRuntime.Cache.Insert(cacheKey, fstate, null, DateTime.Now.AddMinutes(HttpContext.Current.Session.Timeout), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, null);
// 3. 將快取Key儲存到頁面的一個隱藏欄位中,透過PageContext.RegisterStartupScript來註冊一段指令碼 PageContext.RegisterStartupScript(String.Format(
"F.setHidden('{0}','{1}');", FSTATE_CACHE_KEY, cacheKey)); }

 

這裡面的實現方式和在伺服器端儲存ViewState的方式類似。

 

官網示例中也有儲存到伺服器端檔案的實現,可以參考:

private static readonly string FSTATE_FILE_KEY = "__FSTATE_KEY";
private static readonly string FSTATE_FILE_BASE_PATH = "~/App_Data/FState/";

private JObject LoadFStateFromPersistenceMedium()
{
    string filePath = GetFStateFilePath();
    string fileContent;
    using (StreamReader sr = new StreamReader(filePath, System.Text.Encoding.UTF8))
    {
        fileContent = sr.ReadToEnd();
    }
    return JObject.Parse(fileContent);
}

private void SaveFStateToPersistenceMedium(JObject fstate)
{
    string filePath = GenerateFStateFilePath();
    using (StreamWriter streamW = new StreamWriter(filePath, false, System.Text.Encoding.UTF8))
    {
        streamW.Write(fstate.ToString(Formatting.None));
    }
}

private string GenerateFStateFilePath()
{
    DateTime now = DateTime.Now;
    string folderName = now.ToString("yyyyMMddHH");
    string fileName = String.Format("{0}_{1}",
        HttpContext.Current.Session.SessionID,
        now.Ticks.ToString());
        //Page.Request.Url.AbsolutePath.Replace(".", "_").Replace("/", "_"));

    string folderPath = Page.Server.MapPath(Path.Combine(FSTATE_FILE_BASE_PATH, folderName));
    if (!Directory.Exists(folderPath))
    {
        Directory.CreateDirectory(folderPath);
    }

    PageContext.RegisterStartupScript(String.Format("F.setHidden('{0}','{1}');", FSTATE_FILE_KEY, fileName));

    return folderPath + "/" + fileName + ".config";
}

private string GetFStateFilePath()
{
    string fileName = Request.Form[FSTATE_FILE_KEY];
    string[] fileNames = fileName.Split('_');
    string folderName = new DateTime(Convert.ToInt64(fileNames[1])).ToString("yyyyMMddHH");

    return Page.Server.MapPath(Path.Combine(FSTATE_FILE_BASE_PATH, folderName)) + "/" + fileName + ".config";
}

 

當然你也可以方便的擴充套件到外部資料庫(SqlServer,MySql)以及NoSQL資料庫(Memcached,Redis)。

 

使用建議

雖然EnableFStatePersistence可以極大的減少上行資料量,但並不適合於所有的場合。因為每個使用者訪問的每個頁面都可能會產生大量的FState,所以在服務端儲存意味著對伺服器資源的極大消耗(記憶體、檔案或者資料庫)!

有一個簡單的使用原則:

僅在上行頻寬有限(也即是伺服器成本低於網路傳輸成本)時使用,否則適得其反。

 

在 stackoverflow 上有一個帖子,描述了將 ViewState 儲存到伺服器端可能存在的問題。

啟用 FineUI 的 EnableFStatePersistence 適用於同樣的原則(引文中的VS指的是ViewState):

  1. If the application recycles, the VS for all anyone using the application is lost.

  2. It increases the memory consumption of the application. This isn't an issue if there are only a few apps hosted on a server; but there are cases when there could be many websites hosted on one box.

  3. Scalability; the more active the application, the more VS needs to be stored. And you can't assume 1-1 (1 user - 1 VS). A user can have multiple tabs open, can go back, leave tabs inactive, etc... which leads to:

  4. How long do you store VS? Keeping the data encoded on the page ensures that it'll still be there if the user leaves the site open for a while.

  5. What happens if you're hosted on a web farm. We can't guarantee that the user will hit the same machine on each request.

 

 

官網示例:http://fineui.com/demo_pro/

 

 

相關文章