My ASP.NET Core 2.1 API endpoint allows users to download a CSV file by hitting the "Gimme" route:
[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" };
}
Testing this endpoint through POSTMan shows that it returns the CSV file with correct data without any issues.
Now enters Angular. The web application we're using to interact with our API is an Angular-driven Single Page Application. Upon researching how to handle files coming from API endpoints in Angular, I found various methods involving creating Blobs and inline URLs for navigation using JavaScript functions like window.open()
or manipulating a
tags in-memory.
Here's where my question arises: Does the latest version of Angular not offer a built-in solution for handling this scenario? As someone who is not an expert in Angular, I expected there to be documentation or native mechanisms on Angular's website for serving downloaded files to browsers. It appears that current solutions tend to involve workarounds rather than direct support.
While my existing method successfully downloads the file with the specified name from the API, it saves as a temporary file with a GUID-based name instead:
https://i.sstatic.net/H4g2b.png
Included below are snippets from both my Component and Service classes. I've updated the downloadFile()
method based on a solution suggested in this Stack Overflow answer to address filename display concerns, but I'm still searching for a more elegant solution.
// 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);
}
Presently, I am resorting to the createObjectURL
workaround to achieve functionality (borrowed from random online sources). Is there a recommended "Best Practices" approach for this issue?