我目前有一个ASP.NET Core 2.1 API端点,用户可以点击以下载CSV文件:
[HttpPost("Gimme")]
public IActionResult Gimme([FromBody] MyOptions options)
{
MemoryStream reportStream = _reportGenerator.GenerateReportStream(options.StartDate, options.EndDate);
return new FileStreamResult(reportStream, "text/csv") { FileDownloadName = "report.csv" };
}
我可以通过POSTMan测试它,它返回一个包含正确数据的CSV,没问题。
输入Angular。我们与API交互的网站使用Angular-Driven单页应用程序。我已经搜索了有关如何处理来自API端点的文件的各种方法,并且很多似乎都围绕着获取Blob并创建内联URL,然后通过JavaScript中的window.open()
导航到该内联URL。或者创建内存中的a
标签然后调用click()
。
我的问题:最新的Angular真的无法处理开箱即用的问题吗?我不是Angular专家,但我认为在their site上有一个例子,或者有一些内置机制可以将下载的文件提供给浏览器。然而,似乎只涉及很多hackery。
我目前的WIP解决方案确实将文件返回到浏览器进行下载,但无论API中指定的文件名如何,都将其作为临时文件名(一些看起来像GUID的东西)下载:
以下是我在Component和Service类中的内容。我更新了downloadFile()
方法,以使用this SO answer中提供的答案来获得友好的文件名,但我仍然宁愿找到一个不那么hacky的解决方案。
// Component
DownloadReport(options: ReportOptions) {
this._service.getCSV(options).subscribe(data => this.downloadFile(data));
}
downloadFile(blob: Blob) {
const fileName = 'report.csv';
if (navigator.msSaveBlob) {
// IE 10+
navigator.msSaveBlob(blob, fileName);
} else {
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', fileName);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
}
// Service
getCSV(reportOptions: ReportOptions): Observable<any> {
let headers = new HttpHeaders();
headers = headers.set('Accept', 'text/csv');
return this._httpClient
.post(`${this.apiRoot}/Gimme`, reportOptions, { headers: headers, responseType: 'blob' })
.catch(this.handleError);
}
正如你所看到的,我目前正在实施createObjectURL
hack来实现这一功能(一种Found On Internet解决方案)。有没有更好的办法?一种“最佳实践”的方式?
这就是我使用的。这与你使用的基本相同,但是使用Anchor打字看起来有点干净。我研究了正确的方法来做这个很长一段时间,并找不到一个非hacky方式来做到这一点。
download(): void {
this.service.getFile({ id: id, name: name })
.subscribe(data => {
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
//save file for IE
window.navigator.msSaveOrOpenBlob(data, name);
} else {
const objectUrl: string = URL.createObjectURL(data);
const a: HTMLAnchorElement = document.createElement('a') as HTMLAnchorElement;
a.href = objectUrl;
a.download = name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(objectUrl);
}
});
}