这个假期都在做一个综合ASP.NET MVC、Webapi、jquery、ajax、easyui等技术和框架的高速公路大数据分析网站项目。系统用json做前后端通信的数据格式,后端主要用Webapi处理业务逻辑,并返回一个定制的消息对象,前端就是jquery、ajax和easyui等呈现和相关处理。
前面的文件上传、参数传递、CRUD操作、数据集合返回给easyui等都做得十分顺利。最后,要输出报表了。使用NPOI组件在后端生成一个Excel报表,然后像image处理一样转换成base64字符串格式,通过Webapi返回给前端浏览器。Chrome和Firefox均能很好地支持直接在浏览器里打开下载窗口,见如下代码:
function DownloadFile(xlsBase64)
{
if (!document.getElementById("dwnf"))
{
$(document.body).append('<a href="#" id="dwnf" style="display: none"></a>');
}
var reportName = $('#ReportNames').val();
var fileName = reportName + '_' + GetPeriodNo() + '.xls';
var url = 'data:application/vnd.ms-excel;base64,' + xlsBase64;
document.getElementById("dwnf").href = url;
document.getElementById("dwnf").download = fileName;
document.getElementById("dwnf").click();
$('#dwnf').remove();
}
上面代码中,xlsBase64就是Excel文件转换成的base64字符串。
自己测试和网上公开的资料表明,Windows10自带的IE11和Edge均不支持data:base64的Excel文件下载(奇怪的是,IE支持image的base64格式显示!)。显然,得
再找其他处理方法了。
在后端下载一个文件,不管是用控制器还是Webapi,主要代码如下:
[HttpGet]
public HttpResponseMessage Download(string id)
{
HttpResponseMessage response = new HttpResponseMessage();
object xlsObj = this.MyCache.Get(id);
if (xlsObj == null)
{
response.StatusCode = System.Net.HttpStatusCode.NotFound;
return response;
}
TXlsFileStream xlsFile = xlsObj as TXlsFileStream;
response.Content = new StreamContent(xlsFile.XlsFileStream);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = xlsFile.FileName;
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); // 这句话要告诉浏览器要下载文件
return response;
}
}
上述代码中,XlsFileStream为一个MemoryStream,该流接收NPOI的Wookbook对象中的流数据(Wookbook可以直接Save到MemoryStream中),注意,其Position必须为0。
问题是,系统传输均是json格式数据,前端使用jquery的ajax,直接调用ajax将返回不正确的信息或其他异常结果,不会打开一个文件下载窗口。经过多次尝试,最后的解决方法是做两次请求访问:
- 第一次使用ajax,传递相关参数、请求一个报表文件,并在Cache中保留Excel文件流;
- 第二次,是在第一ajax结果返回后,如果success,则直接调用下面模拟<a>下载的脚本代码:
function Download(url, fileName)
{
if (!document.getElementById('dwnf'))
{
$(document.body).append('<a href="#" id="dwnf" style="display: none"></a>');
}
document.getElementById('dwnf').href = url;
document.getElementById('dwnf').download = fileName;
document.getElementById('dwnf').click();
$('#dwnf').remove();
}
上面函数中,url的字符串是Webapi的一般调用格式,如../Api/FileApi/Download?id=..。注意,不能省略临时创建的<a>的.download属性,否则IE时将获得不正确的格式(Chrome和Firefox可以省略这个.download = filename的属性赋值语句)。
获得的体验就是,为了兼容IE,需要多花好多的时间、多伤好多的神!另一个收获是,办法总比困难多!