在服务器上动态生成文件并提供下载时,如果这个过程需要一些时间,比如几秒钟,希望给用户一些反馈,比如显示“请稍候...”的消息和一个漂亮的加载动画。使用JavaScript在用户点击下载链接或按钮并发送请求到服务器时显示这样的动画是很容易的。然而,当服务器直接以文件形式响应下载请求时,没有办法挂钩到这个事件并隐藏动画。
将展示一个巧妙的技巧,可以在下载准备就绪并且浏览器显示下载对话框时立即隐藏动画。这个技巧利用了cookies:就在服务器开始将生成的文件写入响应流之前,它会设置一个cookie。这个cookie作为响应的HTTP头部的一部分发送到浏览器,并且一旦浏览器接收到响应的第一个字节,它就立即可用。
Cookies也可以通过JavaScript读取。所做的是启动一个计时器,在下载链接/按钮被按下时轮询这个cookie的存在。这个计时器在请求运行时继续工作。一旦找到了cookie,知道下载已经准备好了,可以隐藏动画了。
让先来看一下客户端代码。这是MVC视图:
@using (Html.BeginForm(
"Download",
"Home")
{
@Html.Hidden("cookieValue")
}
有一个表单,它将发出HTTP POST并调用名为"Home"的控制器中的"Download"操作。在表单中有一个隐藏字段和一个按钮,开始下载。
JavaScript代码如下: 使用jQuery,为下载按钮附加了一个点击事件。如果这个按钮被点击,生成一个基于当前时间的唯一键,并将这个键存储到隐藏字段中。然后通过向HTML body添加CSS类"loading"来显示加载动画(这篇文章的范围之外,因为有100种方法可以显示加载动画)。接下来启动一个计时器,它每100毫秒触发一个事件。在那个事件中读取当前URL的所有cookies。"document.cookie"返回一个包含所有cookie名称和值的字符串,用分号字符分隔。检查这个字符串是否包含一个名为"dlc"的cookie,并且值与之前存储在隐藏字段中的唯一键匹配。
如果找到了这样的cookie,加载动画就会再次被隐藏。
现在让看看服务器端代码。这是HomeController类的Download方法:
[HttpPost]
public FileResult Download(string cookieValue)
{
// 等待10秒钟以模拟长时间运行的操作
System.Threading.Thread.Sleep(10000);
// 创建一个虚拟文本文件进行下载
StringBuilder sb = new StringBuilder();
sb.AppendLine("This is a demo file that has been generated on the server.");
sb.AppendLine();
for (int i = 1; i <= 1000; i++)
{
sb.AppendLine("Line " + i.ToString() + " " + DateTime.Now.ToString());
}
// 添加一个名为'dlc'的cookie和来自回传的值
ControllerContext.HttpContext.Response.Cookies.Add(new HttpCookie("dlc", cookieValue));
return File(Encoding.ASCII.GetBytes(sb.ToString()), "text/plain", "data.txt");
}
隐藏字段的值自动传递到方法的cookieValue参数中。为了模拟耗时的文件生成,使用Thread.Sleep添加了10秒的延迟。同时生成了一个包含1000行文本的虚拟文本文件。
最重要的部分是向响应中添加一个cookie。使用了"dlc"作为cookie的名称,但任何名称都可以,只要在JavaScript代码中使用相同的名称。cookie的值将设置为cookieValue参数。
设置cookie后,虚拟文件作为FileResult返回。
值得注意的是,每次开始下载时,都要使用另一个唯一的值作为cookie。否则,如果有人再次开始相同的下载,它就不会工作,因为cookie已经存在了!