0

I have two API that communicate to.each other. The first one drives a spa. On spa I click a icon and want to download a file. The request to download a file need to go through both API as only the second API can access the file. The file is an actual file on the file system.

Currently we have process where the API with access to the actual file will convert file to base64 string. This string is passed back to the other API and this is then passed back to the front-end where some JavaScript is doing the conversion back to a file.

Is this approach the correct approach to this. I have noticed that the browser download only appears once the whole file is ready to open whereas with other sites browser download appears and then indicates file size and time left.

JimmyShoe
  • 2,009
  • 4
  • 21
  • 40

2 Answers2

0

What I usually do for file downloads is to return the file in the action of the API. What I would then do in the front end is open the URI to that action in a new tab or even in the same window, which will then start the file download process.

You can do the same, from front-end open the action in the page or new tab, this will then call the API which will call the other API and do the required conversions and return the file as a file response. This needs to be a GET request to work.

Something like this should work...

[HttpGet]
public async Task<IActionResult> Download(long id) {

    Stream stream = await {{__get_stream_based_on_id_here__}}

    if(stream == null)
        return NotFound();

    return File(stream, "application/octet-stream");
}    

Then on my HTML side I would usually do this:

<a href="pathToController/Download/1">Download</a>

Or you can do something like this in JavaScript

window.location = 'pathToController/Download/1';

Because it is a file download the page will not redirect to the new location, but will instead download the file or open the download dialog depending on user settings.

Armand
  • 9,847
  • 9
  • 42
  • 75
0

You can just "tunnel" the responses in binary.

Example project: https://github.com/datvm/Example-Tunnel-Binary-API

Assume Api2 is something like this (of course in real life it could be anything, even if it's not ASP.NET, as long as it returns binary):

    [Route("api2")]
    public IActionResult Api2()
    {
        var filePath = Path.Combine(
            Path.GetDirectoryName(typeof(FileController).Assembly.Location),
            "background.png");

        var fileStream = System.IO.File.OpenRead(filePath);
        return this.File(fileStream, "image/png");
    }

API 1 can simply call API 2 and get the binary content:

    // This API call another API to get the file, it cannot access the file
    [Route("api1")]
    public async Task<IActionResult> Api1()
    {
        // In production you should not initialize HttpClient yourself
        using (var httpClient = new HttpClient())
        {
            // Url should be a server in production
            var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44322/file/api2");

            using (var response = await httpClient.SendAsync(request))
            {
                response.EnsureSuccessStatusCode();

                var content = await response.Content.ReadAsByteArrayAsync();
                return this.File(content, "image/png", "background.png");
            }
        }
    }

The client-side Javascript, you can do anything with the file:

<h1>SPA Page</h1>

<button id="btn-download">Click to download</button>
<button id="btn-download-new">Click to download in new window</button>
<button id="btn-download-fetch">Click to process with fetch</button>

<script>
    document.querySelector("#btn-download").addEventListener("click", () => {
        window.location.href = "/file/api1";
    });

    document.querySelector("#btn-download-new").addEventListener("click", () => {
        window.open("/file/api1");
    });

    document.querySelector("#btn-download-fetch").addEventListener("click", async () => {
        let data = await fetch("/file/api1")
            .then(response => response.arrayBuffer());

        // Do whatever you want with it
        alert(data.byteLength);
    });
</script>
Luke Vo
  • 17,859
  • 21
  • 105
  • 181