0

I am working on a application that has a button, when that button is clicked it calls a web api GET method. This method takes files and creates a zip folder using System.IO.Compression. This part works great. I see the folder it creates and I am able to open / extract that folder with its files. The problem i have is when the file gets returned to the browser and the browser downloads the file I get the following error: " The Compressed (zipped) folder '...pathToTheDownloadedFile.zip' is invalid. I don't understand what I am doing wrong. All other non-zipped files download and open fine.

Here is my web api GET method:

[HttpGet]
[Route("api/OrderManager/ExtractAllDocuments/{orderNum}/{mappingId}/")]
public HttpResponseMessage ExtractAllDocuments(int orderNum, int mappingId)
{
    try
    {
        var docPath = @"" + HttpContext.Current.Server.MapPath("MyPath");
        var files = Directory.GetFiles(docPath);
        string zipName = @"supportingdocuments_order" + orderNum + ".zip";

        FileStream zipToOpen = new FileStream(docPath + zipName, FileMode.Create);

        using (var zipArchive = new ZipArchive(zipToOpen, ZipArchiveMode.Create))
        {
            foreach (var fPath in files)
            {
                if (!fPath.Contains(".zip"))
                {
                    zipArchive.CreateEntryFromFile(fPath, Path.GetFileName(fPath), CompressionLevel.Fastest);
                }
            }
        }
        zipToOpen.Close();
        //At this point the directory is created and saved as it should be

        HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
        var fullZipPath = docPath + zipName;

        byte[] bytes = File.ReadAllBytes(fullZipPath);

        response.Content = new ByteArrayContent(bytes);
        response.Content.Headers.ContentLength = bytes.LongLength;
        response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
        response.Content.Headers.ContentDisposition.FileName = zipName;
        response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(MimeMapping.GetMimeMapping(zipName));


        return response;
    }
    catch (Exception e)
    {
        var b = e.Message;
        HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.NotFound);
        response.ReasonPhrase = string.Format("Failed To Extract Files");
        throw new HttpResponseException(response);
    }
}

Here is my $.ajax call:

$.ajax({
    url: 'myApiUrl',
    method: 'GET'
}).done(function (data, status, xmlHeaderRequest) {
    var downloadLink = document.createElement('a');
    var blob = new Blob([data],
        {
            type: xmlHeaderRequest.getResponseHeader('Content-Type')
        });
    var url = window.URL || window.webkitURL;
    var downloadUrl = url.createObjectURL(blob);
    var fileName = '';

    // get the file name from the content disposition
    var disposition = xmlHeaderRequest.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, '');
        }
    }

    // Blob download logic taken from: https://stackoverflow.com/questions/16086162/handle-file-download-from-ajax-post
    if (typeof window.navigator.msSaveBlob !== 'undefined') {
        // IE workaround for "HTML7007" and "Access Denied" error.
        window.navigator.msSaveBlob(blob, fileName);
    } else {
        if (fileName) {
            if (typeof downloadLink.download === 'undefined') {
                window.location = downloadUrl;
            } else {
                downloadLink.href = downloadUrl;
                downloadLink.download = fileName;
                document.body.appendChild(downloadLink);
                downloadLink.click();
            }
        } else {
            window.location = downloadUrl;
        }

        setTimeout(function () {
            url.revokeObjectURL(downloadUrl);
        },
            100);
    }

}).fail(function (data) {
    $.notify({
        // options
        message:
            '<i class="fas fa-exclamation-circle"></i> Could not download all documents.'
    },
        {
            // settings
            type: 'danger',
            placement: {
                from: "top",
                align: "left"
            },
            delay: 2500,
            z_index: 10031
        });

});

I'm at a total and complete loss on this one. Thank you in advance for any help you can provide as it is greatly appreciated.

Tommy
  • 359
  • 1
  • 3
  • 16
  • 1
    It would be interesting to see the zip file it creates to see what is actually output. Can you do it on files that can be shared publicly so I can see it? Or do a hex dump of the first 1000 bytes and paste it in your question – Andy Sep 19 '20 at 01:34
  • yeah at the moment it's just a blank .txt file. How can I add the zip files for you to view? – Tommy Sep 19 '20 at 03:12
  • here is as link to both the zip files valid until sept 20 http://sendanywhe.re/5IW6NJ01 – Tommy Sep 19 '20 at 03:28
  • Thanks -- so your data is encoded as UTF-8. I have seen this somewhere else. Actually answered something about it. We never did figure out what was causing it: https://stackoverflow.com/questions/63229471/adding-zip-file-as-content-in-web-api-response-doubling-file-size-on-download/63276981 – Andy Sep 19 '20 at 03:51
  • For the fun of it, do you want to try this answer? https://stackoverflow.com/questions/23568098/problems-with-downloading-pdf-file-from-web-api-service except don't use `application/pdf`, use `application/zip`. I'm interested in the `content-disposition` part (use `inline` instead of `attachment`) – Andy Sep 19 '20 at 03:57
  • I changed it to inline on both the server side and client side and it does the same thing – Tommy Sep 19 '20 at 04:33
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/221723/discussion-between-tommy-and-andy). – Tommy Sep 19 '20 at 05:57
  • so i looked at the network tab in chrome and this doesn't seem right... http://sendanywhe.re/OESWRT66 – Tommy Sep 19 '20 at 05:58

1 Answers1

0

So after searching I have found a solution that works. $.ajax doesn't like binary data and thinks everything is UTF-8 text encoding apparently. So I used an XMLHttpRequest (xhr). For those that need it below is as copy of the c# and the javascript solution.

C# WebApi Controller:

public HttpResponseMessage ExtractAllDocuments(int orderNum, int mappingId)
    {
        try
        {
            

            var docPath = @"" + HttpContext.Current.Server.MapPath("myPath");
            var files = Directory.GetFiles(docPath);
            string zipName = @"Supporting-Documents-Order-" + orderNum + ".zip";

            FileStream zipToOpen = new FileStream(docPath + zipName, FileMode.Create);

            using (var zipArchive = new ZipArchive(zipToOpen, ZipArchiveMode.Create))
            {
                foreach (var fPath in files)
                {
                    if (!fPath.Contains(".zip"))
                    {
                        zipArchive.CreateEntryFromFile(fPath, Path.GetFileName(fPath), CompressionLevel.Fastest);
                    }
                }
            }
            zipToOpen.Close();
            HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
            response.Content = new StreamContent(new FileStream(docPath + zipName, FileMode.Open, FileAccess.Read));
            response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = zipName;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");                               

            return response;
        }
        catch (Exception e)
        {
            var b = e.Message;
            HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.NotFound);
            response.ReasonPhrase = string.Format("Failed To Extract Files");
            throw new HttpResponseException(response);
        }
    }

Front End JavaScript:

function extractAllDocuments() {

        let xhr = new XMLHttpRequest();

        xhr.addEventListener('readystatechange', function (e) {
            if (xhr.readyState == 2 && xhr.status == 200) {
                // Download is being started
            }
            else if (xhr.readyState == 3) {
                // Download is under progress
            }
            else if (xhr.readyState == 4) {
                //operation done

                // Create a new Blob object using the response data of the onload object
                var blob = new Blob([this.response], { type: 'application/zip' });
                //Create a link element, hide it, direct it towards the blob, and then 'click' it programatically
                let a = document.createElement("a");
                a.style = "display: none";
                document.body.appendChild(a);
                //Create a DOMString representing the blob and point the link element towards it
                let url = window.URL.createObjectURL(blob);
                a.href = url;
                // get the file name from the content disposition
                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, '');
                    }
                }
                a.download = fileName;
                //programatically click the link to trigger the download
                a.click();
               
                //Remove From Server
                $.ajax({
                    url: 'myUrl',
                    method: 'DELETE'
                }).done(function (data) {

                }).fail(function (data) {

                });
                setTimeout(function () {
                    window.URL.revokeObjectURL(url);
                }, 60 * 1000);
            } else if (xhr.status == 400){
                $.notify({
                    // options
                    message:
                        '<i class="fas fa-exclamation-circle"></i> Could not download all documents.'
                },
                    {
                        // settings
                        type: 'danger',
                        placement: {
                            from: "top",
                            align: "left"
                        },
                        delay: 2500,
                        z_index: 10031
                    });
            }
        });

        //set the request type to post and the destination url to '/convert'
        xhr.open('GET', 'MyUrl');
        //set the reponse type to blob since that's what we're expecting back
        xhr.responseType = 'blob';
        xhr.send();

        return false;
    }
Tommy
  • 359
  • 1
  • 3
  • 16