Search code examples
angularfile.net-corerxjsdownload

Download file (angular - dotnet)


please bare with me as I can't understand why any of this is happening.
I have an api endpoint to download a file at this url: https://localhost:7004/api/Files/download.
If I paste this URL into my browser and navigate to it, the file downloads properly.
However if I refresh the page, the download doesn't start again.

And when I say downloads properly, I mean as in the download gets delegated to the browser's download manager, and you can pause or cancel the progress (this for me means that my api is set correctly, but something's off as refreshing won't redownload, only repasting the url into the browser again triggers the download once again).

However (and this is my major issue), when I try to download the file in my angular app, the download appears in my network tab and only after the whole download progress is complete there, it delegates the finished download to the browser (meaning no progress is ever shown unless the whole download is complete, and this is not user friendly in case of large files & also my break point on the first line inside my subscribe method gets triggered once the whole download is complete, meaning nothing inside my subscription is necessary really or doing anything "const url = window.URL.createObjectURL(res);", I found this by debugging my app ofc.

P.S I tried both "bloc" & "arrayBuffer" as responseType in the get method.

here's my code:

dotnet endpoint:

private readonly string _filePath = "C:/Users/shadi/Desktop/file.zip";

[HttpGet("download")]
public IActionResult DownloadFile()
{
    if (!System.IO.File.Exists(_filePath))
    {
        return NotFound();
    }

    var fileName = "file.zip";
    var fileStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read);
    var fileLength = new FileInfo(_filePath).Length;

    Response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}");
    Response.Headers.Add("Content-Length", fileLength.ToString());
    Response.Headers.Add("Content-Type", "application/octet-stream");

    return new FileStreamResult(fileStream, "application/octet-stream");
}

angular service:

downloadFile(): Observable<Blob> {

    const url = `${this.filesUrl}/download`

    return this.http.get(url, { responseType: 'blob' });
  }

angular component:

download(): void {
    this.fileService.downloadFile().subscribe((res: Blob) => {
      const url = window.URL.createObjectURL(res);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'file.zip';
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      window.URL.revokeObjectURL(url);
    });
  }

Solution

  • The second problem you mentioned is related to the fact that in the Angluar service you are handling the get request yourself via the httpclient instead of delegating it to the browser. In your component you are then reacting to the completion of the whole get request (download fully completed).

    To delegate the download to the browser you can skip the part where you request the file via the http client and just create the a element with your url as href and adding the download attribute

    In your component:

      download = () => {
        const url = this.fileService.filesUrl + '/download';
        const link = document.createElement('a');
        link.setAttribute('target', '_blank');
        link.setAttribute('href', url );
        link.setAttribute('download', `file.zip`);
        document.body.appendChild(link);
        link.click();
        link.remove();
      };