首先簡單敘述一下問題的由來,由於數據的獲取是通過調用相應的WebService從其他系統獲取的,因此每次獲取的數據有限制,並且需要要滿足一次最多只能下載一定數量的記錄,若filter出來的數據大於這個最大值的時候,我們就要分批循環進行下載。由於每次從前台調用后台方法都是一次post-back過程,那么記錄上一次的下載位置以及整體的filter條件 就不能用簡單的局部變量進行記錄了。因此這里才用到了cookie進行記錄,並且后續的下載要自動的彈出popup,所以前台要簡單的寫個timer job,定時的檢測對應的cookie,若條件滿足那么彈出popup,若所有的數據都下載完成,那么關閉這個timer job。當然了第一次觸發timer job的時機我們可以選擇在用戶第一次點擊下載數據之后。
接下來完整簡單的敘述下載整體的設計流程:
前台:對JQuery進行擴展實現對Cookie的操作 --> 模擬timer job定時對相應的Cookie進行檢測
后台:定義四個Cookie值(filter的條件、上一次下載的位置、繼續下載、下載完成),前兩個Cookie是后台取具體數據時用到,后兩個是完成循環彈出popup用到 --> 根據第二個Cookie值的屬性判斷是否下載完成 --> 將第三個或者第四個Cookie添加到Response中 以提供前台的定時檢測
如下將主要的代碼段貼出:
1): 對JQuery進行擴展實現對Cookie的操作
/*! * jQuery Cookie Plugin v1.3.1 * https://github.com/carhartl/jquery-cookie * * Copyright 2013 Klaus Hartl * Released under the MIT license */ (function ($, document, undefined) { var pluses = /\+/g; function raw(s) { return s; } function decoded(s) { return unRfc2068(decodeURIComponent(s.replace(pluses, ' '))); } function unRfc2068(value) { if (value.indexOf('"') === 0) { // This is a quoted cookie as according to RFC2068, unescape value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); } return value; } function fromJSON(value) { return config.json ? JSON.parse(value) : value; } var config = $.cookie = function (key, value, options) { // write if (value !== undefined) { options = $.extend({}, config.defaults, options); if (value === null) { options.expires = -1; } if (typeof options.expires === 'number') { var days = options.expires, t = options.expires = new Date(); t.setDate(t.getDate() + days); } value = config.json ? JSON.stringify(value) : String(value); return (document.cookie = [ encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value), options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE options.path ? '; path=' + options.path : '', options.domain ? '; domain=' + options.domain : '', options.secure ? '; secure' : '' ].join('')); } // read var decode = config.raw ? raw : decoded; var cookies = document.cookie.split('; '); var result = key ? null : {}; for (var i = 0, l = cookies.length; i < l; i++) { var parts = cookies[i].split('='); var name = decode(parts.shift()); var cookie = decode(parts.join('=')); if (key && key === name) { result = fromJSON(cookie); break; } if (!key) { result[name] = fromJSON(cookie); } } return result; }; config.defaults = {}; $.removeCookie = function (key, options) { if ($.cookie(key) !== null) { $.cookie(key, null, options); return true; } return false; }; })(jQuery, document);
2): 在Client端模擬timer job定時對相應的Cookie進行檢測(每2秒鍾檢測一次)
//================================================= // export activity monitor //================================================= var downloadJob; var exportCount = 0; function checkDownload() { clearInterval(downloadJob); downloadJob = setInterval(processDownloadStatus, 2000); //check cookie each 2 second } function closeDownload() { clearInterval(downloadJob); downloadJob = "undefined"; //mark } function processDownloadStatus() { var c1 = $.cookie("TradeDetailsExport-MoreExport"); if (c1 != null && c1 != 'undefined' && c1 != '') { _spFormOnSubmitCalled = false; //continue post-back on SharePoint exportCount++; ExportMorePopUpClick(exportCount); //popup function $.cookie("TradeDetailsExport-MoreExport", null, { path: '/' }); return; } var c2 = $.cookie("TradeDetailsExport-DoneExport"); if (c2 != null && c2 != 'undefined' && c2 != '') { if ($.colorbox != null && $.colorbox != "undefined") { $.colorbox.close(); } exportCount = 0; _spFormOnSubmitCalled = false; //continue post-back on SharePoint $.cookie("TradeDetailsExport-DoneExport", null, { path: '/' }); setNoRecordText(); closeDownload(); return; } }
3): 在Server端操作Cookie並且實現數據的下載
public static void Export(int rows, bool needCookie, bool moreExport, Collection<string> columns) { HttpContext context = HttpContext.Current; System.Web.Script.Serialization.JavaScriptSerializer oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer(); TradeDetailsRequest request = null; // class for query.... TradeDetailsCursor tradeDetailsCursor = null; // class for mark download last key... if (context.Request.Cookies[TradesConstants.TRADE_DETAIL_REQUEST] != null) { request = oSerializer.Deserialize<TradeDetailsRequest>(DecodeCookie(context, TradesConstants.TRADE_DETAIL_REQUEST)); } if (context.Request.Cookies[TradesConstants.TRADE_DETAIL_CURSOR] != null) { tradeDetailsCursor = oSerializer.Deserialize<TradeDetailsCursor>(DecodeCookie(context, TradesConstants.TRADE_DETAIL_CURSOR)); } //// No request provided - return error if (request == null) { // to-do: should redirect to an error page context.Response.ContentType = "text/plain"; context.Response.Write("Error: No request found."); context.Response.End(); } //// Last Request does not match current - cannot be resuming //if (!request.Equals(lastRequest)) if (!moreExport) { tradeDetailsCursor = null; } //// Retrieve Trade Items TradeItems tradeItems = TradeDetailsFacade.GetTradeDetailRequest(request, rows, tradeDetailsCursor); // get data item based on query and last time key /// Prepare to respond With a CSV file context.Response.Clear(); bool overLimit = tradeItems.LastKey.LastRecordStatus != TradesConstants.NO_MORE_RECORD_INDICATOR; // have more data to download next time if (needCookie) { HttpCookie exportCookie = null; if (overLimit) { exportCookie = new HttpCookie(TradesConstants.MORE_EXPORT); exportCookie.Value = "1"; // arbitrary data; not used in code logic } else { exportCookie = new HttpCookie(TradesConstants.NO_MORE_EXPORT); exportCookie.Value = "0"; // arbitrary data; not used in code logic } exportCookie.Expires.AddHours(1); exportCookie.HttpOnly = false; context.Response.Cookies.Add(exportCookie); } // Set Cursor Cookie HttpCookie tradeDetailsCursorCookie = new HttpCookie(TradesConstants.TRADE_DETAIL_CURSOR); tradeDetailsCursorCookie.Value = Encryption.EncryptString(oSerializer.Serialize(tradeItems.LastKey), TradesConstants.ENCRYPTION_KEY); tradeDetailsCursorCookie.Path = "/"; tradeDetailsCursorCookie.Expires.AddHours(1); context.Response.Cookies.Add(tradeDetailsCursorCookie); // if there are no columns, use the default set. if (columns.Count < 1) { columns = TradesConstants.AllTradeColumnFieldNames(); } // do not buffer the output! string filename = TradesConstants.DEFAULT_EXPORT_FILENAME; context.Response.BufferOutput = false; context.Response.ContentType = "text/csv"; context.Response.AddHeader("Content-Disposition", "attachment; filename=" + filename); // iterate over trade entities for (int i = 0; i < tradeItems.Trades.Count; i++) { // create the csv line, will need to pull the list from query string context.Response.Write(tradeItems.Trades[i].GetCsvFormattedEntity(columns)); } context.Response.End(); context.Response.Flush(); } private static string DecodeCookie(HttpContext context, string cookieName) { string encodedString = context.Request.Cookies[cookieName].Value; return Encryption.DecryptString(encodedString, TradesConstants.ENCRYPTION_KEY); }
4): 一些用到的常量聲明
/// <summary> /// This class provides constants and mappings of constants for the RTTA aspect of FNX /// </summary> public static class TradesConstants { /// <summary> /// default export filename. /// </summary> public const string DEFAULT_EXPORT_FILENAME = "TradeDetails.csv"; ///Export completion indicator. /// </summary> public const string NO_MORE_RECORD_INDICATOR = "C"; ///More Export cookie name. /// </summary> public const string MORE_EXPORT = "TradeDetailsExport-MoreExport"; ///No more Export cookie name. /// </summary> public const string NO_MORE_EXPORT = "TradeDetailsExport-DoneExport"; /// <summary> /// Trade detail cursor cookie name. /// </summary> public const string TRADE_DETAIL_CURSOR = "TradeDetailsExport-TradeDetailsCursor"; /// <summary> /// Trade detail request cookie name. /// </summary> public const string TRADE_DETAIL_REQUEST = "TradeDetailsExport-Request"; /// <summary> /// Trade detail last request cookie name. /// </summary> public const string TRADE_DETAIL_LAST_REQUEST = "TradeDetailsExport-LastRequest"; }
ps:如果此行為是應用在SharePoint中的話,那么要修改對應的屬性,來屏蔽postback的失效。 即:第二段代碼中的 _spFormOnSubmitCalled = false;