程式範例 - 耗時匯出作業之檔案下載
0 |
同事提問,某報表匯出作業執行很耗時,長達數十秒到一分鐘,為避免使用者分不清作業是否在執行中陷入焦慮(或狂點滑鼠猛按 F5,你懂的), 打算在下載匯出檔過程顯示下載中動畫,但要如何在檔案下載完成時精準結束動畫是個問題。
這個需求用 AJAX 不難解決,當下我便提供了建議。不過,身為程式魔人光用嘴寫程式總覺不夠踏實, 回家後手癢難耐,最後還是寫出會跑的範例才甘心,哈!
先看執行效果。
按鈕後網頁出現蓋版載入中動畫(這裡用的是 busy-load 套件), 後端模擬的匯出檔案動作刻意延遲 3-5 秒才傳回檔案,執行完成時立即關閉動畫下載檔案。
現在來看程式碼。前端很簡單,Button 點擊時以 jQuery.post 呼叫 Export Action, AJAX 呼叫之前執行 .busyLoad("show", { spinner: "accordion" }) 顯示載入中動晝。 Export 若出錯會傳回以 ERROR 起首的錯誤訊息,以 alert() 顯示之。 Export 執行成功回傳的則是下載檔案用的唯一序號(格式為 GUID), 使用以前介紹過的 IFrame 下載檔案技巧以 Download?token=檔案序號 載回檔案。 $.post() 不管成功失敗,在 .always() 時都會關閉載入中動畫。
Index.cshtml
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<link href="~/lib/busy-load/busy-load.min.css" rel="stylesheet" />
<style>
html,body { height: 100%; font-size: 10pt; }
</style>
</head>
<body>
<div>
<button id="btnExport">匯出檔案</button> (耗時3-5秒)
</div>
<script src="~/lib/jquery/jquery.min.js"></script>
<script src="~/lib/busy-load/busy-load.min.js"></script>
<script>
$("#btnExport").click(function () {
$("body").busyLoad("show", { spinner: "accordion" });
$.post("@Url.Action("Export")").done(function (res) {
if (res.indexOf("ERROR") > -1) {
alert(res);
}
else {
var url = "@Url.Action("Download")?token=" + res;
//REF: https://blog.darkthread.net/blog/ajax-download-with-iframe/
var frm = $("<iframe style='display:none' />");
frm.attr("src", url);
frm.appendTo("body");
frm.on("load", function () {
//if the download link return a page
//load event will be triggered
alert("Error while downloading " + url);
});
}
}).always(function () {
$("body").busyLoad("hide");
});
});
</script>
</body>
</html>
伺服器端程式(HomeController.cs)如下,有三個 Action,Index 僅用於帶出 View;Export 內部以亂數延遲 3-5 秒再產生一個模擬檔案及 Guid token, 以 token 為 Key 存入 MemoryCache (保留一分鐘,逾時未取作廢);Download 時接收 token 參數,從 MemoryCache 取出檔案傳回。
using System;
using System.Runtime.Caching;
using System.Text;
using System.Web.Mvc;
namespace SlowExport.Controllers
{
public class HomeController : Controller
{
// GET: Home
public ActionResult Index()
{
return View();
}
class FileData
{
public string Name;
public byte[] Content;
}
static MemoryCache cache = MemoryCache.Default;
public ActionResult Export()
{
try
{
//裝忙,Delay 3-5 秒再傳回結果
var rnd = new Random();
System.Threading.Thread.Sleep(rnd.Next(2000) + 3000);
//胡亂生個檔案
var file = new FileData
{
Name = $"Hello{DateTime.Now:mmssfff}.txt",
Content = Encoding.UTF8.GetBytes("Hello World!")
};
//產生取檔Token
var token = Guid.NewGuid();
//存入Cache待下載(限一分鐘內有效)
cache.Add(token.ToString(), file, DateTime.Now.AddMinutes(1));
return Content(token.ToString());
}
catch (Exception ex)
{
return Content("ERROR:" + ex.Message);
}
}
public ActionResult Download(string token)
{
var file = cache[token] as FileData;
if (file == null) return HttpNotFound();
cache.Remove(token);
return File(file.Content, "application/octet-stream", file.Name);
}
}
}
練功完畢。
A sample code to provide better export and download experience with AJAX call and loading animation.
Comments
Be the first to post a comment