Tru
Tru

Reputation: 1467

How to download a byte array as a zip file in an AngularJS client?

I'm sure this question and similar variants have been asked many times here over the years. I've gone through almost everything and am still unable to execute this properly.

I've a .NET Core API endpoint that returns a byte array that represents a zip file:

    [HttpGet]
    [Route("download/{fileId}/")]
    public byte[] Download(long fileId)
    {
        ...
    }

I use AngularJS's $http service with arraybuffer as the responseType to initiate a request:

    // AngularJS service (fileApiService)

    this.downloadFile = function (fileId) {
        var url = apiUrl + 'download/' + fileId;
        return $http.get(url, { responseType: 'arraybuffer' });
    };

I get a valid response and it's handled like below. I use FileSaver.js to access the saveAs method and the Blob constructor:

    // AngularJS controller

    fileApiService.downloadFile(fileId)
        .then(function (response) {
           var data = response.data;
           var blob = new Blob([data], { type: 'application/octet-stream' });

           FileSaver.saveAs(blob, 'file.zip');   
        })
        .catch(function () {
            ...
        });

response

Unfortunately this results in a corrupted archive file regardless of whatever tweak I do to the above code. The Window 10 zip utility complains that the archive cannot be opened even if the file is not empty. I've tried the following:

I'm suspecting that this is an encoding issue as another ASP.NET Web Forms application can download a valid zip file from the same API endpoint.

Any pointers would be greatly appreciated. Thanks in advance!

Upvotes: 0

Views: 5481

Answers (1)

Khai Kiong
Khai Kiong

Reputation: 461

Try this

fileApiService.downloadFile(fileId)
.then(function (response) {
    var data = response.data;

    function b64toBlob(b64Data, contentType, sliceSize) {
        contentType = contentType || '';
        sliceSize = sliceSize || 512; // sliceSize represent the bytes to be process in each batch(loop), 512 bytes seems to be the ideal slice size for the performance wise 

        var byteCharacters = atob(b64Data);
        var byteArrays = [];

        for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            var slice = byteCharacters.slice(offset, offset + sliceSize);

            var byteNumbers = new Array(slice.length);
            for (var i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }

            var byteArray = new Uint8Array(byteNumbers);

            byteArrays.push(byteArray);
        }

        var blob = new Blob(byteArrays, { type: contentType });
        return blob;
    }

    var blob = b64toBlob(data, 'application/octet-stream');

    FileSaver.saveAs(blob, 'file.zip');
})
.catch(function () {
    // ...
});

Hope this helps.

Upvotes: 1

Related Questions