ajaxasp.net-mvc-4blobresponse.transmitfile

Why are files are different when downloading from an ASP.NET (AJAX download with Blob)


Using MVC 4.0, I have used the following code to create a download files from the server from an ajax source (using the latest firefox):

This works fine if the output involves are textual files such as csv or txt files, however, when it comes to files like zip or xlsx, it seems the downloaded file is different from the original source (i.e. the zip generated within the server are 15K, but the one downloaded are 26K)

I have been struggling for a few days, can I ask if anyone should shred some light on why it will works for csv/text files, but not for zip or xlsx files?

Many thanks

Controller:

Public Function download(dataIn As myObject) As ActionResult
        'some processing
        'generated zip files and return with the full path            
        Dim zipFullPath = generateFiles(dataIn)

        Response.Clear()
        Response.ContentType = "application/zip"           
        Response.AddHeader("Content-Disposition", "attachment; filename=Out.zip")
        Dim fileLength = New IO.FileInfo(zipFullPath).Length
        'fileLength  reads about 15K of data
        Response.AddHeader("Content-Length", fileLength)
        Response.TransmitFile(zipFullPath)
        Response.End()
        Return View()
    End Function

JavaScript:

$.ajax({
    type: "POST",
    url: "reports/download",
    data: jData,
    contentType: "application/json; charset=utf-8",
    success: function(response, status, xhr) {
        // check for a filename
        var filename = "";
        var disposition = xhr.getResponseHeader('Content-Disposition');
        if (disposition && disposition.indexOf('attachment') !== -1) {
            var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
            var matches = filenameRegex.exec(disposition);
            if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
        }

        var type = xhr.getResponseHeader('Content-Type');
        var blob = new Blob([response], { type: type });

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {                
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();

                    //Here is the problem, the original is about 15k,  
                    // but the download file is about 26K

                }
            } else {                    
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    },
    error: function (data) {
        alert('Error');
    }
});

Solution

  • Currently jQuery ajax can only process text responses, that's why your text files work but your binary files fail.
    To download a non text file from ajax use the XMLHttpRequest object and specify a responseType, for instance blob or arraybuffer.

    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 && this.status == 200){
            ...
            var blob = this.response; //save the blob as usual
            ...
        }
    }
    xhr.open('POST', 'reports/download');
    xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
    xhr.responseType = 'blob'; // the response will be a blob and not text
    xhr.send(jData);