首先简单叙述一下问题的由来,由于数据的获取是通过调用相应的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;