1

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);
    });
  }

1 Answer 1

1

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();
  };
3
  • Thanks a lot for replying, I will check this approach today. But what if I need to send params to the backend? or implement authorization based download? where some users are able to download and others aren't? Commented Jun 5 at 12:28
  • authorization is done on the backend. (btw, you can simplify your backend by just returning File(fileStream, "your file's mime-type"); It'll set headers automatically. You can also just create a link in your angular .html file: <a href="{{filesUrl}}/download?param={{your_param_value}}&param2={{your_param2_value}}">Link</a> You could also use a POST with <form ngNoForm method="post" action = "{{filesURL}}/download"> and send params as hidden fields... Commented Jun 5 at 17:47
  • 1
    Thanks @browsermator, that's the answer I was looking for. I wanted to submit sensitive data when requesting a file, but since the http request isn't working as intended, i didn't know how to. Thank you! Commented Jun 5 at 17:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.