2

On my MVC project I have an AJAX call to a Web API.

I send an array of documents' routes, the API (should) zips them and returns the zip file.

self.zipDocs = function (docs, callback) {
    $.ajax({
        url: "../SharedAPI/documents/zip",
        type: "POST",
        data: docs,
        contentType: "application/json",
        success: function (data) {
            var zip = new JSZip(data);
            var content = zip.generate({ type: "blob" });
            saveAs(content, "example.zip");
        },
        error: function (data) {
            callback(data);
        }
    });
}

And my ZipDocs function on the WebAPI (using the DotNetZip library):

[HttpPost]
    [Route("documents/zip")]
    public HttpResponseMessage ZipDocs([FromBody] string[] docs)
    {

        using (var zipFile = new ZipFile())
        {
            zipFile.AddFiles(docs, false, "");
            return ZipContentResult(zipFile);
        }
    }

    protected HttpResponseMessage ZipContentResult(ZipFile zipFile)
    {
        // inspired from http://stackoverflow.com/a/16171977/92756
        var pushStreamContent = new PushStreamContent((stream, content, context) =>
        {
           zipFile.Save(stream);
            stream.Close(); // After save we close the stream to signal that we are done writing.
        }, "application/zip");

        return new HttpResponseMessage(HttpStatusCode.OK) { Content = pushStreamContent };
    }

But when the Zip is returned I got the following error:

Uncaught Error: Corrupted zip: missing 16053 bytes.

What is really weird, when I save on the API the zip file to the disk it gets saved properly and I can open the file without any problem!

What am I doing wrong? am I missing something? Please help!

Thanks in advance.

user3378165
  • 5,189
  • 15
  • 49
  • 86
  • I am wondering if your `using` statement for the `zipFile` is causing your object to be disposed before the data is actually saved to the stream all the way. Try removing the using statement and see if it works. Your save code won't be ran until after your object returns. – TyCobb May 12 '16 at 18:30
  • Do you have to close the stream at that point? Don't you happen to close it before everytging has been sent over the wire? – Mattias Åslund May 12 '16 at 18:30

1 Answers1

3

Two things:

1/ $.ajax handles text responses and will try to (utf-8) decode the content: your zip file isn't text, you will get a corrupted content. jQuery doesn't support binary content so you need to use the previous link and add an ajax transport on jQuery or use directly a XmlHttpRequest. With an xhr, you need to set xhr.responseType = "blob" and read from xhr.response the blob.

2/ assuming your js code snippet is the whole function, you (try to) get a binary content, parse the zip file, re-generate it, give the content to the user. You can give directly the result:

// with xhr.responseType = "arraybuffer"
var arraybuffer = xhr.response;
var blob = new Blob([arraybuffer], {type:"application/zip"});
saveAs(blob, "example.zip");

// with xhr.responseType = "blob"
var blob = xhr.response;
saveAs(blob, "example.zip");

Edit: examples:

with jquery.binarytransport.js (any library that let you download a Blob or an ArrayBuffer will do)

$.ajax({
  url: "../SharedAPI/documents/zip",
  dataType: 'binary', // to use the binary transport
  // responseType:'blob', this is the default
  // [...]
  success: function (blob) {
    // the result is a blob, we can trigger the download directly
    saveAs(blob, "example.zip");
  }
  // [...]
});

with a raw XMLHttpRequest, you can see this question, you just need to add a xhr.responseType = "blob" to get a blob.

I never used C# but from what I can see, [FromBody] is quite sensitive to the format of the data, the first solution should be easier to implement in your case.

David Duponchel
  • 3,563
  • 3
  • 22
  • 29
  • Thanks for your answer, I understand what you say but not really sure how do it, do I have to use xhr already on my Web API? or just when the data is returned? would you be able to guide me? Sorry for the stupid question but I'm not so familiar with this... Thanks! – user3378165 May 13 '16 at 05:57
  • 1
    The xhr would replace this $.ajax call. The easiest solution is to use the extra ajax transport as it just need to add a library and use `dataType: 'binary'`. I've updated my answer to include an example. – David Duponchel May 13 '16 at 19:47
  • AMAZING!! Working perfectly, with some little edits to the AJAX function! I would love to vote you much more than one time! Thank you so much!!! – user3378165 May 14 '16 at 21:41